diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index 13e4c5bd0..9f7a0a5a7 100644 --- a/crypto/CMakeLists.txt +++ b/crypto/CMakeLists.txt @@ -236,6 +236,7 @@ set(SMC_ENVELOPE_SOURCE smc-envelope/SmartContractCode.cpp smc-envelope/WalletInterface.cpp smc-envelope/WalletV3.cpp + smc-envelope/WalletV4.cpp smc-envelope/GenericAccount.h smc-envelope/HighloadWallet.h @@ -246,6 +247,7 @@ set(SMC_ENVELOPE_SOURCE smc-envelope/SmartContractCode.h smc-envelope/WalletInterface.h smc-envelope/WalletV3.h + smc-envelope/WalletV4.h ) set(ED25519_TEST_SOURCE diff --git a/crypto/smc-envelope/GenericAccount.cpp b/crypto/smc-envelope/GenericAccount.cpp index 4cd6bf3f1..04249699c 100644 --- a/crypto/smc-envelope/GenericAccount.cpp +++ b/crypto/smc-envelope/GenericAccount.cpp @@ -155,7 +155,7 @@ td::Result GenericAccount::get_wallet_id(const SmartContract& sc) { return TRY_VM([&]() -> td::Result { auto answer = sc.run_get_method("wallet_id"); if (!answer.success) { - return td::Status::Error("seqno get method failed"); + return td::Status::Error("wallet_id get method failed"); } return static_cast(answer.stack.write().pop_long_range(std::numeric_limits::max())); }()); diff --git a/crypto/smc-envelope/SmartContractCode.cpp b/crypto/smc-envelope/SmartContractCode.cpp index d10c4b5c8..585450f61 100644 --- a/crypto/smc-envelope/SmartContractCode.cpp +++ b/crypto/smc-envelope/SmartContractCode.cpp @@ -28,6 +28,7 @@ namespace { // WALLET_REVISION = 2; // WALLET2_REVISION = 2; // WALLET3_REVISION = 2; +// WALLET4_REVISION = 2; // HIGHLOAD_WALLET_REVISION = 2; // HIGHLOAD_WALLET2_REVISION = 2; // DNS_REVISION = 1; @@ -92,6 +93,20 @@ const auto& get_map() { "AAXrc52omhpn5jrhf/AABesePaiaGmPmOuFj8ABDbbYHwR7Z5AOAQm1B1tnkA4BTu1E0IEBQNch0x/" "0BNEC2zz4J28QAoAg9HtvpTGX+gAwoXC2CZEw4g8AOiGOETGA8/gzIG6SMHCU0NcLH+IB3yGSAaGSW3/iAAzTB9QC+wAAHssfFMsfEsv/yx/" "0AMntVA=="); + with_tvm_code( + "wallet-v4-r2", + "te6cckECFAEAAtQAART/APSkE/S88sgLAQIBIAIDAgFIBAUE+PKDCNcYINMf0x/THwL4I7vyZO1E0NMf0x/T//" + "QE0VFDuvKhUVG68qIF+QFUEGT5EPKj+AAkpMjLH1JAyx9SMMv/" + "UhD0AMntVPgPAdMHIcAAn2xRkyDXSpbTB9QC+wDoMOAhwAHjACHAAuMAAcADkTDjDQOkyMsfEssfy/" + "8QERITAubQAdDTAyFxsJJfBOAi10nBIJJfBOAC0x8hghBwbHVnvSKCEGRzdHK9sJJfBeAD+kAwIPpEAcjKB8v/" + "ydDtRNCBAUDXIfQEMFyBAQj0Cm+hMbOSXwfgBdM/" + "yCWCEHBsdWe6kjgw4w0DghBkc3RyupJfBuMNBgcCASAICQB4AfoA9AQw+CdvIjBQCqEhvvLgUIIQcGx1Z4MesXCAGFAEywUmzxZY+" + "gIZ9ADLaRfLH1Jgyz8gyYBA+wAGAIpQBIEBCPRZMO1E0IEBQNcgyAHPFvQAye1UAXKwjiOCEGRzdHKDHrFwgBhQBcsFUAPPFiP6AhPLassfyz/" + "JgED7AJJfA+ICASAKCwBZvSQrb2omhAgKBrkPoCGEcNQICEekk30pkQzmkD6f+YN4EoAbeBAUiYcVnzGEAgFYDA0AEbjJftRNDXCx+" + "AA9sp37UTQgQFA1yH0BDACyMoHy//J0AGBAQj0Cm+hMYAIBIA4PABmtznaiaEAga5Drhf/AABmvHfaiaEAQa5DrhY/AAG7SB/" + "oA1NQi+QAFyMoHFcv/ydB3dIAYyMsFywIizxZQBfoCFMtrEszMyXP7AMhAFIEBCPRR8qcCAHCBAQjXGPoA0z/" + "IVCBHgQEI9FHyp4IQbm90ZXB0gBjIywXLAlAGzxZQBPoCFMtqEssfyz/Jc/sAAgBsgQEI1xj6ANM/" + "MFIkgQEI9Fnyp4IQZHN0cnB0gBjIywXLAlAFzxZQA/oCE8tqyx8Syz/Jc/sAAAr0AMntVGliJeU="); return map; }(); return map; @@ -137,9 +152,12 @@ td::Span SmartContractCode::get_revisions(Type type) { static int res[] = {1}; return res; } + case Type::WalletV4: { + static int res[] = {2}; + return res; + } } UNREACHABLE(); - return {}; } td::Result SmartContractCode::validate_revision(Type type, int revision) { @@ -179,9 +197,10 @@ td::Ref SmartContractCode::get_code(Type type, int ext_revision) { return "payment-channel"; case Type::RestrictedWallet: return "restricted-wallet3"; + case Type::WalletV4: + return "wallet-v4"; } UNREACHABLE(); - return ""; }(type); if (revision == -1) { return load(basename).move_as_ok(); diff --git a/crypto/smc-envelope/SmartContractCode.h b/crypto/smc-envelope/SmartContractCode.h index 85be35318..be50d2a15 100644 --- a/crypto/smc-envelope/SmartContractCode.h +++ b/crypto/smc-envelope/SmartContractCode.h @@ -26,7 +26,16 @@ class SmartContractCode { public: static td::Result> load(td::Slice name); - enum Type { WalletV3 = 4, HighloadWalletV1, HighloadWalletV2, ManualDns, Multisig, PaymentChannel, RestrictedWallet }; + enum Type { + WalletV3 = 4, + HighloadWalletV1, + HighloadWalletV2, + ManualDns, + Multisig, + PaymentChannel, + RestrictedWallet, + WalletV4 + }; static td::Span get_revisions(Type type); static td::Result validate_revision(Type type, int revision); static td::Ref get_code(Type type, int revision = 0); diff --git a/crypto/smc-envelope/WalletV4.cpp b/crypto/smc-envelope/WalletV4.cpp new file mode 100644 index 000000000..738fa9c75 --- /dev/null +++ b/crypto/smc-envelope/WalletV4.cpp @@ -0,0 +1,71 @@ +/* + 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 . +*/ +#include "WalletV4.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::Result> WalletV4::make_a_gift_message(const td::Ed25519::PrivateKey& private_key, + td::uint32 valid_until, td::Span gifts) const { + CHECK(gifts.size() <= get_max_gifts_size()); + TRY_RESULT(seqno, get_seqno()); + TRY_RESULT(wallet_id, get_wallet_id()); + vm::CellBuilder cb; + cb.store_long(wallet_id, 32).store_long(valid_until, 32).store_long(seqno, 32); + cb.store_long(0, 8); // The only difference with wallet-v3 + + for (auto& gift : gifts) { + td::int32 send_mode = 3; + if (gift.gramms == -1) { + send_mode += 128; + } + if (gift.send_mode > -1) { + send_mode = gift.send_mode; + } + cb.store_long(send_mode, 8).store_ref(create_int_message(gift)); + } + + 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 WalletV4::get_init_data(const InitData& init_data) noexcept { + return vm::CellBuilder() + .store_long(init_data.seqno, 32) + .store_long(init_data.wallet_id, 32) + .store_bytes(init_data.public_key) + .store_zeroes(1) // plugins dict + .finalize(); +} + +td::Result WalletV4::get_wallet_id() const { + return TRY_VM([&]() -> td::Result { + auto answer = run_get_method("get_subwallet_id"); + if (!answer.success) { + return td::Status::Error("get_subwallet_id get method failed"); + } + return static_cast(answer.stack.write().pop_long_range(std::numeric_limits::max())); + }()); +} +} // namespace ton diff --git a/crypto/smc-envelope/WalletV4.h b/crypto/smc-envelope/WalletV4.h new file mode 100644 index 000000000..721e81039 --- /dev/null +++ b/crypto/smc-envelope/WalletV4.h @@ -0,0 +1,46 @@ +/* + 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 . +*/ +#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 { + +struct WalletV4Traits { + using InitData = WalletInterface::DefaultInitData; + + static constexpr unsigned max_message_size = vm::CellString::max_bytes; + static constexpr unsigned max_gifts_size = 4; + static constexpr auto code_type = SmartContractCode::WalletV4; +}; + +class WalletV4 : public WalletBase { + public: + explicit WalletV4(State state) : WalletBase(std::move(state)) { + } + td::Result> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 valid_until, + td::Span gifts) const override; + static td::Ref get_init_data(const InitData& init_data) noexcept; + + td::Result get_wallet_id() const override; +}; +} // namespace ton \ No newline at end of file diff --git a/crypto/test/test-smartcont.cpp b/crypto/test/test-smartcont.cpp index 673bb7586..d3f188132 100644 --- a/crypto/test/test-smartcont.cpp +++ b/crypto/test/test-smartcont.cpp @@ -35,6 +35,7 @@ #include "smc-envelope/SmartContract.h" #include "smc-envelope/SmartContractCode.h" #include "smc-envelope/WalletV3.h" +#include "smc-envelope/WalletV4.h" #include "smc-envelope/HighloadWallet.h" #include "smc-envelope/HighloadWalletV2.h" #include "smc-envelope/PaymentChannel.h" @@ -526,6 +527,7 @@ void do_test_wallet() { TEST(Tonlib, Wallet) { do_test_wallet(); + do_test_wallet(); do_test_wallet(); do_test_wallet(); do_test_wallet(); diff --git a/tl/generate/scheme/tonlib_api.tl b/tl/generate/scheme/tonlib_api.tl index af08cbd78..a6172376a 100644 --- a/tl/generate/scheme/tonlib_api.tl +++ b/tl/generate/scheme/tonlib_api.tl @@ -61,6 +61,7 @@ pchan.config alice_public_key:string alice_address:accountAddress bob_public_key raw.initialAccountState code:bytes data:bytes = InitialAccountState; wallet.v3.initialAccountState public_key:string wallet_id:int64 = InitialAccountState; +wallet.v4.initialAccountState public_key:string wallet_id:int64 = InitialAccountState; wallet.highload.v1.initialAccountState public_key:string wallet_id:int64 = InitialAccountState; wallet.highload.v2.initialAccountState public_key:string wallet_id:int64 = InitialAccountState; @@ -73,6 +74,7 @@ pchan.initialAccountState config:pchan.config = InitialAccountState; raw.accountState code:bytes data:bytes frozen_hash:bytes = AccountState; wallet.v3.accountState wallet_id:int64 seqno:int32 = AccountState; +wallet.v4.accountState wallet_id:int64 seqno:int32 = AccountState; wallet.highload.v1.accountState wallet_id:int64 seqno:int32 = AccountState; wallet.highload.v2.accountState wallet_id:int64 = AccountState; dns.accountState wallet_id:int64 = AccountState; diff --git a/tl/generate/scheme/tonlib_api.tlo b/tl/generate/scheme/tonlib_api.tlo index 6644d118c..7657852ea 100644 Binary files a/tl/generate/scheme/tonlib_api.tlo and b/tl/generate/scheme/tonlib_api.tlo differ diff --git a/tonlib/tonlib/TonlibClient.cpp b/tonlib/tonlib/TonlibClient.cpp index 74b427dbc..521f961ab 100644 --- a/tonlib/tonlib/TonlibClient.cpp +++ b/tonlib/tonlib/TonlibClient.cpp @@ -31,6 +31,7 @@ #include "smc-envelope/GenericAccount.h" #include "smc-envelope/ManualDns.h" #include "smc-envelope/WalletV3.h" +#include "smc-envelope/WalletV4.h" #include "smc-envelope/HighloadWallet.h" #include "smc-envelope/HighloadWalletV2.h" #include "smc-envelope/PaymentChannel.h" @@ -228,6 +229,14 @@ td::Result to_init_data(const tonlib_api::wallet_v3_ini return std::move(init_data); } +td::Result to_init_data(const tonlib_api::wallet_v4_initialAccountState& wallet_state) { + TRY_RESULT(key_bytes, get_public_key(wallet_state.public_key_)); + ton::WalletV4::InitData init_data; + init_data.public_key = td::SecureString(key_bytes.key); + init_data.wallet_id = static_cast(wallet_state.wallet_id_); + return std::move(init_data); +} + td::Result to_init_data(const tonlib_api::rwallet_initialAccountState& rwallet_state) { TRY_RESULT(init_key_bytes, get_public_key(rwallet_state.init_public_key_)); TRY_RESULT(key_bytes, get_public_key(rwallet_state.public_key_)); @@ -319,6 +328,16 @@ class AccountState { return tonlib_api::make_object(static_cast(wallet_id), static_cast(seqno)); } + td::Result> to_wallet_v4_accountState() const { + if (wallet_type_ != WalletV4) { + return TonlibError::AccountTypeUnexpected("WalletV4"); + } + auto wallet = ton::WalletV4(get_smc_state()); + TRY_RESULT(seqno, wallet.get_seqno()); + TRY_RESULT(wallet_id, wallet.get_wallet_id()); + return tonlib_api::make_object(static_cast(wallet_id), + static_cast(seqno)); + } td::Result> to_wallet_highload_v1_accountState() const { if (wallet_type_ != HighloadWalletV1) { @@ -420,6 +439,8 @@ class AccountState { return f(to_dns_accountState()); case PaymentChannel: return f(to_payment_channel_accountState()); + case WalletV4: + return f(to_wallet_v4_accountState()); } UNREACHABLE(); } @@ -458,7 +479,8 @@ class AccountState { HighloadWalletV2, ManualDns, PaymentChannel, - RestrictedWallet + RestrictedWallet, + WalletV4 }; WalletType get_wallet_type() const { return wallet_type_; @@ -477,6 +499,7 @@ class AccountState { case AccountState::HighloadWalletV1: case AccountState::HighloadWalletV2: case AccountState::RestrictedWallet: + case AccountState::WalletV4: return true; } UNREACHABLE(); @@ -497,6 +520,8 @@ class AccountState { return td::make_unique(get_smc_state()); case AccountState::RestrictedWallet: return td::make_unique(get_smc_state()); + case AccountState::WalletV4: + return td::make_unique(get_smc_state()); } UNREACHABLE(); return {}; @@ -554,6 +579,23 @@ class AccountState { break; } }, + [&](tonlib_api::wallet_v4_initialAccountState& v4wallet) { + for (auto revision : ton::SmartContractCode::get_revisions(ton::SmartContractCode::WalletV4)) { + auto init_data = to_init_data(v4wallet); + if (init_data.is_error()) { + continue; + } + auto wallet = ton::WalletV4::create(init_data.move_as_ok(), revision); + if (!(wallet->get_address(ton::masterchainId) == address_ || + wallet->get_address(ton::basechainId) == address_)) { + continue; + } + wallet_type_ = WalletType::WalletV4; + wallet_revision_ = revision; + set_new_state(wallet->get_state()); + break; + } + }, [&](tonlib_api::rwallet_initialAccountState& rwallet) { for (auto revision : ton::SmartContractCode::get_revisions(ton::SmartContractCode::RestrictedWallet)) { auto r_init_data = to_init_data(rwallet); @@ -597,7 +639,7 @@ class AccountState { return wallet_type_; } auto wallet_id = static_cast(address_.workchain + wallet_id_); - ton::WalletV3::InitData init_data{key.as_octet_string(), wallet_id}; + ton::WalletInterface::DefaultInitData init_data{key.as_octet_string(), wallet_id}; auto o_revision = ton::WalletV3::guess_revision(address_, init_data); if (o_revision) { wallet_type_ = WalletType::WalletV3; @@ -605,6 +647,13 @@ class AccountState { set_new_state(ton::WalletV3::get_init_state(wallet_revision_, init_data)); return wallet_type_; } + o_revision = ton::WalletV4::guess_revision(address_, init_data); + if (o_revision) { + wallet_type_ = WalletType::WalletV4; + wallet_revision_ = o_revision.value(); + set_new_state(ton::WalletV4::get_init_state(wallet_revision_, init_data)); + return wallet_type_; + } o_revision = ton::HighloadWalletV2::guess_revision(address_, init_data); if (o_revision) { wallet_type_ = WalletType::HighloadWalletV2; @@ -682,6 +731,12 @@ class AccountState { wallet_revision_ = o_revision.value(); return wallet_type_; } + o_revision = ton::WalletV4::guess_revision(code_hash); + if (o_revision) { + wallet_type_ = WalletType::WalletV4; + wallet_revision_ = o_revision.value(); + return wallet_type_; + } o_revision = ton::HighloadWalletV2::guess_revision(code_hash); if (o_revision) { wallet_type_ = WalletType::HighloadWalletV2; @@ -2256,6 +2311,13 @@ td::Result get_account_address(const tonlib_api::wallet_v3_in ->get_address(workchain_id); } +td::Result get_account_address(const tonlib_api::wallet_v4_initialAccountState& test_wallet_state, + td::int32 revision, ton::WorkchainId workchain_id) { + TRY_RESULT(key_bytes, get_public_key(test_wallet_state.public_key_)); + return ton::WalletV4::create({key_bytes.key, static_cast(test_wallet_state.wallet_id_)}, revision) + ->get_address(workchain_id); +} + td::Result get_account_address( const tonlib_api::wallet_highload_v1_initialAccountState& test_wallet_state, td::int32 revision, ton::WorkchainId workchain_id) { @@ -2303,6 +2365,7 @@ static td::optional get_wallet_type(tonlib_api::In td::overloaded( [](const tonlib_api::raw_initialAccountState&) { return td::optional(); }, [](const tonlib_api::wallet_v3_initialAccountState&) { return ton::SmartContractCode::WalletV3; }, + [](const tonlib_api::wallet_v4_initialAccountState&) { return ton::SmartContractCode::WalletV4; }, [](const tonlib_api::wallet_highload_v1_initialAccountState&) { return ton::SmartContractCode::HighloadWalletV1; }, @@ -2392,6 +2455,12 @@ td::Status TonlibClient::do_request(tonlib_api::guessAccount& request, sources.push_back(Source{tonlib_api::make_object( request.public_key_, wallet_id_ + ton::basechainId), ton::basechainId}); + sources.push_back(Source{tonlib_api::make_object( + request.public_key_, wallet_id_ + ton::masterchainId), + ton::masterchainId}); + sources.push_back(Source{tonlib_api::make_object( + request.public_key_, wallet_id_ + ton::basechainId), + ton::basechainId}); for (Source& source : sources) { auto o_type = get_wallet_type(*source.init_state); if (!o_type) {