From 77842f9b637dd2efcd684a4668e9ff4a173449f8 Mon Sep 17 00:00:00 2001 From: ton Date: Thu, 6 Feb 2020 21:56:46 +0400 Subject: [PATCH] updated tonlib - updated tonlib - updated validator - updated documentation - first version of http over rldp proxy --- CMakeLists.txt | 6 + GPLv2 | 16 +- adnl/adnl-address-list.cpp | 2 +- adnl/adnl-address-list.h | 2 +- adnl/adnl-channel.cpp | 2 +- adnl/adnl-channel.h | 2 +- adnl/adnl-channel.hpp | 2 +- adnl/adnl-local-id.cpp | 2 +- adnl/adnl-local-id.h | 2 +- adnl/adnl-network-manager.cpp | 2 +- adnl/adnl-packet.h | 2 +- adnl/adnl-peer-table.cpp | 2 +- adnl/adnl-peer-table.h | 2 +- adnl/adnl-peer-table.hpp | 2 +- adnl/adnl-peer.cpp | 2 +- .../blockchain-explorer-http.hpp | 2 +- .../blockchain-explorer-query.cpp | 64 +- blockchain-explorer/blockchain-explorer.cpp | 3 +- blockchain-explorer/blockchain-explorer.hpp | 2 +- crypto/CMakeLists.txt | 11 +- crypto/block/mc-config.cpp | 15 + crypto/block/mc-config.h | 3 +- crypto/block/transaction.cpp | 27 +- crypto/block/transaction.h | 7 +- crypto/fift/lib/Asm.fif | 5 + crypto/fift/lib/TonUtil.fif | 4 +- crypto/fift/words.cpp | 54 +- crypto/smartcont/dns-manual-code.fc | 381 ++++ crypto/smartcont/gen-zerostate.fif | 8 +- crypto/smartcont/show-addr.fif | 6 +- crypto/smartcont/stdlib.fc | 3 + crypto/smartcont/validator-elect-req.fif | 2 +- crypto/smartcont/validator-elect-signed.fif | 2 +- crypto/smc-envelope/HighloadWallet.cpp | 22 +- crypto/smc-envelope/HighloadWallet.h | 23 +- crypto/smc-envelope/HighloadWalletV2.cpp | 151 ++ crypto/smc-envelope/HighloadWalletV2.h | 66 + crypto/smc-envelope/ManualDns.cpp | 542 ++++++ crypto/smc-envelope/ManualDns.h | 339 ++++ crypto/smc-envelope/SmartContract.cpp | 4 +- crypto/smc-envelope/SmartContract.h | 4 +- crypto/smc-envelope/SmartContractCode.cpp | 100 +- crypto/smc-envelope/SmartContractCode.h | 11 +- crypto/smc-envelope/TestGiver.cpp | 25 +- crypto/smc-envelope/TestGiver.h | 21 +- crypto/smc-envelope/TestWallet.cpp | 76 +- crypto/smc-envelope/TestWallet.h | 32 +- crypto/smc-envelope/Wallet.cpp | 78 +- crypto/smc-envelope/Wallet.h | 30 +- crypto/smc-envelope/WalletInterface.h | 62 + crypto/smc-envelope/WalletV3.cpp | 115 +- crypto/smc-envelope/WalletV3.h | 40 +- crypto/test/fift/testdict.fif | 2 +- crypto/test/test-smartcont.cpp | 610 ++++++- crypto/test/vm.cpp | 4 +- crypto/vm/arithops.cpp | 4 +- crypto/vm/cellops.cpp | 4 +- crypto/vm/cells/CellSlice.cpp | 4 +- crypto/vm/cells/CellString.cpp | 93 + crypto/vm/cells/CellString.h | 43 + crypto/vm/continuation.cpp | 561 +----- crypto/vm/continuation.h | 284 +-- crypto/vm/contops.cpp | 3 +- crypto/vm/debugops.cpp | 4 +- crypto/vm/dictops.cpp | 2 +- crypto/vm/opctable.cpp | 4 +- crypto/vm/stackops.cpp | 4 +- crypto/vm/tonops.cpp | 82 +- crypto/vm/tupleops.cpp | 4 +- crypto/vm/vm.cpp | 593 +++++++ crypto/vm/vm.h | 320 ++++ crypto/vm/vmstate.h | 4 +- dht/dht-bucket.cpp | 2 +- dht/dht-bucket.hpp | 2 +- dht/dht-in.hpp | 2 +- dht/dht-query.cpp | 2 +- dht/dht-query.hpp | 2 +- dht/dht-remote-node.cpp | 2 +- dht/dht-remote-node.hpp | 2 +- dht/dht.h | 2 +- dht/dht.hpp | 2 +- doc/TonSites-HOWTO | 78 + doc/catchain.tex | 558 ++++++ doc/fiftbase.tex | 12 +- doc/tvm.tex | 20 +- http/CMakeLists.txt | 25 + http/http-client.cpp | 124 ++ http/http-client.h | 56 + http/http-client.hpp | 118 ++ http/http-connection.cpp | 256 +++ http/http-connection.h | 141 ++ http/http-inbound-connection.cpp | 99 ++ http/http-inbound-connection.h | 81 + http/http-outbound-connection.cpp | 110 ++ http/http-outbound-connection.h | 125 ++ http/http-proxy.cpp | 308 ++++ http/http-server.cpp | 51 + http/http-server.h | 61 + http/http.cpp | 901 ++++++++++ http/http.h | 326 ++++ lite-client/lite-client.cpp | 2 +- rldp-http-proxy/CMakeLists.txt | 5 + rldp-http-proxy/rldp-http-proxy.cpp | 1014 +++++++++++ tdutils/td/utils/Span.h | 8 +- tdutils/td/utils/Status.h | 5 +- tdutils/td/utils/Variant.h | 6 +- tdutils/td/utils/optional.h | 9 +- test/regression-tests.ans | 2 +- test/test-http.cpp | 308 ++++ tl/generate/scheme/tonlib_api.tl | 134 +- tl/generate/scheme/tonlib_api.tlo | Bin 19324 -> 19852 bytes tonlib/test/offline.cpp | 57 +- tonlib/test/online.cpp | 241 ++- tonlib/tonlib/LastConfig.h | 4 +- tonlib/tonlib/TonlibClient.cpp | 1565 ++++++++++------- tonlib/tonlib/TonlibClient.h | 73 +- tonlib/tonlib/TonlibError.h | 8 +- tonlib/tonlib/keys/SimpleEncryption.cpp | 45 +- tonlib/tonlib/keys/SimpleEncryption.h | 8 +- tonlib/tonlib/tonlib-cli.cpp | 754 ++++---- validator-session/validator-session-state.cpp | 8 +- validator/db/archive-manager.cpp | 18 + validator/db/archive-manager.hpp | 18 + validator/db/archive-slice.cpp | 18 + validator/db/package.cpp | 18 + validator/impl/liteserver.cpp | 2 +- validator/manager-disk.cpp | 22 +- validator/manager-disk.hpp | 3 +- 128 files changed, 10555 insertions(+), 2285 deletions(-) create mode 100644 crypto/smartcont/dns-manual-code.fc create mode 100644 crypto/smc-envelope/HighloadWalletV2.cpp create mode 100644 crypto/smc-envelope/HighloadWalletV2.h create mode 100644 crypto/smc-envelope/ManualDns.cpp create mode 100644 crypto/smc-envelope/ManualDns.h create mode 100644 crypto/smc-envelope/WalletInterface.h create mode 100644 crypto/vm/vm.cpp create mode 100644 crypto/vm/vm.h create mode 100644 doc/TonSites-HOWTO create mode 100644 doc/catchain.tex create mode 100644 http/CMakeLists.txt create mode 100644 http/http-client.cpp create mode 100644 http/http-client.h create mode 100644 http/http-client.hpp create mode 100644 http/http-connection.cpp create mode 100644 http/http-connection.h create mode 100644 http/http-inbound-connection.cpp create mode 100644 http/http-inbound-connection.h create mode 100644 http/http-outbound-connection.cpp create mode 100644 http/http-outbound-connection.h create mode 100644 http/http-proxy.cpp create mode 100644 http/http-server.cpp create mode 100644 http/http-server.h create mode 100644 http/http.cpp create mode 100644 http/http.h create mode 100644 rldp-http-proxy/CMakeLists.txt create mode 100644 rldp-http-proxy/rldp-http-proxy.cpp create mode 100644 test/test-http.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d59081b..cbb858e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -323,6 +323,7 @@ if (LATEX_FOUND) add_latex_document(doc/tvm.tex TARGET_NAME ton_vm_description) add_latex_document(doc/tblkch.tex TARGET_NAME ton_blockchain_description) add_latex_document(doc/fiftbase.tex TARGET_NAME fift_basic_description) + add_latex_document(doc/catchain.tex TARGET_NAME catchain_consensus_description) endif() #END internal @@ -374,6 +375,8 @@ add_subdirectory(validator-engine) add_subdirectory(validator-engine-console) add_subdirectory(dht-server) add_subdirectory(utils) +add_subdirectory(http) +add_subdirectory(rldp-http-proxy) endif() #END internal @@ -473,6 +476,9 @@ target_link_libraries(test-ton-collator overlay tdutils tdactor adnl tl_api dht #add_executable(test-ext-client test/test-ext-client.cpp) #target_link_libraries(test-ext-client tdutils tdactor adnl tl_api tl-lite-utils) +add_executable(test-http test/test-http.cpp) +target_link_libraries(test-http PRIVATE tonhttp) + get_directory_property(HAS_PARENT PARENT_DIRECTORY) if (HAS_PARENT) set(ALL_TEST_SOURCE diff --git a/GPLv2 b/GPLv2 index c1e0c158..69910f2e 100644 --- a/GPLv2 +++ b/GPLv2 @@ -1,4 +1,4 @@ -/* +/* This file is part of TON Blockchain source code. TON Blockchain is free software; you can redistribute it and/or @@ -14,13 +14,13 @@ You should have received a copy of the GNU General Public License along with TON Blockchain. If not, see . - In addition, as a special exception, the copyright holders give permission - to link the code of portions of this program with the OpenSSL library. - You must obey the GNU General Public License in all respects for all - of the code used other than OpenSSL. If you modify file(s) with this - exception, you may extend this exception to your version of the file(s), - but you are not obligated to do so. If you do not wish to do so, delete this - exception statement from your version. If you delete this exception statement + In addition, as a special exception, the copyright holders give permission + to link the code of portions of this program with the OpenSSL library. + You must obey the GNU General Public License in all respects for all + of the code used other than OpenSSL. If you modify file(s) with this + exception, you may extend this exception to your version of the file(s), + but you are not obligated to do so. If you do not wish to do so, delete this + exception statement from your version. If you delete this exception statement from all source files in the program, then also delete it here. Copyright 2017-2020 Telegram Systems LLP diff --git a/adnl/adnl-address-list.cpp b/adnl/adnl-address-list.cpp index 8b92d925..fc3d81eb 100644 --- a/adnl/adnl-address-list.cpp +++ b/adnl/adnl-address-list.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "adnl-address-list.hpp" #include "adnl-peer-table.h" diff --git a/adnl/adnl-address-list.h b/adnl/adnl-address-list.h index 686ced65..d9dada69 100644 --- a/adnl/adnl-address-list.h +++ b/adnl/adnl-address-list.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/adnl/adnl-channel.cpp b/adnl/adnl-channel.cpp index f4e09cb0..5c8229ca 100644 --- a/adnl/adnl-channel.cpp +++ b/adnl/adnl-channel.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "adnl-channel.hpp" #include "adnl-peer.h" diff --git a/adnl/adnl-channel.h b/adnl/adnl-channel.h index a8e2c448..94b5877d 100644 --- a/adnl/adnl-channel.h +++ b/adnl/adnl-channel.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/adnl/adnl-channel.hpp b/adnl/adnl-channel.hpp index d4356f93..0c30fbd5 100644 --- a/adnl/adnl-channel.hpp +++ b/adnl/adnl-channel.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/adnl/adnl-local-id.cpp b/adnl/adnl-local-id.cpp index cd9d384e..714464be 100644 --- a/adnl/adnl-local-id.cpp +++ b/adnl/adnl-local-id.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "td/utils/crypto.h" #include "td/utils/Random.h" diff --git a/adnl/adnl-local-id.h b/adnl/adnl-local-id.h index 03998f7a..678adabd 100644 --- a/adnl/adnl-local-id.h +++ b/adnl/adnl-local-id.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/adnl/adnl-network-manager.cpp b/adnl/adnl-network-manager.cpp index 02e73ce0..d6882d85 100644 --- a/adnl/adnl-network-manager.cpp +++ b/adnl/adnl-network-manager.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "adnl-network-manager.hpp" #include "adnl-peer-table.h" diff --git a/adnl/adnl-packet.h b/adnl/adnl-packet.h index 107ed7e5..363a74c2 100644 --- a/adnl/adnl-packet.h +++ b/adnl/adnl-packet.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/adnl/adnl-peer-table.cpp b/adnl/adnl-peer-table.cpp index b4880de2..555ff47b 100644 --- a/adnl/adnl-peer-table.cpp +++ b/adnl/adnl-peer-table.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "adnl-peer-table.hpp" #include "adnl-peer.h" diff --git a/adnl/adnl-peer-table.h b/adnl/adnl-peer-table.h index 42447a44..536ed60e 100644 --- a/adnl/adnl-peer-table.h +++ b/adnl/adnl-peer-table.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/adnl/adnl-peer-table.hpp b/adnl/adnl-peer-table.hpp index c5c6acd8..1ef4f971 100644 --- a/adnl/adnl-peer-table.hpp +++ b/adnl/adnl-peer-table.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/adnl/adnl-peer.cpp b/adnl/adnl-peer.cpp index 4ff8044a..5e3e9700 100644 --- a/adnl/adnl-peer.cpp +++ b/adnl/adnl-peer.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "adnl-peer.h" #include "adnl-peer.hpp" diff --git a/blockchain-explorer/blockchain-explorer-http.hpp b/blockchain-explorer/blockchain-explorer-http.hpp index 130a3a99..e21f1b7e 100644 --- a/blockchain-explorer/blockchain-explorer-http.hpp +++ b/blockchain-explorer/blockchain-explorer-http.hpp @@ -23,7 +23,7 @@ exception statement from your version. If you delete this exception statement from all source files in the program, then also delete it here. - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/blockchain-explorer/blockchain-explorer-query.cpp b/blockchain-explorer/blockchain-explorer-query.cpp index db731f14..4c8ed7b1 100644 --- a/blockchain-explorer/blockchain-explorer-query.cpp +++ b/blockchain-explorer/blockchain-explorer-query.cpp @@ -24,7 +24,7 @@ from all source files in the program, then also delete it here. along with TON Blockchain. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "blockchain-explorer-query.hpp" #include "blockchain-explorer-http.hpp" @@ -47,7 +47,7 @@ #include "vm/boc.h" #include "vm/cellops.h" #include "vm/cells/MerkleProof.h" -#include "vm/continuation.h" +#include "vm/vm.h" #include "vm/cp0.h" namespace { @@ -198,8 +198,9 @@ void HttpQueryCommon::abort_query(td::Status error) { HttpAnswer A{"error", prefix_}; A.abort(std::move(error)); auto page = A.finish(); - promise_.set_value( - MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY)); + auto R = MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY); + MHD_add_response_header(R, "Content-Type", "text/html"); + promise_.set_value(std::move(R)); } stop(); } @@ -291,8 +292,9 @@ void HttpQueryBlockView::finish_query() { A << HttpAnswer::RawData{root}; return A.finish(); }(); - promise_.set_value( - MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY)); + auto R = MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY); + MHD_add_response_header(R, "Content-Type", "text/html"); + promise_.set_value(std::move(R)); } stop(); } @@ -488,8 +490,9 @@ void HttpQueryBlockInfo::finish_query() { return A.finish(); }(); - promise_.set_value( - MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY)); + auto R = MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY); + MHD_add_response_header(R, "Content-Type", "text/html"); + promise_.set_value(std::move(R)); } stop(); } @@ -709,8 +712,9 @@ void HttpQueryBlockSearch::finish_query() { return A.finish(); }(); - promise_.set_value( - MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY)); + auto R = MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY); + MHD_add_response_header(R, "Content-Type", "text/html"); + promise_.set_value(std::move(R)); } stop(); } @@ -795,8 +799,9 @@ void HttpQueryViewAccount::finish_query() { A << HttpAnswer::AccountCell{addr_, res_block_id_, root, Q_roots}; return A.finish(); }(); - promise_.set_value( - MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY)); + auto R = MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY); + MHD_add_response_header(R, "Content-Type", "text/html"); + promise_.set_value(std::move(R)); } stop(); } @@ -893,8 +898,9 @@ void HttpQueryViewTransaction::finish_query() { } return A.finish(); }(); - promise_.set_value( - MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY)); + auto R = MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY); + MHD_add_response_header(R, "Content-Type", "text/html"); + promise_.set_value(std::move(R)); } stop(); } @@ -974,8 +980,9 @@ void HttpQueryViewTransaction2::finish_query() { A << HttpAnswer::TransactionCell{addr_, block_id_, list}; return A.finish(); }(); - promise_.set_value( - MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY)); + auto R = MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY); + MHD_add_response_header(R, "Content-Type", "text/html"); + promise_.set_value(std::move(R)); } stop(); } @@ -1178,8 +1185,9 @@ void HttpQueryConfig::finish_query() { } return A.finish(); }(); - promise_.set_value( - MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY)); + auto R = MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY); + MHD_add_response_header(R, "Content-Type", "text/html"); + promise_.set_value(std::move(R)); } stop(); } @@ -1209,8 +1217,9 @@ void HttpQuerySendForm::finish_query() { << ""; return A.finish(); }(); - promise_.set_value( - MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY)); + auto R = MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY); + MHD_add_response_header(R, "Content-Type", "text/html"); + promise_.set_value(std::move(R)); } stop(); } @@ -1270,8 +1279,9 @@ void HttpQuerySend::finish_query() { } return A.finish(); }(); - promise_.set_value( - MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY)); + auto R = MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY); + MHD_add_response_header(R, "Content-Type", "text/html"); + promise_.set_value(std::move(R)); } stop(); } @@ -1434,8 +1444,9 @@ void HttpQueryRunMethod::finish_query() { return A.finish(); }(); - promise_.set_value( - MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY)); + auto R = MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY); + MHD_add_response_header(R, "Content-Type", "text/html"); + promise_.set_value(std::move(R)); } stop(); } @@ -1511,8 +1522,9 @@ void HttpQueryStatus::finish_query() { A << ""; return A.finish(); }(); - promise_.set_value( - MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY)); + auto R = MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY); + MHD_add_response_header(R, "Content-Type", "text/html"); + promise_.set_value(std::move(R)); } stop(); } diff --git a/blockchain-explorer/blockchain-explorer.cpp b/blockchain-explorer/blockchain-explorer.cpp index adaf4084..47ce7378 100644 --- a/blockchain-explorer/blockchain-explorer.cpp +++ b/blockchain-explorer/blockchain-explorer.cpp @@ -23,7 +23,7 @@ from all source files in the program, then also delete it here. along with TON Blockchain. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "adnl/adnl-ext-client.h" #include "adnl/utils.hpp" @@ -52,7 +52,6 @@ #include "vm/boc.h" #include "vm/cellops.h" #include "vm/cells/MerkleProof.h" -#include "vm/continuation.h" #include "vm/cp0.h" #include "auto/tl/lite_api.h" diff --git a/blockchain-explorer/blockchain-explorer.hpp b/blockchain-explorer/blockchain-explorer.hpp index 8e2ae0e6..b6bc15f1 100644 --- a/blockchain-explorer/blockchain-explorer.hpp +++ b/blockchain-explorer/blockchain-explorer.hpp @@ -23,7 +23,7 @@ exception statement from your version. If you delete this exception statement from all source files in the program, then also delete it here. - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index ff7d9227..0897166f 100644 --- a/crypto/CMakeLists.txt +++ b/crypto/CMakeLists.txt @@ -36,6 +36,7 @@ set(TON_CRYPTO_SOURCE vm/tonops.cpp vm/boc.cpp vm/utils.cpp + vm/vm.cpp tl/tlblib.cpp Ed25519.h @@ -82,6 +83,7 @@ set(TON_CRYPTO_SOURCE vm/tonops.h vm/vmstate.h vm/utils.h + vm/vm.h vm/cells.h vm/cellslice.h @@ -204,6 +206,8 @@ set(BLOCK_SOURCE set(SMC_ENVELOPE_SOURCE smc-envelope/GenericAccount.cpp smc-envelope/HighloadWallet.cpp + smc-envelope/HighloadWalletV2.cpp + smc-envelope/ManualDns.cpp smc-envelope/MultisigWallet.cpp smc-envelope/SmartContract.cpp smc-envelope/SmartContractCode.cpp @@ -214,12 +218,15 @@ set(SMC_ENVELOPE_SOURCE smc-envelope/GenericAccount.h smc-envelope/HighloadWallet.h + smc-envelope/HighloadWalletV2.h + smc-envelope/ManualDns.h smc-envelope/MultisigWallet.h smc-envelope/SmartContract.h smc-envelope/SmartContractCode.h smc-envelope/TestGiver.h smc-envelope/TestWallet.h smc-envelope/Wallet.h + smc-envelope/WalletInterface.h smc-envelope/WalletV3.h ) @@ -359,12 +366,14 @@ if (NOT CMAKE_CROSSCOMPILING) GenFif(DEST smartcont/auto/wallet3-code SOURCE smartcont/wallet3-code.fc NAME wallet3) GenFif(DEST smartcont/auto/simple-wallet-code SOURCE smartcont/simple-wallet-code.fc NAME simple-wallet) GenFif(DEST smartcont/auto/highload-wallet-code SOURCE smartcont/highload-wallet-code.fc NAME highload-wallet) - GenFif(DEST smartcont/auto/highload-wallet-v2-code SOURCE smartcont/highload-wallet-v2-code.fc NAME highoad-wallet-v2) + GenFif(DEST smartcont/auto/highload-wallet-v2-code SOURCE smartcont/highload-wallet-v2-code.fc NAME highload-wallet-v2) GenFif(DEST smartcont/auto/elector-code SOURCE smartcont/elector-code.fc NAME elector-code) GenFif(DEST smartcont/auto/multisig-code SOURCE smartcont/multisig-code.fc NAME multisig) GenFif(DEST smartcont/auto/restricted-wallet-code SOURCE smartcont/restricted-wallet-code.fc NAME restricted-wallet) GenFif(DEST smartcont/auto/restricted-wallet2-code SOURCE smartcont/restricted-wallet2-code.fc NAME restricted-wallet2) + GenFif(DEST smartcont/auto/dns-manual-code SOURCE smartcont/dns-manual-code.fc NAME dns-manual) + GenFif(DEST smartcont/auto/simple-wallet-ext-code SOURCE smartcont/simple-wallet-ext-code.fc NAME simple-wallet-ext) endif() diff --git a/crypto/block/mc-config.cpp b/crypto/block/mc-config.cpp index 46186caa..719d76b0 100644 --- a/crypto/block/mc-config.cpp +++ b/crypto/block/mc-config.cpp @@ -567,6 +567,21 @@ td::Result Config::do_get_gas_limits_prices(td::Ref c } return res; } + +td::Result Config::get_dns_root_addr() const { + auto cell = get_config_param(4); + if (cell.is_null()) { + return td::Status::Error(PSLICE() << "configuration parameter " << 4 << " with dns root address is absent"); + } + auto cs = vm::load_cell_slice(std::move(cell)); + if (cs.size() != 0x100) { + return td::Status::Error(PSLICE() << "configuration parameter " << 4 << " with dns root address has wrong size"); + } + ton::StdSmcAddress res; + CHECK(cs.fetch_bits_to(res)); + return res; +} + td::Result Config::get_gas_limits_prices(bool is_masterchain) const { auto id = is_masterchain ? 20 : 21; auto cell = get_config_param(id); diff --git a/crypto/block/mc-config.h b/crypto/block/mc-config.h index 55bf1122..8541110f 100644 --- a/crypto/block/mc-config.h +++ b/crypto/block/mc-config.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU General Public License along with TON Blockchain. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "common/refcnt.hpp" @@ -534,6 +534,7 @@ class Config { bool create_stats_enabled() const { return has_capability(ton::capCreateStatsEnabled); } + td::Result get_dns_root_addr() const; bool set_block_id_ext(const ton::BlockIdExt& block_id_ext); td::Result> get_special_smartcontracts(bool without_config = false) const; bool is_special_smartcontract(const ton::StdSmcAddress& addr) const; diff --git a/crypto/block/transaction.cpp b/crypto/block/transaction.cpp index df4cf883..2c5f4488 100644 --- a/crypto/block/transaction.cpp +++ b/crypto/block/transaction.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "block/transaction.h" #include "block/block.h" @@ -23,7 +23,7 @@ #include "td/utils/bits.h" #include "td/utils/uint128.h" #include "ton/ton-shard.h" -#include "vm/continuation.h" +#include "vm/vm.h" namespace block { using td::Ref; @@ -691,12 +691,16 @@ bool ComputePhaseConfig::parse_GasLimitsPrices(Ref cs, td::RefInt } block::gen::GasLimitsPrices::Record_gas_flat_pfx flat; if (tlb::csr_unpack(cs, flat)) { - bool ok = parse_GasLimitsPrices(std::move(flat.other), freeze_due_limit, delete_due_limit); - flat_gas_limit = flat.flat_gas_limit; - flat_gas_price = flat.flat_gas_price; - return ok; + 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); } - flat_gas_limit = flat_gas_price = 0; +} + +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; @@ -716,6 +720,8 @@ bool ComputePhaseConfig::parse_GasLimitsPrices(Ref cs, td::RefInt return false; } } + flat_gas_limit = _flat_gas_limit; + flat_gas_price = _flat_gas_price; compute_threshold(); return true; } @@ -1010,10 +1016,9 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) { total_fees += cp.gas_fees; balance -= cp.gas_fees; } - if (verbosity > 2) { - std::cerr << "gas fees: " << cp.gas_fees << " = " << cfg.gas_price256 << " * " << cp.gas_used - << " /2^16 ; price=" << cfg.gas_price << "; remaining balance=" << balance << std::endl; - } + 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; diff --git a/crypto/block/transaction.h b/crypto/block/transaction.h index 10150f1c..8b3a424e 100644 --- a/crypto/block/transaction.h +++ b/crypto/block/transaction.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "common/refcnt.hpp" @@ -130,6 +130,11 @@ struct ComputePhaseConfig { } bool parse_GasLimitsPrices(Ref cs, td::RefInt256& freeze_due_limit, td::RefInt256& delete_due_limit); bool parse_GasLimitsPrices(Ref cell, td::RefInt256& freeze_due_limit, td::RefInt256& delete_due_limit); + + private: + bool parse_GasLimitsPrices_internal(Ref cs, td::RefInt256& freeze_due_limit, + td::RefInt256& delete_due_limit, td::uint64 flat_gas_limit = 0, + td::uint64 flat_gas_price = 0); }; struct ActionPhaseConfig { diff --git a/crypto/fift/lib/Asm.fif b/crypto/fift/lib/Asm.fif index 46fb9564..7c09e82d 100644 --- a/crypto/fift/lib/Asm.fif +++ b/crypto/fift/lib/Asm.fif @@ -1005,6 +1005,11 @@ x{F902} @Defop SHA256U x{F910} @Defop CHKSIGNU x{F911} @Defop CHKSIGNS +x{F940} @Defop CDATASIZEQ +x{F941} @Defop CDATASIZE +x{F942} @Defop SDATASIZEQ +x{F943} @Defop SDATASIZE + x{FA00} dup @Defop LDGRAMS @Defop LDVARUINT16 x{FA01} @Defop LDVARINT16 x{FA02} dup @Defop STGRAMS @Defop STVARUINT16 diff --git a/crypto/fift/lib/TonUtil.fif b/crypto/fift/lib/TonUtil.fif index 71624798..796390f9 100644 --- a/crypto/fift/lib/TonUtil.fif +++ b/crypto/fift/lib/TonUtil.fif @@ -34,8 +34,10 @@ library TonUtil // TON Blockchain Fift Library 1 and 0= } : parse-smc-addr +// ( x -- ) Displays a 64-digit hex number +{ 64 0x. } : 64x. // ( wc addr -- ) Show address in : form -{ swap ._ .":" 64 0x. } : .addr +{ swap ._ .":" 64x. } : .addr // ( wc addr flags -- ) Show address in base64url form { smca>$ type } : .Addr // ( wc addr fname -- ) Save address to file in 36-byte format diff --git a/crypto/fift/words.cpp b/crypto/fift/words.cpp index 2a36cf8f..a4067eb9 100644 --- a/crypto/fift/words.cpp +++ b/crypto/fift/words.cpp @@ -32,7 +32,7 @@ #include "vm/cells.h" #include "vm/cellslice.h" -#include "vm/continuation.h" +#include "vm/vm.h" #include "vm/cp0.h" #include "vm/dict.h" #include "vm/boc.h" @@ -1534,7 +1534,7 @@ void interpret_dict_add(vm::Stack& stack, vm::Dictionary::SetMode mode, bool add stack.push_bool(res); } -void interpret_dict_get(vm::Stack& stack, int sgnd) { +void interpret_dict_get(vm::Stack& stack, int sgnd, int mode) { int n = stack.pop_smallint_range(vm::Dictionary::max_key_bits); vm::Dictionary dict{stack.pop_maybe_cell(), n}; unsigned char buffer[vm::Dictionary::max_key_bytes]; @@ -1543,12 +1543,16 @@ void interpret_dict_get(vm::Stack& stack, int sgnd) { if (!key.is_valid()) { throw IntError{"not enough bits for a dictionary key"}; } - auto res = dict.lookup(std::move(key)); - if (res.not_null()) { + auto res = (mode & 4 ? dict.lookup_delete(std::move(key)) : dict.lookup(std::move(key))); + if (mode & 4) { + stack.push_maybe_cell(std::move(dict).extract_root_cell()); + } + bool found = res.not_null(); + if (found && (mode & 2)) { stack.push_cellslice(std::move(res)); - stack.push_bool(true); - } else { - stack.push_bool(false); + } + if (mode & 1) { + stack.push_bool(found); } } @@ -1572,15 +1576,15 @@ void interpret_dict_map(IntCtx& ctx) { ctx.stack.push_maybe_cell(std::move(dict).extract_root_cell()); } -void interpret_dict_map_ext(IntCtx& ctx) { +void interpret_dict_map_ext(IntCtx& ctx, bool sgnd) { auto func = pop_exec_token(ctx); int n = ctx.stack.pop_smallint_range(vm::Dictionary::max_key_bits); vm::Dictionary dict{ctx.stack.pop_maybe_cell(), n}; - vm::Dictionary::map_func_t map_func = [&ctx, func](vm::CellBuilder& cb, Ref cs_ref, - td::ConstBitPtr key, int key_len) -> bool { + vm::Dictionary::map_func_t map_func = [&ctx, func, sgnd](vm::CellBuilder& cb, Ref cs_ref, + td::ConstBitPtr key, int key_len) -> bool { ctx.stack.push_builder(Ref(cb)); td::RefInt256 x{true}; - x.unique_write().import_bits(key, key_len, false); + x.unique_write().import_bits(key, key_len, sgnd); ctx.stack.push_int(std::move(x)); ctx.stack.push_cellslice(std::move(cs_ref)); func->run(ctx); @@ -1596,20 +1600,20 @@ void interpret_dict_map_ext(IntCtx& ctx) { ctx.stack.push_maybe_cell(std::move(dict).extract_root_cell()); } -void interpret_dict_foreach(IntCtx& ctx) { +void interpret_dict_foreach(IntCtx& ctx, bool sgnd) { auto func = pop_exec_token(ctx); int n = ctx.stack.pop_smallint_range(vm::Dictionary::max_key_bits); vm::Dictionary dict{ctx.stack.pop_maybe_cell(), n}; - vm::Dictionary::foreach_func_t foreach_func = [&ctx, func](Ref cs_ref, td::ConstBitPtr key, - int key_len) -> bool { + vm::Dictionary::foreach_func_t foreach_func = [&ctx, func, sgnd](Ref cs_ref, td::ConstBitPtr key, + int key_len) -> bool { td::RefInt256 x{true}; - x.unique_write().import_bits(key, key_len, false); + x.unique_write().import_bits(key, key_len, sgnd); ctx.stack.push_int(std::move(x)); ctx.stack.push_cellslice(std::move(cs_ref)); func->run(ctx); return ctx.stack.pop_bool(); }; - ctx.stack.push_bool(dict.check_for_each(std::move(foreach_func))); + ctx.stack.push_bool(dict.check_for_each(std::move(foreach_func), sgnd)); } void interpret_dict_merge(IntCtx& ctx) { @@ -2752,23 +2756,31 @@ void init_words_common(Dictionary& d) { d.def_stack_word("sdict! ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Set, false, -1)); d.def_stack_word("b>sdict!+ ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Add, true, -1)); d.def_stack_word("b>sdict! ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Set, true, -1)); - d.def_stack_word("sdict@ ", std::bind(interpret_dict_get, _1, -1)); + d.def_stack_word("sdict@ ", std::bind(interpret_dict_get, _1, -1, 3)); + d.def_stack_word("sdict@- ", std::bind(interpret_dict_get, _1, -1, 7)); + d.def_stack_word("sdict- ", std::bind(interpret_dict_get, _1, -1, 5)); d.def_stack_word("udict!+ ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Add, false, 0)); d.def_stack_word("udict! ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Set, false, 0)); d.def_stack_word("b>udict!+ ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Add, true, 0)); d.def_stack_word("b>udict! ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Set, true, 0)); - d.def_stack_word("udict@ ", std::bind(interpret_dict_get, _1, 0)); + d.def_stack_word("udict@ ", std::bind(interpret_dict_get, _1, 0, 3)); + d.def_stack_word("udict@- ", std::bind(interpret_dict_get, _1, 0, 7)); + d.def_stack_word("udict- ", std::bind(interpret_dict_get, _1, 0, 5)); d.def_stack_word("idict!+ ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Add, false, 1)); d.def_stack_word("idict! ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Set, false, 1)); d.def_stack_word("b>idict!+ ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Add, true, 1)); d.def_stack_word("b>idict! ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Set, true, 1)); - d.def_stack_word("idict@ ", std::bind(interpret_dict_get, _1, 1)); + d.def_stack_word("idict@ ", std::bind(interpret_dict_get, _1, 1, 3)); + d.def_stack_word("idict@- ", std::bind(interpret_dict_get, _1, 1, 7)); + d.def_stack_word("idict- ", std::bind(interpret_dict_get, _1, 1, 5)); d.def_stack_word("pfxdict!+ ", std::bind(interpret_pfx_dict_add, _1, vm::Dictionary::SetMode::Add, false)); d.def_stack_word("pfxdict! ", std::bind(interpret_pfx_dict_add, _1, vm::Dictionary::SetMode::Set, false)); d.def_stack_word("pfxdict@ ", interpret_pfx_dict_get); d.def_ctx_word("dictmap ", interpret_dict_map); - d.def_ctx_word("dictmapext ", interpret_dict_map_ext); - d.def_ctx_word("dictforeach ", interpret_dict_foreach); + d.def_ctx_word("dictmapext ", std::bind(interpret_dict_map_ext, _1, false)); + d.def_ctx_word("idictmapext ", std::bind(interpret_dict_map_ext, _1, true)); + d.def_ctx_word("dictforeach ", std::bind(interpret_dict_foreach, _1, false)); + d.def_ctx_word("idictforeach ", std::bind(interpret_dict_foreach, _1, true)); d.def_ctx_word("dictmerge ", interpret_dict_merge); d.def_ctx_word("dictdiff ", interpret_dict_diff); // slice/bitstring constants diff --git a/crypto/smartcont/dns-manual-code.fc b/crypto/smartcont/dns-manual-code.fc new file mode 100644 index 00000000..7e34a1cc --- /dev/null +++ b/crypto/smartcont/dns-manual-code.fc @@ -0,0 +1,381 @@ +{- + Originally created by: + /------------------------------------------------------------------------\ + | Created for: Telegram (Open Network) Blockchain Contest | + | Task 3: DNS Resolver (Manually controlled) | + >------------------------------------------------------------------------< + | Author: Oleksandr Murzin (tg: @skydev / em: alexhacker64@gmail.com) | + | October 2019 | + \------------------------------------------------------------------------/ +-} + +;;===========================================================================;; +;; Custom ASM instructions ;; +;;===========================================================================;; + +;; Args: s D n | Success: s' x s'' -1 | Failure: s 0 -> s N N 0 +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) + asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT" "NULLSWAPIFNOT"; + +;; Args: x k D n | Success: D' -1 | Failure: D 0 +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) + asm(value key dict key_len) "PFXDICTSET"; + +;; Args: k D n | Success: D' -1 | Failure: D 0 +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) + asm(key dict key_len) "PFXDICTDEL"; + +slice slice_last(slice s, int len) asm "SDCUTLAST"; + +;; Actually, equivalent to dictionaries, provided for clarity +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + +(cell, ()) pfxdict_set_ref(cell dict, int key_len, slice key, cell value) { + throw_unless(33, dict~pfxdict_set?(key_len, key, begin_cell().store_maybe_ref(value).end_cell().begin_parse())); + return (dict, ()); +} + +(slice, cell, slice, int) pfxdict_get_ref(cell dict, int key_len, slice key) { + (slice pfx, slice val, slice tail, int succ) = dict.pfxdict_get?(key_len, key); + cell res = null(); + if (succ) { + res = val~load_maybe_ref(); + } + return (pfx, res, tail, succ); +} + +;;===========================================================================;; +;; Utility functions ;; +;;===========================================================================;; + +(int, int, int, cell, cell) load_data() { + slice cs = get_data().begin_parse(); + var res = (cs~load_uint(32), cs~load_uint(64), cs~load_uint(256), cs~load_dict(), cs~load_dict()); + cs.end_parse(); + return res; +} + +() store_data(int subwallet, int last_cleaned, int public_key, cell root, old_queries) impure { + set_data(begin_cell() + .store_uint(subwallet, 32) + .store_uint(last_cleaned, 64) + .store_uint(public_key, 256) + .store_dict(root) + .store_dict(old_queries) + .end_cell()); +} + +;;===========================================================================;; +;; Internal message handler (Code 0) ;; +;;===========================================================================;; + +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { + ;; not interested at all +} + +;;===========================================================================;; +;; External message handler (Code -1) ;; +;;===========================================================================;; + +{- + External message structure: + [Bytes<512b>:signature] [UInt<32b>:seqno] [UInt<6b>:operation] + [Either b0: inline name (<= 58-x Bytes) or b1: reference-stored name) + x depends on operation + Use of 6-bit op instead of 32-bit allows to save 4 bytes for inline name + Inline [Name] structure: [UInt<6b>:length] [Bytes:data] + Operations (continuation of message): + 00 Contract initialization message (only if seqno = 0) (x=-) + 11 VSet: set specified value to specified subdomain->category (x=2) + [Int<16b>:category] [Name:subdomain] [Cell<1r>:value] + 12 VDel: delete specified subdomain->category (x=2) + [Int<16b>:category] [Name:subdomain] + 21 DSet: replace entire category dictionary of domain with provided (x=0) + [Name:subdomain] [Cell<1r>:new_cat_table] + 22 DDel: delete entire category dictionary of specified domain (x=0) + [Name:subdomain] + 31 TSet: replace ENTIRE DOMAIN TABLE with the provided tree root cell (x=-) + [Cell<1r>:new_domains_table] + 32 TDel: nullify ENTIRE DOMAIN TABLE (x=-) + 51 OSet: replace owner public key with a new one (x=-) + [UInt<256b>:new_public_key] +-} + +(cell, slice) process_op(cell root, slice ops) { + int op = ops~load_uint(6); + int is_name_ref = (ops~load_uint(1) == 1); + + ;; lets assume at this point that special operations 00..09 are handled + throw_if(45, op < 10); + slice name = ops; ;; anything! better do not begin or it costs much gas + cell cat_table = null(); + int cat = 0; + if (op < 20) { + ;; for operations with codes 10..19 category is required + cat = ops~load_int(16); + } + int zeros = 0; + if (op < 30) { + ;; for operations with codes 10..29 name is required + if (is_name_ref) { + ;; name is stored in separate referenced cell + name = ops~load_ref().begin_parse(); + } else { + ;; name is stored inline + int name_len = ops~load_uint(6) * 8; + name = ops~load_bits(name_len); + } + ;; at least one character not counting \0 + throw_unless(38, name.slice_bits() >= 16); + ;; name shall end with \0 + (_, int name_last_byte) = name.slice_last(8).load_uint(8); + throw_unless(40, name_last_byte == 0); + + ;; Multiple zero characters seem to be allowed as per github issue response + ;; Lets change the tactics! + + int loop = -1; + slice cname = name; + ;; better safe then sorry, dont want to catch any of loop bugs + while (loop) { + int lval = cname~load_uint(8); + if (lval == 0) { zeros += 1; } + if (cname.slice_bits() == 0) { loop = 0; } + } + ;; throw_unless(39, zeros == 1); + } + ;; operation with codes 10..19 manipulate category dict + ;; lets try to find it and store into a variable + ;; operations with codes 20..29 replace / delete dict, no need + name = begin_cell().store_uint(zeros, 7).store_slice(name).end_cell().begin_parse(); + if (op < 20) { + ;; lets resolve the name here so as not to duplicate the code + (slice pfx, cell val, slice tail, int succ) = + root.pfxdict_get_ref(1023, name); + if (succ) { + ;; must match EXACTLY to prevent accident changes + throw_unless(35, tail.slice_empty?()); + cat_table = val; + } + ;; otherwise cat_table is null which is reasonable for actions + } + ;; 11 VSet: set specified value to specified subdomain->category + if (op == 11) { + cell new_value = ops~load_maybe_ref(); + if (new_value.cell_null?()) { + cat_table~idict_delete?(16, cat); + } else { + cat_table~idict_set_ref(16, cat, new_value); + } + root~pfxdict_set_ref(1023, name, cat_table); + return (root, ops); + } + ;; 12 VDel: delete specified subdomain->category value + if (op == 12) { + ifnot (cat_table.dict_empty?()) { + cat_table~idict_delete?(16, cat); + root~pfxdict_set_ref(1023, name, cat_table); + } + return (root, ops); + } + ;; 21 DSet: replace entire category dictionary of domain with provided + if (op == 21) { + cell new_cat_table = ops~load_maybe_ref(); + root~pfxdict_set_ref(1023, name, new_cat_table); + return (root, ops); + } + ;; 22 DDel: delete entire category dictionary of specified domain + if (op == 22) { + root~pfxdict_delete?(1023, name); + return (root, ops); + } + ;; 31 TSet: replace ENTIRE DOMAIN TABLE with the provided tree root cell + if (op == 31) { + cell new_tree_root = ops~load_maybe_ref(); + ;; no sanity checks cause they would cost immense gas + return (new_tree_root, ops); + } + ;; 32 TDel: nullify ENTIRE DOMAIN TABLE + if (op == 32) { + return (null(), ops); + } + throw(44); ;; invalid operation + return (root, ops); +} + +cell process_ops(cell root, slice ops) { + int f = -1; + while (f) { + (root, ops) = process_op(root, ops); + if (ops.slice_refs_empty?()) { + f = 0; + } else { + ops = ops~load_ref().begin_parse(); + } + } + return root; +} + +() recv_external(slice in_msg) impure { + ;; Load data + (int stored_subwalet, int last_cleaned, int public_key, cell root, cell old_queries) = load_data(); + + ;; validate signature and seqno + slice signature = in_msg~load_bits(512); + int shash = slice_hash(in_msg); + var (subwallet_id, query_id) = (in_msg~load_uint(32), in_msg~load_uint(64)); + var bound = (now() << 32); + throw_if(35, query_id < bound); + (_, var found?) = old_queries.udict_get?(64, query_id); + throw_if(32, found?); + throw_unless(34, check_signature(shash, signature, public_key)); + accept_message(); ;; message is signed by owner, sanity not guaranteed yet + + + int op = in_msg.preload_uint(6); + ;; 00 Contract initialization message (only if seqno = 0) + if (op == 0) { + ;; noop + } else { if (op == 51) { + in_msg~skip_bits(6); + public_key = in_msg~load_uint(256); + } else { + root = process_ops(root, in_msg); + } } + + bound -= (64 << 32); ;; clean up records expired more than 64 seconds ago + old_queries~udict_set_builder(64, query_id, begin_cell()); + var queries = old_queries; + do { + var (old_queries', i, _, f) = old_queries.udict_delete_get_min(64); + f~touch(); + if (f) { + f = (i < bound); + } + if (f) { + old_queries = old_queries'; + last_cleaned = i; + } + } until (~ f); + + store_data(subwallet_id, last_cleaned, public_key, root, old_queries); +} + +{- + Data structure: + Root cell: [UInt<32b>:seqno] [UInt<256b>:owner_public_key] + [OptRef<1b+1r?>:HashmapCatTable>:domains] + := HashmapE 16 ^DNSRecord + := arbitary? not defined anywhere in documentation or internet! + + STORED DOMAIN NAME SLICE FORMAT: (#ZeroChars<7b>) (Domain name value) + #Zeros allows to simultaneously store, for example, com\0 and com\0google\0 + That will be stored as \1com\0 and \2com\0google\0 (pfx tree has restricitons) + This will allow to resolve more specific requests to subdomains, and resort + to parent domain next resolver lookup if subdomain is not found + com\0goo\0 lookup will, for example look up \2com\0goo\0 and then + \1com\0goo\0 which will return \1com\0 (as per pfx tree) with -1 cat +-} + +;;===========================================================================;; +;; Getter methods ;; +;;===========================================================================;; + +int get_seqno() method_id { ;; Retrieve sequence number + return get_data().begin_parse().preload_uint(32); +} + +;;8m dns-record-value +(int, cell) dnsresolve(slice subdomain, int category) method_id { + cell Null = null(); ;; pseudo-alias + throw_if(30, subdomain.slice_bits() % 8 != 0); ;; malformed input (~ 8n-bit) + if (subdomain.slice_bits() == 0) { return (0, Null); } ;; zero-length input + {- ;; Logic thrown away: return only first ZB-delimited subdomain, + appends \0 if neccessary (not required for pfx, \0 can be ap more eff) + builder b_name = begin_cell(); + slice remaining = subdomain; + int char = remaining~load_uint(8); ;; seems to be the most optimal way + do { + b_name~store_uint(char, 8); + char = remaining~load_uint(8); + } until ((remaining.slice_bits() == 0) | (char == 0)); + if (char == 0) { category = -1; } + if ((remaining.slice_bits() == 0) & (char != 0)) { + b_name~store_uint(0, 8); ;; string was not terminated with zero byte + } + cell c_name = b_name.end_cell(); + slice s_name = c_name.begin_parse(); + -} + (_, int name_last_byte) = subdomain.slice_last(8).load_uint(8); + if ((name_last_byte == 0) & (subdomain.slice_bits() == 8)) { + return (0, Null); ;; zero-length input, but with zero byte + } + slice s_name = subdomain; + if (name_last_byte != 0) { + s_name = begin_cell().store_slice(subdomain) ;; append zero byte + .store_uint(0, 8).end_cell().begin_parse(); + } + (_, _, _, cell root, _) = load_data(); + + ;; Multiple zero characters seem to be allowed as per github issue response + ;; Lets change the tactics! + + int zeros = 0; + int loop = -1; + slice cname = s_name; + ;; better safe then sorry, dont want to catch any of loop bugs + while (loop) { + int lval = cname~load_uint(8); + if (lval == 0) { zeros += 1; } + if (cname.slice_bits() == 0) { loop = 0; } + } + + ;; can't move below, will cause errors! + slice pfx = cname; cell val = null(); + slice tail = cname; int succ = 0; + + while (zeros > 0) { + slice pfname = begin_cell().store_uint(zeros, 7) + .store_slice(s_name).end_cell().begin_parse(); + (pfx, val, tail, succ) = root.pfxdict_get_ref(1023, pfname); + if (succ) { zeros = 1; } ;; break + zeros -= 1; + } + + + zeros = pfx.preload_uint(7); + + if (~ succ) { + return (0, Null); ;; failed to find entry in prefix dictionary + } + if (~ tail.slice_empty?()) { ;; if we have tail then len(pfx) < len(subdomain) + category = -1; ;; incomplete subdomain found, must return next resolver (-1) + } + int pfx_bits = pfx.slice_bits() - 7; + cell cat_table = val; + ;; pfx.slice_bits() will contain 8m, where m is number of bytes in subdomain + ;; COUNTING for the zero byte (if structurally correct: no multiple-ZB keys) + ;; which corresponds to "8m, m=one plus the number of bytes in the subdomain found) + if (category == 0) { + return (pfx_bits, cat_table); ;; return cell with entire dictionary for 0 + } else { + cell cat_found = cat_table.idict_get_ref(16, category); + {- it seems that if subdomain is found but cat is not need to return (8m, Null) + if (cat_found.cell_null?()) { + pfx_bits = 0; ;; to return (0, Null) instead of (8m, Null) + ;; my thoughts about this requirement are in next block comment + } -} + return (pfx_bits, cat_found); ;; no need to unslice and cellize the poor cat now + {- Old logic garbage, replaced with ref functions discovered + ;; dictionary category lookup + (slice cat_value, int cat_found) = cat_table.idict_get?(16, category); + if (~ cat_found) { + ;; we have failed to find the cat :( + return (0, Null); + } + ;; cat is found, turn it's slices into cells + return (pfx.slice_bits(), begin_cell().store_slice(cat_value).end_cell()); + -} + } +} diff --git a/crypto/smartcont/gen-zerostate.fif b/crypto/smartcont/gen-zerostate.fif index 5c4de564..e0a86469 100644 --- a/crypto/smartcont/gen-zerostate.fif +++ b/crypto/smartcont/gen-zerostate.fif @@ -17,10 +17,10 @@ dup dup 31 boc+>B dup Bx. cr dup "basestate0" +suffix +".boc" tuck B>file ."(Initial basechain state saved to file " type .")" cr Bhashu dup =: basestate0_fhash -."file hash=" dup x. space 256 u>B dup B>base64url type cr +."file hash=" dup 64x. space 256 u>B dup B>base64url type cr "basestate0" +suffix +".fhash" B>file hashu dup =: basestate0_rhash -."root hash=" dup x. space 256 u>B dup B>base64url type cr +."root hash=" dup 64x. space 256 u>B dup B>base64url type cr "basestate0" +suffix +".rhash" B>file basestate0_rhash basestate0_fhash now 0 2 32 0 add-std-workchain @@ -128,7 +128,7 @@ GR$666 // balance 2 // mode: create register_smc dup make_special dup constant smc3_addr -."address = " x. cr +."address = " 64x. cr /* * @@ -160,7 +160,7 @@ Masterchain swap // 9 4 1 config.validator_num! 1000 100 13 config.validator_num! // min-stake max-stake min-total-stake max-factor -GR$10000 GR$10000000 GR$500000 sg~10 config.validator_stake_limits! +GR$10000 GR$10000000 GR$500000 sg~3 config.validator_stake_limits! // elected-for elect-start-before elect-end-before stakes-frozen-for // 400000 200000 4000 400000 config.election_params! // 4000 2000 500 1000 config.election_params! // DEBUG diff --git a/crypto/smartcont/show-addr.fif b/crypto/smartcont/show-addr.fif index 506aa492..86b31463 100755 --- a/crypto/smartcont/show-addr.fif +++ b/crypto/smartcont/show-addr.fif @@ -12,9 +12,9 @@ $1 "new-wallet" replace-if-null =: file-base file-base +".addr" dup ."Loading wallet address from " type cr file>B 32 B| dup Blen { 32 B>i@ } { drop Basechain } cond constant wallet_wc 256 B>u@ dup constant wallet_addr -."Source wallet address = " wallet_wc ._ .":" x. cr -wallet_wc wallet_addr 2dup 7 smca>$ ."Non-bounceable address (for init only): " type cr -6 smca>$ ."Bounceable address (for later access): " type cr +."Source wallet address = " wallet_wc swap 2dup .addr cr +."Non-bounceable address (for init only): " 2dup 7 .Addr cr +."Bounceable address (for later access): " 6 .Addr cr file-base +".pk" dup file-exists? { dup file>B dup Blen 32 <> abort"Private key must be exactly 32 bytes long" diff --git a/crypto/smartcont/stdlib.fc b/crypto/smartcont/stdlib.fc index 9041bafe..2dc5c137 100644 --- a/crypto/smartcont/stdlib.fc +++ b/crypto/smartcont/stdlib.fc @@ -31,6 +31,9 @@ int string_hash(slice s) asm "SHA256U"; int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; +(int, int, int) compute_data_size(cell c, int max_cells) asm "CDATASIZE"; +(int, int, int) slice_compute_data_size(slice s, int max_cells) asm "SDATASIZE"; + ;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; () dump_stack() impure asm "DUMPSTK"; diff --git a/crypto/smartcont/validator-elect-req.fif b/crypto/smartcont/validator-elect-req.fif index 215fe9c1..a220b421 100755 --- a/crypto/smartcont/validator-elect-req.fif +++ b/crypto/smartcont/validator-elect-req.fif @@ -22,7 +22,7 @@ def? $5 { @' $5 } { "validator-to-sign.bin" } cond constant output_fname ."Creating a request to participate in validator elections at time " elect_time . ."from smart contract " -1 src_addr 2dup 1 .Addr ." = " .addr ." with maximal stake factor with respect to the minimal stake " max_factor ._ -."/65536 and validator ADNL address " adnl_addr x. cr +."/65536 and validator ADNL address " adnl_addr 64x. cr B{654c5074} elect_time 32 u>B B+ max_factor 32 u>B B+ src_addr 256 u>B B+ adnl_addr 256 u>B B+ dup Bx. cr diff --git a/crypto/smartcont/validator-elect-signed.fif b/crypto/smartcont/validator-elect-signed.fif index 55e3d9dd..4e681f07 100755 --- a/crypto/smartcont/validator-elect-signed.fif +++ b/crypto/smartcont/validator-elect-signed.fif @@ -27,7 +27,7 @@ def? $7 { @' $7 } { "validator-query.boc" } cond constant output_fname ."Creating a request to participate in validator elections at time " elect_time . ."from smart contract " -1 src_addr 2dup 1 .Addr ." = " .addr ." with maximal stake factor with respect to the minimal stake " max_factor ._ -."/65536 and validator ADNL address " adnl_addr x. cr +."/65536 and validator ADNL address " adnl_addr 64x. cr B{654c5074} elect_time 32 u>B B+ max_factor 32 u>B B+ src_addr 256 u>B B+ adnl_addr 256 u>B B+ ."String to sign is: " dup Bx. cr constant to_sign diff --git a/crypto/smc-envelope/HighloadWallet.cpp b/crypto/smc-envelope/HighloadWallet.cpp index 5f540780..e44ce97b 100644 --- a/crypto/smc-envelope/HighloadWallet.cpp +++ b/crypto/smc-envelope/HighloadWallet.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "HighloadWallet.h" #include "GenericAccount.h" @@ -51,7 +51,7 @@ td::Ref HighloadWallet::get_init_message(const td::Ed25519::PrivateKey td::Ref HighloadWallet::make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id, td::uint32 seqno, td::uint32 valid_until, td::Span gifts) noexcept { - CHECK(gifts.size() <= 254); + CHECK(gifts.size() <= max_gifts_size); vm::Dictionary messages(16); for (size_t i = 0; i < gifts.size(); i++) { auto& gift = gifts[i]; @@ -64,7 +64,7 @@ td::Ref HighloadWallet::make_a_gift_message(const td::Ed25519::Private vm::CellBuilder cb; GenericAccount::store_int_message(cb, gift.destination, gramms); cb.store_bytes("\0\0\0\0", 4); - //vm::CellString::store(cb, gift.message, 35 * 8).ensure(); + vm::CellString::store(cb, gift.message, 35 * 8).ensure(); auto message_inner = cb.finalize(); cb = {}; cb.store_long(send_mode, 8).store_ref(message_inner); @@ -123,4 +123,20 @@ td::Result HighloadWallet::get_wallet_id_or_throw() const { return static_cast(cs.fetch_ulong(32)); } +td::Result HighloadWallet::get_public_key() const { + return TRY_VM(get_public_key_or_throw()); +} + +td::Result HighloadWallet::get_public_key_or_throw() const { + if (state_.data.is_null()) { + return td::Status::Error("data is null"); + } + //FIXME use get method + auto cs = vm::load_cell_slice(state_.data); + cs.skip_first(64); + td::SecureString res(td::Ed25519::PublicKey::LENGTH); + cs.fetch_bytes(res.as_mutable_slice().ubegin(), td::narrow_cast(res.size())); + return td::Ed25519::PublicKey(std::move(res)); +} + } // namespace ton diff --git a/crypto/smc-envelope/HighloadWallet.h b/crypto/smc-envelope/HighloadWallet.h index aa1e3a4b..e1db23ef 100644 --- a/crypto/smc-envelope/HighloadWallet.h +++ b/crypto/smc-envelope/HighloadWallet.h @@ -14,29 +14,26 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "smc-envelope/SmartContract.h" +#include "smc-envelope/WalletInterface.h" #include "vm/cells.h" #include "Ed25519.h" #include "block/block.h" #include "vm/cells/CellString.h" namespace ton { -class HighloadWallet : ton::SmartContract { +class HighloadWallet : ton::SmartContract, public WalletInterface { public: explicit HighloadWallet(State state) : ton::SmartContract(std::move(state)) { } static constexpr unsigned max_message_size = vm::CellString::max_bytes; + static constexpr unsigned max_gifts_size = 254; static td::Ref get_init_state(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) noexcept; static td::Ref get_init_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id) noexcept; - struct Gift { - block::StdAddress destination; - td::int64 gramms; - std::string message; - }; static td::Ref make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id, td::uint32 seqno, td::uint32 valid_until, td::Span gifts) noexcept; @@ -47,8 +44,20 @@ class HighloadWallet : ton::SmartContract { td::Result get_seqno() const; td::Result get_wallet_id() const; + td::Result> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 valid_until, + td::Span gifts) const override { + TRY_RESULT(seqno, get_seqno()); + TRY_RESULT(wallet_id, get_wallet_id()); + return make_a_gift_message(private_key, wallet_id, seqno, valid_until, gifts); + } + size_t get_max_gifts_size() const override { + return max_gifts_size; + } + td::Result get_public_key() const override; + private: td::Result get_seqno_or_throw() const; td::Result get_wallet_id_or_throw() const; + td::Result get_public_key_or_throw() const; }; } // namespace ton diff --git a/crypto/smc-envelope/HighloadWalletV2.cpp b/crypto/smc-envelope/HighloadWalletV2.cpp new file mode 100644 index 00000000..823161b9 --- /dev/null +++ b/crypto/smc-envelope/HighloadWalletV2.cpp @@ -0,0 +1,151 @@ +/* + 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 "HighloadWalletV2.h" +#include "GenericAccount.h" +#include "SmartContractCode.h" + +#include "vm/boc.h" +#include "vm/cells/CellString.h" +#include "td/utils/base64.h" + +#include + +namespace ton { +td::optional HighloadWalletV2::guess_revision(const vm::Cell::Hash& code_hash) { + for (td::int32 i = 1; i <= 2; i++) { + if (get_init_code(i)->get_hash() == code_hash) { + return i; + } + } + return {}; +} +td::optional HighloadWalletV2::guess_revision(const block::StdAddress& address, + const td::Ed25519::PublicKey& public_key, + td::uint32 wallet_id) { + for (td::int32 i = 1; i <= 2; i++) { + if (GenericAccount::get_address(address.workchain, get_init_state(public_key, wallet_id, i)) == address) { + return i; + } + } + return {}; +} +td::Ref HighloadWalletV2::get_init_state(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id, + td::int32 revision) noexcept { + auto code = get_init_code(revision); + auto data = get_init_data(public_key, wallet_id); + return GenericAccount::get_init_state(std::move(code), std::move(data)); +} + +td::Ref HighloadWalletV2::get_init_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id, + td::uint32 valid_until) noexcept { + td::uint32 id = -1; + auto append_message = [&](auto&& cb) -> vm::CellBuilder& { + cb.store_long(wallet_id, 32).store_long(valid_until, 32).store_long(id, 32); + CHECK(cb.store_maybe_ref({})); + return cb; + }; + auto signature = private_key.sign(append_message(vm::CellBuilder()).finalize()->get_hash().as_slice()).move_as_ok(); + + return append_message(vm::CellBuilder().store_bytes(signature)).finalize(); +} + +td::Ref HighloadWalletV2::make_a_gift_message(const td::Ed25519::PrivateKey& private_key, + td::uint32 wallet_id, td::uint32 valid_until, + td::Span gifts) noexcept { + CHECK(gifts.size() <= max_gifts_size); + vm::Dictionary messages(16); + for (size_t i = 0; i < gifts.size(); i++) { + auto& gift = gifts[i]; + td::int32 send_mode = 3; + auto gramms = gift.gramms; + if (gramms == -1) { + gramms = 0; + send_mode += 128; + } + vm::CellBuilder cb; + GenericAccount::store_int_message(cb, gift.destination, gramms); + cb.store_bytes("\0\0\0\0", 4); + vm::CellString::store(cb, gift.message, 35 * 8).ensure(); + auto message_inner = cb.finalize(); + cb = {}; + cb.store_long(send_mode, 8).store_ref(message_inner); + auto key = messages.integer_key(td::make_refint(i), 16, false); + messages.set_builder(key.bits(), 16, cb); + } + std::string hash; + { + vm::CellBuilder cb; + CHECK(cb.store_maybe_ref(messages.get_root_cell())); + hash = cb.finalize()->get_hash().as_slice().substr(28, 4).str(); + } + + vm::CellBuilder cb; + cb.store_long(wallet_id, 32).store_long(valid_until, 32).store_bytes(hash); + CHECK(cb.store_maybe_ref(messages.get_root_cell())); + auto message_outer = cb.finalize(); + auto signature = private_key.sign(message_outer->get_hash().as_slice()).move_as_ok(); + return vm::CellBuilder().store_bytes(signature).append_cellslice(vm::load_cell_slice(message_outer)).finalize(); +} + +td::Ref HighloadWalletV2::get_init_code(td::int32 revision) noexcept { + return SmartContractCode::highload_wallet_v2(revision); +} + +vm::CellHash HighloadWalletV2::get_init_code_hash() noexcept { + return get_init_code(0)->get_hash(); +} + +td::Ref HighloadWalletV2::get_init_data(const td::Ed25519::PublicKey& public_key, + td::uint32 wallet_id) noexcept { + vm::CellBuilder cb; + cb.store_long(wallet_id, 32).store_long(0, 64).store_bytes(public_key.as_octet_string()); + CHECK(cb.store_maybe_ref({})); + return cb.finalize(); +} + +td::Result HighloadWalletV2::get_wallet_id() const { + return TRY_VM(get_wallet_id_or_throw()); +} + +td::Result HighloadWalletV2::get_wallet_id_or_throw() const { + if (state_.data.is_null()) { + return 0; + } + //FIXME use get method + auto cs = vm::load_cell_slice(state_.data); + return static_cast(cs.fetch_ulong(32)); +} + +td::Result HighloadWalletV2::get_public_key() const { + return TRY_VM(get_public_key_or_throw()); +} + +td::Result HighloadWalletV2::get_public_key_or_throw() const { + if (state_.data.is_null()) { + return td::Status::Error("data is null"); + } + //FIXME use get method + auto cs = vm::load_cell_slice(state_.data); + cs.skip_first(96); + td::SecureString res(td::Ed25519::PublicKey::LENGTH); + cs.fetch_bytes(res.as_mutable_slice().ubegin(), td::narrow_cast(res.size())); + return td::Ed25519::PublicKey(std::move(res)); +} + +} // namespace ton diff --git a/crypto/smc-envelope/HighloadWalletV2.h b/crypto/smc-envelope/HighloadWalletV2.h new file mode 100644 index 00000000..2b6f99e7 --- /dev/null +++ b/crypto/smc-envelope/HighloadWalletV2.h @@ -0,0 +1,66 @@ +/* + 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 +*/ +#pragma once + +#include "smc-envelope/SmartContract.h" +#include "smc-envelope/WalletInterface.h" +#include "vm/cells.h" +#include "Ed25519.h" +#include "block/block.h" +#include "vm/cells/CellString.h" + +namespace ton { +class HighloadWalletV2 : ton::SmartContract, public WalletInterface { + public: + explicit HighloadWalletV2(State state) : ton::SmartContract(std::move(state)) { + } + static constexpr unsigned max_message_size = vm::CellString::max_bytes; + static constexpr unsigned max_gifts_size = 254; + static td::Ref get_init_state(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id, + td::int32 revision) noexcept; + static td::Ref get_init_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id, + td::uint32 valid_until) noexcept; + + static td::Ref make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id, + td::uint32 valid_until, td::Span gifts) noexcept; + + static td::Ref get_init_code(td::int32 revision) noexcept; + static vm::CellHash get_init_code_hash() noexcept; + static td::Ref get_init_data(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) noexcept; + static td::optional guess_revision(const vm::Cell::Hash& code_hash); + static td::optional guess_revision(const block::StdAddress& address, + const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id); + + td::Result get_wallet_id() const; + + td::Result> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 valid_until, + td::Span gifts) const override { + TRY_RESULT(wallet_id, get_wallet_id()); + return make_a_gift_message(private_key, wallet_id, valid_until, gifts); + } + size_t get_max_gifts_size() const override { + return max_gifts_size; + } + td::Result get_public_key() const override; + + private: + td::Result get_wallet_id_or_throw() const; + td::Result get_public_key_or_throw() const; +}; +} // namespace ton diff --git a/crypto/smc-envelope/ManualDns.cpp b/crypto/smc-envelope/ManualDns.cpp new file mode 100644 index 00000000..c932f49d --- /dev/null +++ b/crypto/smc-envelope/ManualDns.cpp @@ -0,0 +1,542 @@ +/* + 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 2019-2020 Telegram Systems LLP +*/ +#include "ManualDns.h" + +#include "smc-envelope/SmartContractCode.h" + +#include "vm/dict.h" + +#include "td/utils/format.h" +#include "td/utils/overloaded.h" +#include "td/utils/Parser.h" +#include "td/utils/Random.h" + +#include "block/block-auto.h" +#include "block/block-parse.h" + +namespace ton { + +//proto_list_nil$0 = ProtoList; +//proto_list_next$1 head:Protocol tail:ProtoList = ProtoList; +//proto_http#4854 = Protocol; + +//cap_list_nil$0 = SmcCapList; +//cap_list_next$1 head:SmcCapability tail:SmcCapList = SmcCapList; +//cap_method_seqno#5371 = SmcCapability; +//cap_method_pubkey#71f4 = SmcCapability; +//cap_is_wallet#2177 = SmcCapability; +//cap_name#ff name:Text = SmcCapability; +// +td::Result> DnsInterface::EntryData::as_cell() const { + td::Ref res; + td::Status error; + data.visit(td::overloaded( + [&](const EntryDataText& text) { + block::gen::DNSRecord::Record_dns_text dns; + vm::CellBuilder cb; + vm::CellText::store(cb, text.text); + dns.x = vm::load_cell_slice_ref(cb.finalize()); + tlb::pack_cell(res, dns); + }, + [&](const EntryDataNextResolver& resolver) { + block::gen::DNSRecord::Record_dns_next_resolver dns; + vm::CellBuilder cb; + block::tlb::t_MsgAddressInt.store_std_address(cb, resolver.resolver.workchain, resolver.resolver.addr); + dns.resolver = vm::load_cell_slice_ref(cb.finalize()); + tlb::pack_cell(res, dns); + }, + [&](const EntryDataAdnlAddress& adnl_address) { + block::gen::DNSRecord::Record_dns_adnl_address dns; + dns.adnl_addr = adnl_address.adnl_address; + dns.flags = 0; + tlb::pack_cell(res, dns); + }, + [&](const EntryDataSmcAddress& smc_address) { + block::gen::DNSRecord::Record_dns_smc_address dns; + vm::CellBuilder cb; + block::tlb::t_MsgAddressInt.store_std_address(cb, smc_address.smc_address.workchain, + smc_address.smc_address.addr); + dns.smc_addr = vm::load_cell_slice_ref(cb.finalize()); + tlb::pack_cell(res, dns); + })); + if (error.is_error()) { + return error; + } + if (res.is_null()) { + return td::Status::Error("Entry data is emtpy"); + } + return res; + //dns_text#1eda _:Text = DNSRecord; + + //dns_next_resolver#ba93 resolver:MsgAddressInt = DNSRecord; // usually in record #-1 + //dns_adnl_address#ad01 adnl_addr:bits256 flags:(## 8) { flags <= 1 } proto_list:flags . 0?ProtoList = DNSRecord; // often in record #2 + + //dns_smc_address#9fd3 smc_addr:MsgAddressInt flags:(## 8) { flags <= 1 } cap_list:flags . 0?SmcCapList = DNSRecord; // often in record #1 +} + +td::Result DnsInterface::EntryData::from_cellslice(vm::CellSlice& cs) { + switch (block::gen::t_DNSRecord.get_tag(cs)) { + case block::gen::DNSRecord::dns_text: { + block::gen::DNSRecord::Record_dns_text dns; + tlb::unpack(cs, dns); + TRY_RESULT(text, vm::CellText::load(dns.x.write())); + return EntryData::text(std::move(text)); + } + case block::gen::DNSRecord::dns_next_resolver: { + block::gen::DNSRecord::Record_dns_next_resolver dns; + tlb::unpack(cs, dns); + ton::WorkchainId wc; + ton::StdSmcAddress addr; + if (!block::tlb::t_MsgAddressInt.extract_std_address(dns.resolver, wc, addr)) { + return td::Status::Error("Invalid address"); + } + return EntryData::next_resolver(block::StdAddress(wc, addr)); + } + case block::gen::DNSRecord::dns_adnl_address: { + block::gen::DNSRecord::Record_dns_adnl_address dns; + tlb::unpack(cs, dns); + return EntryData::adnl_address(dns.adnl_addr); + } + case block::gen::DNSRecord::dns_smc_address: { + block::gen::DNSRecord::Record_dns_smc_address dns; + tlb::unpack(cs, dns); + ton::WorkchainId wc; + ton::StdSmcAddress addr; + if (!block::tlb::t_MsgAddressInt.extract_std_address(dns.smc_addr, wc, addr)) { + return td::Status::Error("Invalid address"); + } + return EntryData::smc_address(block::StdAddress(wc, addr)); + } + } + return td::Status::Error("Unknown entry data"); +} + +td::Result> DnsInterface::resolve(td::Slice name, td::int32 category) const { + TRY_RESULT(raw_entries, resolve_raw(name, category)); + std::vector entries; + entries.reserve(raw_entries.size()); + for (auto& raw_entry : raw_entries) { + Entry entry; + entry.name = std::move(raw_entry.name); + entry.category = raw_entry.category; + auto cs = vm::load_cell_slice(raw_entry.data); + TRY_RESULT(data, EntryData::from_cellslice(cs)); + entry.data = std::move(data); + entries.push_back(std::move(entry)); + } + return entries; +} + +/* + External message structure: + [Bytes<512b>:signature] [UInt<32b>:seqno] [UInt<6b>:operation] + [Either b0: inline name (<= 58-x Bytes) or b1: reference-stored name) + x depends on operation + Use of 6-bit op instead of 32-bit allows to save 4 bytes for inline name + Inline [Name] structure: [UInt<6b>:length] [Bytes:data] + Operations (continuation of message): + 00 Contract initialization message (only if seqno = 0) (x=-) + 31 TSet: replace ENTIRE DOMAIN TABLE with the provided tree root cell (x=-) + [Cell<1r>:new_domains_table] + 51 OSet: replace owner public key with a new one (x=-) + [UInt<256b>:new_public_key] +*/ +// creation +td::Ref ManualDns::create(td::Ref data) { + return td::Ref(true, State{ton::SmartContractCode::dns_manual(), std::move(data)}); +} +td::Ref ManualDns::create(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) { + return create(create_init_data_fast(public_key, wallet_id)); +} + +td::Result ManualDns::get_wallet_id() const { + return TRY_VM(get_wallet_id_or_throw()); +} +td::Result ManualDns::get_wallet_id_or_throw() const { + if (state_.data.is_null()) { + return 0; + } + //FIXME use get method + return static_cast(vm::load_cell_slice(state_.data).fetch_ulong(32)); +} + +td::Result> ManualDns::create_set_value_unsigned(td::int16 category, td::Slice name, + td::Ref data) const { + //11 VSet: set specified value to specified subdomain->category (x=2) + //[Int<16b>:category] [Name:subdomain] [Cell<1r>:value] + vm::CellBuilder cb; + cb.store_long(11, 6); + if (name.size() <= 58 - 2) { + cb.store_long(0, 1); + cb.store_long(category, 16); + cb.store_long(name.size(), 6); + cb.store_bytes(name); + } else { + cb.store_long(1, 1); + cb.store_long(category, 16); + cb.store_ref(vm::CellBuilder().store_bytes(name).finalize()); + } + cb.store_maybe_ref(std::move(data)); + return cb.finalize(); +} +td::Result> ManualDns::create_delete_value_unsigned(td::int16 category, td::Slice name) const { + //12 VDel: delete specified subdomain->category (x=2) + //[Int<16b>:category] [Name:subdomain] + vm::CellBuilder cb; + cb.store_long(12, 6); + if (name.size() <= 58 - 2) { + cb.store_long(0, 1); + cb.store_long(category, 16); + cb.store_long(name.size(), 6); + cb.store_bytes(name); + } else { + cb.store_long(1, 1); + cb.store_long(category, 16); + cb.store_ref(vm::CellBuilder().store_bytes(name).finalize()); + } + cb.store_long(0, 1); + return cb.finalize(); +} + +td::Result> ManualDns::create_delete_all_unsigned() const { + // 32 TDel: nullify ENTIRE DOMAIN TABLE (x=-) + vm::CellBuilder cb; + cb.store_long(32, 6); + cb.store_long(0, 1); + return cb.finalize(); +} + +td::Result> ManualDns::create_set_all_unsigned(td::Span entries) const { + vm::PrefixDictionary pdict(1023); + for (auto& action : entries) { + auto name_key = encode_name(action.name); + int zero_cnt = 0; + for (auto c : name_key) { + if (c == 0) { + zero_cnt++; + } + } + auto new_name_key = vm::load_cell_slice(vm::CellBuilder().store_long(zero_cnt, 7).store_bytes(name_key).finalize()); + auto ptr = new_name_key.data_bits(); + auto ptr_size = new_name_key.size(); + auto o_dict = pdict.lookup(ptr, ptr_size); + td::Ref dict_root; + if (o_dict.not_null()) { + o_dict->prefetch_maybe_ref(dict_root); + } + vm::Dictionary dict(dict_root, 16); + if (!action.data.value().is_null()) { + auto key = dict.integer_key(td::make_refint(action.category), 16); + dict.set_ref(key.bits(), 16, action.data.value()); + } + pdict.set(ptr, ptr_size, dict.get_root()); + } + + vm::CellBuilder cb; + cb.store_long(31, 6); + cb.store_long(1, 1); + + cb.store_maybe_ref(pdict.get_root_cell()); + + return cb.finalize(); +} + +//21 DSet: replace entire category dictionary of domain with provided (x=0) +//[Name:subdomain] [Cell<1r>:new_cat_table] +//22 DDel: delete entire category dictionary of specified domain (x=0) +//[Name:subdomain] +td::Result> ManualDns::create_delete_name_unsigned(td::Slice name) const { + vm::CellBuilder cb; + cb.store_long(22, 6); + if (name.size() <= 58) { + cb.store_long(0, 1); + cb.store_long(name.size(), 6); + cb.store_bytes(name); + } else { + cb.store_long(1, 1); + cb.store_ref(vm::CellBuilder().store_bytes(name).finalize()); + } + cb.store_long(0, 1); + return cb.finalize(); +} +td::Result> ManualDns::create_set_name_unsigned(td::Slice name, td::Span entries) const { + vm::CellBuilder cb; + cb.store_long(21, 6); + if (name.size() <= 58) { + cb.store_long(0, 1); + cb.store_long(name.size(), 6); + cb.store_bytes(name); + } else { + cb.store_long(1, 1); + cb.store_ref(vm::CellBuilder().store_bytes(name).finalize()); + } + + vm::Dictionary dict(16); + + for (auto& action : entries) { + if (action.data.value().is_null()) { + continue; + } + auto key = dict.integer_key(td::make_refint(action.category), 16); + dict.set_ref(key.bits(), 16, action.data.value()); + } + cb.store_maybe_ref(dict.get_root_cell()); + + return cb.finalize(); +} + +td::Result> ManualDns::prepare(td::Ref data, td::uint32 valid_until) const { + TRY_RESULT(wallet_id, get_wallet_id()); + auto hash = data->get_hash().as_slice().substr(28, 4).str(); + + vm::CellBuilder cb; + cb.store_long(wallet_id, 32).store_long(valid_until, 32); + //cb.store_bytes(hash); + cb.store_long(td::Random::secure_uint32(), 32); + cb.append_cellslice(vm::load_cell_slice(data)); + return cb.finalize(); +} + +td::Result> ManualDns::sign(const td::Ed25519::PrivateKey& private_key, td::Ref data) { + auto signature = private_key.sign(data->get_hash().as_slice()).move_as_ok(); + vm::CellBuilder cb; + cb.store_bytes(signature.as_slice()); + cb.append_cellslice(vm::load_cell_slice(data)); + return cb.finalize(); +} + +td::Result> ManualDns::create_init_query(const td::Ed25519::PrivateKey& private_key, + td::uint32 valid_until) const { + vm::CellBuilder cb; + cb.store_long(0, 6); + cb.store_long(0, 1); + + TRY_RESULT(prepared, prepare(cb.finalize(), valid_until)); + return sign(private_key, std::move(prepared)); +} + +td::Ref ManualDns::create_init_data_fast(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) { + vm::CellBuilder cb; + cb.store_long(wallet_id, 32).store_long(0, 64).store_bytes(public_key.as_octet_string()); + CHECK(cb.store_maybe_ref({})); + CHECK(cb.store_maybe_ref({})); + return cb.finalize(); +} + +size_t ManualDns::get_max_name_size() const { + return 128; +} + +td::Result> ManualDns::resolve_raw(td::Slice name, td::int32 category_big) const { + return TRY_VM(resolve_raw_or_throw(name, category_big)); +} +td::Result> ManualDns::resolve_raw_or_throw(td::Slice name, + td::int32 category_big) const { + TRY_RESULT(category, td::narrow_cast_safe(category_big)); + if (name.size() > get_max_name_size()) { + return td::Status::Error("Name is too long"); + } + auto encoded_name = encode_name(name); + auto res = run_get_method( + "dnsresolve", + {vm::load_cell_slice_ref(vm::CellBuilder().store_bytes(encoded_name).finalize()), td::make_refint(category)}); + if (!res.success) { + return td::Status::Error("get method failed"); + } + std::vector vec; + auto data = res.stack.write().pop_maybe_cell(); + if (data.is_null()) { + return vec; + } + size_t prefix_size = res.stack.write().pop_smallint_range((int)encoded_name.size() * 8); + if (prefix_size % 8 != 0) { + return td::Status::Error("Prefix size is not divisible by 8"); + } + prefix_size /= 8; + if (prefix_size < encoded_name.size()) { + vec.push_back({decode_name(td::Slice(encoded_name).substr(0, prefix_size)), -1, data}); + } else { + if (category == 0) { + vm::Dictionary dict(std::move(data), 16); + dict.check_for_each([&](auto cs, auto x, auto y) { + td::BigInt256 cat; + cat.import_bits(x, y, true); + vec.push_back({name.str(), td::narrow_cast(cat.to_long()), cs->prefetch_ref()}); + return true; + }); + } else { + vec.push_back({name.str(), category, data}); + } + } + + return vec; +} + +td::Result> ManualDns::create_update_query(CombinedActions& combined) const { + if (combined.name.empty()) { + if (combined.actions.value().empty()) { + return create_delete_all_unsigned(); + } + return create_set_all_unsigned(combined.actions.value()); + } + if (combined.category == 0) { + if (!combined.actions) { + return create_delete_name_unsigned(encode_name(combined.name)); + } + return create_set_name_unsigned(encode_name(combined.name), combined.actions.value()); + } + CHECK(combined.actions.value().size() == 1); + auto& action = combined.actions.value()[0]; + if (action.data) { + return create_set_value_unsigned(action.category, encode_name(action.name), action.data.value()); + } else { + return create_delete_value_unsigned(action.category, encode_name(action.name)); + } +} + +td::Result> ManualDns::create_update_query(td::Ed25519::PrivateKey& pk, td::Span actions, + td::uint32 valid_until) const { + auto combined = combine_actions(actions); + std::vector> queries; + for (auto& c : combined) { + TRY_RESULT(q, create_update_query(c)); + queries.push_back(std::move(q)); + } + + td::Ref combined_query; + for (auto& query : td::reversed(queries)) { + if (combined_query.is_null()) { + combined_query = std::move(query); + } else { + auto next = vm::load_cell_slice(combined_query); + combined_query = vm::CellBuilder() + .append_cellslice(vm::load_cell_slice(query)) + .store_ref(vm::CellBuilder().append_cellslice(next).finalize()) + .finalize(); + } + } + + TRY_RESULT(prepared, prepare(std::move(combined_query), valid_until)); + return sign(pk, std::move(prepared)); +} + +std::string ManualDns::encode_name(td::Slice name) { + std::string res; + while (!name.empty()) { + auto pos = name.rfind('.'); + if (pos == name.npos) { + res += name.str(); + name = td::Slice(); + } else { + res += name.substr(pos + 1).str(); + name.truncate(pos); + } + res += '\0'; + } + return res; +} + +std::string ManualDns::decode_name(td::Slice name) { + std::string res; + if (!name.empty() && name.back() == 0) { + name.remove_suffix(1); + } + while (!name.empty()) { + auto pos = name.rfind('\0'); + if (!res.empty()) { + res += '.'; + } + if (pos == name.npos) { + res += name.str(); + name = td::Slice(); + } else { + res += name.substr(pos + 1).str(); + name.truncate(pos); + } + } + return res; +} + +std::string ManualDns::serialize_data(const EntryData& data) { + std::string res; + data.data.visit(td::overloaded([&](const ton::ManualDns::EntryDataText& text) { res = "UNSUPPORTED"; }, + [&](const ton::ManualDns::EntryDataNextResolver& resolver) { res = "UNSUPPORTED"; }, + [&](const ton::ManualDns::EntryDataAdnlAddress& adnl_address) { res = "UNSUPPORTED"; }, + [&](const ton::ManualDns::EntryDataSmcAddress& text) { res = "UNSUPPORTED"; })); + return res; +} + +td::Result> ManualDns::parse_data(td::Slice cmd) { + td::ConstParser parser(cmd); + parser.skip_whitespaces(); + auto type = parser.read_till(':'); + parser.advance(1); + if (type == "TEXT") { + return ManualDns::EntryData::text(parser.read_all().str()); + } else if (type == "DELETED") { + return {}; + } + return td::Status::Error(PSLICE() << "Unknown entry type: " << type); +} + +td::Result ManualDns::parse_line(td::Slice cmd) { + // Cmd = + // set name category data | + // delete.name name | + // delete.all + // data = + // TEXT: | + // DELETED + td::ConstParser parser(cmd); + auto type = parser.read_word(); + if (type == "set") { + auto name = parser.read_word(); + auto category_str = parser.read_word(); + TRY_RESULT(category, td::to_integer_safe(category_str)); + TRY_RESULT(data, parse_data(parser.read_all())); + return ManualDns::ActionExt{name.str(), category, std::move(data)}; + } else if (type == "delete.name") { + auto name = parser.read_word(); + if (name.empty()) { + return td::Status::Error("name is empty"); + } + return ManualDns::ActionExt{name.str(), 0, {}}; + } else if (type == "delete.all") { + return ManualDns::ActionExt{"", 0, {}}; + } + return td::Status::Error(PSLICE() << "Unknown command: " << type); +} + +td::Result> ManualDns::parse(td::Slice cmd) { + auto lines = td::full_split(cmd, '\n'); + std::vector res; + res.reserve(lines.size()); + for (auto& line : lines) { + td::ConstParser parser(line); + parser.skip_whitespaces(); + if (parser.empty()) { + continue; + } + TRY_RESULT(action, parse_line(parser.read_all())); + res.push_back(std::move(action)); + } + return res; +} + +} // namespace ton diff --git a/crypto/smc-envelope/ManualDns.h b/crypto/smc-envelope/ManualDns.h new file mode 100644 index 00000000..08db188e --- /dev/null +++ b/crypto/smc-envelope/ManualDns.h @@ -0,0 +1,339 @@ +/* + 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 2019-2020 Telegram Systems LLP +*/ +#pragma once +#include "td/utils/Variant.h" +#include "td/utils/Status.h" +#include "vm/cells/Cell.h" +#include "vm/cells/CellSlice.h" +#include "vm/cells/CellString.h" + +#include "smc-envelope/SmartContract.h" + +#include "Ed25519.h" + +#include + +namespace ton { +class DnsInterface { + public: + struct EntryDataText { + std::string text; + bool operator==(const EntryDataText& other) const { + return text == other.text; + } + }; + + struct EntryDataNextResolver { + block::StdAddress resolver; + bool operator==(const EntryDataNextResolver& other) const { + return resolver == other.resolver; + } + }; + + struct EntryDataAdnlAddress { + ton::Bits256 adnl_address; + // TODO: proto + bool operator==(const EntryDataAdnlAddress& other) const { + return adnl_address == other.adnl_address; + } + }; + + struct EntryDataSmcAddress { + block::StdAddress smc_address; + bool operator==(const EntryDataSmcAddress& other) const { + return smc_address == other.smc_address; + } + // TODO: capability + }; + + struct EntryData { + enum Type { Empty, Text, NextResolver, AdnlAddress, SmcAddress } type{Empty}; + td::Variant data; + + static EntryData text(std::string text) { + return {Text, EntryDataText{text}}; + } + static EntryData next_resolver(block::StdAddress resolver) { + return {NextResolver, EntryDataNextResolver{resolver}}; + } + static EntryData adnl_address(ton::Bits256 adnl_address) { + return {AdnlAddress, EntryDataAdnlAddress{adnl_address}}; + } + static EntryData smc_address(block::StdAddress smc_address) { + return {SmcAddress, EntryDataSmcAddress{smc_address}}; + } + + bool operator==(const EntryData& other) const { + return data == other.data; + } + friend td::StringBuilder& operator<<(td::StringBuilder& sb, const EntryData& data) { + switch (data.type) { + case Type::Empty: + return sb << ""; + case Type::Text: + return sb << "text{" << data.data.get().text << "}"; + case Type::NextResolver: + return sb << "next{" << data.data.get().resolver.rserialize() << "}"; + case Type::AdnlAddress: + return sb << "adnl{" << data.data.get().adnl_address.to_hex() << "}"; + case Type::SmcAddress: + return sb << "smc{" << data.data.get().smc_address.rserialize() << "}"; + } + return sb << ""; + } + + td::Result> as_cell() const; + static td::Result from_cellslice(vm::CellSlice& cs); + }; + + struct Entry { + std::string name; + td::int16 category; + EntryData data; + auto key() const { + return std::tie(name, category); + } + bool operator<(const Entry& other) const { + return key() < other.key(); + } + bool operator==(const Entry& other) const { + return key() == other.key() && data == other.data; + } + friend td::StringBuilder& operator<<(td::StringBuilder& sb, const Entry& entry) { + sb << entry.name << ":" << entry.category << ":" << entry.data; + return sb; + } + }; + struct RawEntry { + std::string name; + td::int16 category; + td::Ref data; + }; + + struct ActionExt { + std::string name; + td::int16 category; + td::optional data; + static td::Result parse(td::Slice); + }; + + struct Action { + std::string name; + td::int16 category; + td::optional> data; + + bool does_create_category() const { + CHECK(!name.empty()); + CHECK(category != 0); + return static_cast(data); + } + bool does_change_empty() const { + CHECK(!name.empty()); + CHECK(category != 0); + return static_cast(data) && data.value().not_null(); + } + void make_non_empty() { + CHECK(!name.empty()); + CHECK(category != 0); + if (!data) { + data = td::Ref(); + } + } + friend td::StringBuilder& operator<<(td::StringBuilder& sb, const Action& action) { + sb << action.name << ":" << action.category << ":"; + if (action.data) { + if (action.data.value().is_null()) { + sb << ""; + } else { + sb << ""; + } + } else { + sb << ""; + } + return sb; + } + }; + + virtual ~DnsInterface() { + } + virtual size_t get_max_name_size() const = 0; + virtual td::Result> resolve_raw(td::Slice name, td::int32 category) const = 0; + virtual td::Result> create_update_query( + td::Ed25519::PrivateKey& pk, td::Span actions, + td::uint32 valid_until = std::numeric_limits::max()) const = 0; + + td::Result> resolve(td::Slice name, td::int32 category) const; +}; + +class ManualDns : public ton::SmartContract, public DnsInterface { + public: + ManualDns(State state) : SmartContract(std::move(state)) { + } + + ManualDns* make_copy() const override { + return new ManualDns{state_}; + } + + // creation + static td::Ref create(State state) { + return td::Ref(true, std::move(state)); + } + static td::Ref create(td::Ref data = {}); + static td::Ref create(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id); + + static std::string serialize_data(const EntryData& data); + static td::Result> parse_data(td::Slice cmd); + static td::Result parse_line(td::Slice cmd); + static td::Result> parse(td::Slice cmd); + + td::Ref create_init_data(const td::Ed25519::PublicKey& public_key, td::uint32 valid_until) const { + return create_init_data_fast(public_key, valid_until); + } + + td::Result get_wallet_id() const; + td::Result get_wallet_id_or_throw() const; + + td::Result> create_set_value_unsigned(td::int16 category, td::Slice name, + td::Ref data) const; + td::Result> create_delete_value_unsigned(td::int16 category, td::Slice name) const; + td::Result> create_delete_all_unsigned() const; + td::Result> create_set_all_unsigned(td::Span entries) const; + td::Result> create_delete_name_unsigned(td::Slice name) const; + td::Result> create_set_name_unsigned(td::Slice name, td::Span entries) const; + + td::Result> prepare(td::Ref data, td::uint32 valid_until) const; + + static td::Result> sign(const td::Ed25519::PrivateKey& private_key, td::Ref data); + static td::Ref create_init_data_fast(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id); + + size_t get_max_name_size() const override; + td::Result> resolve_raw(td::Slice name, td::int32 category_big) const override; + td::Result> resolve_raw_or_throw(td::Slice name, td::int32 category_big) const; + + td::Result> create_init_query( + const td::Ed25519::PrivateKey& private_key, + td::uint32 valid_until = std::numeric_limits::max()) const; + td::Result> create_update_query( + td::Ed25519::PrivateKey& pk, td::Span actions, + td::uint32 valid_until = std::numeric_limits::max()) const override; + + static std::string encode_name(td::Slice name); + static std::string decode_name(td::Slice name); + + template + struct CombinedActions { + std::string name; + td::int16 category{0}; + td::optional> actions; + friend td::StringBuilder& operator<<(td::StringBuilder& sb, const CombinedActions& action) { + sb << action.name << ":" << action.category << ":"; + if (action.actions) { + sb << "" << action.actions.value().size(); + } else { + sb << ""; + } + return sb; + } + }; + + template + static std::vector> combine_actions(td::Span actions) { + struct Info { + std::set known_category; + std::vector actions; + bool closed{false}; + bool non_empty{false}; + }; + + std::map mp; + std::vector> res; + for (auto& action : td::reversed(actions)) { + if (action.name.empty()) { + CombinedActions set_all; + set_all.actions = std::vector(); + for (auto& it : mp) { + for (auto& e : it.second.actions) { + if (e.does_create_category()) { + set_all.actions.value().push_back(std::move(e)); + } + } + } + res.push_back(std::move(set_all)); + return res; + } + + Info& info = mp[action.name]; + if (info.closed) { + continue; + } + if (action.category != 0 && action.does_create_category()) { + info.non_empty = true; + } + if (!info.known_category.insert(action.category).second) { + continue; + } + if (action.category == 0) { + info.closed = true; + auto old_actions = std::move(info.actions); + bool is_empty = true; + for (auto& action : old_actions) { + if (is_empty && action.does_create_category()) { + info.actions.push_back(std::move(action)); + is_empty = false; + } else if (!is_empty && action.does_change_empty()) { + info.actions.push_back(std::move(action)); + } + } + } else { + info.actions.push_back(std::move(action)); + } + } + + for (auto& it : mp) { + auto& info = it.second; + if (info.closed) { + CombinedActions ca; + ca.name = it.first; + ca.category = 0; + if (!info.actions.empty() || info.non_empty) { + ca.actions = std::move(info.actions); + } + res.push_back(std::move(ca)); + } else { + bool need_non_empty = info.non_empty; + for (auto& a : info.actions) { + if (need_non_empty) { + a.make_non_empty(); + need_non_empty = false; + } + CombinedActions ca; + ca.name = a.name; + ca.category = a.category; + ca.actions = std::vector(); + ca.actions.value().push_back(std::move(a)); + res.push_back(ca); + } + } + } + return res; + } + td::Result> create_update_query(CombinedActions& combined) const; +}; + +} // namespace ton diff --git a/crypto/smc-envelope/SmartContract.cpp b/crypto/smc-envelope/SmartContract.cpp index 02964a7c..17477de9 100644 --- a/crypto/smc-envelope/SmartContract.cpp +++ b/crypto/smc-envelope/SmartContract.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "SmartContract.h" @@ -24,7 +24,7 @@ #include "block/block-auto.h" #include "vm/cellslice.h" #include "vm/cp0.h" -#include "vm/continuation.h" +#include "vm/vm.h" #include "td/utils/crypto.h" diff --git a/crypto/smc-envelope/SmartContract.h b/crypto/smc-envelope/SmartContract.h index d5864361..14b5b947 100644 --- a/crypto/smc-envelope/SmartContract.h +++ b/crypto/smc-envelope/SmartContract.h @@ -14,13 +14,13 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "vm/cells.h" #include "vm/stack.hpp" -#include "vm/continuation.h" +#include "vm/vm.h" #include "td/utils/optional.h" #include "td/utils/crypto.h" diff --git a/crypto/smc-envelope/SmartContractCode.cpp b/crypto/smc-envelope/SmartContractCode.cpp index e61cab24..c97a689c 100644 --- a/crypto/smc-envelope/SmartContractCode.cpp +++ b/crypto/smc-envelope/SmartContractCode.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "SmartContractCode.h" @@ -25,6 +25,11 @@ namespace ton { namespace { +constexpr static int WALLET_REVISION = 2; +constexpr static int WALLET2_REVISION = 2; +constexpr static int WALLET3_REVISION = 2; +constexpr static int HIGHLOAD_WALLET_REVISION = 2; +constexpr static int HIGHLOAD_WALLET2_REVISION = 2; const auto& get_map() { static auto map = [] { std::map, std::less<>> map; @@ -36,6 +41,58 @@ const auto& get_map() { #include "smartcont/auto/simple-wallet-code.cpp" #include "smartcont/auto/wallet-code.cpp" #include "smartcont/auto/highload-wallet-code.cpp" +#include "smartcont/auto/highload-wallet-v2-code.cpp" +#include "smartcont/auto/dns-manual-code.cpp" + + with_tvm_code("highload-wallet-r1", + "te6ccgEBBgEAhgABFP8A9KQT9KDyyAsBAgEgAgMCAUgEBQC88oMI1xgg0x/TH9Mf+CMTu/Jj7UTQ0x/TH9P/" + "0VEyuvKhUUS68qIE+QFUEFX5EPKj9ATR+AB/jhghgBD0eG+hb6EgmALTB9QwAfsAkTLiAbPmWwGkyMsfyx/L/" + "8ntVAAE0DAAEaCZL9qJoa4WPw=="); + with_tvm_code("highload-wallet-r2", + "te6ccgEBCAEAmQABFP8A9KQT9LzyyAsBAgEgAgMCAUgEBQC88oMI1xgg0x/TH9Mf+CMTu/Jj7UTQ0x/TH9P/" + "0VEyuvKhUUS68qIE+QFUEFX5EPKj9ATR+AB/jhghgBD0eG+hb6EgmALTB9QwAfsAkTLiAbPmWwGkyMsfyx/L/" + "8ntVAAE0DACAUgGBwAXuznO1E0NM/MdcL/4ABG4yX7UTQ1wsfg="); + with_tvm_code("highload-wallet-v2-r1", + "te6ccgEBBwEA1gABFP8A9KQT9KDyyAsBAgEgAgMCAUgEBQHu8oMI1xgg0x/TP/gjqh9TILnyY+1E0NMf0z/T//" + "QE0VNggED0Dm+hMfJgUXO68qIH+QFUEIf5EPKjAvQE0fgAf44YIYAQ9HhvoW+" + "hIJgC0wfUMAH7AJEy4gGz5luDJaHIQDSAQPRDiuYxyBLLHxPLP8v/9ADJ7VQGAATQMABBoZfl2omhpj5jpn+n/" + "mPoCaKkQQCB6BzfQmMktv8ld0fFADgggED0lm+hb6EyURCUMFMDud4gkzM2AZIyMOKz"); + with_tvm_code("highload-wallet-v2-r2", + "te6ccgEBCQEA6QABFP8A9KQT9LzyyAsBAgEgAgMCAUgEBQHu8oMI1xgg0x/TP/gjqh9TILnyY+1E0NMf0z/T//" + "QE0VNggED0Dm+hMfJgUXO68qIH+QFUEIf5EPKjAvQE0fgAf44YIYAQ9HhvoW+" + "hIJgC0wfUMAH7AJEy4gGz5luDJaHIQDSAQPRDiuYxyBLLHxPLP8v/9ADJ7VQIAATQMAIBIAYHABe9nOdqJoaa+Y64X/" + "wAQb5fl2omhpj5jpn+n/mPoCaKkQQCB6BzfQmMktv8ld0fFAA4IIBA9JZvoW+hMlEQlDBTA7neIJMzNgGSMjDisw=="); + with_tvm_code("simple-wallet-r1", + "te6ccgEEAQEAAAAAUwAAov8AIN0gggFMl7qXMO1E0NcLH+Ck8mCBAgDXGCDXCx/tRNDTH9P/" + "0VESuvKhIvkBVBBE+RDyovgAAdMfMSDXSpbTB9QC+wDe0aTIyx/L/8ntVA=="); + with_tvm_code("simple-wallet-r2", + "te6ccgEBAQEAXwAAuv8AIN0gggFMl7ohggEznLqxnHGw7UTQ0x/XC//jBOCk8mCBAgDXGCDXCx/tRNDTH9P/" + "0VESuvKhIvkBVBBE+RDyovgAAdMfMSDXSpbTB9QC+wDe0aTIyx/L/8ntVA=="); + with_tvm_code("wallet-r1", + "te6ccgEBAQEAVwAAqv8AIN0gggFMl7qXMO1E0NcLH+Ck8mCDCNcYINMf0x8B+CO78mPtRNDTH9P/0VExuvKhA/" + "kBVBBC+RDyovgAApMg10qW0wfUAvsA6NGkyMsfy//J7VQ="); + with_tvm_code("wallet-r2", + "te6ccgEBAQEAYwAAwv8AIN0gggFMl7ohggEznLqxnHGw7UTQ0x/XC//jBOCk8mCDCNcYINMf0x8B+CO78mPtRNDTH9P/" + "0VExuvKhA/kBVBBC+RDyovgAApMg10qW0wfUAvsA6NGkyMsfy//J7VQ="); + with_tvm_code("wallet3-r1", + "te6ccgEBAQEAYgAAwP8AIN0gggFMl7qXMO1E0NcLH+Ck8mCDCNcYINMf0x/TH/gjE7vyY+1E0NMf0x/T/" + "9FRMrryoVFEuvKiBPkBVBBV+RDyo/gAkyDXSpbTB9QC+wDo0QGkyMsfyx/L/8ntVA=="); + with_tvm_code("wallet3-r2", + "te6ccgEBAQEAcQAA3v8AIN0gggFMl7ohggEznLqxn3Gw7UTQ0x/THzHXC//jBOCk8mCDCNcYINMf0x/TH/gjE7vyY+1E0NMf0x/" + "T/9FRMrryoVFEuvKiBPkBVBBV+RDyo/gAkyDXSpbTB9QC+wDo0QGkyMsfyx/L/8ntVA=="); + auto check_revision = [&](td::Slice name, td::int32 default_revision) { + auto it = map.find(name); + CHECK(it != map.end()); + auto other_it = map.find(PSLICE() << name << "-r" << default_revision); + CHECK(other_it != map.end()); + CHECK(it->second->get_hash() == other_it->second->get_hash()); + }; + check_revision("highload-wallet", HIGHLOAD_WALLET_REVISION); + check_revision("highload-wallet-v2", HIGHLOAD_WALLET2_REVISION); + + //check_revision("simple-wallet", WALLET_REVISION); + //check_revision("wallet", WALLET2_REVISION); + //check_revision("wallet3", WALLET3_REVISION); return map; }(); return map; @@ -46,7 +103,7 @@ td::Result> SmartContractCode::load(td::Slice name) { auto& map = get_map(); auto it = map.find(name); if (it == map.end()) { - return td::Status::Error(PSLICE() << "Can't load td::ref " << name); } return it->second; } @@ -54,20 +111,47 @@ td::Ref SmartContractCode::multisig() { auto res = load("multisig").move_as_ok(); return res; } -td::Ref SmartContractCode::wallet() { - auto res = load("wallet").move_as_ok(); +td::Ref SmartContractCode::wallet3(int revision) { + if (revision == 0) { + revision = WALLET3_REVISION; + } + auto res = load(PSLICE() << "wallet3-r" << revision).move_as_ok(); return res; } -td::Ref SmartContractCode::simple_wallet() { - auto res = load("simple-wallet").move_as_ok(); +td::Ref SmartContractCode::wallet(int revision) { + if (revision == 0) { + revision = WALLET2_REVISION; + } + auto res = load(PSLICE() << "wallet-r" << revision).move_as_ok(); + return res; +} +td::Ref SmartContractCode::simple_wallet(int revision) { + if (revision == 0) { + revision = WALLET_REVISION; + } + auto res = load(PSLICE() << "simple-wallet-r" << revision).move_as_ok(); return res; } td::Ref SmartContractCode::simple_wallet_ext() { static auto res = load("simple-wallet-ext").move_as_ok(); return res; } -td::Ref SmartContractCode::highload_wallet() { - static auto res = load("highload-wallet").move_as_ok(); +td::Ref SmartContractCode::highload_wallet(int revision) { + if (revision == 0) { + revision = HIGHLOAD_WALLET_REVISION; + } + auto res = load(PSLICE() << "highload-wallet-r" << revision).move_as_ok(); + return res; +} +td::Ref SmartContractCode::highload_wallet_v2(int revision) { + if (revision == 0) { + revision = HIGHLOAD_WALLET2_REVISION; + } + auto res = load(PSLICE() << "highload-wallet-v2-r" << revision).move_as_ok(); + return res; +} +td::Ref SmartContractCode::dns_manual() { + static auto res = load("dns-manual").move_as_ok(); return res; } } // namespace ton diff --git a/crypto/smc-envelope/SmartContractCode.h b/crypto/smc-envelope/SmartContractCode.h index 0c9e4764..439dc868 100644 --- a/crypto/smc-envelope/SmartContractCode.h +++ b/crypto/smc-envelope/SmartContractCode.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/cells.h" @@ -23,9 +23,12 @@ class SmartContractCode { public: static td::Result> load(td::Slice name); static td::Ref multisig(); - static td::Ref wallet(); - static td::Ref simple_wallet(); + static td::Ref wallet3(int revision = 0); + static td::Ref wallet(int revision = 0); + static td::Ref simple_wallet(int revision = 0); static td::Ref simple_wallet_ext(); - static td::Ref highload_wallet(); + static td::Ref highload_wallet(int revision = 0); + static td::Ref highload_wallet_v2(int revision = 0); + static td::Ref dns_manual(); }; } // namespace ton diff --git a/crypto/smc-envelope/TestGiver.cpp b/crypto/smc-envelope/TestGiver.cpp index 2d44d730..f9ed60bf 100644 --- a/crypto/smc-envelope/TestGiver.cpp +++ b/crypto/smc-envelope/TestGiver.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "TestGiver.h" #include "GenericAccount.h" @@ -35,14 +35,23 @@ vm::CellHash TestGiver::get_init_code_hash() noexcept { //return vm::CellHash::from_slice(td::base64_decode("YV/IANhoI22HVeatFh6S5LbCHp+5OilARfzW+VQPZgQ=").move_as_ok()); } -td::Ref TestGiver::make_a_gift_message(td::uint32 seqno, td::uint64 gramms, td::Slice message, - const block::StdAddress& dest_address) noexcept { +td::Ref TestGiver::make_a_gift_message_static(td::uint32 seqno, td::Span gifts) noexcept { + CHECK(gifts.size() <= max_gifts_size); + vm::CellBuilder cb; - GenericAccount::store_int_message(cb, dest_address, gramms); - cb.store_bytes("\0\0\0\0", 4); - vm::CellString::store(cb, message, 35 * 8).ensure(); - auto message_inner = cb.finalize(); - return vm::CellBuilder().store_long(seqno, 32).store_long(1, 8).store_ref(message_inner).finalize(); + cb.store_long(seqno, 32); + + for (auto& gift : gifts) { + td::int32 send_mode = 1; + auto gramms = gift.gramms; + vm::CellBuilder cbi; + GenericAccount::store_int_message(cbi, gift.destination, gramms); + store_gift_message(cbi, gift); + auto message_inner = cbi.finalize(); + cb.store_long(send_mode, 8).store_ref(std::move(message_inner)); + } + + return cb.finalize(); } td::Result TestGiver::get_seqno() const { diff --git a/crypto/smc-envelope/TestGiver.h b/crypto/smc-envelope/TestGiver.h index 210b6912..b51ac7db 100644 --- a/crypto/smc-envelope/TestGiver.h +++ b/crypto/smc-envelope/TestGiver.h @@ -14,25 +14,38 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "SmartContract.h" +#include "smc-envelope/WalletInterface.h" #include "block/block.h" #include "vm/cells/CellString.h" namespace ton { -class TestGiver : public SmartContract { +class TestGiver : public SmartContract, public WalletInterface { public: explicit TestGiver(State state) : ton::SmartContract(std::move(state)) { } + TestGiver() : ton::SmartContract({}) { + } static constexpr unsigned max_message_size = vm::CellString::max_bytes; + static constexpr unsigned max_gifts_size = 1; static const block::StdAddress& address() noexcept; static vm::CellHash get_init_code_hash() noexcept; - static td::Ref make_a_gift_message(td::uint32 seqno, td::uint64 gramms, td::Slice message, - const block::StdAddress& dest_address) noexcept; + static td::Ref make_a_gift_message_static(td::uint32 seqno, td::Span) noexcept; td::Result get_seqno() const; + using WalletInterface::get_init_message; + td::Result> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 valid_until, + td::Span gifts) const override { + TRY_RESULT(seqno, get_seqno()); + return make_a_gift_message_static(seqno, gifts); + } + size_t get_max_gifts_size() const override { + return max_gifts_size; + } + private: td::Result get_seqno_or_throw() const; }; diff --git a/crypto/smc-envelope/TestWallet.cpp b/crypto/smc-envelope/TestWallet.cpp index 1edf59f9..6249da09 100644 --- a/crypto/smc-envelope/TestWallet.cpp +++ b/crypto/smc-envelope/TestWallet.cpp @@ -13,17 +13,19 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . -Copyright 2017-2019 Telegram Systems LLP +Copyright 2017-2020 Telegram Systems LLP */ #include "TestWallet.h" #include "GenericAccount.h" +#include "SmartContractCode.h" + #include "vm/boc.h" #include "td/utils/base64.h" namespace ton { -td::Ref TestWallet::get_init_state(const td::Ed25519::PublicKey& public_key) noexcept { - auto code = get_init_code(); +td::Ref TestWallet::get_init_state(const td::Ed25519::PublicKey& public_key, td::int32 revision) noexcept { + auto code = get_init_code(revision); auto data = get_init_data(public_key); return GenericAccount::get_init_state(std::move(code), std::move(data)); } @@ -35,42 +37,46 @@ td::Ref TestWallet::get_init_message(const td::Ed25519::PrivateKey& pr return vm::CellBuilder().store_bytes(signature).store_bytes(seq_no).finalize(); } -td::Ref TestWallet::make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 seqno, - td::int64 gramms, td::Slice message, - const block::StdAddress& dest_address) noexcept { - td::int32 send_mode = 3; - if (gramms == -1) { - gramms = 0; - send_mode += 128; - } +td::Ref TestWallet::make_a_gift_message_static(const td::Ed25519::PrivateKey& private_key, td::uint32 seqno, + td::Span gifts) noexcept { + CHECK(gifts.size() <= max_gifts_size); + vm::CellBuilder cb; - GenericAccount::store_int_message(cb, dest_address, gramms); - cb.store_bytes("\0\0\0\0", 4); - vm::CellString::store(cb, message, 35 * 8).ensure(); - auto message_inner = cb.finalize(); - auto message_outer = - vm::CellBuilder().store_long(seqno, 32).store_long(send_mode, 8).store_ref(message_inner).finalize(); + cb.store_long(seqno, 32); + + for (auto& gift : gifts) { + td::int32 send_mode = 3; + auto gramms = gift.gramms; + if (gramms == -1) { + gramms = 0; + send_mode += 128; + } + vm::CellBuilder cbi; + GenericAccount::store_int_message(cbi, gift.destination, gramms); + store_gift_message(cbi, gift); + auto message_inner = cbi.finalize(); + cb.store_long(send_mode, 8).store_ref(std::move(message_inner)); + } + + auto message_outer = cb.finalize(); auto signature = private_key.sign(message_outer->get_hash().as_slice()).move_as_ok(); return vm::CellBuilder().store_bytes(signature).append_cellslice(vm::load_cell_slice(message_outer)).finalize(); } -td::Ref TestWallet::get_init_code() noexcept { - static auto res = [] { - auto serialized_code = td::base64_decode( - "te6ccgEEAQEAAAAAUwAAov8AIN0gggFMl7qXMO1E0NcLH+Ck8mCBAgDXGCDXCx/tRNDTH9P/" - "0VESuvKhIvkBVBBE+RDyovgAAdMfMSDXSpbTB9QC+wDe0aTIyx/L/8ntVA==") - .move_as_ok(); - return vm::std_boc_deserialize(serialized_code).move_as_ok(); - }(); - return res; +td::Ref TestWallet::get_init_code(td::int32 revision) noexcept { + return ton::SmartContractCode::simple_wallet(revision); } vm::CellHash TestWallet::get_init_code_hash() noexcept { return get_init_code()->get_hash(); } +td::Ref TestWallet::get_data(const td::Ed25519::PublicKey& public_key, td::uint32 seqno) noexcept { + return vm::CellBuilder().store_long(seqno, 32).store_bytes(public_key.as_octet_string()).finalize(); +} + td::Ref TestWallet::get_init_data(const td::Ed25519::PublicKey& public_key) noexcept { - return vm::CellBuilder().store_long(0, 32).store_bytes(public_key.as_octet_string()).finalize(); + return get_data(public_key, 0); } td::Result TestWallet::get_seqno() const { @@ -88,4 +94,20 @@ td::Result TestWallet::get_seqno_or_throw() const { return static_cast(seqno); } +td::Result TestWallet::get_public_key() const { + return TRY_VM(get_public_key_or_throw()); +} + +td::Result TestWallet::get_public_key_or_throw() const { + if (state_.data.is_null()) { + return td::Status::Error("data is null"); + } + //FIXME use get method + auto cs = vm::load_cell_slice(state_.data); + cs.skip_first(32); + td::SecureString res(td::Ed25519::PublicKey::LENGTH); + cs.fetch_bytes(res.as_mutable_slice().ubegin(), td::narrow_cast(res.size())); + return td::Ed25519::PublicKey(std::move(res)); +} + } // namespace ton diff --git a/crypto/smc-envelope/TestWallet.h b/crypto/smc-envelope/TestWallet.h index 161aef58..46891aa9 100644 --- a/crypto/smc-envelope/TestWallet.h +++ b/crypto/smc-envelope/TestWallet.h @@ -14,35 +14,53 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "smc-envelope/SmartContract.h" +#include "smc-envelope/WalletInterface.h" #include "vm/cells.h" #include "Ed25519.h" #include "block/block.h" #include "vm/cells/CellString.h" namespace ton { -class TestWallet : public ton::SmartContract { +class TestWallet : public ton::SmartContract, public WalletInterface { public: explicit TestWallet(State state) : ton::SmartContract(std::move(state)) { } + explicit TestWallet(const td::Ed25519::PublicKey& public_key, td::uint32 seqno) + : TestWallet(State{get_init_code(), get_data(public_key, seqno)}) { + } static constexpr unsigned max_message_size = vm::CellString::max_bytes; - static td::Ref get_init_state(const td::Ed25519::PublicKey& public_key) noexcept; + static constexpr unsigned max_gifts_size = 1; + static td::Ref get_init_state(const td::Ed25519::PublicKey& public_key, td::int32 revision = 0) noexcept; static td::Ref get_init_message(const td::Ed25519::PrivateKey& private_key) noexcept; - static td::Ref make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 seqno, - td::int64 gramms, td::Slice message, - const block::StdAddress& dest_address) noexcept; + static td::Ref make_a_gift_message_static(const td::Ed25519::PrivateKey& private_key, td::uint32 seqno, + td::Span gifts) noexcept; - static td::Ref get_init_code() noexcept; + static td::Ref get_init_code(td::int32 revision = 0) noexcept; static vm::CellHash get_init_code_hash() noexcept; + static td::Ref get_data(const td::Ed25519::PublicKey& public_key, td::uint32 seqno) noexcept; static td::Ref get_init_data(const td::Ed25519::PublicKey& public_key) noexcept; td::Result get_seqno() const; + using WalletInterface::get_init_message; + td::Result> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 valid_until, + td::Span gifts) const override { + TRY_RESULT(seqno, get_seqno()); + return make_a_gift_message_static(private_key, seqno, gifts); + } + size_t get_max_gifts_size() const override { + return max_gifts_size; + } + + td::Result get_public_key() const override; + private: td::Result get_seqno_or_throw() const; + td::Result get_public_key_or_throw() const; }; } // namespace ton diff --git a/crypto/smc-envelope/Wallet.cpp b/crypto/smc-envelope/Wallet.cpp index 61958ed9..e354a7a0 100644 --- a/crypto/smc-envelope/Wallet.cpp +++ b/crypto/smc-envelope/Wallet.cpp @@ -14,10 +14,11 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "Wallet.h" #include "GenericAccount.h" +#include "SmartContractCode.h" #include "vm/boc.h" #include "vm/cells/CellString.h" @@ -26,8 +27,8 @@ #include namespace ton { -td::Ref Wallet::get_init_state(const td::Ed25519::PublicKey& public_key) noexcept { - auto code = get_init_code(); +td::Ref Wallet::get_init_state(const td::Ed25519::PublicKey& public_key, td::int32 revision) noexcept { + auto code = get_init_code(revision); auto data = get_init_data(public_key); return GenericAccount::get_init_state(std::move(code), std::move(data)); } @@ -43,46 +44,45 @@ td::Ref Wallet::get_init_message(const td::Ed25519::PrivateKey& privat } td::Ref Wallet::make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 seqno, - td::uint32 valid_until, td::int64 gramms, td::Slice message, - const block::StdAddress& dest_address) noexcept { - td::int32 send_mode = 3; - if (gramms == -1) { - gramms = 0; - send_mode += 128; - } - vm::CellBuilder cb; - GenericAccount::store_int_message(cb, dest_address, gramms); - cb.store_bytes("\0\0\0\0", 4); - vm::CellString::store(cb, message, 35 * 8).ensure(); - auto message_inner = cb.finalize(); + td::uint32 valid_until, td::Span gifts) noexcept { + CHECK(gifts.size() <= max_gifts_size); - auto message_outer = vm::CellBuilder() - .store_long(seqno, 32) - .store_long(valid_until, 32) - .store_long(send_mode, 8) - .store_ref(message_inner) - .finalize(); + vm::CellBuilder cb; + cb.store_long(seqno, 32).store_long(valid_until, 32); + + for (auto& gift : gifts) { + td::int32 send_mode = 3; + auto gramms = gift.gramms; + if (gramms == -1) { + gramms = 0; + send_mode += 128; + } + vm::CellBuilder cbi; + GenericAccount::store_int_message(cbi, gift.destination, gramms); + store_gift_message(cbi, gift); + auto message_inner = cbi.finalize(); + cb.store_long(send_mode, 8).store_ref(std::move(message_inner)); + } + + auto message_outer = cb.finalize(); auto signature = private_key.sign(message_outer->get_hash().as_slice()).move_as_ok(); return vm::CellBuilder().store_bytes(signature).append_cellslice(vm::load_cell_slice(message_outer)).finalize(); } -td::Ref Wallet::get_init_code() noexcept { - static auto res = [] { - auto serialized_code = td::base64_decode( - "te6ccgEEAQEAAAAAVwAAqv8AIN0gggFMl7qXMO1E0NcLH+Ck8mCDCNcYINMf0x8B+CO78mPtRNDTH9P/" - "0VExuvKhA/kBVBBC+RDyovgAApMg10qW0wfUAvsA6NGkyMsfy//J7VQ=") - .move_as_ok(); - return vm::std_boc_deserialize(serialized_code).move_as_ok(); - }(); - return res; +td::Ref Wallet::get_init_code(td::int32 revision) noexcept { + return SmartContractCode::wallet(revision); } vm::CellHash Wallet::get_init_code_hash() noexcept { return get_init_code()->get_hash(); } +td::Ref Wallet::get_data(const td::Ed25519::PublicKey& public_key, td::uint32 seqno) noexcept { + return vm::CellBuilder().store_long(seqno, 32).store_bytes(public_key.as_octet_string()).finalize(); +} + td::Ref Wallet::get_init_data(const td::Ed25519::PublicKey& public_key) noexcept { - return vm::CellBuilder().store_long(0, 32).store_bytes(public_key.as_octet_string()).finalize(); + return get_data(public_key, 0); } td::Result Wallet::get_seqno() const { @@ -97,4 +97,20 @@ td::Result Wallet::get_seqno_or_throw() const { return static_cast(vm::load_cell_slice(state_.data).fetch_ulong(32)); } +td::Result Wallet::get_public_key() const { + return TRY_VM(get_public_key_or_throw()); +} + +td::Result Wallet::get_public_key_or_throw() const { + if (state_.data.is_null()) { + return td::Status::Error("data is null"); + } + //FIXME use get method + auto cs = vm::load_cell_slice(state_.data); + cs.skip_first(32); + td::SecureString res(td::Ed25519::PublicKey::LENGTH); + cs.fetch_bytes(res.as_mutable_slice().ubegin(), td::narrow_cast(res.size())); + return td::Ed25519::PublicKey(std::move(res)); +} + } // namespace ton diff --git a/crypto/smc-envelope/Wallet.h b/crypto/smc-envelope/Wallet.h index 7cd33c81..2299be7c 100644 --- a/crypto/smc-envelope/Wallet.h +++ b/crypto/smc-envelope/Wallet.h @@ -14,35 +14,53 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "smc-envelope/SmartContract.h" +#include "smc-envelope/WalletInterface.h" #include "vm/cells.h" #include "Ed25519.h" #include "block/block.h" #include "vm/cells/CellString.h" namespace ton { -class Wallet : ton::SmartContract { +class Wallet : ton::SmartContract, public WalletInterface { public: explicit Wallet(State state) : ton::SmartContract(std::move(state)) { } + explicit Wallet(const td::Ed25519::PublicKey& public_key, td::uint32 seqno) + : Wallet(State{get_init_code(), get_data(public_key, seqno)}) { + } static constexpr unsigned max_message_size = vm::CellString::max_bytes; - static td::Ref get_init_state(const td::Ed25519::PublicKey& public_key) noexcept; + static constexpr unsigned max_gifts_size = 4; + static td::Ref get_init_state(const td::Ed25519::PublicKey& public_key, td::int32 revision = 0) noexcept; static td::Ref get_init_message(const td::Ed25519::PrivateKey& private_key) noexcept; static td::Ref make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 seqno, - td::uint32 valid_until, td::int64 gramms, td::Slice message, - const block::StdAddress& dest_address) noexcept; + td::uint32 valid_until, td::Span gifts) noexcept; - static td::Ref get_init_code() noexcept; + static td::Ref get_init_code(td::int32 revision = 0) noexcept; static vm::CellHash get_init_code_hash() noexcept; static td::Ref get_init_data(const td::Ed25519::PublicKey& public_key) noexcept; + static td::Ref get_data(const td::Ed25519::PublicKey& public_key, td::uint32 seqno) noexcept; td::Result get_seqno() const; + using WalletInterface::get_init_message; + td::Result> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 valid_until, + td::Span gifts) const override { + TRY_RESULT(seqno, get_seqno()); + return make_a_gift_message(private_key, seqno, valid_until, gifts); + } + size_t get_max_gifts_size() const override { + return max_gifts_size; + } + + td::Result get_public_key() const override; + private: td::Result get_seqno_or_throw() const; + td::Result get_public_key_or_throw() const; }; } // namespace ton diff --git a/crypto/smc-envelope/WalletInterface.h b/crypto/smc-envelope/WalletInterface.h new file mode 100644 index 00000000..e0e439c3 --- /dev/null +++ b/crypto/smc-envelope/WalletInterface.h @@ -0,0 +1,62 @@ +/* + 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 +*/ +#pragma once + +#include "td/utils/common.h" +#include "Ed25519.h" +#include "block/block.h" +#include "vm/cells/CellString.h" + +#include "SmartContract.h" + +namespace ton { +class WalletInterface { + public: + struct Gift { + block::StdAddress destination; + td::int64 gramms; + bool is_encrypted{false}; + std::string message; + }; + + virtual ~WalletInterface() { + } + + virtual size_t get_max_gifts_size() const = 0; + virtual td::Result> make_a_gift_message(const td::Ed25519::PrivateKey &private_key, + td::uint32 valid_until, td::Span gifts) const = 0; + virtual td::Result get_public_key() const { + return td::Status::Error("TODO"); + } + + td::Result> get_init_message(const td::Ed25519::PrivateKey &private_key, + td::uint32 valid_until = std::numeric_limits::max()) { + return make_a_gift_message(private_key, valid_until, {}); + } + static void store_gift_message(vm::CellBuilder &cb, const Gift &gift) { + if (gift.is_encrypted) { + cb.store_long(1, 32); + } else { + cb.store_long(0, 32); + } + vm::CellString::store(cb, gift.message, 35 * 8).ensure(); + } +}; + +} // namespace ton diff --git a/crypto/smc-envelope/WalletV3.cpp b/crypto/smc-envelope/WalletV3.cpp index db39c725..39b9b4a9 100644 --- a/crypto/smc-envelope/WalletV3.cpp +++ b/crypto/smc-envelope/WalletV3.cpp @@ -14,10 +14,11 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "WalletV3.h" #include "GenericAccount.h" +#include "SmartContractCode.h" #include "vm/boc.h" #include "vm/cells/CellString.h" @@ -26,76 +27,70 @@ #include namespace ton { -td::Ref WalletV3::get_init_state(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) noexcept { - auto code = get_init_code(); +td::Ref WalletV3::get_init_state(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id, + td::int32 revision) noexcept { + auto code = get_init_code(revision); auto data = get_init_data(public_key, wallet_id); return GenericAccount::get_init_state(std::move(code), std::move(data)); } -td::Ref WalletV3::get_init_message(const td::Ed25519::PrivateKey& private_key, - td::uint32 wallet_id) noexcept { - td::uint32 seqno = 0; - td::uint32 valid_until = std::numeric_limits::max(); - auto signature = private_key - .sign(vm::CellBuilder() - .store_long(wallet_id, 32) - .store_long(valid_until, 32) - .store_long(seqno, 32) - .finalize() - ->get_hash() - .as_slice()) - .move_as_ok(); - return vm::CellBuilder() - .store_bytes(signature) - .store_long(wallet_id, 32) - .store_long(valid_until, 32) - .store_long(seqno, 32) - .finalize(); +td::optional WalletV3::guess_revision(const vm::Cell::Hash& code_hash) { + for (td::int32 i = 1; i <= 2; i++) { + if (get_init_code(i)->get_hash() == code_hash) { + return i; + } + } + return {}; +} +td::optional WalletV3::guess_revision(const block::StdAddress& address, + const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) { + for (td::int32 i = 1; i <= 2; i++) { + if (GenericAccount::get_address(address.workchain, get_init_state(public_key, wallet_id, i)) == address) { + return i; + } + } + return {}; } td::Ref WalletV3::make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id, - td::uint32 seqno, td::uint32 valid_until, td::int64 gramms, - td::Slice message, const block::StdAddress& dest_address) noexcept { - td::int32 send_mode = 3; - if (gramms == -1) { - gramms = 0; - send_mode += 128; - } - vm::CellBuilder cb; - GenericAccount::store_int_message(cb, dest_address, gramms); - cb.store_bytes("\0\0\0\0", 4); - vm::CellString::store(cb, message, 35 * 8).ensure(); - auto message_inner = cb.finalize(); + td::uint32 seqno, td::uint32 valid_until, + td::Span gifts) noexcept { + CHECK(gifts.size() <= max_gifts_size); - auto message_outer = vm::CellBuilder() - .store_long(wallet_id, 32) - .store_long(valid_until, 32) - .store_long(seqno, 32) - .store_long(send_mode, 8) - .store_ref(message_inner) - .finalize(); + vm::CellBuilder cb; + cb.store_long(wallet_id, 32).store_long(valid_until, 32).store_long(seqno, 32); + + for (auto& gift : gifts) { + td::int32 send_mode = 3; + auto gramms = gift.gramms; + if (gramms == -1) { + gramms = 0; + send_mode += 128; + } + vm::CellBuilder cbi; + GenericAccount::store_int_message(cbi, gift.destination, gramms); + store_gift_message(cbi, gift); + auto message_inner = cbi.finalize(); + cb.store_long(send_mode, 8).store_ref(std::move(message_inner)); + } + + auto message_outer = cb.finalize(); auto signature = private_key.sign(message_outer->get_hash().as_slice()).move_as_ok(); return vm::CellBuilder().store_bytes(signature).append_cellslice(vm::load_cell_slice(message_outer)).finalize(); } -td::Ref WalletV3::get_init_code() noexcept { - static auto res = [] { - auto serialized_code = td::base64_decode( - "te6ccgEBAQEAYgAAwP8AIN0gggFMl7qXMO1E0NcLH+Ck8mCDCNcYINMf0x/TH/gjE7vyY+1E0NMf0x/T/" - "9FRMrryoVFEuvKiBPkBVBBV+RDyo/gAkyDXSpbTB9QC+wDo0QGkyMsfyx/L/8ntVA==") - .move_as_ok(); - return vm::std_boc_deserialize(serialized_code).move_as_ok(); - }(); - return res; +td::Ref WalletV3::get_init_code(td::int32 revision) noexcept { + return SmartContractCode::wallet3(revision); } vm::CellHash WalletV3::get_init_code_hash() noexcept { return get_init_code()->get_hash(); } -td::Ref WalletV3::get_init_data(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) noexcept { +td::Ref WalletV3::get_init_data(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id, + td::uint32 seqno) noexcept { return vm::CellBuilder() - .store_long(0, 32) + .store_long(seqno, 32) .store_long(wallet_id, 32) .store_bytes(public_key.as_octet_string()) .finalize(); @@ -127,4 +122,20 @@ td::Result WalletV3::get_wallet_id_or_throw() const { return static_cast(cs.fetch_ulong(32)); } +td::Result WalletV3::get_public_key() const { + return TRY_VM(get_public_key_or_throw()); +} + +td::Result WalletV3::get_public_key_or_throw() const { + if (state_.data.is_null()) { + return td::Status::Error("data is null"); + } + //FIXME use get method + auto cs = vm::load_cell_slice(state_.data); + cs.skip_first(64); + td::SecureString res(td::Ed25519::PublicKey::LENGTH); + cs.fetch_bytes(res.as_mutable_slice().ubegin(), td::narrow_cast(res.size())); + return td::Ed25519::PublicKey(std::move(res)); +} + } // namespace ton diff --git a/crypto/smc-envelope/WalletV3.h b/crypto/smc-envelope/WalletV3.h index a6e4162d..b7c211ca 100644 --- a/crypto/smc-envelope/WalletV3.h +++ b/crypto/smc-envelope/WalletV3.h @@ -14,37 +14,59 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "smc-envelope/SmartContract.h" +#include "smc-envelope/WalletInterface.h" #include "vm/cells.h" #include "Ed25519.h" #include "block/block.h" #include "vm/cells/CellString.h" namespace ton { -class WalletV3 : ton::SmartContract { +class WalletV3 : ton::SmartContract, public WalletInterface { public: explicit WalletV3(State state) : ton::SmartContract(std::move(state)) { } + explicit WalletV3(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id, td::uint32 seqno = 0) + : WalletV3(State{get_init_code(), get_init_data(public_key, wallet_id, seqno)}) { + } static constexpr unsigned max_message_size = vm::CellString::max_bytes; - static td::Ref get_init_state(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) noexcept; - static td::Ref get_init_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id) noexcept; - static td::Ref make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id, - td::uint32 seqno, td::uint32 valid_until, td::int64 gramms, - td::Slice message, const block::StdAddress& dest_address) noexcept; + static constexpr unsigned max_gifts_size = 4; - static td::Ref get_init_code() noexcept; + static td::optional guess_revision(const vm::Cell::Hash& code_hash); + static td::optional guess_revision(const block::StdAddress& address, + const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id); + static td::Ref get_init_state(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id, + td::int32 revision = 0) noexcept; + static td::Ref make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id, + td::uint32 seqno, td::uint32 valid_until, td::Span gifts) noexcept; + + static td::Ref get_init_code(td::int32 revision = 0) noexcept; static vm::CellHash get_init_code_hash() noexcept; - static td::Ref get_init_data(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) noexcept; + static td::Ref get_init_data(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id, + td::uint32 seqno = 0) noexcept; td::Result get_seqno() const; td::Result get_wallet_id() const; + using WalletInterface::get_init_message; + td::Result> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 valid_until, + td::Span gifts) const override { + TRY_RESULT(seqno, get_seqno()); + TRY_RESULT(wallet_id, get_wallet_id()); + return make_a_gift_message(private_key, wallet_id, seqno, valid_until, gifts); + } + size_t get_max_gifts_size() const override { + return max_gifts_size; + } + td::Result get_public_key() const override; + private: td::Result get_seqno_or_throw() const; td::Result get_wallet_id_or_throw() const; + td::Result get_public_key_or_throw() const; }; } // namespace ton diff --git a/crypto/test/fift/testdict.fif b/crypto/test/fift/testdict.fif index 307bd2e8..b9491ea8 100644 --- a/crypto/test/fift/testdict.fif +++ b/crypto/test/fift/testdict.fif @@ -1,4 +1,4 @@ -"Lisp.fif" include +"Lists.fif" include 16 constant key-bits 16 constant val-bits { val-bits u, } : val, diff --git a/crypto/test/test-smartcont.cpp b/crypto/test/test-smartcont.cpp index f129e0b3..f6f66b80 100644 --- a/crypto/test/test-smartcont.cpp +++ b/crypto/test/test-smartcont.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/dict.h" #include "common/bigint.hpp" @@ -28,6 +28,7 @@ #include "fift/utils.h" #include "smc-envelope/GenericAccount.h" +#include "smc-envelope/ManualDns.h" #include "smc-envelope/MultisigWallet.h" #include "smc-envelope/SmartContract.h" #include "smc-envelope/SmartContractCode.h" @@ -36,6 +37,7 @@ #include "smc-envelope/Wallet.h" #include "smc-envelope/WalletV3.h" #include "smc-envelope/HighloadWallet.h" +#include "smc-envelope/HighloadWalletV2.h" #include "td/utils/base64.h" #include "td/utils/crypto.h" @@ -47,6 +49,7 @@ #include "td/utils/PathView.h" #include "td/utils/filesystem.h" #include "td/utils/port/path.h" +#include "td/utils/Variant.h" #include #include @@ -63,8 +66,8 @@ std::string load_source(std::string name) { td::Ref 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 +DUP 85143 INT EQUAL OVER 78748 INT EQUAL OR IFJMP:<{ // "seqno" and "get_public_key" get-methods + 1 INT AND c4 PUSHCTR CTOS 32 LDU 256 PLDU CONDSEL // cnt or pubk }> INC 32 THROWIF // fail unless recv_external 512 INT LDSLICEX DUP 32 PLDU // sign cs cnt @@ -91,8 +94,8 @@ INC NEWC 32 STU 256 STU ENDC c4 POPCTR td::Ref 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 + DUP 85143 INT EQUAL OVER 78748 INT EQUAL OR IFJMP:<{ // "seqno" and "get_public_key" get-methods + 1 INT AND c4 PUSHCTR CTOS 32 LDU 256 PLDU CONDSEL // cnt or pubk }> INC 32 THROWIF // fail unless recv_external 9 PUSHPOW2 LDSLICEX DUP 32 LDU 32 LDU // signature in_msg msg_seqno valid_until cs @@ -119,8 +122,8 @@ SETCP0 DUP IFNOTRET // return if recv_internal td::Ref get_wallet_v3_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 + DUP 85143 INT EQUAL OVER 78748 INT EQUAL OR IFJMP:<{ // "seqno" and "get_public_key" get-methods + 1 INT AND c4 PUSHCTR CTOS 32 LDU 32 LDU NIP 256 PLDU CONDSEL // cnt or pubk }> INC 32 THROWIF // fail unless recv_external 9 PUSHPOW2 LDSLICEX DUP 32 LDU 32 LDU 32 LDU // signature in_msg subwallet_id valid_until msg_seqno cs @@ -176,8 +179,15 @@ TEST(Tonlib, TestWallet) { "321", "-C", "TEST"}) .move_as_ok(); auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data; + ton::TestWallet::Gift gift; + gift.destination = dest; + gift.message = "TEST"; + gift.gramms = 321000000000ll; + ton::TestWallet wallet(priv_key.get_public_key().move_as_ok(), 123); + ASSERT_EQ(123u, wallet.get_seqno().ok()); + CHECK(priv_key.get_public_key().ok().as_octet_string() == wallet.get_public_key().ok().as_octet_string()); auto gift_message = ton::GenericAccount::create_ext_message( - address, {}, ton::TestWallet::make_a_gift_message(priv_key, 123, 321000000000ll, "TEST", dest)); + address, {}, wallet.make_a_gift_message(priv_key, 0, {gift}).move_as_ok()); LOG(ERROR) << "-------"; vm::load_cell_slice(gift_message).print_rec(std::cerr); LOG(ERROR) << "-------"; @@ -224,13 +234,20 @@ TEST(Tonlib, Wallet) { }; fift_output.source_lookup.set_os_time(std::make_unique()); 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", "-C", "TESTv2", "Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX", "123", "321"}) - .move_as_ok(); + fift_output = fift::mem_run_fift(std::move(fift_output.source_lookup), + {"aba", "new-wallet", "-C", "TESTv2", + "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; + ton::TestWallet::Gift gift; + gift.destination = dest; + gift.message = "TESTv2"; + gift.gramms = 321000000000ll; + ton::Wallet wallet(priv_key.get_public_key().move_as_ok(), 123); + ASSERT_EQ(123u, wallet.get_seqno().ok()); + CHECK(priv_key.get_public_key().ok().as_octet_string() == wallet.get_public_key().ok().as_octet_string()); auto gift_message = ton::GenericAccount::create_ext_message( - address, {}, ton::Wallet::make_a_gift_message(priv_key, 123, 60, 321000000000ll, "TESTv2", dest)); + address, {}, wallet.make_a_gift_message(priv_key, 60, {gift}).move_as_ok()); LOG(ERROR) << "-------"; vm::load_cell_slice(gift_message).print_rec(std::cerr); LOG(ERROR) << "-------"; @@ -251,7 +268,8 @@ TEST(Tonlib, WalletV3) { td::Ed25519::PrivateKey priv_key{td::SecureString{new_wallet_pk}}; auto pub_key = priv_key.get_public_key().move_as_ok(); auto init_state = ton::WalletV3::get_init_state(pub_key, 239); - auto init_message = ton::WalletV3::get_init_message(priv_key, 239); + auto init_message = + ton::WalletV3(priv_key.get_public_key().move_as_ok(), 239).get_init_message(priv_key).move_as_ok(); auto address = ton::GenericAccount::get_address(0, init_state); CHECK(address.addr.as_slice() == td::Slice(new_wallet_addr).substr(0, 32)); @@ -273,13 +291,24 @@ TEST(Tonlib, WalletV3) { }; fift_output.source_lookup.set_os_time(std::make_unique()); 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", "-C", "TESTv3", "Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX", "239", "123", "321"}) - .move_as_ok(); + fift_output = fift::mem_run_fift(std::move(fift_output.source_lookup), + {"aba", "new-wallet", "-C", "TESTv3", + "Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX", "239", "123", "321"}) + .move_as_ok(); auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data; + + ton::WalletV3::Gift gift; + gift.destination = dest; + gift.message = "TESTv3"; + gift.gramms = 321000000000ll; + + ton::WalletV3 wallet(priv_key.get_public_key().move_as_ok(), 239, 123); + ASSERT_EQ(239u, wallet.get_wallet_id().ok()); + ASSERT_EQ(123u, wallet.get_seqno().ok()); + CHECK(priv_key.get_public_key().ok().as_octet_string() == wallet.get_public_key().ok().as_octet_string()); + auto gift_message = ton::GenericAccount::create_ext_message( - address, {}, ton::WalletV3::make_a_gift_message(priv_key, 239, 123, 60, 321000000000ll, "TESTv3", dest)); + address, {}, wallet.make_a_gift_message(priv_key, 60, {gift}).move_as_ok()); LOG(ERROR) << "-------"; vm::load_cell_slice(gift_message).print_rec(std::cerr); LOG(ERROR) << "-------"; @@ -304,6 +333,11 @@ TEST(Tonlib, HighloadWallet) { auto init_message = ton::HighloadWallet::get_init_message(priv_key, 239); auto address = ton::GenericAccount::get_address(0, init_state); + ton::HighloadWallet wallet({ton::HighloadWallet::get_init_code(), ton::HighloadWallet::get_init_data(pub_key, 239)}); + ASSERT_EQ(239u, wallet.get_wallet_id().ok()); + ASSERT_EQ(0u, wallet.get_seqno().ok()); + CHECK(pub_key.as_octet_string() == wallet.get_public_key().ok().as_octet_string()); + CHECK(address.addr.as_slice() == td::Slice(new_wallet_addr).substr(0, 32)); td::Ref res = ton::GenericAccount::create_ext_message(address, init_state, init_message); @@ -354,6 +388,80 @@ TEST(Tonlib, HighloadWallet) { CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == gift_message->get_hash()); } +TEST(Tonlib, HighloadWalletV2) { + auto source_lookup = fift::create_mem_source_lookup(load_source("smartcont/new-highload-wallet-v2.fif")).move_as_ok(); + source_lookup + .write_file("/auto/highload-wallet-v2-code.fif", load_source("smartcont/auto/highload-wallet-v2-code.fif")) + .ensure(); + class ZeroOsTime : public fift::OsTime { + public: + td::uint32 now() override { + return 0; + } + }; + source_lookup.set_os_time(std::make_unique()); + auto fift_output = fift::mem_run_fift(std::move(source_lookup), {"aba", "0", "239"}).move_as_ok(); + + LOG(ERROR) << fift_output.output; + 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-wallet239-query.boc").move_as_ok().data; + auto new_wallet_addr = fift_output.source_lookup.read_file("new-wallet239.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 = ton::HighloadWalletV2::get_init_state(pub_key, 239, 0); + auto init_message = ton::HighloadWalletV2::get_init_message(priv_key, 239, 65535); + auto address = ton::GenericAccount::get_address(0, init_state); + + ton::HighloadWalletV2 wallet( + {ton::HighloadWalletV2::get_init_code(0), ton::HighloadWalletV2::get_init_data(pub_key, 239)}); + ASSERT_EQ(239u, wallet.get_wallet_id().ok()); + CHECK(pub_key.as_octet_string() == wallet.get_public_key().ok().as_octet_string()); + + CHECK(address.addr.as_slice() == td::Slice(new_wallet_addr).substr(0, 32)); + + td::Ref res = ton::GenericAccount::create_ext_message(address, init_state, init_message); + + LOG(ERROR) << "---smc-envelope----"; + vm::load_cell_slice(res).print_rec(std::cerr); + LOG(ERROR) << "---fift scripts----"; + 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/highload-wallet-v2.fif")).ensure(); + std::string order; + std::vector gifts; + auto add_order = [&](td::Slice dest_str, td::int64 gramms) { + auto g = td::to_string(gramms); + if (g.size() < 10) { + g = std::string(10 - g.size(), '0') + g; + } + order += PSTRING() << "SEND " << dest_str << " " << g.substr(0, g.size() - 9) << "." << g.substr(g.size() - 9) + << "\n"; + + ton::HighloadWalletV2::Gift gift; + gift.destination = block::StdAddress::parse(dest_str).move_as_ok(); + gift.gramms = gramms; + gifts.push_back(gift); + }; + std::string dest_str = "Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX"; + add_order(dest_str, 0); + add_order(dest_str, 321000000000ll); + add_order(dest_str, 321ll); + fift_output.source_lookup.write_file("/order", order).ensure(); + fift_output.source_lookup.set_os_time(std::make_unique()); + fift_output = + fift::mem_run_fift(std::move(fift_output.source_lookup), {"aba", "new-wallet", "239", "order"}).move_as_ok(); + auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data; + auto gift_message = ton::GenericAccount::create_ext_message( + address, {}, ton::HighloadWalletV2::make_a_gift_message(priv_key, 239, 60, gifts)); + LOG(ERROR) << "---smc-envelope----"; + vm::load_cell_slice(gift_message).print_rec(std::cerr); + LOG(ERROR) << "---fift scripts----"; + 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(); @@ -365,9 +473,13 @@ TEST(Tonlib, TestGiver) { auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data; - auto res = ton::GenericAccount::create_ext_message( - ton::TestGiver::address(), {}, - ton::TestGiver::make_a_gift_message(0, 1000000000ll * 6666 / 1000, "GIFT", address)); + ton::TestGiver::Gift gift; + gift.gramms = 1000000000ll * 6666 / 1000; + gift.message = "GIFT"; + gift.destination = address; + td::Ed25519::PrivateKey key{td::SecureString()}; + auto res = ton::GenericAccount::create_ext_message(ton::TestGiver::address(), {}, + ton::TestGiver().make_a_gift_message(key, 0, {gift}).move_as_ok()); vm::CellSlice(vm::NoVm(), res).print_rec(std::cerr); CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == res->get_hash()); } @@ -670,3 +782,457 @@ TEST(Smartcont, MultisigStress) { LOG(INFO) << "Final code size: " << ms->code_size(); LOG(INFO) << "Final data size: " << ms->data_size(); } + +class MapDns { + public: + using ManualDns = ton::ManualDns; + struct Entry { + std::string name; + td::int16 category{0}; + std::string text; + + auto key() const { + return std::tie(name, category); + } + bool operator<(const Entry& other) const { + return key() < other.key(); + } + bool operator==(const ton::DnsInterface::Entry& other) const { + return key() == other.key() && other.data.type == ManualDns::EntryData::Type::Text && + other.data.data.get().text == text; + } + bool operator==(const Entry& other) const { + return key() == other.key() && text == other.text; + } + friend td::StringBuilder& operator<<(td::StringBuilder& sb, const Entry& entry) { + return sb << "[" << entry.name << ":" << entry.category << ":" << entry.text << "]"; + } + }; + struct Action { + std::string name; + td::int16 category{0}; + td::optional text; + + bool does_create_category() const { + CHECK(!name.empty()); + CHECK(category != 0); + return static_cast(text); + } + bool does_change_empty() const { + CHECK(!name.empty()); + CHECK(category != 0); + return static_cast(text) && !text.value().empty(); + } + void make_non_empty() { + CHECK(!name.empty()); + CHECK(category != 0); + if (!text) { + text = ""; + } + } + friend td::StringBuilder& operator<<(td::StringBuilder& sb, const Action& entry) { + return sb << "[" << entry.name << ":" << entry.category << ":" << (entry.text ? entry.text.value() : "") + << "]"; + } + }; + void update(td::Span actions) { + for (auto& action : actions) { + do_update(action); + } + } + using CombinedActions = ton::ManualDns::CombinedActions; + void update_combined(td::Span actions) { + LOG(ERROR) << "BEGIN"; + LOG(ERROR) << td::format::as_array(actions); + auto combined_actions = ton::ManualDns::combine_actions(actions); + for (auto& c : combined_actions) { + LOG(ERROR) << c.name << ":" << c.category; + if (c.actions) { + LOG(ERROR) << td::format::as_array(c.actions.value()); + } + } + LOG(ERROR) << "END"; + for (auto& combined_action : combined_actions) { + do_update(combined_action); + } + } + + std::vector resolve(td::Slice name, td::int16 category) { + std::vector res; + if (name.empty()) { + for (auto& a : entries_) { + for (auto& b : a.second) { + res.push_back({a.first, b.first, b.second}); + } + } + } else { + auto it = entries_.find(name); + while (it == entries_.end()) { + auto sz = name.find('.'); + category = -1; + if (sz != td::Slice::npos) { + name = name.substr(sz + 1); + } else { + break; + } + it = entries_.find(name); + } + if (it != entries_.end()) { + for (auto& b : it->second) { + if (category == 0 || category == b.first) { + res.push_back({name.str(), b.first, b.second}); + } + } + } + } + + std::sort(res.begin(), res.end()); + return res; + } + + private: + std::map, std::less<>> entries_; + void do_update(const Action& action) { + if (action.name.empty()) { + entries_.clear(); + return; + } + if (action.category == 0) { + entries_.erase(action.name); + return; + } + if (action.text) { + if (action.text.value().empty()) { + entries_[action.name].erase(action.category); + } else { + entries_[action.name][action.category] = action.text.value(); + } + } else { + auto it = entries_.find(action.name); + if (it != entries_.end()) { + it->second.erase(action.category); + } + } + } + + void do_update(const CombinedActions& actions) { + if (actions.name.empty()) { + entries_.clear(); + LOG(ERROR) << "CLEAR"; + if (!actions.actions) { + return; + } + for (auto& action : actions.actions.value()) { + CHECK(!action.name.empty()); + CHECK(action.category != 0); + CHECK(action.text); + if (action.text.value().empty()) { + entries_[action.name]; + } else { + entries_[action.name][action.category] = action.text.value(); + } + } + return; + } + if (actions.category == 0) { + entries_.erase(actions.name); + LOG(ERROR) << "CLEAR " << actions.name; + if (!actions.actions) { + return; + } + entries_[actions.name]; + for (auto& action : actions.actions.value()) { + CHECK(action.name == actions.name); + CHECK(action.category != 0); + CHECK(action.text); + if (action.text.value().empty()) { + entries_[action.name]; + } else { + entries_[action.name][action.category] = action.text.value(); + } + } + return; + } + CHECK(actions.actions); + CHECK(actions.actions.value().size() == 1); + for (auto& action : actions.actions.value()) { + CHECK(action.name == actions.name); + CHECK(action.category != 0); + if (action.text) { + if (action.text.value().empty()) { + entries_[action.name].erase(action.category); + } else { + entries_[action.name][action.category] = action.text.value(); + } + } else { + auto it = entries_.find(action.name); + if (it != entries_.end()) { + it->second.erase(action.category); + } + } + } + } +}; + +class CheckedDns { + public: + explicit CheckedDns(bool check_smc = true, bool check_combine = true) { + if (check_smc) { + key_ = td::Ed25519::generate_private_key().move_as_ok(); + dns_ = ManualDns::create(ManualDns::create_init_data_fast(key_.value().get_public_key().move_as_ok(), 123)); + } + if (check_combine) { + combined_map_dns_ = MapDns(); + } + } + using Action = MapDns::Action; + using Entry = MapDns::Entry; + void update(td::Span entries) { + if (dns_.not_null()) { + auto smc_actions = td::transform(entries, [](auto& entry) { + ton::DnsInterface::Action action; + action.name = entry.name; + action.category = entry.category; + if (entry.text) { + if (entry.text.value().empty()) { + action.data = td::Ref(); + } else { + action.data = ManualDns::EntryData::text(entry.text.value()).as_cell().move_as_ok(); + } + } + return action; + }); + auto query = dns_->create_update_query(key_.value(), smc_actions).move_as_ok(); + CHECK(dns_.write().send_external_message(std::move(query)).code == 0); + } + map_dns_.update(entries); + if (combined_map_dns_) { + combined_map_dns_.value().update_combined(entries); + } + } + void update(const Action& action) { + return update(td::Span(&action, 1)); + } + + std::vector resolve(td::Slice name, td::int16 category) { + LOG(ERROR) << "RESOLVE: " << name << " " << category; + auto res = map_dns_.resolve(name, category); + LOG(ERROR) << td::format::as_array(res); + + if (dns_.not_null()) { + auto other_res = dns_->resolve(name, category).move_as_ok(); + + std::sort(other_res.begin(), other_res.end()); + if (res.size() != other_res.size()) { + LOG(ERROR) << td::format::as_array(res); + LOG(FATAL) << td::format::as_array(other_res); + } + for (size_t i = 0; i < res.size(); i++) { + if (!(res[i] == other_res[i])) { + LOG(ERROR) << td::format::as_array(res); + LOG(FATAL) << td::format::as_array(other_res); + } + } + } + if (combined_map_dns_) { + auto other_res = combined_map_dns_.value().resolve(name, category); + + std::sort(other_res.begin(), other_res.end()); + if (res.size() != other_res.size()) { + LOG(ERROR) << td::format::as_array(res); + LOG(FATAL) << td::format::as_array(other_res); + } + for (size_t i = 0; i < res.size(); i++) { + if (!(res[i] == other_res[i])) { + LOG(ERROR) << td::format::as_array(res); + LOG(FATAL) << td::format::as_array(other_res); + } + } + } + + return res; + } + + private: + using ManualDns = ton::ManualDns; + td::optional key_; + td::Ref dns_; + + MapDns map_dns_; + td::optional combined_map_dns_; + + void do_update_smc(const Action& entry) { + LOG(ERROR) << td::format::escaped(ManualDns::encode_name(entry.name)); + ton::DnsInterface::Action action; + action.name = entry.name; + action.category = entry.category; + action.data = ManualDns::EntryData::text(entry.text.value()).as_cell().move_as_ok(); + } +}; + +void do_dns_test(CheckedDns&& dns) { + using Action = CheckedDns::Action; + std::vector actions; + + td::Random::Xorshift128plus rnd(123); + + auto gen_name = [&] { + auto cnt = rnd.fast(1, 2); + std::string res; + for (int i = 0; i < cnt; i++) { + if (i != 0) { + res += '.'; + } + auto len = rnd.fast(1, 1); + for (int j = 0; j < len; j++) { + res += static_cast(rnd.fast('a', 'b')); + } + } + return res; + }; + auto gen_text = [&] { + std::string res; + int len = 5; + for (int j = 0; j < len; j++) { + res += static_cast(rnd.fast('a', 'b')); + } + return res; + }; + + auto gen_action = [&] { + Action action; + if (rnd.fast(0, 1000) == 0) { + return action; + } + action.name = gen_name(); + if (rnd.fast(0, 20) == 0) { + return action; + } + action.category = td::narrow_cast(rnd.fast(1, 5)); + if (rnd.fast(0, 4) == 0) { + return action; + } + if (rnd.fast(0, 4) == 0) { + action.text = ""; + return action; + } + action.text = gen_text(); + return action; + }; + + SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR)); + for (int i = 0; i < 100000; i++) { + actions.push_back(gen_action()); + if (rnd.fast(0, 10) == 0) { + dns.update(actions); + actions.clear(); + } + dns.resolve(gen_name(), td::narrow_cast(rnd.fast(0, 5))); + } +}; + +TEST(Smartcont, DnsManual) { + using ManualDns = ton::ManualDns; + auto test_entry_data = [](auto&& entry_data) { + auto cell = entry_data.as_cell().move_as_ok(); + auto cs = vm::load_cell_slice(cell); + auto new_entry_data = ManualDns::EntryData::from_cellslice(cs).move_as_ok(); + ASSERT_EQ(entry_data, new_entry_data); + }; + test_entry_data(ManualDns::EntryData::text("abcd")); + test_entry_data(ManualDns::EntryData::adnl_address(ton::Bits256{})); + + CHECK(td::Slice("a\0b\0") == ManualDns::encode_name("b.a")); + CHECK(td::Slice("a\0b\0") == ManualDns::encode_name(".b.a")); + ASSERT_EQ("b.a", ManualDns::decode_name("a\0b\0")); + ASSERT_EQ("b.a", ManualDns::decode_name("a\0b")); + ASSERT_EQ("", ManualDns::decode_name("")); + + auto key = td::Ed25519::generate_private_key().move_as_ok(); + + auto manual = ManualDns::create(ManualDns::create_init_data_fast(key.get_public_key().move_as_ok(), 123)); + CHECK(manual->get_wallet_id().move_as_ok() == 123); + auto init_query = manual->create_init_query(key).move_as_ok(); + LOG(ERROR) << "A"; + CHECK(manual.write().send_external_message(init_query).code == 0); + LOG(ERROR) << "B"; + CHECK(manual.write().send_external_message(init_query).code != 0); + + auto value = vm::CellBuilder().store_bytes("hello world").finalize(); + auto set_query = + manual + ->sign(key, + manual->prepare(manual->create_set_value_unsigned(1, "a\0b\0", value).move_as_ok(), 1).move_as_ok()) + .move_as_ok(); + CHECK(manual.write().send_external_message(set_query).code == 0); + + auto res = manual->run_get_method( + "dnsresolve", {vm::load_cell_slice_ref(vm::CellBuilder().store_bytes("a\0b\0").finalize()), td::make_refint(1)}); + CHECK(res.code == 0); + CHECK(res.stack.write().pop_cell()->get_hash() == value->get_hash()); + + CheckedDns dns; + dns.update(CheckedDns::Action{"a.b.c", 1, "hello"}); + CHECK(dns.resolve("a.b.c", 1).at(0).text == "hello"); + dns.resolve("a", 1); + dns.resolve("a.b", 1); + CHECK(dns.resolve("a.b.c", 2).empty()); + dns.update(CheckedDns::Action{"a.b.c", 2, "test"}); + CHECK(dns.resolve("a.b.c", 2).at(0).text == "test"); + dns.resolve("a.b.c", 1); + dns.resolve("a.b.c", 2); + LOG(ERROR) << "Test zero category"; + dns.resolve("a.b.c", 0); + dns.update(CheckedDns::Action{"", 0, ""}); + CHECK(dns.resolve("a.b.c", 2).empty()); + + LOG(ERROR) << "Test multipe update"; + { + CheckedDns::Action e[4] = {CheckedDns::Action{"", 0, ""}, CheckedDns::Action{"a.b.c", 1, "hello"}, + CheckedDns::Action{"a.b.c", 2, "world"}, CheckedDns::Action{"x.y.z", 3, "abc"}}; + dns.update(td::Span(e, 4)); + } + dns.resolve("a.b.c", 1); + dns.resolve("a.b.c", 2); + dns.resolve("x.y.z", 3); + + { + CheckedDns::Action e[1] = {CheckedDns::Action{"x.y.z", 0, ""}}; + dns.update(td::Span(e, 1)); + } + + dns.resolve("a.b.c", 1); + dns.resolve("a.b.c", 2); + dns.resolve("x.y.z", 3); + + { + CheckedDns::Action e[3] = {CheckedDns::Action{"x.y.z", 0, ""}, CheckedDns::Action{"x.y.z", 1, "xxx"}, + CheckedDns::Action{"x.y.z", 2, "yyy"}}; + dns.update(td::Span(e, 3)); + } + dns.resolve("a.b.c", 1); + dns.resolve("a.b.c", 2); + dns.resolve("x.y.z", 1); + dns.resolve("x.y.z", 2); + dns.resolve("x.y.z", 3); + + { + auto actions_ext = + ton::ManualDns::parse("delete.name one\nset one 1 TEXT:one\ndelete.name two\nset two 2 TEXT:two").move_as_ok(); + + auto actions = td::transform(actions_ext, [](auto& action) { + td::optional data; + if (action.data) { + data = action.data.value().data.template get().text; + } + return CheckedDns::Action{action.name, action.category, std::move(data)}; + }); + + dns.update(actions); + } + dns.resolve("one", 1); + dns.resolve("two", 2); + + // TODO: rethink semantic of creating an empty dictionary + do_dns_test(CheckedDns(true, true)); +} diff --git a/crypto/test/vm.cpp b/crypto/test/vm.cpp index 72e2f8c6..77157f6d 100644 --- a/crypto/test/vm.cpp +++ b/crypto/test/vm.cpp @@ -14,9 +14,9 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ -#include "vm/continuation.h" +#include "vm/vm.h" #include "vm/cp0.h" #include "vm/dict.h" #include "fift/utils.h" diff --git a/crypto/vm/arithops.cpp b/crypto/vm/arithops.cpp index 19279cf1..69e6f083 100644 --- a/crypto/vm/arithops.cpp +++ b/crypto/vm/arithops.cpp @@ -14,15 +14,15 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include #include "vm/arithops.h" #include "vm/log.h" #include "vm/opctable.h" #include "vm/stack.hpp" -#include "vm/continuation.h" #include "vm/excno.hpp" +#include "vm/vm.h" #include "common/bigint.hpp" #include "common/refint.h" diff --git a/crypto/vm/cellops.cpp b/crypto/vm/cellops.cpp index 5346e168..0cd7d5e1 100644 --- a/crypto/vm/cellops.cpp +++ b/crypto/vm/cellops.cpp @@ -14,16 +14,16 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include #include "vm/cellops.h" #include "vm/log.h" #include "vm/opctable.h" #include "vm/stack.hpp" -#include "vm/continuation.h" #include "vm/excno.hpp" #include "vm/vmstate.h" +#include "vm/vm.h" #include "common/bigint.hpp" #include "common/refint.h" diff --git a/crypto/vm/cells/CellSlice.cpp b/crypto/vm/cells/CellSlice.cpp index 74a78970..8e46bb95 100644 --- a/crypto/vm/cells/CellSlice.cpp +++ b/crypto/vm/cells/CellSlice.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/cells/CellSlice.h" #include "vm/excno.hpp" @@ -1026,7 +1026,7 @@ std::ostream& operator<<(std::ostream& os, Ref cs_ref) { VirtualCell::LoadedCell load_cell_slice_impl(const Ref& cell, bool* can_be_special) { auto* vm_state_interface = VmStateInterface::get(); if (vm_state_interface) { - vm_state_interface->register_cell_load(); + vm_state_interface->register_cell_load(cell->get_hash()); } auto r_loaded_cell = cell->load_cell(); if (r_loaded_cell.is_error()) { diff --git a/crypto/vm/cells/CellString.cpp b/crypto/vm/cells/CellString.cpp index ad2cbf5f..9889dc8e 100644 --- a/crypto/vm/cells/CellString.cpp +++ b/crypto/vm/cells/CellString.cpp @@ -1,3 +1,21 @@ +/* + 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 2019-2020 Telegram Systems LLP +*/ #include "CellString.h" #include "td/utils/misc.h" @@ -61,4 +79,79 @@ td::Result CellString::load(CellSlice &cs, unsigned int top_bits) { CHECK(to.offs == (int)size); return res; } + +td::Status CellText::store(CellBuilder &cb, td::Slice slice, unsigned int top_bits) { + td::uint32 size = td::narrow_cast(slice.size() * 8); + return store(cb, td::BitSlice(slice.ubegin(), size), top_bits); +} + +td::Status CellText::store(CellBuilder &cb, td::BitSlice slice, unsigned int top_bits) { + if (slice.size() > max_bytes * 8) { + return td::Status::Error("String is too long (1)"); + } + if (cb.remaining_bits() < 16) { + return td::Status::Error("Not enough space in a builder"); + } + if (top_bits < 16) { + return td::Status::Error("Need at least 16 top bits"); + } + if (slice.size() == 0) { + cb.store_long(0, 8); + return td::Status::OK(); + } + unsigned int head = td::min(slice.size(), td::min(cb.remaining_bits(), top_bits) - 16) / 8 * 8; + auto max_bits = vm::Cell::max_bits / 8 * 8; + auto depth = 1 + (slice.size() - head + max_bits - 8 - 1) / (max_bits - 8); + if (depth > max_chain_length) { + return td::Status::Error("String is too long (2)"); + } + cb.store_long(depth, 8); + cb.store_long(head / 8, 8); + cb.append_bitslice(slice.subslice(0, head)); + slice.advance(head); + if (slice.size() == 0) { + return td::Status::OK(); + } + cb.store_ref(do_store(std::move(slice))); + return td::Status::OK(); +} + +td::Ref CellText::do_store(td::BitSlice slice) { + vm::CellBuilder cb; + unsigned int head = td::min(slice.size(), cb.remaining_bits() - 8) / 8 * 8; + cb.store_long(head / 8, 8); + cb.append_bitslice(slice.subslice(0, head)); + slice.advance(head); + if (slice.size() != 0) { + cb.store_ref(do_store(std::move(slice))); + } + return cb.finalize(); +} + +template +void CellText::for_each(F &&f, CellSlice cs) { + auto depth = cs.fetch_ulong(8); + + for (td::uint32 i = 0; i < depth; i++) { + auto size = cs.fetch_ulong(8); + f(cs.fetch_bits(td::narrow_cast(size) * 8)); + if (i + 1 < depth) { + cs = vm::load_cell_slice(cs.prefetch_ref()); + } + } +} + +td::Result CellText::load(CellSlice &cs) { + unsigned int size = 0; + for_each([&](auto slice) { size += slice.size(); }, cs); + if (size % 8 != 0) { + return td::Status::Error("Size is not divisible by 8"); + } + std::string res(size / 8, 0); + + td::BitPtr to(td::MutableSlice(res).ubegin()); + for_each([&](auto slice) { to.concat(slice); }, cs); + CHECK(to.offs == (int)size); + return res; +} } // namespace vm diff --git a/crypto/vm/cells/CellString.h b/crypto/vm/cells/CellString.h index 89c933d8..ab93203f 100644 --- a/crypto/vm/cells/CellString.h +++ b/crypto/vm/cells/CellString.h @@ -1,3 +1,21 @@ +/* + 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 2019-2020 Telegram Systems LLP +*/ #pragma once #include "td/utils/Status.h" @@ -13,10 +31,35 @@ class CellString { static td::Status store(CellBuilder &cb, td::Slice slice, unsigned int top_bits = Cell::max_bits); static td::Status store(CellBuilder &cb, td::BitSlice slice, unsigned int top_bits = Cell::max_bits); static td::Result load(CellSlice &cs, unsigned int top_bits = Cell::max_bits); + static td::Result> create(td::Slice slice, unsigned int top_bits = Cell::max_bits) { + vm::CellBuilder cb; + TRY_STATUS(store(cb, slice, top_bits)); + return cb.finalize(); + } private: template static void for_each(F &&f, CellSlice &cs, unsigned int top_bits = Cell::max_bits); }; +class CellText { + public: + static constexpr unsigned int max_bytes = 1024; + static constexpr unsigned int max_chain_length = 16; + + static td::Status store(CellBuilder &cb, td::Slice slice, unsigned int top_bits = Cell::max_bits); + static td::Status store(CellBuilder &cb, td::BitSlice slice, unsigned int top_bits = Cell::max_bits); + static td::Result load(CellSlice &cs); + static td::Result> create(td::Slice slice, unsigned int top_bits = Cell::max_bits) { + vm::CellBuilder cb; + TRY_STATUS(store(cb, slice, top_bits)); + return cb.finalize(); + } + + private: + template + static void for_each(F &&f, CellSlice cs); + static td::Ref do_store(td::BitSlice slice); +}; + } // namespace vm diff --git a/crypto/vm/continuation.cpp b/crypto/vm/continuation.cpp index f75aff75..fdd27ce6 100644 --- a/crypto/vm/continuation.cpp +++ b/crypto/vm/continuation.cpp @@ -14,12 +14,13 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/dispatch.h" #include "vm/continuation.h" #include "vm/dict.h" #include "vm/log.h" +#include "vm/vm.h" namespace vm { @@ -625,562 +626,4 @@ void VmState::init_cregs(bool same_c3, bool push_0) { } } -VmState::VmState() : cp(-1), dispatch(&dummy_dispatch_table), quit0(true, 0), quit1(true, 1) { - ensure_throw(init_cp(0)); - init_cregs(); -} - -VmState::VmState(Ref _code) - : code(std::move(_code)), cp(-1), dispatch(&dummy_dispatch_table), quit0(true, 0), quit1(true, 1) { - ensure_throw(init_cp(0)); - init_cregs(); -} - -VmState::VmState(Ref _code, Ref _stack, int flags, Ref _data, VmLog log, - std::vector> _libraries, Ref init_c7) - : code(std::move(_code)) - , stack(std::move(_stack)) - , cp(-1) - , dispatch(&dummy_dispatch_table) - , quit0(true, 0) - , quit1(true, 1) - , log(log) - , libraries(std::move(_libraries)) { - ensure_throw(init_cp(0)); - set_c4(std::move(_data)); - if (init_c7.not_null()) { - set_c7(std::move(init_c7)); - } - init_cregs(flags & 1, flags & 2); -} - -VmState::VmState(Ref _code, Ref _stack, const GasLimits& gas, int flags, Ref _data, VmLog log, - std::vector> _libraries, Ref init_c7) - : code(std::move(_code)) - , stack(std::move(_stack)) - , cp(-1) - , dispatch(&dummy_dispatch_table) - , quit0(true, 0) - , quit1(true, 1) - , log(log) - , gas(gas) - , libraries(std::move(_libraries)) { - ensure_throw(init_cp(0)); - set_c4(std::move(_data)); - if (init_c7.not_null()) { - set_c7(std::move(init_c7)); - } - init_cregs(flags & 1, flags & 2); -} - -Ref VmState::convert_code_cell(Ref code_cell) { - if (code_cell.is_null()) { - return {}; - } - Ref csr{true, NoVmOrd(), code_cell}; - if (csr->is_valid()) { - return csr; - } - return load_cell_slice_ref(CellBuilder{}.store_ref(std::move(code_cell)).finalize()); -} - -bool VmState::init_cp(int new_cp) { - const DispatchTable* dt = DispatchTable::get_table(new_cp); - if (dt) { - cp = new_cp; - dispatch = dt; - return true; - } else { - return false; - } -} - -bool VmState::set_cp(int new_cp) { - return new_cp == cp || init_cp(new_cp); -} - -void VmState::force_cp(int new_cp) { - if (!set_cp(new_cp)) { - throw VmError{Excno::inv_opcode, "unsupported codepage"}; - } -} - -// simple call to a continuation cont -int VmState::call(Ref cont) { - const ControlData* cont_data = cont->get_cdata(); - if (cont_data) { - if (cont_data->save.c[0].not_null()) { - // call reduces to a jump - return jump(std::move(cont)); - } - if (cont_data->stack.not_null() || cont_data->nargs >= 0) { - // if cont has non-empty stack or expects fixed number of arguments, call is not simple - return call(std::move(cont), -1, -1); - } - // create return continuation, to be stored into new c0 - Ref ret = Ref{true, std::move(code), cp}; - ret.unique_write().get_cdata()->save.set_c0(std::move(cr.c[0])); - cr.set_c0( - std::move(ret)); // set c0 to its final value before switching to cont; notice that cont.save.c0 is not set - return jump_to(std::move(cont)); - } - // create return continuation, to be stored into new c0 - Ref ret = Ref{true, std::move(code), cp}; - ret.unique_write().get_cdata()->save.set_c0(std::move(cr.c[0])); - // general implementation of a simple call - cr.set_c0(std::move(ret)); // set c0 to its final value before switching to cont; notice that cont.save.c0 is not set - return jump_to(std::move(cont)); -} - -// call with parameters to continuation cont -int VmState::call(Ref cont, int pass_args, int ret_args) { - const ControlData* cont_data = cont->get_cdata(); - if (cont_data) { - if (cont_data->save.c[0].not_null()) { - // call reduces to a jump - return jump(std::move(cont), pass_args); - } - int depth = stack->depth(); - if (pass_args > depth || cont_data->nargs > depth) { - throw VmError{Excno::stk_und, "stack underflow while calling a continuation: not enough arguments on stack"}; - } - if (cont_data->nargs > pass_args && pass_args >= 0) { - throw VmError{Excno::stk_und, - "stack underflow while calling a closure continuation: not enough arguments passed"}; - } - auto old_c0 = std::move(cr.c[0]); - // optimization(?): decrease refcnts of unused continuations in c[i] as early as possible - preclear_cr(cont_data->save); - // no exceptions should be thrown after this point - int copy = cont_data->nargs, skip = 0; - if (pass_args >= 0) { - if (copy >= 0) { - skip = pass_args - copy; - } else { - copy = pass_args; - } - } - // copy=-1 : pass whole stack, else pass top `copy` elements, drop next `skip` elements. - Ref new_stk; - if (cont_data->stack.not_null() && !cont_data->stack->is_empty()) { - // `cont` already has a stack, create resulting stack from it - if (copy < 0) { - copy = stack->depth(); - } - if (cont->is_unique()) { - // optimization: avoid copying stack if we hold the only copy of `cont` - new_stk = std::move(cont.unique_write().get_cdata()->stack); - } else { - new_stk = cont_data->stack; - } - new_stk.write().move_from_stack(get_stack(), copy); - if (skip > 0) { - get_stack().pop_many(skip); - } - } else if (copy >= 0) { - new_stk = get_stack().split_top(copy, skip); - } else { - new_stk = std::move(stack); - stack.clear(); - } - // create return continuation using the remainder of current stack - Ref ret = Ref{true, std::move(code), cp, std::move(stack), ret_args}; - ret.unique_write().get_cdata()->save.set_c0(std::move(old_c0)); - Ref ord_cont = static_cast>(cont); - set_stack(std::move(new_stk)); - cr.set_c0(std::move(ret)); // ??? if codepage of code in ord_cont is unknown, will end up with incorrect c0 - return jump_to(std::move(cont)); - } else { - // have no continuation data, situation is somewhat simpler - int depth = stack->depth(); - if (pass_args > depth) { - throw VmError{Excno::stk_und, "stack underflow while calling a continuation: not enough arguments on stack"}; - } - // create new stack from the top `pass_args` elements of the current stack - Ref new_stk = (pass_args >= 0 ? get_stack().split_top(pass_args) : std::move(stack)); - // create return continuation using the remainder of the current stack - Ref ret = Ref{true, std::move(code), cp, std::move(stack), ret_args}; - ret.unique_write().get_cdata()->save.set_c0(std::move(cr.c[0])); - set_stack(std::move(new_stk)); - cr.set_c0(std::move(ret)); // ??? if codepage of code in ord_cont is unknown, will end up with incorrect c0 - return jump_to(std::move(cont)); - } -} - -// simple jump to continuation cont -int VmState::jump(Ref cont) { - const ControlData* cont_data = cont->get_cdata(); - if (cont_data && (cont_data->stack.not_null() || cont_data->nargs >= 0)) { - // if cont has non-empty stack or expects fixed number of arguments, jump is not simple - return jump(std::move(cont), -1); - } else { - return jump_to(std::move(cont)); - } -} - -// general jump to continuation cont -int VmState::jump(Ref cont, int pass_args) { - const ControlData* cont_data = cont->get_cdata(); - if (cont_data) { - // first do the checks - int depth = stack->depth(); - if (pass_args > depth || cont_data->nargs > depth) { - throw VmError{Excno::stk_und, "stack underflow while jumping to a continuation: not enough arguments on stack"}; - } - if (cont_data->nargs > pass_args && pass_args >= 0) { - throw VmError{Excno::stk_und, - "stack underflow while jumping to closure continuation: not enough arguments passed"}; - } - // optimization(?): decrease refcnts of unused continuations in c[i] as early as possible - preclear_cr(cont_data->save); - // no exceptions should be thrown after this point - int copy = cont_data->nargs; - if (pass_args >= 0 && copy < 0) { - copy = pass_args; - } - // copy=-1 : pass whole stack, else pass top `copy` elements, drop the remainder. - if (cont_data->stack.not_null() && !cont_data->stack->is_empty()) { - // `cont` already has a stack, create resulting stack from it - if (copy < 0) { - copy = get_stack().depth(); - } - Ref new_stk; - if (cont->is_unique()) { - // optimization: avoid copying the stack if we hold the only copy of `cont` - new_stk = std::move(cont.unique_write().get_cdata()->stack); - } else { - new_stk = cont_data->stack; - } - new_stk.write().move_from_stack(get_stack(), copy); - set_stack(std::move(new_stk)); - } else { - if (copy >= 0) { - get_stack().drop_bottom(stack->depth() - copy); - } - } - return jump_to(std::move(cont)); - } else { - // have no continuation data, situation is somewhat simpler - if (pass_args >= 0) { - int depth = get_stack().depth(); - if (pass_args > depth) { - throw VmError{Excno::stk_und, "stack underflow while jumping to a continuation: not enough arguments on stack"}; - } - get_stack().drop_bottom(depth - pass_args); - } - return jump_to(std::move(cont)); - } -} - -int VmState::ret() { - Ref cont = quit0; - cont.swap(cr.c[0]); - return jump(std::move(cont)); -} - -int VmState::ret(int ret_args) { - Ref cont = quit0; - cont.swap(cr.c[0]); - return jump(std::move(cont), ret_args); -} - -int VmState::ret_alt() { - Ref cont = quit1; - cont.swap(cr.c[1]); - return jump(std::move(cont)); -} - -int VmState::ret_alt(int ret_args) { - Ref cont = quit1; - cont.swap(cr.c[1]); - return jump(std::move(cont), ret_args); -} - -Ref VmState::extract_cc(int save_cr, int stack_copy, int cc_args) { - Ref new_stk; - if (stack_copy < 0 || stack_copy == stack->depth()) { - new_stk = std::move(stack); - stack.clear(); - } else if (stack_copy > 0) { - stack->check_underflow(stack_copy); - new_stk = get_stack().split_top(stack_copy); - } else { - new_stk = Ref{true}; - } - Ref cc = Ref{true, std::move(code), cp, std::move(stack), cc_args}; - stack = std::move(new_stk); - if (save_cr & 7) { - ControlData* cdata = cc.unique_write().get_cdata(); - if (save_cr & 1) { - cdata->save.set_c0(std::move(cr.c[0])); - cr.set_c0(quit0); - } - if (save_cr & 2) { - cdata->save.set_c1(std::move(cr.c[1])); - cr.set_c1(quit1); - } - if (save_cr & 4) { - cdata->save.set_c2(std::move(cr.c[2])); - // cr.set_c2(Ref{true}); - } - } - return cc; -} - -int VmState::throw_exception(int excno) { - Stack& stack_ref = get_stack(); - stack_ref.clear(); - stack_ref.push_smallint(0); - stack_ref.push_smallint(excno); - code.clear(); - consume_gas(exception_gas_price); - return jump(get_c2()); -} - -int VmState::throw_exception(int excno, StackEntry&& arg) { - Stack& stack_ref = get_stack(); - stack_ref.clear(); - stack_ref.push(std::move(arg)); - stack_ref.push_smallint(excno); - code.clear(); - consume_gas(exception_gas_price); - return jump(get_c2()); -} - -void GasLimits::gas_exception() const { - throw VmNoGas{}; -} - -void GasLimits::set_limits(long long _max, long long _limit, long long _credit) { - gas_max = _max; - gas_limit = _limit; - gas_credit = _credit; - change_base(_limit + _credit); -} - -void GasLimits::change_limit(long long _limit) { - _limit = std::min(std::max(_limit, 0LL), gas_max); - gas_credit = 0; - gas_limit = _limit; - change_base(_limit); -} - -bool VmState::set_gas_limits(long long _max, long long _limit, long long _credit) { - gas.set_limits(_max, _limit, _credit); - return true; -} - -void VmState::change_gas_limit(long long new_limit) { - VM_LOG(this) << "changing gas limit to " << std::min(new_limit, gas.gas_max); - gas.change_limit(new_limit); -} - -int VmState::step() { - assert(!code.is_null()); - //VM_LOG(st) << "stack:"; stack->dump(VM_LOG(st)); - //VM_LOG(st) << "; cr0.refcnt = " << get_c0()->get_refcnt() - 1 << std::endl; - if (stack_trace) { - stack->dump(std::cerr, 3); - } - ++steps; - if (code->size()) { - return dispatch->dispatch(this, code.write()); - } else if (code->size_refs()) { - VM_LOG(this) << "execute implicit JMPREF\n"; - Ref cont = Ref{true, load_cell_slice_ref(code->prefetch_ref()), get_cp()}; - return jump(std::move(cont)); - } else { - VM_LOG(this) << "execute implicit RET\n"; - return ret(); - } -} - -int VmState::run() { - if (code.is_null()) { - throw VmError{Excno::fatal, "cannot run an uninitialized VM"}; - } - int res; - Guard guard(this); - do { - // LOG(INFO) << "[BS] data cells: " << DataCell::get_total_data_cells(); - try { - try { - res = step(); - gas.check(); - } catch (vm::CellBuilder::CellWriteError) { - throw VmError{Excno::cell_ov}; - } catch (vm::CellBuilder::CellCreateError) { - throw VmError{Excno::cell_ov}; - } catch (vm::CellSlice::CellReadError) { - throw VmError{Excno::cell_und}; - } - } catch (const VmError& vme) { - VM_LOG(this) << "handling exception code " << vme.get_errno() << ": " << vme.get_msg(); - try { - // LOG(INFO) << "[EX] data cells: " << DataCell::get_total_data_cells(); - ++steps; - res = throw_exception(vme.get_errno()); - } catch (const VmError& vme2) { - VM_LOG(this) << "exception " << vme2.get_errno() << " while handling exception: " << vme.get_msg(); - // LOG(INFO) << "[EXX] data cells: " << DataCell::get_total_data_cells(); - return ~vme2.get_errno(); - } - } catch (VmNoGas vmoog) { - ++steps; - VM_LOG(this) << "unhandled out-of-gas exception: gas consumed=" << gas.gas_consumed() - << ", limit=" << gas.gas_limit; - get_stack().clear(); - get_stack().push_smallint(gas.gas_consumed()); - return vmoog.get_errno(); // no ~ for unhandled exceptions (to make their faking impossible) - } - } while (!res); - // LOG(INFO) << "[EN] data cells: " << DataCell::get_total_data_cells(); - if ((res | 1) == -1) { - commit(); - } - return res; -} - -ControlData* force_cdata(Ref& cont) { - if (!cont->get_cdata()) { - cont = Ref{true, cont}; - return cont.unique_write().get_cdata(); - } else { - return cont.write().get_cdata(); - } -} - -ControlRegs* force_cregs(Ref& cont) { - return &force_cdata(cont)->save; -} - -int run_vm_code(Ref code, Ref& stack, int flags, Ref* data_ptr, VmLog log, long long* steps, - GasLimits* gas_limits, std::vector> libraries, Ref init_c7, Ref* actions_ptr) { - VmState vm{code, - std::move(stack), - gas_limits ? *gas_limits : GasLimits{}, - flags, - data_ptr ? *data_ptr : Ref{}, - log, - std::move(libraries), - std::move(init_c7)}; - int res = vm.run(); - stack = vm.get_stack_ref(); - if (vm.committed() && data_ptr) { - *data_ptr = vm.get_committed_state().c4; - } - if (vm.committed() && actions_ptr) { - *actions_ptr = vm.get_committed_state().c5; - } - if (steps) { - *steps = vm.get_steps_count(); - } - if (gas_limits) { - *gas_limits = vm.get_gas_limits(); - LOG(INFO) << "steps: " << vm.get_steps_count() << " gas: used=" << gas_limits->gas_consumed() - << ", max=" << gas_limits->gas_max << ", limit=" << gas_limits->gas_limit - << ", credit=" << gas_limits->gas_credit; - } - if ((vm.get_log().log_mask & vm::VmLog::DumpStack) != 0) { - VM_LOG(&vm) << "BEGIN_STACK_DUMP"; - for (int i = stack->depth(); i > 0; i--) { - VM_LOG(&vm) << (*stack)[i - 1].to_string(); - } - VM_LOG(&vm) << "END_STACK_DUMP"; - } - - return ~res; -} - -int run_vm_code(Ref code, Stack& stack, int flags, Ref* data_ptr, VmLog log, long long* steps, - GasLimits* gas_limits, std::vector> libraries, Ref init_c7, Ref* actions_ptr) { - Ref stk{true}; - stk.unique_write().set_contents(std::move(stack)); - stack.clear(); - int res = run_vm_code(code, stk, flags, data_ptr, log, steps, gas_limits, std::move(libraries), std::move(init_c7), - actions_ptr); - CHECK(stack.is_unique()); - if (stk.is_null()) { - stack.clear(); - } else if (&(*stk) != &stack) { - VmState* st = nullptr; - if (stk->is_unique()) { - VM_LOG(st) << "move resulting stack (" << stk->depth() << " entries)"; - stack.set_contents(std::move(stk.unique_write())); - } else { - VM_LOG(st) << "copying resulting stack (" << stk->depth() << " entries)"; - stack.set_contents(*stk); - } - } - return res; -} - -// may throw a dictionary exception; returns nullptr if library is not found in context -Ref VmState::load_library(td::ConstBitPtr hash) { - std::unique_ptr tmp_ctx; - // install temporary dummy vm state interface to prevent charging for cell load operations during library lookup - VmStateInterface::Guard(tmp_ctx.get()); - for (const auto& lib_collection : libraries) { - auto lib = lookup_library_in(hash, lib_collection); - if (lib.not_null()) { - return lib; - } - } - return {}; -} - -bool VmState::register_library_collection(Ref lib) { - if (lib.is_null()) { - return true; - } - libraries.push_back(std::move(lib)); - return true; -} - -void VmState::register_cell_load() { - consume_gas(cell_load_gas_price); -} - -void VmState::register_cell_create() { - consume_gas(cell_create_gas_price); -} - -td::BitArray<256> VmState::get_state_hash() const { - // TODO: implement properly, by serializing the stack etc, and computing the Merkle hash - td::BitArray<256> res; - res.clear(); - return res; -} - -td::BitArray<256> VmState::get_final_state_hash(int exit_code) const { - // TODO: implement properly, by serializing the stack etc, and computing the Merkle hash - td::BitArray<256> res; - res.clear(); - return res; -} - -Ref lookup_library_in(td::ConstBitPtr key, vm::Dictionary& dict) { - try { - auto val = dict.lookup(key, 256); - if (val.is_null() || !val->have_refs()) { - return {}; - } - auto root = val->prefetch_ref(); - if (root.not_null() && !root->get_hash().bits().compare(key, 256)) { - return root; - } - return {}; - } catch (vm::VmError) { - return {}; - } -} - -Ref lookup_library_in(td::ConstBitPtr key, Ref lib_root) { - if (lib_root.is_null()) { - return lib_root; - } - vm::Dictionary dict{std::move(lib_root), 256}; - return lookup_library_in(key, dict); -} - } // namespace vm diff --git a/crypto/vm/continuation.h b/crypto/vm/continuation.h index 1c24c4cd..37abe869 100644 --- a/crypto/vm/continuation.h +++ b/crypto/vm/continuation.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -371,289 +371,7 @@ class OrdCont : public Continuation { static Ref deserialize(CellSlice& cs, int mode = 0); }; -struct GasLimits { - static constexpr long long infty = (1ULL << 63) - 1; - long long gas_max, gas_limit, gas_credit, gas_remaining, gas_base; - GasLimits() : gas_max(infty), gas_limit(infty), gas_credit(0), gas_remaining(infty), gas_base(infty) { - } - GasLimits(long long _limit, long long _max = infty, long long _credit = 0) - : gas_max(_max) - , gas_limit(_limit) - , gas_credit(_credit) - , gas_remaining(_limit + _credit) - , gas_base(gas_remaining) { - } - long long gas_consumed() const { - return gas_base - gas_remaining; - } - void set_limits(long long _max, long long _limit, long long _credit = 0); - void change_base(long long _base) { - gas_remaining += _base - gas_base; - gas_base = _base; - } - void change_limit(long long _limit); - void consume(long long amount) { - // LOG(DEBUG) << "consume " << amount << " gas (" << gas_remaining << " remaining)"; - gas_remaining -= amount; - } - bool try_consume(long long amount) { - // LOG(DEBUG) << "try consume " << amount << " gas (" << gas_remaining << " remaining)"; - return (gas_remaining -= amount) >= 0; - } - void gas_exception() const; - void gas_exception(bool cond) const { - if (!cond) { - gas_exception(); - } - } - void consume_chk(long long amount) { - gas_exception(try_consume(amount)); - } - void check() const { - gas_exception(gas_remaining >= 0); - } - bool final_ok() const { - return gas_remaining >= gas_credit; - } -}; - -struct CommittedState { - Ref c4, c5; - bool committed{false}; -}; - -class VmState final : public VmStateInterface { - Ref code; - Ref stack; - ControlRegs cr; - CommittedState cstate; - int cp; - long long steps{0}; - const DispatchTable* dispatch; - Ref quit0, quit1; - VmLog log; - GasLimits gas; - std::vector> libraries; - int stack_trace{0}, debug_off{0}; - - bool chksig_always_succeed{false}; - - public: - static constexpr unsigned cell_load_gas_price = 100, cell_create_gas_price = 500, exception_gas_price = 50, - tuple_entry_gas_price = 1; - VmState(); - VmState(Ref _code); - VmState(Ref _code, Ref _stack, int flags = 0, Ref _data = {}, VmLog log = {}, - std::vector> _libraries = {}, Ref init_c7 = {}); - VmState(Ref _code, Ref _stack, const GasLimits& _gas, int flags = 0, Ref _data = {}, - VmLog log = {}, std::vector> _libraries = {}, Ref init_c7 = {}); - template - VmState(Ref code_cell, Args&&... args) - : VmState(convert_code_cell(std::move(code_cell)), std::forward(args)...) { - } - VmState(const VmState&) = delete; - VmState(VmState&&) = delete; - VmState& operator=(const VmState&) = delete; - VmState& operator=(VmState&&) = delete; - bool set_gas_limits(long long _max, long long _limit, long long _credit = 0); - bool final_gas_ok() const { - return gas.final_ok(); - } - long long gas_consumed() const { - return gas.gas_consumed(); - } - bool committed() const { - return cstate.committed; - } - const CommittedState& get_committed_state() const { - return cstate; - } - void consume_gas(long long amount) { - gas.consume(amount); - } - void consume_tuple_gas(unsigned tuple_len) { - consume_gas(tuple_len * tuple_entry_gas_price); - } - void consume_tuple_gas(const Ref& tup) { - if (tup.not_null()) { - consume_tuple_gas((unsigned)tup->size()); - } - } - GasLimits get_gas_limits() const { - return gas; - } - void change_gas_limit(long long new_limit); - template - void check_underflow(Args... args) { - stack->check_underflow(args...); - } - bool register_library_collection(Ref lib); - Ref load_library( - td::ConstBitPtr hash) override; // may throw a dictionary exception; returns nullptr if library is not found - void register_cell_load() override; - void register_cell_create() override; - bool init_cp(int new_cp); - bool set_cp(int new_cp); - void force_cp(int new_cp); - int get_cp() const { - return cp; - } - int incr_stack_trace(int v) { - return stack_trace += v; - } - long long get_steps_count() const { - return steps; - } - td::BitArray<256> get_state_hash() const; - td::BitArray<256> get_final_state_hash(int exit_code) const; - int step(); - int run(); - Stack& get_stack() { - return stack.write(); - } - const Stack& get_stack_const() const { - return *stack; - } - Ref get_stack_ref() const { - return stack; - } - Ref get_c0() const { - return cr.c[0]; - } - Ref get_c1() const { - return cr.c[1]; - } - Ref get_c2() const { - return cr.c[2]; - } - Ref get_c3() const { - return cr.c[3]; - } - Ref get_c4() const { - return cr.d[0]; - } - Ref get_c7() const { - return cr.c7; - } - Ref get_c(unsigned idx) const { - return cr.get_c(idx); - } - Ref get_d(unsigned idx) const { - return cr.get_d(idx); - } - StackEntry get(unsigned idx) const { - return cr.get(idx); - } - const VmLog& get_log() const { - return log; - } - void define_c0(Ref cont) { - cr.define_c0(std::move(cont)); - } - void set_c0(Ref cont) { - cr.set_c0(std::move(cont)); - } - void set_c1(Ref cont) { - cr.set_c1(std::move(cont)); - } - void set_c2(Ref cont) { - cr.set_c2(std::move(cont)); - } - bool set_c(unsigned idx, Ref val) { - return cr.set_c(idx, std::move(val)); - } - bool set_d(unsigned idx, Ref val) { - return cr.set_d(idx, std::move(val)); - } - void set_c4(Ref val) { - cr.set_c4(std::move(val)); - } - bool set_c7(Ref val) { - return cr.set_c7(std::move(val)); - } - bool set(unsigned idx, StackEntry val) { - return cr.set(idx, std::move(val)); - } - void set_stack(Ref new_stk) { - stack = std::move(new_stk); - } - Ref swap_stack(Ref new_stk) { - stack.swap(new_stk); - return new_stk; - } - void ensure_throw(bool cond) const { - if (!cond) { - fatal(); - } - } - void set_code(Ref _code, int _cp) { - code = std::move(_code); - force_cp(_cp); - } - Ref get_code() const { - return code; - } - void push_code() { - get_stack().push_cellslice(get_code()); - } - void adjust_cr(const ControlRegs& save) { - cr ^= save; - } - void adjust_cr(ControlRegs&& save) { - cr ^= save; - } - void preclear_cr(const ControlRegs& save) { - cr &= save; - } - int call(Ref cont); - int call(Ref cont, int pass_args, int ret_args = -1); - int jump(Ref cont); - int jump(Ref cont, int pass_args); - int ret(); - int ret(int ret_args); - int ret_alt(); - int ret_alt(int ret_args); - int repeat(Ref body, Ref after, long long count); - int again(Ref body); - int until(Ref body, Ref after); - int loop_while(Ref cond, Ref body, Ref after); - int throw_exception(int excno, StackEntry&& arg); - int throw_exception(int excno); - Ref extract_cc(int save_cr = 1, int stack_copy = -1, int cc_args = -1); - void fatal(void) const { - throw VmFatal{}; - } - int jump_to(Ref cont) { - return cont->is_unique() ? cont.unique_write().jump_w(this) : cont->jump(this); - } - static Ref convert_code_cell(Ref code_cell); - void commit() { - cstate.c4 = cr.d[0]; - cstate.c5 = cr.d[1]; - cstate.committed = true; - } - - void set_chksig_always_succeed(bool flag) { - chksig_always_succeed = flag; - } - bool get_chksig_always_succeed() const { - return chksig_always_succeed; - } - - private: - void init_cregs(bool same_c3 = false, bool push_0 = true); -}; - -int run_vm_code(Ref _code, Ref& _stack, int flags = 0, Ref* data_ptr = nullptr, VmLog log = {}, - long long* steps = nullptr, GasLimits* gas_limits = nullptr, std::vector> libraries = {}, - Ref init_c7 = {}, Ref* actions_ptr = nullptr); -int run_vm_code(Ref _code, Stack& _stack, int flags = 0, Ref* data_ptr = nullptr, VmLog log = {}, - long long* steps = nullptr, GasLimits* gas_limits = nullptr, std::vector> libraries = {}, - Ref init_c7 = {}, Ref* actions_ptr = nullptr); - ControlData* force_cdata(Ref& cont); ControlRegs* force_cregs(Ref& cont); -Ref lookup_library_in(td::ConstBitPtr key, Ref lib_root); - } // namespace vm diff --git a/crypto/vm/contops.cpp b/crypto/vm/contops.cpp index 6fcda8de..63d55705 100644 --- a/crypto/vm/contops.cpp +++ b/crypto/vm/contops.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include #include "vm/contops.h" @@ -24,6 +24,7 @@ #include "vm/continuation.h" #include "vm/cellops.h" #include "vm/excno.hpp" +#include "vm/vm.h" namespace vm { diff --git a/crypto/vm/debugops.cpp b/crypto/vm/debugops.cpp index 5c22740a..f8dc4396 100644 --- a/crypto/vm/debugops.cpp +++ b/crypto/vm/debugops.cpp @@ -14,15 +14,15 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include #include "vm/debugops.h" #include "vm/log.h" #include "vm/opctable.h" #include "vm/stack.hpp" -#include "vm/continuation.h" #include "vm/excno.hpp" +#include "vm/vm.h" namespace vm { diff --git a/crypto/vm/dictops.cpp b/crypto/vm/dictops.cpp index 820e1e97..c798b333 100644 --- a/crypto/vm/dictops.cpp +++ b/crypto/vm/dictops.cpp @@ -20,8 +20,8 @@ #include "vm/log.h" #include "vm/opctable.h" #include "vm/stack.hpp" -#include "vm/continuation.h" #include "vm/excno.hpp" +#include "vm/vm.h" #include "common/bigint.hpp" #include "common/refint.h" #include "vm/dictops.h" diff --git a/crypto/vm/opctable.cpp b/crypto/vm/opctable.cpp index fbdc2578..d4f0f3e9 100644 --- a/crypto/vm/opctable.cpp +++ b/crypto/vm/opctable.cpp @@ -14,14 +14,14 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include #include #include "vm/opctable.h" #include "vm/cellslice.h" #include "vm/excno.hpp" -#include "vm/continuation.h" +#include "vm/vm.h" #include #include #include diff --git a/crypto/vm/stackops.cpp b/crypto/vm/stackops.cpp index 7fdbcba0..ac7d9f88 100644 --- a/crypto/vm/stackops.cpp +++ b/crypto/vm/stackops.cpp @@ -14,14 +14,14 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/log.h" #include "vm/stackops.h" #include "vm/opctable.h" #include "vm/stack.hpp" -#include "vm/continuation.h" #include "vm/excno.hpp" +#include "vm/vm.h" namespace vm { diff --git a/crypto/vm/tonops.cpp b/crypto/vm/tonops.cpp index 5dcd0d54..5fd8bd64 100644 --- a/crypto/vm/tonops.cpp +++ b/crypto/vm/tonops.cpp @@ -14,15 +14,15 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include #include "vm/tonops.h" #include "vm/log.h" #include "vm/opctable.h" #include "vm/stack.hpp" -#include "vm/continuation.h" #include "vm/excno.hpp" +#include "vm/vm.h" #include "vm/dict.h" #include "Ed25519.h" @@ -397,6 +397,83 @@ void register_ton_crypto_ops(OpcodeTable& cp0) { .insert(OpcodeInstr::mksimple(0xf911, 16, "CHKSIGNS", std::bind(exec_ed25519_check_signature, _1, true))); } +struct VmStorageStat { + td::uint64 cells{0}, bits{0}, refs{0}, limit; + td::HashSet visited; + VmStorageStat(td::uint64 _limit) : limit(_limit) { + } + bool add_storage(Ref cell); + bool add_storage(const CellSlice& cs); + bool check_visited(const CellHash& cell_hash) { + return visited.insert(cell_hash).second; + } + bool check_visited(const Ref& cell) { + return check_visited(cell->get_hash()); + } +}; + +bool VmStorageStat::add_storage(Ref cell) { + if (cell.is_null() || !check_visited(cell)) { + return true; + } + if (cells >= limit) { + return false; + } + ++cells; + bool special; + auto cs = load_cell_slice_special(std::move(cell), special); + return cs.is_valid() && add_storage(std::move(cs)); +} + +bool VmStorageStat::add_storage(const CellSlice& cs) { + bits += cs.size(); + refs += cs.size_refs(); + for (unsigned i = 0; i < cs.size_refs(); i++) { + if (!add_storage(cs.prefetch_ref(i))) { + return false; + } + } + return true; +} + +int exec_compute_data_size(VmState* st, int mode) { + VM_LOG(st) << (mode & 2 ? 'S' : 'C') << "DATASIZE" << (mode & 1 ? "Q" : ""); + Stack& stack = st->get_stack(); + stack.check_underflow(2); + auto bound = stack.pop_int(); + Ref cell; + Ref cs; + if (mode & 2) { + cs = stack.pop_cellslice(); + } else { + cell = stack.pop_maybe_cell(); + } + if (!bound->is_valid() || bound->sgn() < 0) { + throw VmError{Excno::range_chk, "finite non-negative integer expected"}; + } + VmStorageStat stat{bound->unsigned_fits_bits(63) ? bound->to_long() : (1ULL << 63) - 1}; + bool ok = (mode & 2 ? stat.add_storage(cs.write()) : stat.add_storage(std::move(cell))); + if (ok) { + stack.push_smallint(stat.cells); + stack.push_smallint(stat.bits); + stack.push_smallint(stat.refs); + } else if (!(mode & 1)) { + throw VmError{Excno::cell_ov, "scanned too many cells"}; + } + if (mode & 1) { + stack.push_bool(ok); + } + return 0; +} + +void register_ton_misc_ops(OpcodeTable& cp0) { + using namespace std::placeholders; + cp0.insert(OpcodeInstr::mksimple(0xf940, 16, "CDATASIZEQ", std::bind(exec_compute_data_size, _1, 1))) + .insert(OpcodeInstr::mksimple(0xf941, 16, "CDATASIZE", std::bind(exec_compute_data_size, _1, 0))) + .insert(OpcodeInstr::mksimple(0xf942, 16, "SDATASIZEQ", std::bind(exec_compute_data_size, _1, 3))) + .insert(OpcodeInstr::mksimple(0xf943, 16, "SDATASIZE", std::bind(exec_compute_data_size, _1, 2))); +} + int exec_load_var_integer(VmState* st, int len_bits, bool sgnd, bool quiet) { if (len_bits == 4 && !sgnd) { VM_LOG(st) << "execute LDGRAMS" << (quiet ? "Q" : ""); @@ -822,6 +899,7 @@ void register_ton_ops(OpcodeTable& cp0) { register_prng_ops(cp0); register_ton_config_ops(cp0); register_ton_crypto_ops(cp0); + register_ton_misc_ops(cp0); register_ton_currency_address_ops(cp0); register_ton_message_ops(cp0); } diff --git a/crypto/vm/tupleops.cpp b/crypto/vm/tupleops.cpp index de4de834..96f78fe3 100644 --- a/crypto/vm/tupleops.cpp +++ b/crypto/vm/tupleops.cpp @@ -14,14 +14,14 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/log.h" #include "vm/stackops.h" #include "vm/opctable.h" #include "vm/stack.hpp" -#include "vm/continuation.h" #include "vm/excno.hpp" +#include "vm/vm.h" namespace vm { diff --git a/crypto/vm/vm.cpp b/crypto/vm/vm.cpp new file mode 100644 index 00000000..cba973bc --- /dev/null +++ b/crypto/vm/vm.cpp @@ -0,0 +1,593 @@ +/* + 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 "vm/dispatch.h" +#include "vm/continuation.h" +#include "vm/dict.h" +#include "vm/log.h" +#include "vm/vm.h" + +namespace vm { + +VmState::VmState() : cp(-1), dispatch(&dummy_dispatch_table), quit0(true, 0), quit1(true, 1) { + ensure_throw(init_cp(0)); + init_cregs(); +} + +VmState::VmState(Ref _code) + : code(std::move(_code)), cp(-1), dispatch(&dummy_dispatch_table), quit0(true, 0), quit1(true, 1) { + ensure_throw(init_cp(0)); + init_cregs(); +} + +VmState::VmState(Ref _code, Ref _stack, int flags, Ref _data, VmLog log, + std::vector> _libraries, Ref init_c7) + : code(std::move(_code)) + , stack(std::move(_stack)) + , cp(-1) + , dispatch(&dummy_dispatch_table) + , quit0(true, 0) + , quit1(true, 1) + , log(log) + , libraries(std::move(_libraries)) { + ensure_throw(init_cp(0)); + set_c4(std::move(_data)); + if (init_c7.not_null()) { + set_c7(std::move(init_c7)); + } + init_cregs(flags & 1, flags & 2); +} + +VmState::VmState(Ref _code, Ref _stack, const GasLimits& gas, int flags, Ref _data, VmLog log, + std::vector> _libraries, Ref init_c7) + : code(std::move(_code)) + , stack(std::move(_stack)) + , cp(-1) + , dispatch(&dummy_dispatch_table) + , quit0(true, 0) + , quit1(true, 1) + , log(log) + , gas(gas) + , libraries(std::move(_libraries)) { + ensure_throw(init_cp(0)); + set_c4(std::move(_data)); + if (init_c7.not_null()) { + set_c7(std::move(init_c7)); + } + init_cregs(flags & 1, flags & 2); +} + +Ref VmState::convert_code_cell(Ref code_cell) { + if (code_cell.is_null()) { + return {}; + } + Ref csr{true, NoVmOrd(), code_cell}; + if (csr->is_valid()) { + return csr; + } + return load_cell_slice_ref(CellBuilder{}.store_ref(std::move(code_cell)).finalize()); +} + +bool VmState::init_cp(int new_cp) { + const DispatchTable* dt = DispatchTable::get_table(new_cp); + if (dt) { + cp = new_cp; + dispatch = dt; + return true; + } else { + return false; + } +} + +bool VmState::set_cp(int new_cp) { + return new_cp == cp || init_cp(new_cp); +} + +void VmState::force_cp(int new_cp) { + if (!set_cp(new_cp)) { + throw VmError{Excno::inv_opcode, "unsupported codepage"}; + } +} + +// simple call to a continuation cont +int VmState::call(Ref cont) { + const ControlData* cont_data = cont->get_cdata(); + if (cont_data) { + if (cont_data->save.c[0].not_null()) { + // call reduces to a jump + return jump(std::move(cont)); + } + if (cont_data->stack.not_null() || cont_data->nargs >= 0) { + // if cont has non-empty stack or expects fixed number of arguments, call is not simple + return call(std::move(cont), -1, -1); + } + // create return continuation, to be stored into new c0 + Ref ret = Ref{true, std::move(code), cp}; + ret.unique_write().get_cdata()->save.set_c0(std::move(cr.c[0])); + cr.set_c0( + std::move(ret)); // set c0 to its final value before switching to cont; notice that cont.save.c0 is not set + return jump_to(std::move(cont)); + } + // create return continuation, to be stored into new c0 + Ref ret = Ref{true, std::move(code), cp}; + ret.unique_write().get_cdata()->save.set_c0(std::move(cr.c[0])); + // general implementation of a simple call + cr.set_c0(std::move(ret)); // set c0 to its final value before switching to cont; notice that cont.save.c0 is not set + return jump_to(std::move(cont)); +} + +// call with parameters to continuation cont +int VmState::call(Ref cont, int pass_args, int ret_args) { + const ControlData* cont_data = cont->get_cdata(); + if (cont_data) { + if (cont_data->save.c[0].not_null()) { + // call reduces to a jump + return jump(std::move(cont), pass_args); + } + int depth = stack->depth(); + if (pass_args > depth || cont_data->nargs > depth) { + throw VmError{Excno::stk_und, "stack underflow while calling a continuation: not enough arguments on stack"}; + } + if (cont_data->nargs > pass_args && pass_args >= 0) { + throw VmError{Excno::stk_und, + "stack underflow while calling a closure continuation: not enough arguments passed"}; + } + auto old_c0 = std::move(cr.c[0]); + // optimization(?): decrease refcnts of unused continuations in c[i] as early as possible + preclear_cr(cont_data->save); + // no exceptions should be thrown after this point + int copy = cont_data->nargs, skip = 0; + if (pass_args >= 0) { + if (copy >= 0) { + skip = pass_args - copy; + } else { + copy = pass_args; + } + } + // copy=-1 : pass whole stack, else pass top `copy` elements, drop next `skip` elements. + Ref new_stk; + if (cont_data->stack.not_null() && !cont_data->stack->is_empty()) { + // `cont` already has a stack, create resulting stack from it + if (copy < 0) { + copy = stack->depth(); + } + if (cont->is_unique()) { + // optimization: avoid copying stack if we hold the only copy of `cont` + new_stk = std::move(cont.unique_write().get_cdata()->stack); + } else { + new_stk = cont_data->stack; + } + new_stk.write().move_from_stack(get_stack(), copy); + if (skip > 0) { + get_stack().pop_many(skip); + } + } else if (copy >= 0) { + new_stk = get_stack().split_top(copy, skip); + } else { + new_stk = std::move(stack); + stack.clear(); + } + // create return continuation using the remainder of current stack + Ref ret = Ref{true, std::move(code), cp, std::move(stack), ret_args}; + ret.unique_write().get_cdata()->save.set_c0(std::move(old_c0)); + Ref ord_cont = static_cast>(cont); + set_stack(std::move(new_stk)); + cr.set_c0(std::move(ret)); // ??? if codepage of code in ord_cont is unknown, will end up with incorrect c0 + return jump_to(std::move(cont)); + } else { + // have no continuation data, situation is somewhat simpler + int depth = stack->depth(); + if (pass_args > depth) { + throw VmError{Excno::stk_und, "stack underflow while calling a continuation: not enough arguments on stack"}; + } + // create new stack from the top `pass_args` elements of the current stack + Ref new_stk = (pass_args >= 0 ? get_stack().split_top(pass_args) : std::move(stack)); + // create return continuation using the remainder of the current stack + Ref ret = Ref{true, std::move(code), cp, std::move(stack), ret_args}; + ret.unique_write().get_cdata()->save.set_c0(std::move(cr.c[0])); + set_stack(std::move(new_stk)); + cr.set_c0(std::move(ret)); // ??? if codepage of code in ord_cont is unknown, will end up with incorrect c0 + return jump_to(std::move(cont)); + } +} + +// simple jump to continuation cont +int VmState::jump(Ref cont) { + const ControlData* cont_data = cont->get_cdata(); + if (cont_data && (cont_data->stack.not_null() || cont_data->nargs >= 0)) { + // if cont has non-empty stack or expects fixed number of arguments, jump is not simple + return jump(std::move(cont), -1); + } else { + return jump_to(std::move(cont)); + } +} + +// general jump to continuation cont +int VmState::jump(Ref cont, int pass_args) { + const ControlData* cont_data = cont->get_cdata(); + if (cont_data) { + // first do the checks + int depth = stack->depth(); + if (pass_args > depth || cont_data->nargs > depth) { + throw VmError{Excno::stk_und, "stack underflow while jumping to a continuation: not enough arguments on stack"}; + } + if (cont_data->nargs > pass_args && pass_args >= 0) { + throw VmError{Excno::stk_und, + "stack underflow while jumping to closure continuation: not enough arguments passed"}; + } + // optimization(?): decrease refcnts of unused continuations in c[i] as early as possible + preclear_cr(cont_data->save); + // no exceptions should be thrown after this point + int copy = cont_data->nargs; + if (pass_args >= 0 && copy < 0) { + copy = pass_args; + } + // copy=-1 : pass whole stack, else pass top `copy` elements, drop the remainder. + if (cont_data->stack.not_null() && !cont_data->stack->is_empty()) { + // `cont` already has a stack, create resulting stack from it + if (copy < 0) { + copy = get_stack().depth(); + } + Ref new_stk; + if (cont->is_unique()) { + // optimization: avoid copying the stack if we hold the only copy of `cont` + new_stk = std::move(cont.unique_write().get_cdata()->stack); + } else { + new_stk = cont_data->stack; + } + new_stk.write().move_from_stack(get_stack(), copy); + set_stack(std::move(new_stk)); + } else { + if (copy >= 0) { + get_stack().drop_bottom(stack->depth() - copy); + } + } + return jump_to(std::move(cont)); + } else { + // have no continuation data, situation is somewhat simpler + if (pass_args >= 0) { + int depth = get_stack().depth(); + if (pass_args > depth) { + throw VmError{Excno::stk_und, "stack underflow while jumping to a continuation: not enough arguments on stack"}; + } + get_stack().drop_bottom(depth - pass_args); + } + return jump_to(std::move(cont)); + } +} + +int VmState::ret() { + Ref cont = quit0; + cont.swap(cr.c[0]); + return jump(std::move(cont)); +} + +int VmState::ret(int ret_args) { + Ref cont = quit0; + cont.swap(cr.c[0]); + return jump(std::move(cont), ret_args); +} + +int VmState::ret_alt() { + Ref cont = quit1; + cont.swap(cr.c[1]); + return jump(std::move(cont)); +} + +int VmState::ret_alt(int ret_args) { + Ref cont = quit1; + cont.swap(cr.c[1]); + return jump(std::move(cont), ret_args); +} + +Ref VmState::extract_cc(int save_cr, int stack_copy, int cc_args) { + Ref new_stk; + if (stack_copy < 0 || stack_copy == stack->depth()) { + new_stk = std::move(stack); + stack.clear(); + } else if (stack_copy > 0) { + stack->check_underflow(stack_copy); + new_stk = get_stack().split_top(stack_copy); + } else { + new_stk = Ref{true}; + } + Ref cc = Ref{true, std::move(code), cp, std::move(stack), cc_args}; + stack = std::move(new_stk); + if (save_cr & 7) { + ControlData* cdata = cc.unique_write().get_cdata(); + if (save_cr & 1) { + cdata->save.set_c0(std::move(cr.c[0])); + cr.set_c0(quit0); + } + if (save_cr & 2) { + cdata->save.set_c1(std::move(cr.c[1])); + cr.set_c1(quit1); + } + if (save_cr & 4) { + cdata->save.set_c2(std::move(cr.c[2])); + // cr.set_c2(Ref{true}); + } + } + return cc; +} + +int VmState::throw_exception(int excno) { + Stack& stack_ref = get_stack(); + stack_ref.clear(); + stack_ref.push_smallint(0); + stack_ref.push_smallint(excno); + code.clear(); + consume_gas(exception_gas_price); + return jump(get_c2()); +} + +int VmState::throw_exception(int excno, StackEntry&& arg) { + Stack& stack_ref = get_stack(); + stack_ref.clear(); + stack_ref.push(std::move(arg)); + stack_ref.push_smallint(excno); + code.clear(); + consume_gas(exception_gas_price); + return jump(get_c2()); +} + +void GasLimits::gas_exception() const { + throw VmNoGas{}; +} + +void GasLimits::set_limits(long long _max, long long _limit, long long _credit) { + gas_max = _max; + gas_limit = _limit; + gas_credit = _credit; + change_base(_limit + _credit); +} + +void GasLimits::change_limit(long long _limit) { + _limit = std::min(std::max(_limit, 0LL), gas_max); + gas_credit = 0; + gas_limit = _limit; + change_base(_limit); +} + +bool VmState::set_gas_limits(long long _max, long long _limit, long long _credit) { + gas.set_limits(_max, _limit, _credit); + return true; +} + +void VmState::change_gas_limit(long long new_limit) { + VM_LOG(this) << "changing gas limit to " << std::min(new_limit, gas.gas_max); + gas.change_limit(new_limit); +} + +int VmState::step() { + assert(!code.is_null()); + //VM_LOG(st) << "stack:"; stack->dump(VM_LOG(st)); + //VM_LOG(st) << "; cr0.refcnt = " << get_c0()->get_refcnt() - 1 << std::endl; + if (stack_trace) { + stack->dump(std::cerr, 3); + } + ++steps; + if (code->size()) { + return dispatch->dispatch(this, code.write()); + } else if (code->size_refs()) { + VM_LOG(this) << "execute implicit JMPREF\n"; + Ref cont = Ref{true, load_cell_slice_ref(code->prefetch_ref()), get_cp()}; + return jump(std::move(cont)); + } else { + VM_LOG(this) << "execute implicit RET\n"; + return ret(); + } +} + +int VmState::run() { + if (code.is_null()) { + throw VmError{Excno::fatal, "cannot run an uninitialized VM"}; + } + int res; + Guard guard(this); + do { + // LOG(INFO) << "[BS] data cells: " << DataCell::get_total_data_cells(); + try { + try { + res = step(); + gas.check(); + } catch (vm::CellBuilder::CellWriteError) { + throw VmError{Excno::cell_ov}; + } catch (vm::CellBuilder::CellCreateError) { + throw VmError{Excno::cell_ov}; + } catch (vm::CellSlice::CellReadError) { + throw VmError{Excno::cell_und}; + } + } catch (const VmError& vme) { + VM_LOG(this) << "handling exception code " << vme.get_errno() << ": " << vme.get_msg(); + try { + // LOG(INFO) << "[EX] data cells: " << DataCell::get_total_data_cells(); + ++steps; + res = throw_exception(vme.get_errno()); + } catch (const VmError& vme2) { + VM_LOG(this) << "exception " << vme2.get_errno() << " while handling exception: " << vme.get_msg(); + // LOG(INFO) << "[EXX] data cells: " << DataCell::get_total_data_cells(); + return ~vme2.get_errno(); + } + } catch (VmNoGas vmoog) { + ++steps; + VM_LOG(this) << "unhandled out-of-gas exception: gas consumed=" << gas.gas_consumed() + << ", limit=" << gas.gas_limit; + get_stack().clear(); + get_stack().push_smallint(gas.gas_consumed()); + return vmoog.get_errno(); // no ~ for unhandled exceptions (to make their faking impossible) + } + } while (!res); + // LOG(INFO) << "[EN] data cells: " << DataCell::get_total_data_cells(); + if ((res | 1) == -1) { + commit(); + } + return res; +} + +ControlData* force_cdata(Ref& cont) { + if (!cont->get_cdata()) { + cont = Ref{true, cont}; + return cont.unique_write().get_cdata(); + } else { + return cont.write().get_cdata(); + } +} + +ControlRegs* force_cregs(Ref& cont) { + return &force_cdata(cont)->save; +} + +int run_vm_code(Ref code, Ref& stack, int flags, Ref* data_ptr, VmLog log, long long* steps, + GasLimits* gas_limits, std::vector> libraries, Ref init_c7, Ref* actions_ptr) { + VmState vm{code, + std::move(stack), + gas_limits ? *gas_limits : GasLimits{}, + flags, + data_ptr ? *data_ptr : Ref{}, + log, + std::move(libraries), + std::move(init_c7)}; + int res = vm.run(); + stack = vm.get_stack_ref(); + if (vm.committed() && data_ptr) { + *data_ptr = vm.get_committed_state().c4; + } + if (vm.committed() && actions_ptr) { + *actions_ptr = vm.get_committed_state().c5; + } + if (steps) { + *steps = vm.get_steps_count(); + } + if (gas_limits) { + *gas_limits = vm.get_gas_limits(); + LOG(INFO) << "steps: " << vm.get_steps_count() << " gas: used=" << gas_limits->gas_consumed() + << ", max=" << gas_limits->gas_max << ", limit=" << gas_limits->gas_limit + << ", credit=" << gas_limits->gas_credit; + } + if ((vm.get_log().log_mask & vm::VmLog::DumpStack) != 0) { + VM_LOG(&vm) << "BEGIN_STACK_DUMP"; + for (int i = stack->depth(); i > 0; i--) { + VM_LOG(&vm) << (*stack)[i - 1].to_string(); + } + VM_LOG(&vm) << "END_STACK_DUMP"; + } + + return ~res; +} + +int run_vm_code(Ref code, Stack& stack, int flags, Ref* data_ptr, VmLog log, long long* steps, + GasLimits* gas_limits, std::vector> libraries, Ref init_c7, Ref* actions_ptr) { + Ref stk{true}; + stk.unique_write().set_contents(std::move(stack)); + stack.clear(); + int res = run_vm_code(code, stk, flags, data_ptr, log, steps, gas_limits, std::move(libraries), std::move(init_c7), + actions_ptr); + CHECK(stack.is_unique()); + if (stk.is_null()) { + stack.clear(); + } else if (&(*stk) != &stack) { + VmState* st = nullptr; + if (stk->is_unique()) { + VM_LOG(st) << "move resulting stack (" << stk->depth() << " entries)"; + stack.set_contents(std::move(stk.unique_write())); + } else { + VM_LOG(st) << "copying resulting stack (" << stk->depth() << " entries)"; + stack.set_contents(*stk); + } + } + return res; +} + +// may throw a dictionary exception; returns nullptr if library is not found in context +Ref VmState::load_library(td::ConstBitPtr hash) { + std::unique_ptr tmp_ctx; + // install temporary dummy vm state interface to prevent charging for cell load operations during library lookup + VmStateInterface::Guard(tmp_ctx.get()); + for (const auto& lib_collection : libraries) { + auto lib = lookup_library_in(hash, lib_collection); + if (lib.not_null()) { + return lib; + } + } + return {}; +} + +bool VmState::register_library_collection(Ref lib) { + if (lib.is_null()) { + return true; + } + libraries.push_back(std::move(lib)); + return true; +} + +void VmState::register_cell_load(const CellHash& cell_hash) { + if (cell_load_gas_price == cell_reload_gas_price) { + consume_gas(cell_load_gas_price); + } else { + auto ok = loaded_cells.insert(cell_hash); // check whether this is the first time this cell is loaded + if (ok.second) { + loaded_cells_count++; + } + consume_gas(ok.second ? cell_load_gas_price : cell_reload_gas_price); + } +} + +void VmState::register_cell_create() { + consume_gas(cell_create_gas_price); +} + +td::BitArray<256> VmState::get_state_hash() const { + // TODO: implement properly, by serializing the stack etc, and computing the Merkle hash + td::BitArray<256> res; + res.clear(); + return res; +} + +td::BitArray<256> VmState::get_final_state_hash(int exit_code) const { + // TODO: implement properly, by serializing the stack etc, and computing the Merkle hash + td::BitArray<256> res; + res.clear(); + return res; +} + +Ref lookup_library_in(td::ConstBitPtr key, vm::Dictionary& dict) { + try { + auto val = dict.lookup(key, 256); + if (val.is_null() || !val->have_refs()) { + return {}; + } + auto root = val->prefetch_ref(); + if (root.not_null() && !root->get_hash().bits().compare(key, 256)) { + return root; + } + return {}; + } catch (vm::VmError) { + return {}; + } +} + +Ref lookup_library_in(td::ConstBitPtr key, Ref lib_root) { + if (lib_root.is_null()) { + return lib_root; + } + vm::Dictionary dict{std::move(lib_root), 256}; + return lookup_library_in(key, dict); +} + +} // namespace vm diff --git a/crypto/vm/vm.h b/crypto/vm/vm.h new file mode 100644 index 00000000..eac5f2fd --- /dev/null +++ b/crypto/vm/vm.h @@ -0,0 +1,320 @@ +/* + 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 +*/ +#pragma once + +#include "common/refcnt.hpp" +#include "vm/cellslice.h" +#include "vm/stack.hpp" +#include "vm/vmstate.h" +#include "vm/log.h" +#include "vm/continuation.h" +#include "td/utils/HashSet.h" + +namespace vm { + +using td::Ref; +struct GasLimits { + static constexpr long long infty = (1ULL << 63) - 1; + long long gas_max, gas_limit, gas_credit, gas_remaining, gas_base; + GasLimits() : gas_max(infty), gas_limit(infty), gas_credit(0), gas_remaining(infty), gas_base(infty) { + } + GasLimits(long long _limit, long long _max = infty, long long _credit = 0) + : gas_max(_max) + , gas_limit(_limit) + , gas_credit(_credit) + , gas_remaining(_limit + _credit) + , gas_base(gas_remaining) { + } + long long gas_consumed() const { + return gas_base - gas_remaining; + } + void set_limits(long long _max, long long _limit, long long _credit = 0); + void change_base(long long _base) { + gas_remaining += _base - gas_base; + gas_base = _base; + } + void change_limit(long long _limit); + void consume(long long amount) { + // LOG(DEBUG) << "consume " << amount << " gas (" << gas_remaining << " remaining)"; + gas_remaining -= amount; + } + bool try_consume(long long amount) { + // LOG(DEBUG) << "try consume " << amount << " gas (" << gas_remaining << " remaining)"; + return (gas_remaining -= amount) >= 0; + } + void gas_exception() const; + void gas_exception(bool cond) const { + if (!cond) { + gas_exception(); + } + } + void consume_chk(long long amount) { + gas_exception(try_consume(amount)); + } + void check() const { + gas_exception(gas_remaining >= 0); + } + bool final_ok() const { + return gas_remaining >= gas_credit; + } +}; + +struct CommittedState { + Ref c4, c5; + bool committed{false}; +}; + +class VmState final : public VmStateInterface { + Ref code; + Ref stack; + ControlRegs cr; + CommittedState cstate; + int cp; + long long steps{0}; + const DispatchTable* dispatch; + Ref quit0, quit1; + VmLog log; + GasLimits gas; + std::vector> libraries; + td::HashSet loaded_cells; + td::int64 loaded_cells_count{0}; + int stack_trace{0}, debug_off{0}; + bool chksig_always_succeed{false}; + + public: + enum { + cell_load_gas_price = 100, + cell_reload_gas_price = 25, + cell_create_gas_price = 500, + exception_gas_price = 50, + tuple_entry_gas_price = 1 + }; + VmState(); + VmState(Ref _code); + VmState(Ref _code, Ref _stack, int flags = 0, Ref _data = {}, VmLog log = {}, + std::vector> _libraries = {}, Ref init_c7 = {}); + VmState(Ref _code, Ref _stack, const GasLimits& _gas, int flags = 0, Ref _data = {}, + VmLog log = {}, std::vector> _libraries = {}, Ref init_c7 = {}); + template + VmState(Ref code_cell, Args&&... args) + : VmState(convert_code_cell(std::move(code_cell)), std::forward(args)...) { + } + VmState(const VmState&) = delete; + VmState(VmState&&) = delete; + VmState& operator=(const VmState&) = delete; + VmState& operator=(VmState&&) = delete; + bool set_gas_limits(long long _max, long long _limit, long long _credit = 0); + bool final_gas_ok() const { + return gas.final_ok(); + } + long long gas_consumed() const { + return gas.gas_consumed(); + } + bool committed() const { + return cstate.committed; + } + const CommittedState& get_committed_state() const { + return cstate; + } + void consume_gas(long long amount) { + gas.consume(amount); + } + void consume_tuple_gas(unsigned tuple_len) { + consume_gas(tuple_len * tuple_entry_gas_price); + } + void consume_tuple_gas(const Ref& tup) { + if (tup.not_null()) { + consume_tuple_gas((unsigned)tup->size()); + } + } + GasLimits get_gas_limits() const { + return gas; + } + void change_gas_limit(long long new_limit); + template + void check_underflow(Args... args) { + stack->check_underflow(args...); + } + bool register_library_collection(Ref lib); + Ref load_library( + td::ConstBitPtr hash) override; // may throw a dictionary exception; returns nullptr if library is not found + void register_cell_load(const CellHash& cell_hash) override; + void register_cell_create() override; + bool init_cp(int new_cp); + bool set_cp(int new_cp); + void force_cp(int new_cp); + int get_cp() const { + return cp; + } + int incr_stack_trace(int v) { + return stack_trace += v; + } + long long get_steps_count() const { + return steps; + } + td::BitArray<256> get_state_hash() const; + td::BitArray<256> get_final_state_hash(int exit_code) const; + int step(); + int run(); + Stack& get_stack() { + return stack.write(); + } + const Stack& get_stack_const() const { + return *stack; + } + Ref get_stack_ref() const { + return stack; + } + Ref get_c0() const { + return cr.c[0]; + } + Ref get_c1() const { + return cr.c[1]; + } + Ref get_c2() const { + return cr.c[2]; + } + Ref get_c3() const { + return cr.c[3]; + } + Ref get_c4() const { + return cr.d[0]; + } + Ref get_c7() const { + return cr.c7; + } + Ref get_c(unsigned idx) const { + return cr.get_c(idx); + } + Ref get_d(unsigned idx) const { + return cr.get_d(idx); + } + StackEntry get(unsigned idx) const { + return cr.get(idx); + } + const VmLog& get_log() const { + return log; + } + void define_c0(Ref cont) { + cr.define_c0(std::move(cont)); + } + void set_c0(Ref cont) { + cr.set_c0(std::move(cont)); + } + void set_c1(Ref cont) { + cr.set_c1(std::move(cont)); + } + void set_c2(Ref cont) { + cr.set_c2(std::move(cont)); + } + bool set_c(unsigned idx, Ref val) { + return cr.set_c(idx, std::move(val)); + } + bool set_d(unsigned idx, Ref val) { + return cr.set_d(idx, std::move(val)); + } + void set_c4(Ref val) { + cr.set_c4(std::move(val)); + } + bool set_c7(Ref val) { + return cr.set_c7(std::move(val)); + } + bool set(unsigned idx, StackEntry val) { + return cr.set(idx, std::move(val)); + } + void set_stack(Ref new_stk) { + stack = std::move(new_stk); + } + Ref swap_stack(Ref new_stk) { + stack.swap(new_stk); + return new_stk; + } + void ensure_throw(bool cond) const { + if (!cond) { + fatal(); + } + } + void set_code(Ref _code, int _cp) { + code = std::move(_code); + force_cp(_cp); + } + Ref get_code() const { + return code; + } + void push_code() { + get_stack().push_cellslice(get_code()); + } + void adjust_cr(const ControlRegs& save) { + cr ^= save; + } + void adjust_cr(ControlRegs&& save) { + cr ^= save; + } + void preclear_cr(const ControlRegs& save) { + cr &= save; + } + int call(Ref cont); + int call(Ref cont, int pass_args, int ret_args = -1); + int jump(Ref cont); + int jump(Ref cont, int pass_args); + int ret(); + int ret(int ret_args); + int ret_alt(); + int ret_alt(int ret_args); + int repeat(Ref body, Ref after, long long count); + int again(Ref body); + int until(Ref body, Ref after); + int loop_while(Ref cond, Ref body, Ref after); + int throw_exception(int excno, StackEntry&& arg); + int throw_exception(int excno); + Ref extract_cc(int save_cr = 1, int stack_copy = -1, int cc_args = -1); + void fatal(void) const { + throw VmFatal{}; + } + int jump_to(Ref cont) { + return cont->is_unique() ? cont.unique_write().jump_w(this) : cont->jump(this); + } + static Ref convert_code_cell(Ref code_cell); + void commit() { + cstate.c4 = cr.d[0]; + cstate.c5 = cr.d[1]; + cstate.committed = true; + } + + void set_chksig_always_succeed(bool flag) { + chksig_always_succeed = flag; + } + bool get_chksig_always_succeed() const { + return chksig_always_succeed; + } + + private: + void init_cregs(bool same_c3 = false, bool push_0 = true); +}; + +int run_vm_code(Ref _code, Ref& _stack, int flags = 0, Ref* data_ptr = nullptr, VmLog log = {}, + long long* steps = nullptr, GasLimits* gas_limits = nullptr, std::vector> libraries = {}, + Ref init_c7 = {}, Ref* actions_ptr = nullptr); +int run_vm_code(Ref _code, Stack& _stack, int flags = 0, Ref* data_ptr = nullptr, VmLog log = {}, + long long* steps = nullptr, GasLimits* gas_limits = nullptr, std::vector> libraries = {}, + Ref init_c7 = {}, Ref* actions_ptr = nullptr); + +Ref lookup_library_in(td::ConstBitPtr key, Ref lib_root); + +} // namespace vm diff --git a/crypto/vm/vmstate.h b/crypto/vm/vmstate.h index 312b6626..39e5d48a 100644 --- a/crypto/vm/vmstate.h +++ b/crypto/vm/vmstate.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "common/refcnt.hpp" @@ -32,7 +32,7 @@ class VmStateInterface : public td::Context { td::ConstBitPtr hash) { // may throw a dictionary exception; returns nullptr if library is not found return {}; } - virtual void register_cell_load(){}; + virtual void register_cell_load(const CellHash& cell_hash){}; virtual void register_cell_create(){}; }; diff --git a/dht/dht-bucket.cpp b/dht/dht-bucket.cpp index bc30a45a..4f9d75eb 100644 --- a/dht/dht-bucket.cpp +++ b/dht/dht-bucket.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "td/utils/tl_storers.h" #include "td/utils/crypto.h" diff --git a/dht/dht-bucket.hpp b/dht/dht-bucket.hpp index 3d16a5d8..812f670d 100644 --- a/dht/dht-bucket.hpp +++ b/dht/dht-bucket.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/dht/dht-in.hpp b/dht/dht-in.hpp index 5407c824..c4f67819 100644 --- a/dht/dht-in.hpp +++ b/dht/dht-in.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/dht/dht-query.cpp b/dht/dht-query.cpp index beefbc5c..7e1f1b92 100644 --- a/dht/dht-query.cpp +++ b/dht/dht-query.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "dht.hpp" diff --git a/dht/dht-query.hpp b/dht/dht-query.hpp index af39f7cf..aa607f56 100644 --- a/dht/dht-query.hpp +++ b/dht/dht-query.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/dht/dht-remote-node.cpp b/dht/dht-remote-node.cpp index 381a332e..f1ea2197 100644 --- a/dht/dht-remote-node.cpp +++ b/dht/dht-remote-node.cpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "dht.hpp" diff --git a/dht/dht-remote-node.hpp b/dht/dht-remote-node.hpp index ed37415c..e65c0429 100644 --- a/dht/dht-remote-node.hpp +++ b/dht/dht-remote-node.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/dht/dht.h b/dht/dht.h index c21a2e85..eacb2e4b 100644 --- a/dht/dht.h +++ b/dht/dht.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/dht/dht.hpp b/dht/dht.hpp index 5e83e548..b8d73c8e 100644 --- a/dht/dht.hpp +++ b/dht/dht.hpp @@ -14,7 +14,7 @@ You should have received a copy of the GNU Lesser General Public License along with TON Blockchain Library. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/doc/TonSites-HOWTO b/doc/TonSites-HOWTO new file mode 100644 index 00000000..9d4005f0 --- /dev/null +++ b/doc/TonSites-HOWTO @@ -0,0 +1,78 @@ +The aim of this document is to provide a gentle introduction into TON Sites, which are (TON) Web sites accessed through the TON Network. TON Sites may be used as a convenient entry point for other TON Services. In particular, HTML pages downloaded from TON Sites may contain links to ton://... URIs representing payments that can be performed by the user by clicking to the link, provided a TON Wallet is installed on the user's device. + +From the technical perspective, TON Sites are very much like the usual Web sites, but they are accessed through the TON Network (which is an overlay network inside the Internet) instead of the Internet. More specifically, they have an ADNL address (instead of a more customary IPv4 or IPv6 address), and they accept HTTP queries via RLDP protocol (which is a higher-level RPC protocol built upon ADNL, the main protocol of TON Network) instead of the usual TCP/IP. All encryption is handled by ADNL, so there is no need to use HTTPS (i.e., TLS). + +In order to access existing and create new TON Sites one needs special gateways between the "ordinary" internet and the TON Network. Essentially, TON Sites are accessed with the aid of a HTTP->RLDP proxy running locally on the client's machine, and they are created by means of a reverse RLDP->HTTP proxy running on a remote web server. + +1. Compiling RLDP-HTTP Proxy +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The RLDP-HTTP Proxy is a special utility specially designed for accessing and creating TON Sites. Its current (alpha) version is a part of the general TON Blockchain source tree, available at GitHub repository ton-blockchain/ton. In order to compile the RLDP-HTTP Proxy, follow the instructions outlined in README and Validator-HOWTO. The Proxy binary will be located as + + rldp-http-proxy/rldp-http-proxy + +in the build directory. Alternatively, you may want to build just the Proxy instead of building all TON Blockchain projects. This can be done by invoking + + cmake --build . --target rldp-http-proxy + +in the build directory. + +2. Running RLDP-HTTP Proxy to access TON Sites +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In order to access existing TON Sites, you need a running instance of RLDP-HTTP Proxy on your computer. It can be invoked as follows: + + rldp-http-proxy/rldp-http-proxy -p 8080 -c 3333 -C ton-global.config.json + +or + + rldp-http-proxy/rldp-http-proxy -p 8080 -a :3333 -C ton-global.config.json + +where is your public IPv4 address, provided you have one on your home computer. The TON Network global configuration file `ton-global.config.json` can be downloaded at https://test.ton.org/ton-global.config.json : + + wget https://test.ton.org/ton-global.config.json + +In the above example, 8080 is the TCP port that will be listened to at localhost for incoming HTTP queries, and 3333 is the UDP port that will be used for all outbound and inbound RLDP and ADNL activity, i.e., for connecting to the TON Sites via the TON Network. + +If you have done everything correctly, the Proxy will not terminate, but it will continue running in the terminal. It can be used now for accessing TON Sites. When you don't need it anymore, you can terminate it by pressing Ctrl-C, or simply by closing the terminal window. + +3. Accessing TON Sites +~~~~~~~~~~~~~~~~~~~~~~ + +Now suppose that you have a running instance of the RLDP-HTTP Proxy running on your computer and listening on localhost:8080 for inbound TCP connections, as explained above in Section 2. + +A simple test that everything is working property may be performed using programs such as Curl or WGet. For example, + + curl -x 127.0.0.1:8080 http://test.ton + +attempts to download the main page of (TON) Site `test.ton` using the proxy at `127.0.0.1:8080`. If the proxy is up and running, you'll see something like + + +

TON Blockchain Test Network — files and resources

+

News

+