mirror of
https://github.com/ton-blockchain/ton
synced 2025-03-09 15:40:10 +00:00
updated tonlib
- updated tonlib - updated validator - updated documentation - first version of http over rldp proxy
This commit is contained in:
parent
53ec9684bd
commit
77842f9b63
128 changed files with 10555 additions and 2285 deletions
|
@ -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()
|
||||
|
||||
|
|
|
@ -567,6 +567,21 @@ td::Result<GasLimitsPrices> Config::do_get_gas_limits_prices(td::Ref<vm::Cell> c
|
|||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
td::Result<ton::StdSmcAddress> 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<GasLimitsPrices> Config::get_gas_limits_prices(bool is_masterchain) const {
|
||||
auto id = is_masterchain ? 20 : 21;
|
||||
auto cell = get_config_param(id);
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
You should have received a copy of the GNU General Public License
|
||||
along with TON Blockchain. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<ton::StdSmcAddress> get_dns_root_addr() const;
|
||||
bool set_block_id_ext(const ton::BlockIdExt& block_id_ext);
|
||||
td::Result<std::vector<ton::StdSmcAddress>> get_special_smartcontracts(bool without_config = false) const;
|
||||
bool is_special_smartcontract(const ton::StdSmcAddress& addr) const;
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<vm::CellSlice> 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<vm::CellSlice> 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<vm::CellSlice> 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;
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<vm::CellSlice> cs, td::RefInt256& freeze_due_limit, td::RefInt256& delete_due_limit);
|
||||
bool parse_GasLimitsPrices(Ref<vm::Cell> cell, td::RefInt256& freeze_due_limit, td::RefInt256& delete_due_limit);
|
||||
|
||||
private:
|
||||
bool parse_GasLimitsPrices_internal(Ref<vm::CellSlice> 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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 <workchain>:<account> 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
|
||||
|
|
|
@ -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<vm::CellSlice> cs_ref,
|
||||
td::ConstBitPtr key, int key_len) -> bool {
|
||||
vm::Dictionary::map_func_t map_func = [&ctx, func, sgnd](vm::CellBuilder& cb, Ref<vm::CellSlice> cs_ref,
|
||||
td::ConstBitPtr key, int key_len) -> bool {
|
||||
ctx.stack.push_builder(Ref<vm::CellBuilder>(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<vm::CellSlice> cs_ref, td::ConstBitPtr key,
|
||||
int key_len) -> bool {
|
||||
vm::Dictionary::foreach_func_t foreach_func = [&ctx, func, sgnd](Ref<vm::CellSlice> 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
|
||||
|
|
381
crypto/smartcont/dns-manual-code.fc
Normal file
381
crypto/smartcont/dns-manual-code.fc
Normal file
|
@ -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<lengthB>: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?>:Hashmap<PfxDict:Slice->CatTable>:domains]
|
||||
<CatTable> := HashmapE 16 ^DNSRecord
|
||||
<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());
|
||||
-}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
Copyright 2017-2020 Telegram Systems LLP
|
||||
*/
|
||||
#include "HighloadWallet.h"
|
||||
#include "GenericAccount.h"
|
||||
|
@ -51,7 +51,7 @@ td::Ref<vm::Cell> HighloadWallet::get_init_message(const td::Ed25519::PrivateKey
|
|||
td::Ref<vm::Cell> HighloadWallet::make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id,
|
||||
td::uint32 seqno, td::uint32 valid_until,
|
||||
td::Span<Gift> 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<vm::Cell> 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<td::uint32> HighloadWallet::get_wallet_id_or_throw() const {
|
|||
return static_cast<td::uint32>(cs.fetch_ulong(32));
|
||||
}
|
||||
|
||||
td::Result<td::Ed25519::PublicKey> HighloadWallet::get_public_key() const {
|
||||
return TRY_VM(get_public_key_or_throw());
|
||||
}
|
||||
|
||||
td::Result<td::Ed25519::PublicKey> 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<td::int32>(res.size()));
|
||||
return td::Ed25519::PublicKey(std::move(res));
|
||||
}
|
||||
|
||||
} // namespace ton
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<vm::Cell> get_init_state(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) noexcept;
|
||||
static td::Ref<vm::Cell> 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<vm::Cell> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id,
|
||||
td::uint32 seqno, td::uint32 valid_until, td::Span<Gift> gifts) noexcept;
|
||||
|
||||
|
@ -47,8 +44,20 @@ class HighloadWallet : ton::SmartContract {
|
|||
td::Result<td::uint32> get_seqno() const;
|
||||
td::Result<td::uint32> get_wallet_id() const;
|
||||
|
||||
td::Result<td::Ref<vm::Cell>> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 valid_until,
|
||||
td::Span<Gift> 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<td::Ed25519::PublicKey> get_public_key() const override;
|
||||
|
||||
private:
|
||||
td::Result<td::uint32> get_seqno_or_throw() const;
|
||||
td::Result<td::uint32> get_wallet_id_or_throw() const;
|
||||
td::Result<td::Ed25519::PublicKey> get_public_key_or_throw() const;
|
||||
};
|
||||
} // namespace ton
|
||||
|
|
151
crypto/smc-envelope/HighloadWalletV2.cpp
Normal file
151
crypto/smc-envelope/HighloadWalletV2.cpp
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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 <limits>
|
||||
|
||||
namespace ton {
|
||||
td::optional<td::int32> 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<td::int32> 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<vm::Cell> 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<vm::Cell> 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<vm::Cell> HighloadWalletV2::make_a_gift_message(const td::Ed25519::PrivateKey& private_key,
|
||||
td::uint32 wallet_id, td::uint32 valid_until,
|
||||
td::Span<Gift> 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<vm::Cell> 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<vm::Cell> 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<td::uint32> HighloadWalletV2::get_wallet_id() const {
|
||||
return TRY_VM(get_wallet_id_or_throw());
|
||||
}
|
||||
|
||||
td::Result<td::uint32> 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<td::uint32>(cs.fetch_ulong(32));
|
||||
}
|
||||
|
||||
td::Result<td::Ed25519::PublicKey> HighloadWalletV2::get_public_key() const {
|
||||
return TRY_VM(get_public_key_or_throw());
|
||||
}
|
||||
|
||||
td::Result<td::Ed25519::PublicKey> 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<td::int32>(res.size()));
|
||||
return td::Ed25519::PublicKey(std::move(res));
|
||||
}
|
||||
|
||||
} // namespace ton
|
66
crypto/smc-envelope/HighloadWalletV2.h
Normal file
66
crypto/smc-envelope/HighloadWalletV2.h
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<vm::Cell> get_init_state(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id,
|
||||
td::int32 revision) noexcept;
|
||||
static td::Ref<vm::Cell> get_init_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id,
|
||||
td::uint32 valid_until) noexcept;
|
||||
|
||||
static td::Ref<vm::Cell> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id,
|
||||
td::uint32 valid_until, td::Span<Gift> gifts) noexcept;
|
||||
|
||||
static td::Ref<vm::Cell> get_init_code(td::int32 revision) noexcept;
|
||||
static vm::CellHash get_init_code_hash() noexcept;
|
||||
static td::Ref<vm::Cell> get_init_data(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) noexcept;
|
||||
static td::optional<td::int32> guess_revision(const vm::Cell::Hash& code_hash);
|
||||
static td::optional<td::int32> guess_revision(const block::StdAddress& address,
|
||||
const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id);
|
||||
|
||||
td::Result<td::uint32> get_wallet_id() const;
|
||||
|
||||
td::Result<td::Ref<vm::Cell>> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 valid_until,
|
||||
td::Span<Gift> 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<td::Ed25519::PublicKey> get_public_key() const override;
|
||||
|
||||
private:
|
||||
td::Result<td::uint32> get_wallet_id_or_throw() const;
|
||||
td::Result<td::Ed25519::PublicKey> get_public_key_or_throw() const;
|
||||
};
|
||||
} // namespace ton
|
542
crypto/smc-envelope/ManualDns.cpp
Normal file
542
crypto/smc-envelope/ManualDns.cpp
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<td::Ref<vm::Cell>> DnsInterface::EntryData::as_cell() const {
|
||||
td::Ref<vm::Cell> 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> 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<std::vector<DnsInterface::Entry>> DnsInterface::resolve(td::Slice name, td::int32 category) const {
|
||||
TRY_RESULT(raw_entries, resolve_raw(name, category));
|
||||
std::vector<Entry> 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<lengthB>: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> ManualDns::create(td::Ref<vm::Cell> data) {
|
||||
return td::Ref<ManualDns>(true, State{ton::SmartContractCode::dns_manual(), std::move(data)});
|
||||
}
|
||||
td::Ref<ManualDns> ManualDns::create(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) {
|
||||
return create(create_init_data_fast(public_key, wallet_id));
|
||||
}
|
||||
|
||||
td::Result<td::uint32> ManualDns::get_wallet_id() const {
|
||||
return TRY_VM(get_wallet_id_or_throw());
|
||||
}
|
||||
td::Result<td::uint32> ManualDns::get_wallet_id_or_throw() const {
|
||||
if (state_.data.is_null()) {
|
||||
return 0;
|
||||
}
|
||||
//FIXME use get method
|
||||
return static_cast<td::uint32>(vm::load_cell_slice(state_.data).fetch_ulong(32));
|
||||
}
|
||||
|
||||
td::Result<td::Ref<vm::Cell>> ManualDns::create_set_value_unsigned(td::int16 category, td::Slice name,
|
||||
td::Ref<vm::Cell> 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<td::Ref<vm::Cell>> 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<td::Ref<vm::Cell>> 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<td::Ref<vm::Cell>> ManualDns::create_set_all_unsigned(td::Span<Action> 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<vm::Cell> 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<td::Ref<vm::Cell>> 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<td::Ref<vm::Cell>> ManualDns::create_set_name_unsigned(td::Slice name, td::Span<Action> 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<td::Ref<vm::Cell>> ManualDns::prepare(td::Ref<vm::Cell> 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<td::Ref<vm::Cell>> ManualDns::sign(const td::Ed25519::PrivateKey& private_key, td::Ref<vm::Cell> 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<td::Ref<vm::Cell>> 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<vm::Cell> 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<std::vector<ManualDns::RawEntry>> ManualDns::resolve_raw(td::Slice name, td::int32 category_big) const {
|
||||
return TRY_VM(resolve_raw_or_throw(name, category_big));
|
||||
}
|
||||
td::Result<std::vector<ManualDns::RawEntry>> ManualDns::resolve_raw_or_throw(td::Slice name,
|
||||
td::int32 category_big) const {
|
||||
TRY_RESULT(category, td::narrow_cast_safe<td::int16>(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<RawEntry> 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<td::int16>(cat.to_long()), cs->prefetch_ref()});
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
vec.push_back({name.str(), category, data});
|
||||
}
|
||||
}
|
||||
|
||||
return vec;
|
||||
}
|
||||
|
||||
td::Result<td::Ref<vm::Cell>> ManualDns::create_update_query(CombinedActions<Action>& 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<td::Ref<vm::Cell>> ManualDns::create_update_query(td::Ed25519::PrivateKey& pk, td::Span<Action> actions,
|
||||
td::uint32 valid_until) const {
|
||||
auto combined = combine_actions(actions);
|
||||
std::vector<td::Ref<vm::Cell>> queries;
|
||||
for (auto& c : combined) {
|
||||
TRY_RESULT(q, create_update_query(c));
|
||||
queries.push_back(std::move(q));
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> 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<td::optional<ManualDns::EntryData>> 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::ActionExt> ManualDns::parse_line(td::Slice cmd) {
|
||||
// Cmd =
|
||||
// set name category data |
|
||||
// delete.name name |
|
||||
// delete.all
|
||||
// data =
|
||||
// TEXT:<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<td::int16>(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<std::vector<ManualDns::ActionExt>> ManualDns::parse(td::Slice cmd) {
|
||||
auto lines = td::full_split(cmd, '\n');
|
||||
std::vector<ManualDns::ActionExt> 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
|
339
crypto/smc-envelope/ManualDns.h
Normal file
339
crypto/smc-envelope/ManualDns.h
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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 <map>
|
||||
|
||||
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<EntryDataText, EntryDataNextResolver, EntryDataAdnlAddress, EntryDataSmcAddress> 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 << "<empty>";
|
||||
case Type::Text:
|
||||
return sb << "text{" << data.data.get<EntryDataText>().text << "}";
|
||||
case Type::NextResolver:
|
||||
return sb << "next{" << data.data.get<EntryDataNextResolver>().resolver.rserialize() << "}";
|
||||
case Type::AdnlAddress:
|
||||
return sb << "adnl{" << data.data.get<EntryDataAdnlAddress>().adnl_address.to_hex() << "}";
|
||||
case Type::SmcAddress:
|
||||
return sb << "smc{" << data.data.get<EntryDataSmcAddress>().smc_address.rserialize() << "}";
|
||||
}
|
||||
return sb << "<unknown>";
|
||||
}
|
||||
|
||||
td::Result<td::Ref<vm::Cell>> as_cell() const;
|
||||
static td::Result<EntryData> 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<vm::Cell> data;
|
||||
};
|
||||
|
||||
struct ActionExt {
|
||||
std::string name;
|
||||
td::int16 category;
|
||||
td::optional<EntryData> data;
|
||||
static td::Result<ActionExt> parse(td::Slice);
|
||||
};
|
||||
|
||||
struct Action {
|
||||
std::string name;
|
||||
td::int16 category;
|
||||
td::optional<td::Ref<vm::Cell>> data;
|
||||
|
||||
bool does_create_category() const {
|
||||
CHECK(!name.empty());
|
||||
CHECK(category != 0);
|
||||
return static_cast<bool>(data);
|
||||
}
|
||||
bool does_change_empty() const {
|
||||
CHECK(!name.empty());
|
||||
CHECK(category != 0);
|
||||
return static_cast<bool>(data) && data.value().not_null();
|
||||
}
|
||||
void make_non_empty() {
|
||||
CHECK(!name.empty());
|
||||
CHECK(category != 0);
|
||||
if (!data) {
|
||||
data = td::Ref<vm::Cell>();
|
||||
}
|
||||
}
|
||||
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 << "<null>";
|
||||
} else {
|
||||
sb << "<data>";
|
||||
}
|
||||
} else {
|
||||
sb << "<empty>";
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
};
|
||||
|
||||
virtual ~DnsInterface() {
|
||||
}
|
||||
virtual size_t get_max_name_size() const = 0;
|
||||
virtual td::Result<std::vector<RawEntry>> resolve_raw(td::Slice name, td::int32 category) const = 0;
|
||||
virtual td::Result<td::Ref<vm::Cell>> create_update_query(
|
||||
td::Ed25519::PrivateKey& pk, td::Span<Action> actions,
|
||||
td::uint32 valid_until = std::numeric_limits<td::uint32>::max()) const = 0;
|
||||
|
||||
td::Result<std::vector<Entry>> 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<ManualDns> create(State state) {
|
||||
return td::Ref<ManualDns>(true, std::move(state));
|
||||
}
|
||||
static td::Ref<ManualDns> create(td::Ref<vm::Cell> data = {});
|
||||
static td::Ref<ManualDns> create(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id);
|
||||
|
||||
static std::string serialize_data(const EntryData& data);
|
||||
static td::Result<td::optional<ManualDns::EntryData>> parse_data(td::Slice cmd);
|
||||
static td::Result<ManualDns::ActionExt> parse_line(td::Slice cmd);
|
||||
static td::Result<std::vector<ManualDns::ActionExt>> parse(td::Slice cmd);
|
||||
|
||||
td::Ref<vm::Cell> 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<td::uint32> get_wallet_id() const;
|
||||
td::Result<td::uint32> get_wallet_id_or_throw() const;
|
||||
|
||||
td::Result<td::Ref<vm::Cell>> create_set_value_unsigned(td::int16 category, td::Slice name,
|
||||
td::Ref<vm::Cell> data) const;
|
||||
td::Result<td::Ref<vm::Cell>> create_delete_value_unsigned(td::int16 category, td::Slice name) const;
|
||||
td::Result<td::Ref<vm::Cell>> create_delete_all_unsigned() const;
|
||||
td::Result<td::Ref<vm::Cell>> create_set_all_unsigned(td::Span<Action> entries) const;
|
||||
td::Result<td::Ref<vm::Cell>> create_delete_name_unsigned(td::Slice name) const;
|
||||
td::Result<td::Ref<vm::Cell>> create_set_name_unsigned(td::Slice name, td::Span<Action> entries) const;
|
||||
|
||||
td::Result<td::Ref<vm::Cell>> prepare(td::Ref<vm::Cell> data, td::uint32 valid_until) const;
|
||||
|
||||
static td::Result<td::Ref<vm::Cell>> sign(const td::Ed25519::PrivateKey& private_key, td::Ref<vm::Cell> data);
|
||||
static td::Ref<vm::Cell> create_init_data_fast(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id);
|
||||
|
||||
size_t get_max_name_size() const override;
|
||||
td::Result<std::vector<RawEntry>> resolve_raw(td::Slice name, td::int32 category_big) const override;
|
||||
td::Result<std::vector<RawEntry>> resolve_raw_or_throw(td::Slice name, td::int32 category_big) const;
|
||||
|
||||
td::Result<td::Ref<vm::Cell>> create_init_query(
|
||||
const td::Ed25519::PrivateKey& private_key,
|
||||
td::uint32 valid_until = std::numeric_limits<td::uint32>::max()) const;
|
||||
td::Result<td::Ref<vm::Cell>> create_update_query(
|
||||
td::Ed25519::PrivateKey& pk, td::Span<Action> actions,
|
||||
td::uint32 valid_until = std::numeric_limits<td::uint32>::max()) const override;
|
||||
|
||||
static std::string encode_name(td::Slice name);
|
||||
static std::string decode_name(td::Slice name);
|
||||
|
||||
template <class ActionT>
|
||||
struct CombinedActions {
|
||||
std::string name;
|
||||
td::int16 category{0};
|
||||
td::optional<std::vector<ActionT>> actions;
|
||||
friend td::StringBuilder& operator<<(td::StringBuilder& sb, const CombinedActions& action) {
|
||||
sb << action.name << ":" << action.category << ":";
|
||||
if (action.actions) {
|
||||
sb << "<data>" << action.actions.value().size();
|
||||
} else {
|
||||
sb << "<empty>";
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
};
|
||||
|
||||
template <class ActionT = Action>
|
||||
static std::vector<CombinedActions<ActionT>> combine_actions(td::Span<ActionT> actions) {
|
||||
struct Info {
|
||||
std::set<td::int16> known_category;
|
||||
std::vector<ActionT> actions;
|
||||
bool closed{false};
|
||||
bool non_empty{false};
|
||||
};
|
||||
|
||||
std::map<std::string, Info> mp;
|
||||
std::vector<CombinedActions<ActionT>> res;
|
||||
for (auto& action : td::reversed(actions)) {
|
||||
if (action.name.empty()) {
|
||||
CombinedActions<ActionT> set_all;
|
||||
set_all.actions = std::vector<ActionT>();
|
||||
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<ActionT> 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<ActionT> ca;
|
||||
ca.name = a.name;
|
||||
ca.category = a.category;
|
||||
ca.actions = std::vector<ActionT>();
|
||||
ca.actions.value().push_back(std::move(a));
|
||||
res.push_back(ca);
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
td::Result<td::Ref<vm::Cell>> create_update_query(CombinedActions<Action>& combined) const;
|
||||
};
|
||||
|
||||
} // namespace ton
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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"
|
||||
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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"
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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::string, td::Ref<vm::Cell>, 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<td::Ref<vm::Cell>> 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<vm::cell " << name);
|
||||
return td::Status::Error(PSLICE() << "Can't load td::Ref<vm::Cell> " << name);
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
@ -54,20 +111,47 @@ td::Ref<vm::Cell> SmartContractCode::multisig() {
|
|||
auto res = load("multisig").move_as_ok();
|
||||
return res;
|
||||
}
|
||||
td::Ref<vm::Cell> SmartContractCode::wallet() {
|
||||
auto res = load("wallet").move_as_ok();
|
||||
td::Ref<vm::Cell> SmartContractCode::wallet3(int revision) {
|
||||
if (revision == 0) {
|
||||
revision = WALLET3_REVISION;
|
||||
}
|
||||
auto res = load(PSLICE() << "wallet3-r" << revision).move_as_ok();
|
||||
return res;
|
||||
}
|
||||
td::Ref<vm::Cell> SmartContractCode::simple_wallet() {
|
||||
auto res = load("simple-wallet").move_as_ok();
|
||||
td::Ref<vm::Cell> SmartContractCode::wallet(int revision) {
|
||||
if (revision == 0) {
|
||||
revision = WALLET2_REVISION;
|
||||
}
|
||||
auto res = load(PSLICE() << "wallet-r" << revision).move_as_ok();
|
||||
return res;
|
||||
}
|
||||
td::Ref<vm::Cell> 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<vm::Cell> SmartContractCode::simple_wallet_ext() {
|
||||
static auto res = load("simple-wallet-ext").move_as_ok();
|
||||
return res;
|
||||
}
|
||||
td::Ref<vm::Cell> SmartContractCode::highload_wallet() {
|
||||
static auto res = load("highload-wallet").move_as_ok();
|
||||
td::Ref<vm::Cell> 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<vm::Cell> 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<vm::Cell> SmartContractCode::dns_manual() {
|
||||
static auto res = load("dns-manual").move_as_ok();
|
||||
return res;
|
||||
}
|
||||
} // namespace ton
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<td::Ref<vm::Cell>> load(td::Slice name);
|
||||
static td::Ref<vm::Cell> multisig();
|
||||
static td::Ref<vm::Cell> wallet();
|
||||
static td::Ref<vm::Cell> simple_wallet();
|
||||
static td::Ref<vm::Cell> wallet3(int revision = 0);
|
||||
static td::Ref<vm::Cell> wallet(int revision = 0);
|
||||
static td::Ref<vm::Cell> simple_wallet(int revision = 0);
|
||||
static td::Ref<vm::Cell> simple_wallet_ext();
|
||||
static td::Ref<vm::Cell> highload_wallet();
|
||||
static td::Ref<vm::Cell> highload_wallet(int revision = 0);
|
||||
static td::Ref<vm::Cell> highload_wallet_v2(int revision = 0);
|
||||
static td::Ref<vm::Cell> dns_manual();
|
||||
};
|
||||
} // namespace ton
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<vm::Cell> TestGiver::make_a_gift_message(td::uint32 seqno, td::uint64 gramms, td::Slice message,
|
||||
const block::StdAddress& dest_address) noexcept {
|
||||
td::Ref<vm::Cell> TestGiver::make_a_gift_message_static(td::uint32 seqno, td::Span<Gift> 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<td::uint32> TestGiver::get_seqno() const {
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<vm::Cell> make_a_gift_message(td::uint32 seqno, td::uint64 gramms, td::Slice message,
|
||||
const block::StdAddress& dest_address) noexcept;
|
||||
static td::Ref<vm::Cell> make_a_gift_message_static(td::uint32 seqno, td::Span<Gift>) noexcept;
|
||||
|
||||
td::Result<td::uint32> get_seqno() const;
|
||||
|
||||
using WalletInterface::get_init_message;
|
||||
td::Result<td::Ref<vm::Cell>> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 valid_until,
|
||||
td::Span<Gift> 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<td::uint32> get_seqno_or_throw() const;
|
||||
};
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
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<vm::Cell> TestWallet::get_init_state(const td::Ed25519::PublicKey& public_key) noexcept {
|
||||
auto code = get_init_code();
|
||||
td::Ref<vm::Cell> 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<vm::Cell> TestWallet::get_init_message(const td::Ed25519::PrivateKey& pr
|
|||
return vm::CellBuilder().store_bytes(signature).store_bytes(seq_no).finalize();
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> 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<vm::Cell> TestWallet::make_a_gift_message_static(const td::Ed25519::PrivateKey& private_key, td::uint32 seqno,
|
||||
td::Span<Gift> 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<vm::Cell> 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<vm::Cell> 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<vm::Cell> 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<vm::Cell> 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<td::uint32> TestWallet::get_seqno() const {
|
||||
|
@ -88,4 +94,20 @@ td::Result<td::uint32> TestWallet::get_seqno_or_throw() const {
|
|||
return static_cast<td::uint32>(seqno);
|
||||
}
|
||||
|
||||
td::Result<td::Ed25519::PublicKey> TestWallet::get_public_key() const {
|
||||
return TRY_VM(get_public_key_or_throw());
|
||||
}
|
||||
|
||||
td::Result<td::Ed25519::PublicKey> 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<td::int32>(res.size()));
|
||||
return td::Ed25519::PublicKey(std::move(res));
|
||||
}
|
||||
|
||||
} // namespace ton
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<vm::Cell> get_init_state(const td::Ed25519::PublicKey& public_key) noexcept;
|
||||
static constexpr unsigned max_gifts_size = 1;
|
||||
static td::Ref<vm::Cell> get_init_state(const td::Ed25519::PublicKey& public_key, td::int32 revision = 0) noexcept;
|
||||
static td::Ref<vm::Cell> get_init_message(const td::Ed25519::PrivateKey& private_key) noexcept;
|
||||
static td::Ref<vm::Cell> 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<vm::Cell> make_a_gift_message_static(const td::Ed25519::PrivateKey& private_key, td::uint32 seqno,
|
||||
td::Span<Gift> gifts) noexcept;
|
||||
|
||||
static td::Ref<vm::Cell> get_init_code() noexcept;
|
||||
static td::Ref<vm::Cell> get_init_code(td::int32 revision = 0) noexcept;
|
||||
static vm::CellHash get_init_code_hash() noexcept;
|
||||
static td::Ref<vm::Cell> get_data(const td::Ed25519::PublicKey& public_key, td::uint32 seqno) noexcept;
|
||||
static td::Ref<vm::Cell> get_init_data(const td::Ed25519::PublicKey& public_key) noexcept;
|
||||
|
||||
td::Result<td::uint32> get_seqno() const;
|
||||
|
||||
using WalletInterface::get_init_message;
|
||||
td::Result<td::Ref<vm::Cell>> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 valid_until,
|
||||
td::Span<Gift> 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<td::Ed25519::PublicKey> get_public_key() const override;
|
||||
|
||||
private:
|
||||
td::Result<td::uint32> get_seqno_or_throw() const;
|
||||
td::Result<td::Ed25519::PublicKey> get_public_key_or_throw() const;
|
||||
};
|
||||
} // namespace ton
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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 <limits>
|
||||
|
||||
namespace ton {
|
||||
td::Ref<vm::Cell> Wallet::get_init_state(const td::Ed25519::PublicKey& public_key) noexcept {
|
||||
auto code = get_init_code();
|
||||
td::Ref<vm::Cell> 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<vm::Cell> Wallet::get_init_message(const td::Ed25519::PrivateKey& privat
|
|||
}
|
||||
|
||||
td::Ref<vm::Cell> 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<Gift> 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<vm::Cell> 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<vm::Cell> 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<vm::Cell> 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<vm::Cell> 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<td::uint32> Wallet::get_seqno() const {
|
||||
|
@ -97,4 +97,20 @@ td::Result<td::uint32> Wallet::get_seqno_or_throw() const {
|
|||
return static_cast<td::uint32>(vm::load_cell_slice(state_.data).fetch_ulong(32));
|
||||
}
|
||||
|
||||
td::Result<td::Ed25519::PublicKey> Wallet::get_public_key() const {
|
||||
return TRY_VM(get_public_key_or_throw());
|
||||
}
|
||||
|
||||
td::Result<td::Ed25519::PublicKey> 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<td::int32>(res.size()));
|
||||
return td::Ed25519::PublicKey(std::move(res));
|
||||
}
|
||||
|
||||
} // namespace ton
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<vm::Cell> get_init_state(const td::Ed25519::PublicKey& public_key) noexcept;
|
||||
static constexpr unsigned max_gifts_size = 4;
|
||||
static td::Ref<vm::Cell> get_init_state(const td::Ed25519::PublicKey& public_key, td::int32 revision = 0) noexcept;
|
||||
static td::Ref<vm::Cell> get_init_message(const td::Ed25519::PrivateKey& private_key) noexcept;
|
||||
static td::Ref<vm::Cell> 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<Gift> gifts) noexcept;
|
||||
|
||||
static td::Ref<vm::Cell> get_init_code() noexcept;
|
||||
static td::Ref<vm::Cell> get_init_code(td::int32 revision = 0) noexcept;
|
||||
static vm::CellHash get_init_code_hash() noexcept;
|
||||
static td::Ref<vm::Cell> get_init_data(const td::Ed25519::PublicKey& public_key) noexcept;
|
||||
static td::Ref<vm::Cell> get_data(const td::Ed25519::PublicKey& public_key, td::uint32 seqno) noexcept;
|
||||
|
||||
td::Result<td::uint32> get_seqno() const;
|
||||
|
||||
using WalletInterface::get_init_message;
|
||||
td::Result<td::Ref<vm::Cell>> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 valid_until,
|
||||
td::Span<Gift> 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<td::Ed25519::PublicKey> get_public_key() const override;
|
||||
|
||||
private:
|
||||
td::Result<td::uint32> get_seqno_or_throw() const;
|
||||
td::Result<td::Ed25519::PublicKey> get_public_key_or_throw() const;
|
||||
};
|
||||
} // namespace ton
|
||||
|
|
62
crypto/smc-envelope/WalletInterface.h
Normal file
62
crypto/smc-envelope/WalletInterface.h
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<td::Ref<vm::Cell>> make_a_gift_message(const td::Ed25519::PrivateKey &private_key,
|
||||
td::uint32 valid_until, td::Span<Gift> gifts) const = 0;
|
||||
virtual td::Result<td::Ed25519::PublicKey> get_public_key() const {
|
||||
return td::Status::Error("TODO");
|
||||
}
|
||||
|
||||
td::Result<td::Ref<vm::Cell>> get_init_message(const td::Ed25519::PrivateKey &private_key,
|
||||
td::uint32 valid_until = std::numeric_limits<td::uint32>::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
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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 <limits>
|
||||
|
||||
namespace ton {
|
||||
td::Ref<vm::Cell> WalletV3::get_init_state(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) noexcept {
|
||||
auto code = get_init_code();
|
||||
td::Ref<vm::Cell> 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<vm::Cell> 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<td::uint32>::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<td::int32> 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<td::int32> 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<vm::Cell> 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<Gift> 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<vm::Cell> 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<vm::Cell> 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<vm::Cell> WalletV3::get_init_data(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) noexcept {
|
||||
td::Ref<vm::Cell> 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<td::uint32> WalletV3::get_wallet_id_or_throw() const {
|
|||
return static_cast<td::uint32>(cs.fetch_ulong(32));
|
||||
}
|
||||
|
||||
td::Result<td::Ed25519::PublicKey> WalletV3::get_public_key() const {
|
||||
return TRY_VM(get_public_key_or_throw());
|
||||
}
|
||||
|
||||
td::Result<td::Ed25519::PublicKey> 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<td::int32>(res.size()));
|
||||
return td::Ed25519::PublicKey(std::move(res));
|
||||
}
|
||||
|
||||
} // namespace ton
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<vm::Cell> get_init_state(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) noexcept;
|
||||
static td::Ref<vm::Cell> get_init_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id) noexcept;
|
||||
static td::Ref<vm::Cell> 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<vm::Cell> get_init_code() noexcept;
|
||||
static td::optional<td::int32> guess_revision(const vm::Cell::Hash& code_hash);
|
||||
static td::optional<td::int32> guess_revision(const block::StdAddress& address,
|
||||
const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id);
|
||||
static td::Ref<vm::Cell> get_init_state(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id,
|
||||
td::int32 revision = 0) noexcept;
|
||||
static td::Ref<vm::Cell> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id,
|
||||
td::uint32 seqno, td::uint32 valid_until, td::Span<Gift> gifts) noexcept;
|
||||
|
||||
static td::Ref<vm::Cell> get_init_code(td::int32 revision = 0) noexcept;
|
||||
static vm::CellHash get_init_code_hash() noexcept;
|
||||
static td::Ref<vm::Cell> get_init_data(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) noexcept;
|
||||
static td::Ref<vm::Cell> get_init_data(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id,
|
||||
td::uint32 seqno = 0) noexcept;
|
||||
|
||||
td::Result<td::uint32> get_seqno() const;
|
||||
td::Result<td::uint32> get_wallet_id() const;
|
||||
|
||||
using WalletInterface::get_init_message;
|
||||
td::Result<td::Ref<vm::Cell>> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 valid_until,
|
||||
td::Span<Gift> 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<td::Ed25519::PublicKey> get_public_key() const override;
|
||||
|
||||
private:
|
||||
td::Result<td::uint32> get_seqno_or_throw() const;
|
||||
td::Result<td::uint32> get_wallet_id_or_throw() const;
|
||||
td::Result<td::Ed25519::PublicKey> get_public_key_or_throw() const;
|
||||
};
|
||||
} // namespace ton
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"Lisp.fif" include
|
||||
"Lists.fif" include
|
||||
16 constant key-bits
|
||||
16 constant val-bits
|
||||
{ val-bits u, } : val,
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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 <bitset>
|
||||
#include <set>
|
||||
|
@ -63,8 +66,8 @@ std::string load_source(std::string name) {
|
|||
td::Ref<vm::Cell> get_test_wallet_source() {
|
||||
std::string code = R"ABCD(
|
||||
SETCP0 DUP IFNOTRET // return if recv_internal
|
||||
DUP 85143 INT EQUAL IFJMP:<{ // "seqno" get-method
|
||||
DROP c4 PUSHCTR CTOS 32 PLDU // cnt
|
||||
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<vm::Cell> get_wallet_source() {
|
||||
std::string code = R"ABCD(
|
||||
SETCP0 DUP IFNOTRET // return if recv_internal
|
||||
DUP 85143 INT EQUAL IFJMP:<{ // "seqno" get-method
|
||||
DROP c4 PUSHCTR CTOS 32 PLDU // cnt
|
||||
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<vm::Cell> 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<ZeroOsTime>());
|
||||
auto dest = block::StdAddress::parse("Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX").move_as_ok();
|
||||
fift_output =
|
||||
fift::mem_run_fift(std::move(fift_output.source_lookup),
|
||||
{"aba", "new-wallet", "-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<ZeroOsTime>());
|
||||
auto dest = block::StdAddress::parse("Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX").move_as_ok();
|
||||
fift_output =
|
||||
fift::mem_run_fift(std::move(fift_output.source_lookup),
|
||||
{"aba", "new-wallet", "-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<vm::Cell> 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<ZeroOsTime>());
|
||||
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<vm::Cell> 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<ton::HighloadWalletV2::Gift> 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<ZeroOsTime>());
|
||||
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<ManualDns::EntryDataText>().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<std::string> text;
|
||||
|
||||
bool does_create_category() const {
|
||||
CHECK(!name.empty());
|
||||
CHECK(category != 0);
|
||||
return static_cast<bool>(text);
|
||||
}
|
||||
bool does_change_empty() const {
|
||||
CHECK(!name.empty());
|
||||
CHECK(category != 0);
|
||||
return static_cast<bool>(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() : "<empty>")
|
||||
<< "]";
|
||||
}
|
||||
};
|
||||
void update(td::Span<Action> actions) {
|
||||
for (auto& action : actions) {
|
||||
do_update(action);
|
||||
}
|
||||
}
|
||||
using CombinedActions = ton::ManualDns::CombinedActions<Action>;
|
||||
void update_combined(td::Span<Action> 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<Entry> resolve(td::Slice name, td::int16 category) {
|
||||
std::vector<Entry> 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::string, std::map<td::int16, std::string>, 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<Action> 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<vm::Cell>();
|
||||
} 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>(&action, 1));
|
||||
}
|
||||
|
||||
std::vector<Entry> 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<td::Ed25519::PrivateKey> key_;
|
||||
td::Ref<ManualDns> dns_;
|
||||
|
||||
MapDns map_dns_;
|
||||
td::optional<MapDns> 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<Action> 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<char>(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<char>(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<td::int16>(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<td::int16>(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<CheckedDns::Action>(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<CheckedDns::Action>(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<CheckedDns::Action>(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<std::string> data;
|
||||
if (action.data) {
|
||||
data = action.data.value().data.template get<ton::ManualDns::EntryDataText>().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));
|
||||
}
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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"
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
Copyright 2017-2020 Telegram Systems LLP
|
||||
*/
|
||||
#include <functional>
|
||||
#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"
|
||||
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
Copyright 2017-2020 Telegram Systems LLP
|
||||
*/
|
||||
#include <functional>
|
||||
#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"
|
||||
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<CellSlice> cs_ref) {
|
|||
VirtualCell::LoadedCell load_cell_slice_impl(const Ref<Cell>& 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()) {
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2019-2020 Telegram Systems LLP
|
||||
*/
|
||||
#include "CellString.h"
|
||||
#include "td/utils/misc.h"
|
||||
|
||||
|
@ -61,4 +79,79 @@ td::Result<td::string> 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<td::uint32>(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<vm::Cell> 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 <class F>
|
||||
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<int>(size) * 8));
|
||||
if (i + 1 < depth) {
|
||||
cs = vm::load_cell_slice(cs.prefetch_ref());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
td::Result<td::string> 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
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<td::string> load(CellSlice &cs, unsigned int top_bits = Cell::max_bits);
|
||||
static td::Result<td::Ref<vm::Cell>> 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 <class F>
|
||||
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<td::string> load(CellSlice &cs);
|
||||
static td::Result<td::Ref<vm::Cell>> 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 <class F>
|
||||
static void for_each(F &&f, CellSlice cs);
|
||||
static td::Ref<vm::Cell> do_store(td::BitSlice slice);
|
||||
};
|
||||
|
||||
} // namespace vm
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<CellSlice> _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<CellSlice> _code, Ref<Stack> _stack, int flags, Ref<Cell> _data, VmLog log,
|
||||
std::vector<Ref<Cell>> _libraries, Ref<Tuple> 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<CellSlice> _code, Ref<Stack> _stack, const GasLimits& gas, int flags, Ref<Cell> _data, VmLog log,
|
||||
std::vector<Ref<Cell>> _libraries, Ref<Tuple> 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<CellSlice> VmState::convert_code_cell(Ref<Cell> code_cell) {
|
||||
if (code_cell.is_null()) {
|
||||
return {};
|
||||
}
|
||||
Ref<CellSlice> 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<Continuation> 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<OrdCont> ret = Ref<OrdCont>{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<OrdCont> ret = Ref<OrdCont>{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<Continuation> 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<Stack> 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<OrdCont> ret = Ref<OrdCont>{true, std::move(code), cp, std::move(stack), ret_args};
|
||||
ret.unique_write().get_cdata()->save.set_c0(std::move(old_c0));
|
||||
Ref<OrdCont> ord_cont = static_cast<Ref<OrdCont>>(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<Stack> 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<OrdCont> ret = Ref<OrdCont>{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<Continuation> 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<Continuation> 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<Stack> 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<Continuation> cont = quit0;
|
||||
cont.swap(cr.c[0]);
|
||||
return jump(std::move(cont));
|
||||
}
|
||||
|
||||
int VmState::ret(int ret_args) {
|
||||
Ref<Continuation> cont = quit0;
|
||||
cont.swap(cr.c[0]);
|
||||
return jump(std::move(cont), ret_args);
|
||||
}
|
||||
|
||||
int VmState::ret_alt() {
|
||||
Ref<Continuation> cont = quit1;
|
||||
cont.swap(cr.c[1]);
|
||||
return jump(std::move(cont));
|
||||
}
|
||||
|
||||
int VmState::ret_alt(int ret_args) {
|
||||
Ref<Continuation> cont = quit1;
|
||||
cont.swap(cr.c[1]);
|
||||
return jump(std::move(cont), ret_args);
|
||||
}
|
||||
|
||||
Ref<OrdCont> VmState::extract_cc(int save_cr, int stack_copy, int cc_args) {
|
||||
Ref<Stack> 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<Stack>{true};
|
||||
}
|
||||
Ref<OrdCont> cc = Ref<OrdCont>{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<ExcQuitCont>{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<Continuation> cont = Ref<OrdCont>{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<Continuation>& cont) {
|
||||
if (!cont->get_cdata()) {
|
||||
cont = Ref<ArgContExt>{true, cont};
|
||||
return cont.unique_write().get_cdata();
|
||||
} else {
|
||||
return cont.write().get_cdata();
|
||||
}
|
||||
}
|
||||
|
||||
ControlRegs* force_cregs(Ref<Continuation>& cont) {
|
||||
return &force_cdata(cont)->save;
|
||||
}
|
||||
|
||||
int run_vm_code(Ref<CellSlice> code, Ref<Stack>& stack, int flags, Ref<Cell>* data_ptr, VmLog log, long long* steps,
|
||||
GasLimits* gas_limits, std::vector<Ref<Cell>> libraries, Ref<Tuple> init_c7, Ref<Cell>* actions_ptr) {
|
||||
VmState vm{code,
|
||||
std::move(stack),
|
||||
gas_limits ? *gas_limits : GasLimits{},
|
||||
flags,
|
||||
data_ptr ? *data_ptr : Ref<Cell>{},
|
||||
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<CellSlice> code, Stack& stack, int flags, Ref<Cell>* data_ptr, VmLog log, long long* steps,
|
||||
GasLimits* gas_limits, std::vector<Ref<Cell>> libraries, Ref<Tuple> init_c7, Ref<Cell>* actions_ptr) {
|
||||
Ref<Stack> 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<Cell> VmState::load_library(td::ConstBitPtr hash) {
|
||||
std::unique_ptr<VmStateInterface> 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<Cell> 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<vm::Cell> 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<vm::Cell> lookup_library_in(td::ConstBitPtr key, Ref<vm::Cell> 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
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
Copyright 2017-2020 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
@ -371,289 +371,7 @@ class OrdCont : public Continuation {
|
|||
static Ref<OrdCont> 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<vm::Cell> c4, c5;
|
||||
bool committed{false};
|
||||
};
|
||||
|
||||
class VmState final : public VmStateInterface {
|
||||
Ref<CellSlice> code;
|
||||
Ref<Stack> stack;
|
||||
ControlRegs cr;
|
||||
CommittedState cstate;
|
||||
int cp;
|
||||
long long steps{0};
|
||||
const DispatchTable* dispatch;
|
||||
Ref<QuitCont> quit0, quit1;
|
||||
VmLog log;
|
||||
GasLimits gas;
|
||||
std::vector<Ref<Cell>> 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<CellSlice> _code);
|
||||
VmState(Ref<CellSlice> _code, Ref<Stack> _stack, int flags = 0, Ref<Cell> _data = {}, VmLog log = {},
|
||||
std::vector<Ref<Cell>> _libraries = {}, Ref<Tuple> init_c7 = {});
|
||||
VmState(Ref<CellSlice> _code, Ref<Stack> _stack, const GasLimits& _gas, int flags = 0, Ref<Cell> _data = {},
|
||||
VmLog log = {}, std::vector<Ref<Cell>> _libraries = {}, Ref<Tuple> init_c7 = {});
|
||||
template <typename... Args>
|
||||
VmState(Ref<Cell> code_cell, Args&&... args)
|
||||
: VmState(convert_code_cell(std::move(code_cell)), std::forward<Args>(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<vm::Tuple>& 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 <typename... Args>
|
||||
void check_underflow(Args... args) {
|
||||
stack->check_underflow(args...);
|
||||
}
|
||||
bool register_library_collection(Ref<Cell> lib);
|
||||
Ref<Cell> 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<Stack> get_stack_ref() const {
|
||||
return stack;
|
||||
}
|
||||
Ref<Continuation> get_c0() const {
|
||||
return cr.c[0];
|
||||
}
|
||||
Ref<Continuation> get_c1() const {
|
||||
return cr.c[1];
|
||||
}
|
||||
Ref<Continuation> get_c2() const {
|
||||
return cr.c[2];
|
||||
}
|
||||
Ref<Continuation> get_c3() const {
|
||||
return cr.c[3];
|
||||
}
|
||||
Ref<Cell> get_c4() const {
|
||||
return cr.d[0];
|
||||
}
|
||||
Ref<Tuple> get_c7() const {
|
||||
return cr.c7;
|
||||
}
|
||||
Ref<Continuation> get_c(unsigned idx) const {
|
||||
return cr.get_c(idx);
|
||||
}
|
||||
Ref<Cell> 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<Continuation> cont) {
|
||||
cr.define_c0(std::move(cont));
|
||||
}
|
||||
void set_c0(Ref<Continuation> cont) {
|
||||
cr.set_c0(std::move(cont));
|
||||
}
|
||||
void set_c1(Ref<Continuation> cont) {
|
||||
cr.set_c1(std::move(cont));
|
||||
}
|
||||
void set_c2(Ref<Continuation> cont) {
|
||||
cr.set_c2(std::move(cont));
|
||||
}
|
||||
bool set_c(unsigned idx, Ref<Continuation> val) {
|
||||
return cr.set_c(idx, std::move(val));
|
||||
}
|
||||
bool set_d(unsigned idx, Ref<Cell> val) {
|
||||
return cr.set_d(idx, std::move(val));
|
||||
}
|
||||
void set_c4(Ref<Cell> val) {
|
||||
cr.set_c4(std::move(val));
|
||||
}
|
||||
bool set_c7(Ref<Tuple> 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<Stack> new_stk) {
|
||||
stack = std::move(new_stk);
|
||||
}
|
||||
Ref<Stack> swap_stack(Ref<Stack> new_stk) {
|
||||
stack.swap(new_stk);
|
||||
return new_stk;
|
||||
}
|
||||
void ensure_throw(bool cond) const {
|
||||
if (!cond) {
|
||||
fatal();
|
||||
}
|
||||
}
|
||||
void set_code(Ref<CellSlice> _code, int _cp) {
|
||||
code = std::move(_code);
|
||||
force_cp(_cp);
|
||||
}
|
||||
Ref<CellSlice> 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<Continuation> cont);
|
||||
int call(Ref<Continuation> cont, int pass_args, int ret_args = -1);
|
||||
int jump(Ref<Continuation> cont);
|
||||
int jump(Ref<Continuation> cont, int pass_args);
|
||||
int ret();
|
||||
int ret(int ret_args);
|
||||
int ret_alt();
|
||||
int ret_alt(int ret_args);
|
||||
int repeat(Ref<Continuation> body, Ref<Continuation> after, long long count);
|
||||
int again(Ref<Continuation> body);
|
||||
int until(Ref<Continuation> body, Ref<Continuation> after);
|
||||
int loop_while(Ref<Continuation> cond, Ref<Continuation> body, Ref<Continuation> after);
|
||||
int throw_exception(int excno, StackEntry&& arg);
|
||||
int throw_exception(int excno);
|
||||
Ref<OrdCont> extract_cc(int save_cr = 1, int stack_copy = -1, int cc_args = -1);
|
||||
void fatal(void) const {
|
||||
throw VmFatal{};
|
||||
}
|
||||
int jump_to(Ref<Continuation> cont) {
|
||||
return cont->is_unique() ? cont.unique_write().jump_w(this) : cont->jump(this);
|
||||
}
|
||||
static Ref<CellSlice> convert_code_cell(Ref<Cell> 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<CellSlice> _code, Ref<Stack>& _stack, int flags = 0, Ref<Cell>* data_ptr = nullptr, VmLog log = {},
|
||||
long long* steps = nullptr, GasLimits* gas_limits = nullptr, std::vector<Ref<Cell>> libraries = {},
|
||||
Ref<Tuple> init_c7 = {}, Ref<Cell>* actions_ptr = nullptr);
|
||||
int run_vm_code(Ref<CellSlice> _code, Stack& _stack, int flags = 0, Ref<Cell>* data_ptr = nullptr, VmLog log = {},
|
||||
long long* steps = nullptr, GasLimits* gas_limits = nullptr, std::vector<Ref<Cell>> libraries = {},
|
||||
Ref<Tuple> init_c7 = {}, Ref<Cell>* actions_ptr = nullptr);
|
||||
|
||||
ControlData* force_cdata(Ref<Continuation>& cont);
|
||||
ControlRegs* force_cregs(Ref<Continuation>& cont);
|
||||
|
||||
Ref<vm::Cell> lookup_library_in(td::ConstBitPtr key, Ref<vm::Cell> lib_root);
|
||||
|
||||
} // namespace vm
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
Copyright 2017-2020 Telegram Systems LLP
|
||||
*/
|
||||
#include <functional>
|
||||
#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 {
|
||||
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
Copyright 2017-2020 Telegram Systems LLP
|
||||
*/
|
||||
#include <functional>
|
||||
#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 {
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
Copyright 2017-2020 Telegram Systems LLP
|
||||
*/
|
||||
#include <cassert>
|
||||
#include <iterator>
|
||||
#include "vm/opctable.h"
|
||||
#include "vm/cellslice.h"
|
||||
#include "vm/excno.hpp"
|
||||
#include "vm/continuation.h"
|
||||
#include "vm/vm.h"
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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 {
|
||||
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
Copyright 2017-2020 Telegram Systems LLP
|
||||
*/
|
||||
#include <functional>
|
||||
#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<CellHash> visited;
|
||||
VmStorageStat(td::uint64 _limit) : limit(_limit) {
|
||||
}
|
||||
bool add_storage(Ref<Cell> 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>& cell) {
|
||||
return check_visited(cell->get_hash());
|
||||
}
|
||||
};
|
||||
|
||||
bool VmStorageStat::add_storage(Ref<Cell> 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> cell;
|
||||
Ref<CellSlice> 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);
|
||||
}
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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 {
|
||||
|
||||
|
|
593
crypto/vm/vm.cpp
Normal file
593
crypto/vm/vm.cpp
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<CellSlice> _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<CellSlice> _code, Ref<Stack> _stack, int flags, Ref<Cell> _data, VmLog log,
|
||||
std::vector<Ref<Cell>> _libraries, Ref<Tuple> 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<CellSlice> _code, Ref<Stack> _stack, const GasLimits& gas, int flags, Ref<Cell> _data, VmLog log,
|
||||
std::vector<Ref<Cell>> _libraries, Ref<Tuple> 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<CellSlice> VmState::convert_code_cell(Ref<Cell> code_cell) {
|
||||
if (code_cell.is_null()) {
|
||||
return {};
|
||||
}
|
||||
Ref<CellSlice> 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<Continuation> 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<OrdCont> ret = Ref<OrdCont>{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<OrdCont> ret = Ref<OrdCont>{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<Continuation> 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<Stack> 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<OrdCont> ret = Ref<OrdCont>{true, std::move(code), cp, std::move(stack), ret_args};
|
||||
ret.unique_write().get_cdata()->save.set_c0(std::move(old_c0));
|
||||
Ref<OrdCont> ord_cont = static_cast<Ref<OrdCont>>(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<Stack> 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<OrdCont> ret = Ref<OrdCont>{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<Continuation> 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<Continuation> 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<Stack> 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<Continuation> cont = quit0;
|
||||
cont.swap(cr.c[0]);
|
||||
return jump(std::move(cont));
|
||||
}
|
||||
|
||||
int VmState::ret(int ret_args) {
|
||||
Ref<Continuation> cont = quit0;
|
||||
cont.swap(cr.c[0]);
|
||||
return jump(std::move(cont), ret_args);
|
||||
}
|
||||
|
||||
int VmState::ret_alt() {
|
||||
Ref<Continuation> cont = quit1;
|
||||
cont.swap(cr.c[1]);
|
||||
return jump(std::move(cont));
|
||||
}
|
||||
|
||||
int VmState::ret_alt(int ret_args) {
|
||||
Ref<Continuation> cont = quit1;
|
||||
cont.swap(cr.c[1]);
|
||||
return jump(std::move(cont), ret_args);
|
||||
}
|
||||
|
||||
Ref<OrdCont> VmState::extract_cc(int save_cr, int stack_copy, int cc_args) {
|
||||
Ref<Stack> 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<Stack>{true};
|
||||
}
|
||||
Ref<OrdCont> cc = Ref<OrdCont>{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<ExcQuitCont>{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<Continuation> cont = Ref<OrdCont>{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<Continuation>& cont) {
|
||||
if (!cont->get_cdata()) {
|
||||
cont = Ref<ArgContExt>{true, cont};
|
||||
return cont.unique_write().get_cdata();
|
||||
} else {
|
||||
return cont.write().get_cdata();
|
||||
}
|
||||
}
|
||||
|
||||
ControlRegs* force_cregs(Ref<Continuation>& cont) {
|
||||
return &force_cdata(cont)->save;
|
||||
}
|
||||
|
||||
int run_vm_code(Ref<CellSlice> code, Ref<Stack>& stack, int flags, Ref<Cell>* data_ptr, VmLog log, long long* steps,
|
||||
GasLimits* gas_limits, std::vector<Ref<Cell>> libraries, Ref<Tuple> init_c7, Ref<Cell>* actions_ptr) {
|
||||
VmState vm{code,
|
||||
std::move(stack),
|
||||
gas_limits ? *gas_limits : GasLimits{},
|
||||
flags,
|
||||
data_ptr ? *data_ptr : Ref<Cell>{},
|
||||
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<CellSlice> code, Stack& stack, int flags, Ref<Cell>* data_ptr, VmLog log, long long* steps,
|
||||
GasLimits* gas_limits, std::vector<Ref<Cell>> libraries, Ref<Tuple> init_c7, Ref<Cell>* actions_ptr) {
|
||||
Ref<Stack> 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<Cell> VmState::load_library(td::ConstBitPtr hash) {
|
||||
std::unique_ptr<VmStateInterface> 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<Cell> 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<vm::Cell> 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<vm::Cell> lookup_library_in(td::ConstBitPtr key, Ref<vm::Cell> 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
|
320
crypto/vm/vm.h
Normal file
320
crypto/vm/vm.h
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<vm::Cell> c4, c5;
|
||||
bool committed{false};
|
||||
};
|
||||
|
||||
class VmState final : public VmStateInterface {
|
||||
Ref<CellSlice> code;
|
||||
Ref<Stack> stack;
|
||||
ControlRegs cr;
|
||||
CommittedState cstate;
|
||||
int cp;
|
||||
long long steps{0};
|
||||
const DispatchTable* dispatch;
|
||||
Ref<QuitCont> quit0, quit1;
|
||||
VmLog log;
|
||||
GasLimits gas;
|
||||
std::vector<Ref<Cell>> libraries;
|
||||
td::HashSet<CellHash> 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<CellSlice> _code);
|
||||
VmState(Ref<CellSlice> _code, Ref<Stack> _stack, int flags = 0, Ref<Cell> _data = {}, VmLog log = {},
|
||||
std::vector<Ref<Cell>> _libraries = {}, Ref<Tuple> init_c7 = {});
|
||||
VmState(Ref<CellSlice> _code, Ref<Stack> _stack, const GasLimits& _gas, int flags = 0, Ref<Cell> _data = {},
|
||||
VmLog log = {}, std::vector<Ref<Cell>> _libraries = {}, Ref<Tuple> init_c7 = {});
|
||||
template <typename... Args>
|
||||
VmState(Ref<Cell> code_cell, Args&&... args)
|
||||
: VmState(convert_code_cell(std::move(code_cell)), std::forward<Args>(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<vm::Tuple>& 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 <typename... Args>
|
||||
void check_underflow(Args... args) {
|
||||
stack->check_underflow(args...);
|
||||
}
|
||||
bool register_library_collection(Ref<Cell> lib);
|
||||
Ref<Cell> 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<Stack> get_stack_ref() const {
|
||||
return stack;
|
||||
}
|
||||
Ref<Continuation> get_c0() const {
|
||||
return cr.c[0];
|
||||
}
|
||||
Ref<Continuation> get_c1() const {
|
||||
return cr.c[1];
|
||||
}
|
||||
Ref<Continuation> get_c2() const {
|
||||
return cr.c[2];
|
||||
}
|
||||
Ref<Continuation> get_c3() const {
|
||||
return cr.c[3];
|
||||
}
|
||||
Ref<Cell> get_c4() const {
|
||||
return cr.d[0];
|
||||
}
|
||||
Ref<Tuple> get_c7() const {
|
||||
return cr.c7;
|
||||
}
|
||||
Ref<Continuation> get_c(unsigned idx) const {
|
||||
return cr.get_c(idx);
|
||||
}
|
||||
Ref<Cell> 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<Continuation> cont) {
|
||||
cr.define_c0(std::move(cont));
|
||||
}
|
||||
void set_c0(Ref<Continuation> cont) {
|
||||
cr.set_c0(std::move(cont));
|
||||
}
|
||||
void set_c1(Ref<Continuation> cont) {
|
||||
cr.set_c1(std::move(cont));
|
||||
}
|
||||
void set_c2(Ref<Continuation> cont) {
|
||||
cr.set_c2(std::move(cont));
|
||||
}
|
||||
bool set_c(unsigned idx, Ref<Continuation> val) {
|
||||
return cr.set_c(idx, std::move(val));
|
||||
}
|
||||
bool set_d(unsigned idx, Ref<Cell> val) {
|
||||
return cr.set_d(idx, std::move(val));
|
||||
}
|
||||
void set_c4(Ref<Cell> val) {
|
||||
cr.set_c4(std::move(val));
|
||||
}
|
||||
bool set_c7(Ref<Tuple> 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<Stack> new_stk) {
|
||||
stack = std::move(new_stk);
|
||||
}
|
||||
Ref<Stack> swap_stack(Ref<Stack> new_stk) {
|
||||
stack.swap(new_stk);
|
||||
return new_stk;
|
||||
}
|
||||
void ensure_throw(bool cond) const {
|
||||
if (!cond) {
|
||||
fatal();
|
||||
}
|
||||
}
|
||||
void set_code(Ref<CellSlice> _code, int _cp) {
|
||||
code = std::move(_code);
|
||||
force_cp(_cp);
|
||||
}
|
||||
Ref<CellSlice> 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<Continuation> cont);
|
||||
int call(Ref<Continuation> cont, int pass_args, int ret_args = -1);
|
||||
int jump(Ref<Continuation> cont);
|
||||
int jump(Ref<Continuation> cont, int pass_args);
|
||||
int ret();
|
||||
int ret(int ret_args);
|
||||
int ret_alt();
|
||||
int ret_alt(int ret_args);
|
||||
int repeat(Ref<Continuation> body, Ref<Continuation> after, long long count);
|
||||
int again(Ref<Continuation> body);
|
||||
int until(Ref<Continuation> body, Ref<Continuation> after);
|
||||
int loop_while(Ref<Continuation> cond, Ref<Continuation> body, Ref<Continuation> after);
|
||||
int throw_exception(int excno, StackEntry&& arg);
|
||||
int throw_exception(int excno);
|
||||
Ref<OrdCont> extract_cc(int save_cr = 1, int stack_copy = -1, int cc_args = -1);
|
||||
void fatal(void) const {
|
||||
throw VmFatal{};
|
||||
}
|
||||
int jump_to(Ref<Continuation> cont) {
|
||||
return cont->is_unique() ? cont.unique_write().jump_w(this) : cont->jump(this);
|
||||
}
|
||||
static Ref<CellSlice> convert_code_cell(Ref<Cell> 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<CellSlice> _code, Ref<Stack>& _stack, int flags = 0, Ref<Cell>* data_ptr = nullptr, VmLog log = {},
|
||||
long long* steps = nullptr, GasLimits* gas_limits = nullptr, std::vector<Ref<Cell>> libraries = {},
|
||||
Ref<Tuple> init_c7 = {}, Ref<Cell>* actions_ptr = nullptr);
|
||||
int run_vm_code(Ref<CellSlice> _code, Stack& _stack, int flags = 0, Ref<Cell>* data_ptr = nullptr, VmLog log = {},
|
||||
long long* steps = nullptr, GasLimits* gas_limits = nullptr, std::vector<Ref<Cell>> libraries = {},
|
||||
Ref<Tuple> init_c7 = {}, Ref<Cell>* actions_ptr = nullptr);
|
||||
|
||||
Ref<vm::Cell> lookup_library_in(td::ConstBitPtr key, Ref<vm::Cell> lib_root);
|
||||
|
||||
} // namespace vm
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<VmStateInterface> {
|
|||
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(){};
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue