mirror of
https://github.com/ton-blockchain/ton
synced 2025-03-09 15:40:10 +00:00
updated submodules, bugfixes
- added new fift/func code for validator complaint creation - bugfixes in validator - updates in tonlib - new versions of rocksdb/abseil - hardfork support
This commit is contained in:
parent
16a4566091
commit
9f008b129f
129 changed files with 8438 additions and 879 deletions
|
@ -49,6 +49,7 @@ set(TON_CRYPTO_SOURCE
|
|||
common/bigexp.h
|
||||
common/util.h
|
||||
common/linalloc.hpp
|
||||
common/promiseop.hpp
|
||||
|
||||
ellcurve/Ed25519.h
|
||||
ellcurve/Fp25519.h
|
||||
|
@ -212,6 +213,7 @@ set(SMC_ENVELOPE_SOURCE
|
|||
smc-envelope/HighloadWalletV2.cpp
|
||||
smc-envelope/ManualDns.cpp
|
||||
smc-envelope/MultisigWallet.cpp
|
||||
smc-envelope/PaymentChannel.cpp
|
||||
smc-envelope/SmartContract.cpp
|
||||
smc-envelope/SmartContractCode.cpp
|
||||
smc-envelope/TestGiver.cpp
|
||||
|
@ -341,7 +343,7 @@ if (NOT CMAKE_CROSSCOMPILING)
|
|||
set(multiValueArgs SOURCE)
|
||||
set(FUNC_LIB_SOURCE smartcont/stdlib.fc)
|
||||
cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} )
|
||||
string(REGEX REPLACE "[^a-zA-Z_]" "_" ID ${ARG_DEST})
|
||||
string(REGEX REPLACE "[^0-9a-zA-Z_]" "_" ID ${ARG_DEST})
|
||||
set(ARG_DEST_FIF "${ARG_DEST}.fif")
|
||||
add_custom_command(
|
||||
COMMENT "Generate ${ARG_DEST_FIF}"
|
||||
|
@ -374,10 +376,13 @@ if (NOT CMAKE_CROSSCOMPILING)
|
|||
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/restricted-wallet3-code SOURCE smartcont/restricted-wallet3-code.fc NAME restricted-wallet3)
|
||||
|
||||
GenFif(DEST smartcont/auto/dns-manual-code SOURCE smartcont/dns-manual-code.fc NAME dns-manual)
|
||||
GenFif(DEST smartcont/auto/dns-auto-code SOURCE smartcont/dns-auto-code.fc NAME dns-auto)
|
||||
|
||||
GenFif(DEST smartcont/auto/payment-channel-code SOURCE smartcont/payment-channel-code.fc NAME payment-channel)
|
||||
|
||||
GenFif(DEST smartcont/auto/simple-wallet-ext-code SOURCE smartcont/simple-wallet-ext-code.fc NAME simple-wallet-ext)
|
||||
endif()
|
||||
|
||||
|
@ -420,5 +425,13 @@ if (WINGETOPT_FOUND)
|
|||
target_link_libraries_system(dump-block wingetopt)
|
||||
endif()
|
||||
|
||||
add_executable(test-weight-distr block/test-weight-distr.cpp)
|
||||
target_include_directories(test-weight-distr PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/..>)
|
||||
target_link_libraries(test-weight-distr PUBLIC ton_crypto fift-lib ton_block)
|
||||
if (WINGETOPT_FOUND)
|
||||
target_link_libraries_system(test-weight-distr wingetopt)
|
||||
endif()
|
||||
|
||||
install(TARGETS fift func RUNTIME DESTINATION bin)
|
||||
install(DIRECTORY fift/lib/ DESTINATION lib/fift)
|
||||
|
|
|
@ -241,6 +241,14 @@ bool MsgAddressInt::extract_std_address(vm::CellSlice& cs, ton::WorkchainId& wor
|
|||
return false;
|
||||
}
|
||||
|
||||
bool MsgAddressInt::extract_std_address(Ref<vm::CellSlice> cs_ref, block::StdAddress& addr, bool rewrite) const {
|
||||
return extract_std_address(std::move(cs_ref), addr.workchain, addr.addr, rewrite);
|
||||
}
|
||||
|
||||
bool MsgAddressInt::extract_std_address(vm::CellSlice& cs, block::StdAddress& addr, bool rewrite) const {
|
||||
return extract_std_address(cs, addr.workchain, addr.addr, rewrite);
|
||||
}
|
||||
|
||||
bool MsgAddressInt::store_std_address(vm::CellBuilder& cb, ton::WorkchainId workchain,
|
||||
const ton::StdSmcAddress& addr) const {
|
||||
if (workchain >= -128 && workchain < 128) {
|
||||
|
@ -263,6 +271,14 @@ Ref<vm::CellSlice> MsgAddressInt::pack_std_address(ton::WorkchainId workchain, c
|
|||
}
|
||||
}
|
||||
|
||||
bool MsgAddressInt::store_std_address(vm::CellBuilder& cb, const block::StdAddress& addr) const {
|
||||
return store_std_address(cb, addr.workchain, addr.addr);
|
||||
}
|
||||
|
||||
Ref<vm::CellSlice> MsgAddressInt::pack_std_address(const block::StdAddress& addr) const {
|
||||
return pack_std_address(addr.workchain, addr.addr);
|
||||
}
|
||||
|
||||
const MsgAddressInt t_MsgAddressInt;
|
||||
|
||||
bool MsgAddress::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const {
|
||||
|
|
|
@ -298,8 +298,13 @@ struct MsgAddressInt final : TLB_Complex {
|
|||
bool rewrite = true) const;
|
||||
bool extract_std_address(vm::CellSlice& cs, ton::WorkchainId& workchain, ton::StdSmcAddress& addr,
|
||||
bool rewrite = true) const;
|
||||
bool extract_std_address(Ref<vm::CellSlice> cs_ref, block::StdAddress& addr, bool rewrite = true) const;
|
||||
bool extract_std_address(vm::CellSlice& cs, block::StdAddress& addr, bool rewrite = true) const;
|
||||
bool store_std_address(vm::CellBuilder& cb, ton::WorkchainId workchain, const ton::StdSmcAddress& addr) const;
|
||||
Ref<vm::CellSlice> pack_std_address(ton::WorkchainId workchain, const ton::StdSmcAddress& addr) const;
|
||||
|
||||
bool store_std_address(vm::CellBuilder& cb, const block::StdAddress& addr) const;
|
||||
Ref<vm::CellSlice> pack_std_address(const block::StdAddress& addr) const;
|
||||
};
|
||||
|
||||
extern const MsgAddressInt t_MsgAddressInt;
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include "td/utils/crypto.h"
|
||||
#include "td/utils/tl_storers.h"
|
||||
#include "td/utils/misc.h"
|
||||
#include "td/utils/Random.h"
|
||||
|
||||
namespace block {
|
||||
using namespace std::literals::string_literals;
|
||||
|
@ -369,14 +370,14 @@ std::unique_ptr<MsgProcessedUptoCollection> MsgProcessedUptoCollection::unpack(t
|
|||
return v && v->valid ? std::move(v) : std::unique_ptr<MsgProcessedUptoCollection>{};
|
||||
}
|
||||
|
||||
bool MsgProcessedUpto::contains(const MsgProcessedUpto& other) const & {
|
||||
bool MsgProcessedUpto::contains(const MsgProcessedUpto& other) const& {
|
||||
return ton::shard_is_ancestor(shard, other.shard) && mc_seqno >= other.mc_seqno &&
|
||||
(last_inmsg_lt > other.last_inmsg_lt ||
|
||||
(last_inmsg_lt == other.last_inmsg_lt && !(last_inmsg_hash < other.last_inmsg_hash)));
|
||||
}
|
||||
|
||||
bool MsgProcessedUpto::contains(ton::ShardId other_shard, ton::LogicalTime other_lt, td::ConstBitPtr other_hash,
|
||||
ton::BlockSeqno other_mc_seqno) const & {
|
||||
ton::BlockSeqno other_mc_seqno) const& {
|
||||
return ton::shard_is_ancestor(shard, other_shard) && mc_seqno >= other_mc_seqno &&
|
||||
(last_inmsg_lt > other_lt || (last_inmsg_lt == other_lt && !(last_inmsg_hash < other_hash)));
|
||||
}
|
||||
|
@ -1546,6 +1547,89 @@ bool unpack_CreatorStats(Ref<vm::CellSlice> cs, DiscountedCounter& mc_cnt, Disco
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* Monte Carlo simulator for computing the share of shardchain blocks generated by each validator
|
||||
*
|
||||
*/
|
||||
|
||||
bool MtCarloComputeShare::compute() {
|
||||
ok = false;
|
||||
if (W.size() >= (1U << 31) || W.empty()) {
|
||||
return false;
|
||||
}
|
||||
K = std::min(K, N);
|
||||
if (K <= 0 || iterations <= 0) {
|
||||
return false;
|
||||
}
|
||||
double tot_weight = 0., acc = 0.;
|
||||
for (int i = 0; i < N; i++) {
|
||||
if (W[i] <= 0.) {
|
||||
return false;
|
||||
}
|
||||
tot_weight += W[i];
|
||||
}
|
||||
CW.resize(N);
|
||||
RW.resize(N);
|
||||
for (int i = 0; i < N; i++) {
|
||||
CW[i] = acc;
|
||||
acc += W[i] /= tot_weight;
|
||||
RW[i] = 0.;
|
||||
}
|
||||
R0 = 0.;
|
||||
H.resize(N);
|
||||
A.resize(K);
|
||||
for (long long it = 0; it < iterations; ++it) {
|
||||
gen_vset();
|
||||
}
|
||||
for (int i = 0; i < N; i++) {
|
||||
RW[i] = W[i] * (RW[i] + R0) / (double)iterations;
|
||||
}
|
||||
return ok = true;
|
||||
}
|
||||
|
||||
void MtCarloComputeShare::gen_vset() {
|
||||
double total_wt = 1.;
|
||||
int hc = 0;
|
||||
for (int i = 0; i < K; i++) {
|
||||
CHECK(total_wt > 0);
|
||||
double inv_wt = 1. / total_wt;
|
||||
R0 += inv_wt;
|
||||
for (int j = 0; j < i; j++) {
|
||||
RW[A[j]] -= inv_wt;
|
||||
}
|
||||
// double p = drand48() * total_wt;
|
||||
double p = (double)td::Random::fast_uint64() * total_wt / (1. * (1LL << 32) * (1LL << 32));
|
||||
for (int h = 0; h < hc; h++) {
|
||||
if (p < H[h].first) {
|
||||
break;
|
||||
}
|
||||
p += H[h].second;
|
||||
}
|
||||
int a = -1, b = N, c;
|
||||
while (b - a > 1) {
|
||||
c = ((a + b) >> 1);
|
||||
if (CW[c] <= p) {
|
||||
a = c;
|
||||
} else {
|
||||
b = c;
|
||||
}
|
||||
}
|
||||
CHECK(a >= 0 && a < N);
|
||||
CHECK(total_wt >= W[a]);
|
||||
total_wt -= W[a];
|
||||
double x = CW[a];
|
||||
c = hc++;
|
||||
while (c > 0 && H[c - 1].first > x) {
|
||||
H[c] = H[c - 1];
|
||||
--c;
|
||||
}
|
||||
H[c].first = x;
|
||||
H[c].second = W[a];
|
||||
A[i] = a;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* Other block-related functions
|
||||
|
@ -1723,7 +1807,7 @@ ton::AccountIdPrefixFull interpolate_addr(const ton::AccountIdPrefixFull& src, c
|
|||
unsigned long long mask = (std::numeric_limits<td::uint64>::max() >> (d - 32));
|
||||
return ton::AccountIdPrefixFull{dest.workchain, (dest.account_id_prefix & ~mask) | (src.account_id_prefix & mask)};
|
||||
} else {
|
||||
int mask = (-1 >> d);
|
||||
int mask = (int)(~0U >> d);
|
||||
return ton::AccountIdPrefixFull{(dest.workchain & ~mask) | (src.workchain & mask), src.account_id_prefix};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -163,12 +163,12 @@ struct MsgProcessedUpto {
|
|||
MsgProcessedUpto(ton::ShardId _shard, ton::BlockSeqno _mcseqno, ton::LogicalTime _lt, td::ConstBitPtr _hash)
|
||||
: shard(_shard), mc_seqno(_mcseqno), last_inmsg_lt(_lt), last_inmsg_hash(_hash) {
|
||||
}
|
||||
bool operator<(const MsgProcessedUpto& other) const & {
|
||||
bool operator<(const MsgProcessedUpto& other) const& {
|
||||
return shard < other.shard || (shard == other.shard && mc_seqno < other.mc_seqno);
|
||||
}
|
||||
bool contains(const MsgProcessedUpto& other) const &;
|
||||
bool contains(const MsgProcessedUpto& other) const&;
|
||||
bool contains(ton::ShardId other_shard, ton::LogicalTime other_lt, td::ConstBitPtr other_hash,
|
||||
ton::BlockSeqno other_mc_seqno) const &;
|
||||
ton::BlockSeqno other_mc_seqno) const&;
|
||||
// NB: this is for checking whether we have already imported an internal message
|
||||
bool already_processed(const EnqueuedMsgDescr& msg) const;
|
||||
bool can_check_processed() const {
|
||||
|
@ -596,6 +596,62 @@ struct BlockProofChain {
|
|||
td::Status validate(td::CancellationToken cancellation_token = {});
|
||||
};
|
||||
|
||||
// compute the share of shardchain blocks generated by each validator using Monte Carlo method
|
||||
class MtCarloComputeShare {
|
||||
int K, N;
|
||||
long long iterations;
|
||||
std::vector<double> W;
|
||||
std::vector<double> CW, RW;
|
||||
std::vector<std::pair<double, double>> H;
|
||||
std::vector<int> A;
|
||||
double R0;
|
||||
bool ok;
|
||||
|
||||
public:
|
||||
MtCarloComputeShare(int subset_size, const std::vector<double>& weights, long long iteration_count = 1000000)
|
||||
: K(subset_size), N((int)weights.size()), iterations(iteration_count), W(weights), ok(false) {
|
||||
compute();
|
||||
}
|
||||
MtCarloComputeShare(int subset_size, int set_size, const double* weights, long long iteration_count = 1000000)
|
||||
: K(subset_size), N(set_size), iterations(iteration_count), W(weights, weights + set_size), ok(false) {
|
||||
compute();
|
||||
}
|
||||
bool is_ok() const {
|
||||
return ok;
|
||||
}
|
||||
const double* share_array() const {
|
||||
return ok ? RW.data() : nullptr;
|
||||
}
|
||||
const double* weights_array() const {
|
||||
return ok ? W.data() : nullptr;
|
||||
}
|
||||
double operator[](int i) const {
|
||||
return ok ? RW.at(i) : -1.;
|
||||
}
|
||||
double share(int i) const {
|
||||
return ok ? RW.at(i) : -1.;
|
||||
}
|
||||
double weight(int i) const {
|
||||
return ok ? W.at(i) : -1.;
|
||||
}
|
||||
int size() const {
|
||||
return N;
|
||||
}
|
||||
int subset_size() const {
|
||||
return K;
|
||||
}
|
||||
long long performed_iterations() const {
|
||||
return iterations;
|
||||
}
|
||||
|
||||
private:
|
||||
bool set_error() {
|
||||
return ok = false;
|
||||
}
|
||||
bool compute();
|
||||
void gen_vset();
|
||||
};
|
||||
|
||||
int filter_out_msg_queue(vm::AugmentedDictionary& out_queue, ton::ShardIdFull old_shard, ton::ShardIdFull subshard);
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const ShardId& shard_id);
|
||||
|
|
|
@ -272,6 +272,7 @@ transaction$0111 account_addr:bits256 lt:uint64
|
|||
old:^X new:^X = MERKLE_UPDATE X;
|
||||
update_hashes#72 {X:Type} old_hash:bits256 new_hash:bits256
|
||||
= HASH_UPDATE X;
|
||||
!merkle_proof#03 {X:Type} virtual_hash:bits256 depth:uint16 virtual_root:^X = MERKLE_PROOF X;
|
||||
|
||||
acc_trans#5 account_addr:bits256
|
||||
transactions:(HashmapAug 64 ^Transaction CurrencyCollection)
|
||||
|
@ -731,8 +732,11 @@ top_block_descr_set#4ac789f3 collection:(HashmapE 96 ^TopBlockDescr) = TopBlockD
|
|||
//
|
||||
// VALIDATOR MISBEHAVIOR COMPLAINTS
|
||||
//
|
||||
no_blk_gen mc_blk_ref:ExtBlkRef from_utime:uint32 to_utime:uint32 state_proof:^Cell prod_proof:^Cell = ComplaintDescr;
|
||||
validator_complaint#bc validator_pubkey:uint256 description:^ComplaintDescr created_at:uint32 severity:uint8 reward_addr:uint256 paid:Grams suggested_fine:Grams suggested_fine_part:uint32 = ValidatorComplaint;
|
||||
prod_info#34 utime:uint32 mc_blk_ref:ExtBlkRef state_proof:^(MERKLE_PROOF Block)
|
||||
prod_proof:^(MERKLE_PROOF ShardState) = ProducerInfo;
|
||||
no_blk_gen from_utime:uint32 prod_info:^ProducerInfo = ComplaintDescr;
|
||||
no_blk_gen_diff prod_info_old:^ProducerInfo prod_info_new:^ProducerInfo = ComplaintDescr;
|
||||
validator_complaint#bc validator_pubkey:bits256 description:^ComplaintDescr created_at:uint32 severity:uint8 reward_addr:uint256 paid:Grams suggested_fine:Grams suggested_fine_part:uint32 = ValidatorComplaint;
|
||||
complaint_status#2d complaint:^ValidatorComplaint voters:(HashmapE 16 True) vset_id:uint256 weight_remaining:int64 = ValidatorComplaintStatus;
|
||||
|
||||
//
|
||||
|
@ -808,3 +812,25 @@ cap_method_pubkey#71f4 = SmcCapability;
|
|||
cap_is_wallet#2177 = SmcCapability;
|
||||
cap_name#ff name:Text = SmcCapability;
|
||||
|
||||
//
|
||||
// PAYMENT CHANNELS
|
||||
//
|
||||
|
||||
chan_config$_ init_timeout:uint32 close_timeout:uint32 a_key:bits256 b_key:bits256
|
||||
a_addr:^MsgAddressInt b_addr:^MsgAddressInt channel_id:uint64 = ChanConfig;
|
||||
|
||||
chan_state_init$000 signed_A:Bool signed_B:Bool min_A:Grams min_B:Grams expire_at:uint32 A:Grams B:Grams = ChanState;
|
||||
chan_state_close$001 signed_A:Bool signed_B:Bool promise_A:Grams promise_B:Grams expire_at:uint32 A:Grams B:Grams = ChanState;
|
||||
chan_state_payout$010 A:Grams B:Grams = ChanState;
|
||||
|
||||
chan_promise$_ channel_id:uint64 promise_A:Grams promise_B:Grams = ChanPromise;
|
||||
chan_signed_promise#_ sig:(Maybe ^bits512) promise:ChanPromise = ChanSignedPromise;
|
||||
|
||||
chan_msg_init#27317822 inc_A:Grams inc_B:Grams min_A:Grams min_B:Grams channel_id:uint64 = ChanMsg;
|
||||
chan_msg_close#f28ae183 extra_A:Grams extra_B:Grams promise:ChanSignedPromise = ChanMsg;
|
||||
chan_msg_timeout#43278a28 = ChanMsg;
|
||||
|
||||
chan_signed_msg$_ sig_A:(Maybe ^bits512) sig_B:(Maybe ^bits512) msg:ChanMsg = ChanSignedMsg;
|
||||
|
||||
chan_data$_ config:^ChanConfig state:^ChanState = ChanData;
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
namespace block {
|
||||
using namespace std::literals::string_literals;
|
||||
|
||||
td::Status check_block_header_proof(td::Ref<vm::Cell> root, ton::BlockIdExt blkid, ton::Bits256* store_shard_hash_to,
|
||||
td::Status check_block_header_proof(td::Ref<vm::Cell> root, ton::BlockIdExt blkid, ton::Bits256* store_state_hash_to,
|
||||
bool check_state_hash, td::uint32* save_utime, ton::LogicalTime* save_lt) {
|
||||
ton::RootHash vhash{root->get_hash().bits()};
|
||||
if (vhash != blkid.root_hash) {
|
||||
|
@ -53,7 +53,7 @@ td::Status check_block_header_proof(td::Ref<vm::Cell> root, ton::BlockIdExt blki
|
|||
if (save_lt) {
|
||||
*save_lt = info.end_lt;
|
||||
}
|
||||
if (store_shard_hash_to) {
|
||||
if (store_state_hash_to) {
|
||||
vm::CellSlice upd_cs{vm::NoVmSpec(), blk.state_update};
|
||||
if (!(upd_cs.is_special() && upd_cs.prefetch_long(8) == 4 // merkle update
|
||||
&& upd_cs.size_ext() == 0x20228)) {
|
||||
|
@ -61,11 +61,11 @@ td::Status check_block_header_proof(td::Ref<vm::Cell> root, ton::BlockIdExt blki
|
|||
}
|
||||
auto upd_hash = upd_cs.prefetch_ref(1)->get_hash(0);
|
||||
if (!check_state_hash) {
|
||||
*store_shard_hash_to = upd_hash.bits();
|
||||
} else if (store_shard_hash_to->compare(upd_hash.bits())) {
|
||||
*store_state_hash_to = upd_hash.bits();
|
||||
} else if (store_state_hash_to->compare(upd_hash.bits())) {
|
||||
return td::Status::Error(PSTRING() << "state hash mismatch in block header of " << blkid.to_str()
|
||||
<< " : header declares " << upd_hash.bits().to_hex(256) << " expected "
|
||||
<< store_shard_hash_to->to_hex());
|
||||
<< store_state_hash_to->to_hex());
|
||||
}
|
||||
}
|
||||
return td::Status::OK();
|
||||
|
|
|
@ -25,7 +25,7 @@ namespace block {
|
|||
using td::Ref;
|
||||
|
||||
td::Status check_block_header_proof(td::Ref<vm::Cell> root, ton::BlockIdExt blkid,
|
||||
ton::Bits256* store_shard_hash_to = nullptr, bool check_state_hash = false,
|
||||
ton::Bits256* store_state_hash_to = nullptr, bool check_state_hash = false,
|
||||
td::uint32* save_utime = nullptr, ton::LogicalTime* save_lt = nullptr);
|
||||
td::Status check_shard_proof(ton::BlockIdExt blk, ton::BlockIdExt shard_blk, td::Slice shard_proof);
|
||||
td::Status check_account_proof(td::Slice proof, ton::BlockIdExt shard_blk, const block::StdAddress& addr,
|
||||
|
|
|
@ -1785,6 +1785,31 @@ std::vector<ton::ValidatorDescr> ValidatorSet::export_validator_set() const {
|
|||
return l;
|
||||
}
|
||||
|
||||
std::map<ton::Bits256, int> ValidatorSet::compute_validator_map() const {
|
||||
std::map<ton::Bits256, int> res;
|
||||
for (int i = 0; i < (int)list.size(); i++) {
|
||||
res.emplace(list[i].pubkey.as_bits256(), i);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<double> ValidatorSet::export_scaled_validator_weights() const {
|
||||
std::vector<double> res;
|
||||
for (const auto& node : list) {
|
||||
res.push_back((double)node.weight / (double)total_weight);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
int ValidatorSet::lookup_public_key(td::ConstBitPtr pubkey) const {
|
||||
for (int i = 0; i < (int)list.size(); i++) {
|
||||
if (list[i].pubkey.as_bits256() == pubkey) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::vector<ton::ValidatorDescr> Config::do_compute_validator_set(const block::CatchainValidatorsConfig& ccv_conf,
|
||||
ton::ShardIdFull shard,
|
||||
const block::ValidatorSet& vset, ton::UnixTime time,
|
||||
|
|
|
@ -71,6 +71,12 @@ struct ValidatorSet {
|
|||
}
|
||||
const ValidatorDescr& at_weight(td::uint64 weight_pos) const;
|
||||
std::vector<ton::ValidatorDescr> export_validator_set() const;
|
||||
std::map<ton::Bits256, int> compute_validator_map() const;
|
||||
std::vector<double> export_scaled_validator_weights() const;
|
||||
int lookup_public_key(td::ConstBitPtr pubkey) const;
|
||||
int lookup_public_key(const td::Bits256& pubkey) const {
|
||||
return lookup_public_key(pubkey.bits());
|
||||
}
|
||||
};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
|
199
crypto/block/test-weight-distr.cpp
Normal file
199
crypto/block/test-weight-distr.cpp
Normal file
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
This file is part of TON Blockchain source code.
|
||||
|
||||
TON Blockchain is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU 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 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with TON Blockchain. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
You must obey the GNU General Public License in all respects for all
|
||||
of the code used other than OpenSSL. If you modify file(s) with this
|
||||
exception, you may extend this exception to your version of the file(s),
|
||||
but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
exception statement from your version. If you delete this exception statement
|
||||
from all source files in the program, then also delete it here.
|
||||
|
||||
Copyright 2020 Telegram Systems LLP
|
||||
*/
|
||||
#include <iostream>
|
||||
#include "td/utils/Random.h"
|
||||
#include "td/utils/misc.h"
|
||||
#include "block/block.h"
|
||||
#include <getopt.h>
|
||||
|
||||
const int MAX_N = 1000, MAX_K = 100, DEFAULT_K = 7;
|
||||
|
||||
int verbosity;
|
||||
int N, K = DEFAULT_K;
|
||||
long long iterations = 1000000;
|
||||
|
||||
td::uint64 TWL, WL[MAX_N];
|
||||
double W[MAX_N], CW[MAX_N + 1], RW[MAX_N], R0;
|
||||
int A[MAX_N], C[MAX_N];
|
||||
long long TC;
|
||||
|
||||
void gen_vset() {
|
||||
static std::pair<double, double> H[MAX_N];
|
||||
double total_wt = 1.;
|
||||
int hc = 0;
|
||||
for (int i = 0; i < K; i++) {
|
||||
CHECK(total_wt > 0);
|
||||
double inv_wt = 1. / total_wt;
|
||||
R0 += inv_wt; // advanced mtcarlo stats
|
||||
for (int j = 0; j < i; j++) {
|
||||
RW[A[j]] -= inv_wt; // advanced mtcarlo stats
|
||||
}
|
||||
// double p = drand48() * total_wt;
|
||||
double p = (double)td::Random::fast_uint64() * total_wt / (1. * (1LL << 32) * (1LL << 32));
|
||||
for (int h = 0; h < hc; h++) {
|
||||
if (p < H[h].first) {
|
||||
break;
|
||||
}
|
||||
p += H[h].second;
|
||||
}
|
||||
int a = -1, b = N, c;
|
||||
while (b - a > 1) {
|
||||
c = ((a + b) >> 1);
|
||||
if (CW[c] <= p) {
|
||||
a = c;
|
||||
} else {
|
||||
b = c;
|
||||
}
|
||||
}
|
||||
CHECK(a >= 0 && a < N);
|
||||
CHECK(total_wt >= W[a]);
|
||||
total_wt -= W[a];
|
||||
double x = CW[a];
|
||||
c = hc++;
|
||||
while (c > 0 && H[c - 1].first > x) {
|
||||
H[c] = H[c - 1];
|
||||
--c;
|
||||
}
|
||||
H[c].first = x;
|
||||
H[c].second = W[a];
|
||||
A[i] = a;
|
||||
C[a]++; // simple mtcarlo stats
|
||||
// std::cout << a << ' ';
|
||||
}
|
||||
// std::cout << std::endl;
|
||||
++TC; // simple mtcarlo stats
|
||||
}
|
||||
|
||||
void mt_carlo() {
|
||||
for (int i = 0; i < N; i++) {
|
||||
C[i] = 0;
|
||||
RW[i] = 0.;
|
||||
}
|
||||
TC = 0;
|
||||
R0 = 0.;
|
||||
std::cout << "running " << iterations << " steps of Monte Carlo simulation\n";
|
||||
for (long long it = 0; it < iterations; ++it) {
|
||||
gen_vset();
|
||||
}
|
||||
for (int i = 0; i < N; i++) {
|
||||
RW[i] = W[i] * (RW[i] + R0) / (double)iterations;
|
||||
}
|
||||
}
|
||||
|
||||
double B[MAX_N];
|
||||
|
||||
void compute_bad_approx() {
|
||||
static double S[MAX_K + 1];
|
||||
S[0] = 1.;
|
||||
for (int i = 1; i <= K; i++) {
|
||||
S[i] = 0.;
|
||||
}
|
||||
for (int i = 0; i < N; i++) {
|
||||
double p = W[i];
|
||||
for (int j = K; j > 0; j--) {
|
||||
S[j] += p * S[j - 1];
|
||||
}
|
||||
}
|
||||
double Sk = S[K];
|
||||
for (int i = 0; i < N; i++) {
|
||||
double t = 1., p = W[i];
|
||||
for (int j = 1; j <= K; j++) {
|
||||
t = S[j] - p * t;
|
||||
}
|
||||
B[i] = 1. - t / Sk;
|
||||
}
|
||||
}
|
||||
|
||||
void usage() {
|
||||
std::cout
|
||||
<< "usage: test-weight-distr [-k<shard-val-num>][-m<iterations>][-s<rand-seed>]\nReads the set of validator "
|
||||
"weights from stdin and emulates validator shard distribution load\n\t-k <shard-val-num>\tSets the number of "
|
||||
"validators generating each shard\n\t-m <iterations>\tMonte Carlo simulation steps\n";
|
||||
std::exit(2);
|
||||
}
|
||||
|
||||
int main(int argc, char* const argv[]) {
|
||||
int i;
|
||||
int new_verbosity_level = VERBOSITY_NAME(INFO);
|
||||
// long seed = 0;
|
||||
while ((i = getopt(argc, argv, "hs:k:m:v:")) != -1) {
|
||||
switch (i) {
|
||||
case 'k':
|
||||
K = td::to_integer<int>(td::Slice(optarg));
|
||||
CHECK(K > 0 && K <= 100);
|
||||
break;
|
||||
case 'm':
|
||||
iterations = td::to_integer<long long>(td::Slice(optarg));
|
||||
CHECK(iterations > 0);
|
||||
break;
|
||||
case 's':
|
||||
// seed = td::to_integer<long>(td::Slice(optarg));
|
||||
// srand48(seed);
|
||||
break;
|
||||
case 'v':
|
||||
new_verbosity_level = VERBOSITY_NAME(FATAL) + (verbosity = td::to_integer<int>(td::Slice(optarg)));
|
||||
break;
|
||||
case 'h':
|
||||
usage();
|
||||
std::exit(2);
|
||||
default:
|
||||
usage();
|
||||
std::exit(2);
|
||||
}
|
||||
}
|
||||
SET_VERBOSITY_LEVEL(new_verbosity_level);
|
||||
for (N = 0; N < MAX_N && (std::cin >> WL[N]); N++) {
|
||||
CHECK(WL[N] > 0);
|
||||
TWL += WL[N];
|
||||
}
|
||||
CHECK(std::cin.eof());
|
||||
CHECK(N > 0 && TWL > 0 && N <= MAX_N);
|
||||
K = std::min(K, N);
|
||||
CHECK(K > 0 && K <= MAX_K);
|
||||
double acc = 0.;
|
||||
for (i = 0; i < N; i++) {
|
||||
CW[i] = acc;
|
||||
acc += W[i] = (double)WL[i] / (double)TWL;
|
||||
std::cout << "#" << i << ":\t" << W[i] << std::endl;
|
||||
}
|
||||
compute_bad_approx();
|
||||
mt_carlo();
|
||||
std::cout << "result of Monte Carlo simulation (" << iterations << " iterations):" << std::endl;
|
||||
std::cout << "idx\tweight\tmtcarlo1\tmtcarlo2\tapprox\n";
|
||||
for (i = 0; i < N; i++) {
|
||||
std::cout << "#" << i << ":\t" << W[i] << '\t' << (double)C[i] / (double)iterations << '\t' << RW[i] << '\t' << B[i]
|
||||
<< std::endl;
|
||||
}
|
||||
// same computation, but using a MtCarloComputeShare object
|
||||
block::MtCarloComputeShare MT(K, N, W, iterations);
|
||||
std::cout << "-----------------------\n";
|
||||
for (i = 0; i < N; i++) {
|
||||
std::cout << '#' << i << ":\t" << MT.weight(i) << '\t' << MT.share(i) << std::endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
|
@ -1429,6 +1429,9 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap,
|
|||
if (!tlb::csr_unpack(msg.info, erec)) {
|
||||
return -1;
|
||||
}
|
||||
if (act_rec.mode & ~3) {
|
||||
return -1; // invalid mode for an external message
|
||||
}
|
||||
info.src = std::move(erec.src);
|
||||
info.dest = std::move(erec.dest);
|
||||
// created_lt and created_at are ignored
|
||||
|
|
|
@ -272,7 +272,7 @@ class AnyIntView {
|
|||
int parse_binary_any(const char* str, int str_len, int* frac = nullptr);
|
||||
std::string to_dec_string_destroy_any();
|
||||
std::string to_dec_string_slow_destroy_any();
|
||||
std::string to_hex_string_any(bool upcase = false) const;
|
||||
std::string to_hex_string_any(bool upcase = false, int zero_pad = 0) const;
|
||||
std::string to_hex_string_slow_destroy_any();
|
||||
std::string to_binary_string_any() const;
|
||||
|
||||
|
@ -650,7 +650,7 @@ class BigIntG {
|
|||
std::string to_dec_string_destroy();
|
||||
std::string to_dec_string_slow() const;
|
||||
std::string to_hex_string_slow() const;
|
||||
std::string to_hex_string(bool upcase = false) const;
|
||||
std::string to_hex_string(bool upcase = false, int zero_pad = 0) const;
|
||||
std::string to_binary_string() const;
|
||||
double to_double() const {
|
||||
return is_valid() ? ldexp(top_double(), (n - 1) * word_shift) : NAN;
|
||||
|
@ -2290,16 +2290,19 @@ std::string AnyIntView<Tr>::to_hex_string_slow_destroy_any() {
|
|||
}
|
||||
|
||||
template <class Tr>
|
||||
std::string AnyIntView<Tr>::to_hex_string_any(bool upcase) const {
|
||||
std::string AnyIntView<Tr>::to_hex_string_any(bool upcase, int zero_pad) const {
|
||||
if (!is_valid()) {
|
||||
return "NaN";
|
||||
}
|
||||
int s = sgn(), k = 0;
|
||||
if (!s) {
|
||||
if (zero_pad > 0) {
|
||||
return std::string(zero_pad, '0');
|
||||
}
|
||||
return "0";
|
||||
}
|
||||
std::string x;
|
||||
x.reserve(((size() * word_shift + word_bits) >> 2) + 2);
|
||||
x.reserve(2 + std::max((size() * word_shift + word_bits) >> 2, zero_pad));
|
||||
assert(word_shift < word_bits - 4);
|
||||
const char* hex_digs = (upcase ? HEX_digits : hex_digits);
|
||||
word_t v = 0;
|
||||
|
@ -2317,6 +2320,11 @@ std::string AnyIntView<Tr>::to_hex_string_any(bool upcase) const {
|
|||
x += hex_digs[v & 15];
|
||||
v >>= 4;
|
||||
}
|
||||
if (zero_pad > 0) {
|
||||
while (x.size() < (unsigned)zero_pad) {
|
||||
x += '0';
|
||||
}
|
||||
}
|
||||
if (s < 0) {
|
||||
x += '-';
|
||||
}
|
||||
|
@ -2498,8 +2506,8 @@ std::string BigIntG<len, Tr>::to_hex_string_slow() const {
|
|||
}
|
||||
|
||||
template <int len, class Tr>
|
||||
std::string BigIntG<len, Tr>::to_hex_string(bool upcase) const {
|
||||
return as_any_int().to_hex_string_any(upcase);
|
||||
std::string BigIntG<len, Tr>::to_hex_string(bool upcase, int zero_pad) const {
|
||||
return as_any_int().to_hex_string_any(upcase, zero_pad);
|
||||
}
|
||||
|
||||
template <int len, class Tr>
|
||||
|
|
72
crypto/common/promiseop.hpp
Normal file
72
crypto/common/promiseop.hpp
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
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 2020 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "refcnt.hpp"
|
||||
#include "td/actor/PromiseFuture.h"
|
||||
|
||||
namespace td {
|
||||
|
||||
template <typename S, typename T>
|
||||
class BinaryPromiseMerger : public CntObject {
|
||||
Result<S> first_;
|
||||
Result<T> second_;
|
||||
Promise<std::pair<S, T>> promise_;
|
||||
std::atomic<int> pending_;
|
||||
|
||||
public:
|
||||
BinaryPromiseMerger(Promise<std::pair<S, T>> promise) : promise_(std::move(promise)), pending_(2) {
|
||||
}
|
||||
static std::pair<Promise<S>, Promise<T>> split(Promise<std::pair<S, T>> promise) {
|
||||
auto ref = make_ref<BinaryPromiseMerger>(std::move(promise));
|
||||
auto& obj = ref.write();
|
||||
return std::make_pair(obj.left(), obj.right());
|
||||
}
|
||||
|
||||
private:
|
||||
Promise<S> left() {
|
||||
return [this, self = Ref<BinaryPromiseMerger>(this)](Result<S> res) {
|
||||
first_ = std::move(res);
|
||||
work();
|
||||
};
|
||||
}
|
||||
Promise<T> right() {
|
||||
return [this, self = Ref<BinaryPromiseMerger>(this)](Result<T> res) {
|
||||
second_ = std::move(res);
|
||||
work();
|
||||
};
|
||||
}
|
||||
void work() {
|
||||
if (!--pending_) {
|
||||
if (first_.is_error()) {
|
||||
promise_.set_error(first_.move_as_error());
|
||||
} else if (second_.is_error()) {
|
||||
promise_.set_error(second_.move_as_error());
|
||||
} else {
|
||||
promise_.set_result(std::pair<S, T>(first_.move_as_ok(), second_.move_as_ok()));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename S, typename T>
|
||||
std::pair<Promise<S>, Promise<T>> split_promise(Promise<std::pair<S, T>> promise) {
|
||||
return BinaryPromiseMerger<S, T>::split(std::move(promise));
|
||||
}
|
||||
|
||||
} // namespace td
|
|
@ -287,8 +287,8 @@ std::string dec_string2(RefInt256&& x) {
|
|||
return x.is_null() ? "(null)" : (x.is_unique() ? x.unique_write().to_dec_string_destroy() : x->to_dec_string());
|
||||
}
|
||||
|
||||
std::string hex_string(RefInt256 x, bool upcase) {
|
||||
return x.is_null() ? "(null)" : x->to_hex_string(upcase);
|
||||
std::string hex_string(RefInt256 x, bool upcase, int zero_pad) {
|
||||
return x.is_null() ? "(null)" : x->to_hex_string(upcase, zero_pad);
|
||||
}
|
||||
|
||||
std::string binary_string(RefInt256 x) {
|
||||
|
|
|
@ -120,7 +120,7 @@ extern RefInt256 bits_to_refint(td::ConstBitPtr bits, int n, bool sgnd = false);
|
|||
|
||||
extern std::string dec_string(RefInt256 x);
|
||||
extern std::string dec_string2(RefInt256&& x);
|
||||
extern std::string hex_string(RefInt256 x, bool upcase = false);
|
||||
extern std::string hex_string(RefInt256 x, bool upcase = false, int zero_pad = 0);
|
||||
extern std::string binary_string(RefInt256 x);
|
||||
|
||||
extern RefInt256 dec_string_to_int256(const std::string& s);
|
||||
|
|
|
@ -36,6 +36,7 @@ library TonUtil // TON Blockchain Fift Library
|
|||
|
||||
// ( x -- ) Displays a 64-digit hex number
|
||||
{ 64 0x. } : 64x.
|
||||
{ 64 0X. } : 64X.
|
||||
// ( wc addr -- ) Show address in <workchain>:<account> form
|
||||
{ swap ._ .":" 64x. } : .addr
|
||||
// ( wc addr flags -- ) Show address in base64url form
|
||||
|
@ -205,6 +206,13 @@ recursive append-long-bytes {
|
|||
{ 256 u>B B{3ee6} swap B+ dup crc16 16 u>B B+ B>base64 } : pubkey>$
|
||||
{ pubkey>$ type } : .pubkey
|
||||
|
||||
// ( S -- x ) parse validator-encoded public key
|
||||
{ base64>B dup Blen 36 <> abort"public key with magic must be 36 bytes long"
|
||||
4 B| swap 32 B>u@ 0xC6B41348 <> abort"unknown magic for public key (not Ed25519)"
|
||||
} : parse-val-pubkey
|
||||
{ bl word parse-val-pubkey 1 'nop } ::_ VPK'
|
||||
{ char } word base64>B 1 'nop } ::_ B64{
|
||||
|
||||
// adnl address parser
|
||||
{ 256 u>B B{2D} swap B+ dup crc16 16 u>B B+ } : adnl-preconv
|
||||
{ swap 32 /mod dup 26 < { 65 } { 24 } cond + rot swap hold } : Base32#
|
||||
|
|
|
@ -62,15 +62,31 @@ variable validators-weight
|
|||
validators-weight 0!
|
||||
|
||||
{ validator-dict @ second } : validator#
|
||||
// val-pubkey weight --
|
||||
{ dup 0<= abort"validator weight must be non-negative"
|
||||
dup 64 ufits not abort"validator weight must fit into 64 bits"
|
||||
64 ufits not abort"validator weight must fit into 64 bits"
|
||||
} : check-val-weight
|
||||
// ( val-pubkey weight -- c )
|
||||
{ dup check-val-weight
|
||||
over Blen 32 <> abort"validator public key must be 32 bytes long"
|
||||
tuck <b x{538e81278a} s, rot B, swap 64 u, b> <s
|
||||
validator-dict 2@ dup 1+ 3 -roll swap
|
||||
<b x{538e81278a} s, rot B, swap 64 u, b>
|
||||
} : serialize-validator
|
||||
// ( val-pubkey adnl weight -- c )
|
||||
{ dup check-val-weight
|
||||
over 256 ufits not abort"adnl address must fit into 256 bits"
|
||||
rot dup Blen 32 <> abort"validator public key must be 32 bytes long"
|
||||
<b x{738e81278a} s, swap B, swap 64 u, swap 256 u, b>
|
||||
} : serialize-adnl-validator
|
||||
// ( weight val-cell -- )
|
||||
{ swap validators-weight +!
|
||||
<s validator-dict 2@ dup 1+ 3 -roll swap
|
||||
16 udict!+ 0= abort"cannot add validator"
|
||||
swap validator-dict 2! validators-weight +!
|
||||
} : add-validator
|
||||
swap validator-dict 2!
|
||||
} : register-validator
|
||||
// val-pubkey weight --
|
||||
{ tuck serialize-validator register-validator } : add-validator
|
||||
// val-pubkey adnl-addr weight --
|
||||
{ -rot 2 pick serialize-adnl-validator register-validator
|
||||
} : add-adnl-validator
|
||||
// since-ut until-ut main-val-cnt-or-0 --
|
||||
{ ?dup 0= { validator# } if
|
||||
validator# 0= abort"no initial validators defined"
|
||||
|
@ -230,21 +246,24 @@ variable special-dict
|
|||
} : create-wallet1
|
||||
|
||||
// D x t -- D'
|
||||
{ <b rot Gram, swap rot 16 b>idict! not abort"cannot add value"
|
||||
{ <b rot Gram, swap rot 32 b>idict! not abort"cannot add value"
|
||||
} : rdict-entry
|
||||
{ 86400 * } : days*
|
||||
// balance -- dict
|
||||
{ dictnew
|
||||
over -32768 rdict-entry
|
||||
over 3/4 */ 92 rdict-entry
|
||||
over 1/2 */ 183 rdict-entry
|
||||
swap 1/4 */ 366 rdict-entry
|
||||
0 548 rdict-entry
|
||||
over 31 -1<< rdict-entry
|
||||
over 3/4 */ 91 days* rdict-entry
|
||||
over 1/2 */ 183 days* rdict-entry
|
||||
swap 1/4 */ 365 days* rdict-entry
|
||||
0 548 days* rdict-entry
|
||||
} : make-rdict
|
||||
|
||||
variable wallet2-start-at wallet2-start-at 0!
|
||||
now 86400 / 1+ 86400 * wallet2-start-at !
|
||||
// pubkey amount --
|
||||
{ over ."Key " pubkey>$ type ." -> "
|
||||
RWCode2 // code
|
||||
<b 1 32 u, 3 pick 256 u, 3 roll make-rdict dict, b> // data
|
||||
<b 1 32 u, 3 pick 256 u, 3 roll wallet2-start-at @ 32 u, make-rdict dict, b> // data
|
||||
empty_cell // libs
|
||||
3 roll // balance
|
||||
0 // split_depth
|
||||
|
|
29
crypto/smartcont/complaint-vote-req.fif
Normal file
29
crypto/smartcont/complaint-vote-req.fif
Normal file
|
@ -0,0 +1,29 @@
|
|||
#!/usr/bin/fift -s
|
||||
"TonUtil.fif" include
|
||||
"GetOpt.fif" include
|
||||
|
||||
"validator-to-sign.req" =: savefile
|
||||
|
||||
{ ."usage: " @' $0 type ." <validator-idx> <elect-id> <complaint-hash> [<savefile>]" cr
|
||||
."Creates an unsigned request to vote for complaint <complaint-hash> (decimal; prefix with '0x' if needed) of past validator set <elect-id> on behalf of validator with zero-based index <validator-idx> in current validator set (as stored in configuration parameter 34)." cr
|
||||
."The result is saved into <savefile> (" savefile type ." by default) and output in hexadecimal form, to be signed later by the validator public key" cr 1 halt
|
||||
} : usage
|
||||
|
||||
$# dup 3 < swap 4 > or ' usage if
|
||||
4 :$1..n
|
||||
|
||||
$1 parse-int dup =: val-idx
|
||||
16 ufits not abort"validator index out of range"
|
||||
$2 parse-int dup =: elect-id
|
||||
32 ufits not abort"invalid election id"
|
||||
$3 parse-int dup =: compl-hash
|
||||
256 ufits not abort"invalid complaint hash"
|
||||
$4 savefile replace-if-null =: savefile
|
||||
|
||||
."Creating a request to vote for complaint 0x" compl-hash 64x. ."of past validator set " elect-id .
|
||||
."on behalf of current validator with index " val-idx . cr
|
||||
|
||||
B{56744350} val-idx 16 u>B B+ elect-id 32 u>B B+ compl-hash 256 u>B B+
|
||||
dup Bx. cr
|
||||
dup B>base64url type cr
|
||||
savefile tuck B>file ."Saved to file " type cr
|
44
crypto/smartcont/complaint-vote-signed.fif
Normal file
44
crypto/smartcont/complaint-vote-signed.fif
Normal file
|
@ -0,0 +1,44 @@
|
|||
#!/usr/bin/fift -s
|
||||
"TonUtil.fif" include
|
||||
"GetOpt.fif" include
|
||||
|
||||
"vote-query.boc" =: savefile
|
||||
|
||||
{ ."usage: " @' $0 type ." <validator-idx> <elect-id> <complaint-hash> <validator-pubkey> <validator-signature> [<savefile>]" cr
|
||||
."Creates an internal message body to be sent from any smart contract residing in the masterchain to the elections smart contract containing a signed request to vote for complaint <complaint-hash> (decimal; prefix with '0x' if needed) of past validator set <elect-id> on behalf of current validator with zero-based index <validator-idx> and (Base64) public key <validator-pubkey> in current validator set (as stored in configuration parameter 34)" cr
|
||||
."<validator-signature> must be the base64 representation of Ed25519 signature of the previously generated unsigned request by means of <validator-pubkey>" cr
|
||||
."The result is saved into <savefile> (" savefile type ." by default), to be embedded later into an internal message" cr 1 halt
|
||||
} : usage
|
||||
|
||||
$# dup 5 < swap 6 > or ' usage if
|
||||
6 :$1..n
|
||||
|
||||
$1 parse-int dup =: val-idx
|
||||
16 ufits not abort"validator index out of range"
|
||||
$2 parse-int dup =: elect-id
|
||||
32 ufits not abort"invalid election id"
|
||||
$3 parse-int dup =: compl-hash
|
||||
256 ufits not abort"invalid complaint hash"
|
||||
$4 base64>B dup Blen 36 <> abort"validator Ed25519 public key must be exactly 36 bytes long"
|
||||
32 B>u@+ 0xC6B41348 <> abort"invalid Ed25519 public key: unknown magic number"
|
||||
=: pubkey
|
||||
$5 base64>B dup Blen 64 <> abort"validator Ed25519 signature must be exactly 64 bytes long"
|
||||
=: signature
|
||||
$6 savefile replace-if-null =: savefile
|
||||
|
||||
."Creating the body of an internal message to be sent to the elections smart contract" cr }
|
||||
."containing a signed request to vote for complaint 0x" compl-hash 64x. ."of past validator set " elect-id .
|
||||
."on behalf of current validator with index " val-idx . "and public key" pubkey Bx. cr
|
||||
|
||||
B{56744350} val-idx 16 u>B B+ elect-id 32 u>B B+ compl-hash 256 u>B B+ dup =: to_sign
|
||||
."String to sign is " Bx. cr
|
||||
|
||||
to_sign signature pubkey ed25519_chksign not abort"Ed25519 signature is invalid"
|
||||
."Provided a valid Ed25519 signature " signature Bx. ." with validator public key " pubkey Bx. cr
|
||||
|
||||
now 32 << compl-hash 32 1<< mod + =: query-id
|
||||
|
||||
<b x{56744370} s, query-id 64 u, signature B, to_sign B, b>
|
||||
."Internal message body is " dup <s csr. cr
|
||||
|
||||
2 boc+>B savefile tuck B>file ."Saved to file " type cr
|
|
@ -1097,8 +1097,21 @@ tuple past_elections() method_id {
|
|||
do {
|
||||
(id, var fs, var found) = past_elections.udict_get_prev?(32, id);
|
||||
if (found) {
|
||||
var info = [unpack_past_election(fs)];
|
||||
list = cons(pair(id, info), list);
|
||||
list = cons([id, unpack_past_election(fs)], list);
|
||||
}
|
||||
} until (~ found);
|
||||
return list;
|
||||
}
|
||||
|
||||
tuple past_elections_list() method_id {
|
||||
var (elect, credits, past_elections, grams, active_id, active_hash) = load_data();
|
||||
var id = (1 << 32);
|
||||
var list = null();
|
||||
do {
|
||||
(id, var fs, var found) = past_elections.udict_get_prev?(32, id);
|
||||
if (found) {
|
||||
var (unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints) = unpack_past_election(fs);
|
||||
list = cons([id, unfreeze_at, vset_hash, stake_held], list);
|
||||
}
|
||||
} until (~ found);
|
||||
return list;
|
||||
|
@ -1117,7 +1130,7 @@ _ complete_unpack_complaint(slice cs) inline_ref {
|
|||
return [[complaint.begin_parse().unpack_complaint()], voters_list, vset_id, weight_remaining];
|
||||
}
|
||||
|
||||
cell get_past_complaints(int election_id) inline_ref {
|
||||
cell get_past_complaints(int election_id) inline_ref method_id {
|
||||
var (elect, credits, past_elections, grams, active_id, active_hash) = load_data();
|
||||
var (fs, found?) = past_elections.udict_get?(32, election_id);
|
||||
ifnot (found?) {
|
||||
|
@ -1145,3 +1158,11 @@ tuple list_complaints(int election_id) method_id {
|
|||
} until (~ found?);
|
||||
return list;
|
||||
}
|
||||
|
||||
int complaint_storage_price(int bits, int refs, int expire_in) method_id {
|
||||
;; compute complaint storage/creation price
|
||||
var (deposit, bit_price, cell_price) = get_complaint_prices();
|
||||
var pps = (bits + 1024) * bit_price + (refs + 2) * cell_price;
|
||||
var paid = pps * expire_in + deposit;
|
||||
return paid + (1 << 30);
|
||||
}
|
||||
|
|
50
crypto/smartcont/envelope-complaint.fif
Normal file
50
crypto/smartcont/envelope-complaint.fif
Normal file
|
@ -0,0 +1,50 @@
|
|||
#!/usr/bin/fift -s
|
||||
"TonUtil.fif" include
|
||||
"GetOpt.fif" include
|
||||
|
||||
{ show-options-help 1 halt } : usage
|
||||
86400 3 * =: expire-in
|
||||
false =: critical
|
||||
-1 =: old-hash
|
||||
|
||||
begin-options
|
||||
" <election-id> <complaint-boc> [-x <expire-in>] [<savefile>]" +cr +tab
|
||||
+"Embeds a validator complaint loaded from file <complaint-boc> into an internal message body to be sent to the elector smart contract "
|
||||
+"and saves it as an internal message body into <savefile>.boc ('complaint-msg-body.boc' by default)"
|
||||
disable-digit-options generic-help-setopt
|
||||
"x" "--expires-in" { parse-int =: expire-in } short-long-option-arg
|
||||
"Sets complaint expiration time in seconds (default " expire-in (.) $+ +")" option-help
|
||||
"h" "--help" { usage } short-long-option
|
||||
"Shows a help message" option-help
|
||||
parse-options
|
||||
|
||||
$# dup 2 < swap 3 > or ' usage if
|
||||
3 :$1..n
|
||||
|
||||
$1 parse-int dup =: election-id
|
||||
32 ufits not abort"invalid election id"
|
||||
$2 =: boc-filename
|
||||
$3 "complaint-msg-body.boc" replace-if-null =: savefile
|
||||
expire-in now + =: expire-at
|
||||
|
||||
boc-filename dup ."Loading complaint from file `" type ."`" cr
|
||||
file>B B>boc dup =: complaint hash =: c-hash
|
||||
complaint <s 8 u@ 0xbc <> abort"Not a valid ValidatorComplaint"
|
||||
complaint <s csr.
|
||||
|
||||
." complaint envelope will expire at " expire-at . ."(in " expire-in . ."seconds)" cr
|
||||
now 32 << c-hash 0xffffffff and or =: query-id
|
||||
."Query id is " query-id . cr
|
||||
|
||||
<b x{52674370} s, query-id 64 u, election-id 32 u, expire-at 32 u,
|
||||
complaint <s s, b>
|
||||
|
||||
dup ."resulting internal message body: " <s csr. cr
|
||||
2 boc+>B dup Bx. cr
|
||||
|
||||
complaint
|
||||
totalcsize swap ."(a total of " . ."data bits, " . ."cell references -> "
|
||||
drop dup Blen . ."BoC data bytes)" cr
|
||||
|
||||
savefile tuck B>file
|
||||
."(Saved to file " type .")" cr
|
|
@ -29,8 +29,8 @@ def? $3 { @' $3 } { "rwallet" } cond constant file-base
|
|||
} : make-rdict
|
||||
|
||||
// Create new restricted wallet; code taken from `auto/restricted-wallet2-code.fif`
|
||||
"auto/restricted-wallet-code.fif" include // code
|
||||
<b 0 32 u, PubKey 256 u, amount make-rdict dict, b> // data
|
||||
"auto/restricted-wallet2-code.fif" include // code
|
||||
<b 0 32 u, PubKey 256 u, 0 32 u, amount make-rdict dict, b> // data
|
||||
null // no libraries
|
||||
<b b{0011} s, 3 roll ref, rot ref, swap dict, b> // create StateInit
|
||||
dup ."StateInit: " <s csr. cr
|
||||
|
|
315
crypto/smartcont/payment-channel-code.fc
Normal file
315
crypto/smartcont/payment-channel-code.fc
Normal file
|
@ -0,0 +1,315 @@
|
|||
;; WARINIG: NOT READY FOR A PRODUCTION!
|
||||
|
||||
int err:wrong_a_signature() asm "31 PUSHINT";
|
||||
int err:wrong_b_signature() asm "32 PUSHINT";
|
||||
int err:msg_value_too_small() asm "33 PUSHINT";
|
||||
int err:replay_protection() asm "34 PUSHINT";
|
||||
int err:no_timeout() asm "35 PUSHINT";
|
||||
int err:expected_init() asm "36 PUSHINT";
|
||||
int err:expected_close() asm "37 PUSHINT";
|
||||
int err:no_promise_signature() asm "38 PUSHINT";
|
||||
int err:wrong_channel_id() asm "39 PUSHINT";
|
||||
|
||||
int msg:init() asm "0x27317822 PUSHINT";
|
||||
int msg:close() asm "0xf28ae183 PUSHINT";
|
||||
int msg:timeout() asm "0x43278a28 PUSHINT";
|
||||
int msg:payout() asm "0x37fe7810 PUSHINT";
|
||||
|
||||
int state:init() asm "0 PUSHINT";
|
||||
int state:close() asm "1 PUSHINT";
|
||||
int state:payout() asm "2 PUSHINT";
|
||||
|
||||
|
||||
;; A - initial balance of Alice,
|
||||
;; B - initial balance of B
|
||||
;;
|
||||
;; To determine balance we track nondecreasing list of promises
|
||||
;; promise_A ;; promised by Alice to Bob
|
||||
;; promise_B ;; promised by Bob to Alice
|
||||
;;
|
||||
;; diff - balance between Alice and Bob. 0 in the beginning
|
||||
;; diff = promise_B - promise_A;
|
||||
;; diff = clamp(diff, -A, +B);
|
||||
;;
|
||||
;; final_A = A + diff;
|
||||
;; final_B = B + diff;
|
||||
|
||||
;; Data pack/unpack
|
||||
;;
|
||||
_ unpack_data() inline_ref {
|
||||
var cs = get_data().begin_parse();
|
||||
var res = (cs~load_ref(), cs~load_ref());
|
||||
cs.end_parse();
|
||||
return res;
|
||||
}
|
||||
|
||||
_ pack_data(cell config, cell state) impure inline_ref {
|
||||
set_data(begin_cell().store_ref(config).store_ref(state).end_cell());
|
||||
}
|
||||
|
||||
;; Config pack/unpack
|
||||
;;
|
||||
;; config$_ initTimeout:int exitTimeout:int a_key:int256 b_key:int256 a_addr b_addr channel_id:uint64 = Config;
|
||||
;;
|
||||
_ unpack_config(cell config) {
|
||||
var cs = config.begin_parse();
|
||||
var res = (
|
||||
cs~load_uint(32),
|
||||
cs~load_uint(32),
|
||||
cs~load_uint(256),
|
||||
cs~load_uint(256),
|
||||
cs~load_ref().begin_parse(),
|
||||
cs~load_ref().begin_parse(),
|
||||
cs~load_uint(64));
|
||||
cs.end_parse();
|
||||
return res;
|
||||
}
|
||||
|
||||
;; takes
|
||||
;; signedMesage$_ a_sig:Maybe<int256> b_sig:Maybe<int256> msg:Message = SignedMessage;
|
||||
;; checks signatures and unwap message.
|
||||
(slice, (int, int)) unwrap_signatures(slice cs, int a_key, int b_key) {
|
||||
int a? = cs~load_int(1);
|
||||
slice a_sig = cs;
|
||||
if (a?) {
|
||||
a_sig = cs~load_ref().begin_parse().preload_bits(512);
|
||||
}
|
||||
var b? = cs~load_int(1);
|
||||
slice b_sig = cs;
|
||||
if (b?) {
|
||||
b_sig = cs~load_ref().begin_parse().preload_bits(512);
|
||||
}
|
||||
int hash = cs.slice_hash();
|
||||
if (a?) {
|
||||
throw_unless(err:wrong_a_signature(), check_signature(hash, a_sig, a_key));
|
||||
}
|
||||
if (b?) {
|
||||
throw_unless(err:wrong_b_signature(), check_signature(hash, b_sig, b_key));
|
||||
}
|
||||
return (cs, (a?, b?));
|
||||
}
|
||||
|
||||
;; process message, give state is stateInit
|
||||
;;
|
||||
;; stateInit signed_A?:Bool signed_B?:Bool min_A:Grams min_B:Grams expire_at:uint32 A:Grams B:Grams = State;
|
||||
_ unpack_state_init(slice state) {
|
||||
return (
|
||||
state~load_int(1),
|
||||
state~load_int(1),
|
||||
state~load_grams(),
|
||||
state~load_grams(),
|
||||
state~load_uint(32),
|
||||
state~load_grams(),
|
||||
state~load_grams());
|
||||
|
||||
}
|
||||
_ pack_state_init(int signed_A?, int signed_B?, int min_A, int min_B, int expire_at, int A, int B) {
|
||||
return begin_cell()
|
||||
.store_int(state:init(), 3)
|
||||
.store_int(signed_A?, 1)
|
||||
.store_int(signed_B?, 1)
|
||||
.store_grams(min_A)
|
||||
.store_grams(min_B)
|
||||
.store_uint(expire_at, 32)
|
||||
.store_grams(A)
|
||||
.store_grams(B).end_cell();
|
||||
}
|
||||
|
||||
;; stateClosing$10 signed_A?:bool signed_B?:Bool promise_A:Grams promise_B:Grams exipire_at:uint32 A:Grams B:Grams = State;
|
||||
_ unpack_state_close(slice state) {
|
||||
return (
|
||||
state~load_int(1),
|
||||
state~load_int(1),
|
||||
state~load_grams(),
|
||||
state~load_grams(),
|
||||
state~load_uint(32),
|
||||
state~load_grams(),
|
||||
state~load_grams());
|
||||
}
|
||||
|
||||
_ pack_state_close(int signed_A?, int signed_B?, int promise_A, int promise_B, int expire_at, int A, int B) {
|
||||
return begin_cell()
|
||||
.store_int(state:close(), 3)
|
||||
.store_int(signed_A?, 1)
|
||||
.store_int(signed_B?, 1)
|
||||
.store_grams(promise_A)
|
||||
.store_grams(promise_B)
|
||||
.store_uint(expire_at, 32)
|
||||
.store_grams(A)
|
||||
.store_grams(B).end_cell();
|
||||
}
|
||||
|
||||
_ send_payout(slice s_addr, int amount, int channel_id, int flags) impure {
|
||||
send_raw_message(begin_cell()
|
||||
.store_uint(0x10, 6)
|
||||
.store_slice(s_addr)
|
||||
.store_grams(amount)
|
||||
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
|
||||
.store_uint(msg:payout(), 32)
|
||||
.store_uint(channel_id, 64)
|
||||
.end_cell(), flags);
|
||||
}
|
||||
|
||||
|
||||
cell do_payout(int promise_A, int promise_B, int A, int B, slice a_addr, slice b_addr, int channel_id) impure {
|
||||
accept_message();
|
||||
|
||||
int diff = promise_B - promise_A;
|
||||
if (diff < - A) {
|
||||
diff = - A;
|
||||
}
|
||||
if (diff > B) {
|
||||
diff = B;
|
||||
}
|
||||
A += diff;
|
||||
B -= diff;
|
||||
|
||||
send_payout(a_addr, A, channel_id, 3);
|
||||
send_payout(b_addr, B, channel_id, 3);
|
||||
|
||||
return begin_cell()
|
||||
.store_int(state:payout(), 3)
|
||||
.store_grams(A)
|
||||
.store_grams(B)
|
||||
.end_cell();
|
||||
}
|
||||
|
||||
|
||||
;;
|
||||
;; init$000 inc_A:Grams inc_B:Grams min_A:Grams min_B:Grams = Message;
|
||||
;;
|
||||
cell with_init(slice state, int msg_value, slice msg, int msg_signed_A?, int msg_signed_B?,
|
||||
slice a_addr, slice b_addr, int init_timeout, int channel_id) {
|
||||
;; parse state
|
||||
(int signed_A?, int signed_B?, int min_A, int min_B, int expire_at, int A, int B) = unpack_state_init(state);
|
||||
|
||||
if (expire_at == 0) {
|
||||
expire_at = now() + init_timeout;
|
||||
}
|
||||
|
||||
int op = msg~load_uint(32);
|
||||
if (op == msg:timeout()) {
|
||||
throw_unless(err:no_timeout(), expire_at < now());
|
||||
return do_payout(0, 0, A, B, a_addr, b_addr, channel_id);
|
||||
}
|
||||
throw_unless(err:expected_init(), op == msg:init());
|
||||
|
||||
;; unpack init message
|
||||
(int inc_A, int inc_B, int upd_min_A, int upd_min_B, int got_channel_id) =
|
||||
(msg~load_grams(), msg~load_grams(), msg~load_grams(), msg~load_grams(), msg~load_uint(64));
|
||||
throw_unless(err:wrong_channel_id(), got_channel_id == channel_id);
|
||||
|
||||
;; TODO: we should reserve some part of the value for comission
|
||||
throw_if(err:msg_value_too_small(), msg_value < inc_A + inc_B);
|
||||
throw_unless(err:replay_protection(), (msg_signed_A? < signed_A?) | (msg_signed_B? < signed_B?));
|
||||
|
||||
A += inc_A;
|
||||
B += inc_B;
|
||||
|
||||
signed_A? |= msg_signed_A?;
|
||||
if (min_A < upd_min_A) {
|
||||
min_A = upd_min_A;
|
||||
}
|
||||
|
||||
signed_B? |= msg_signed_B?;
|
||||
if (min_B < upd_min_B) {
|
||||
min_B = upd_min_B;
|
||||
}
|
||||
|
||||
if (signed_A? & signed_B?) {
|
||||
if ((min_A > A) | (min_B > B)) {
|
||||
return do_payout(0, 0, A, B, a_addr, b_addr, channel_id);
|
||||
}
|
||||
|
||||
return pack_state_close(0, 0, 0, 0, 0, A, B);
|
||||
}
|
||||
|
||||
return pack_state_init(signed_A?, signed_B?, min_A, min_B, expire_at, A, B);
|
||||
}
|
||||
|
||||
;; close$001 extra_A:Grams extra_B:Grams sig:Maybe<int256> promise_A:Grams promise_B:Grams = Message;
|
||||
|
||||
cell with_close(slice cs, slice msg, int msg_signed_A?, int msg_signed_B?, int a_key, int b_key,
|
||||
slice a_addr, slice b_addr, int expire_timeout, int channel_id) {
|
||||
;; parse state
|
||||
(int signed_A?, int signed_B?, int promise_A, int promise_B, int expire_at, int A, int B) = unpack_state_close(cs);
|
||||
|
||||
if (expire_at == 0) {
|
||||
expire_at = now() + expire_timeout;
|
||||
}
|
||||
|
||||
int op = msg~load_uint(32);
|
||||
if (op == msg:timeout()) {
|
||||
throw_unless(err:no_timeout(), expire_at < now());
|
||||
return do_payout(promise_A, promise_B, A, B, a_addr, b_addr, channel_id);
|
||||
}
|
||||
throw_unless(err:expected_close(), op == msg:close());
|
||||
|
||||
;; also ensures that (msg_signed_A? | msg_signed_B?) is true
|
||||
throw_unless(err:replay_protection(), (msg_signed_A? < signed_A?) | (msg_signed_B? < signed_B?));
|
||||
signed_A? |= msg_signed_A?;
|
||||
signed_B? |= msg_signed_B?;
|
||||
|
||||
;; unpack close message
|
||||
(int extra_A, int extra_B) = (msg~load_grams(), msg~load_grams());
|
||||
int has_sig = msg~load_int(1);
|
||||
if (has_sig) {
|
||||
slice sig = msg~load_ref().begin_parse().preload_bits(512);
|
||||
int hash = msg.slice_hash();
|
||||
ifnot (msg_signed_A?) {
|
||||
throw_unless(err:wrong_a_signature(), check_signature(hash, sig, a_key));
|
||||
extra_A = 0;
|
||||
}
|
||||
ifnot (msg_signed_B?) {
|
||||
throw_unless(err:wrong_b_signature(), check_signature(hash, sig, b_key));
|
||||
extra_B = 0;
|
||||
}
|
||||
} else {
|
||||
throw_unless(err:no_promise_signature(), msg_signed_A? & msg_signed_B?);
|
||||
extra_A = 0;
|
||||
extra_B = 0;
|
||||
}
|
||||
(int got_channel_id, int update_promise_A, int update_promise_B) = (msg~load_uint(64), msg~load_grams(), msg~load_grams());
|
||||
throw_unless(err:wrong_channel_id(), got_channel_id == channel_id);
|
||||
|
||||
|
||||
accept_message();
|
||||
update_promise_A += extra_A;
|
||||
if (promise_A < update_promise_A) {
|
||||
promise_A = update_promise_A;
|
||||
}
|
||||
update_promise_B += extra_B;
|
||||
if (promise_B < update_promise_B) {
|
||||
promise_B = update_promise_B;
|
||||
}
|
||||
|
||||
if (signed_A? & signed_B?) {
|
||||
return do_payout(promise_A, promise_B, A, B, a_addr, b_addr, channel_id);
|
||||
}
|
||||
return pack_state_close(signed_A?, signed_B?, promise_A, promise_B, expire_at, A, B);
|
||||
}
|
||||
|
||||
() recv_any(int msg_value, slice msg) impure {
|
||||
(cell config, cell state) = unpack_data();
|
||||
(int init_timeout, int close_timeout, int a_key, int b_key, slice a_addr, slice b_addr, int channel_id) = config.unpack_config();
|
||||
(int msg_signed_A?, int msg_signed_B?) = msg~unwrap_signatures(a_key, b_key);
|
||||
|
||||
slice cs = state.begin_parse();
|
||||
int state_type = cs~load_uint(3);
|
||||
|
||||
if (state_type == state:init()) { ;; init
|
||||
state = with_init(cs, msg_value, msg, msg_signed_A?, msg_signed_B?, a_addr, b_addr, init_timeout, channel_id);
|
||||
} if (state_type == state:close()) {
|
||||
state = with_close(cs, msg, msg_signed_A?, msg_signed_B?, a_key, b_key, a_addr, b_addr, close_timeout, channel_id);
|
||||
}
|
||||
|
||||
pack_data(config, state);
|
||||
}
|
||||
|
||||
() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure {
|
||||
recv_any(msg_value, in_msg);
|
||||
}
|
||||
|
||||
() recv_external(slice in_msg) impure {
|
||||
recv_any(0, in_msg);
|
||||
}
|
|
@ -1,13 +1,16 @@
|
|||
;; Restricted wallet (a variant of wallet-code.fc)
|
||||
;; until configuration parameter -13 is set, accepts messages only to elector smc
|
||||
;; restricts access to parts of balance until certain dates
|
||||
|
||||
() recv_internal(slice in_msg) impure {
|
||||
;; do nothing for internal messages
|
||||
}
|
||||
|
||||
_ days_passed() inline {
|
||||
var p = config_param(-13);
|
||||
return null?(p) ? -1 : (now() - begin_parse(p).preload_uint(32)) / 86400;
|
||||
_ seconds_passed(int start_at, int utime) inline_ref {
|
||||
ifnot (start_at) {
|
||||
var p = config_param(-13);
|
||||
start_at = null?(p) ? 0 : begin_parse(p).preload_uint(32);
|
||||
}
|
||||
return start_at ? utime - start_at : -1;
|
||||
}
|
||||
|
||||
() recv_external(slice in_msg) impure {
|
||||
|
@ -16,7 +19,7 @@ _ days_passed() inline {
|
|||
var (msg_seqno, valid_until) = (cs~load_uint(32), cs~load_uint(32));
|
||||
throw_if(35, valid_until <= now());
|
||||
var ds = get_data().begin_parse();
|
||||
var (stored_seqno, public_key, rdict) = (ds~load_uint(32), ds~load_uint(256), ds~load_dict());
|
||||
var (stored_seqno, public_key, start_at, rdict) = (ds~load_uint(32), ds~load_uint(256), ds~load_uint(32), ds~load_dict());
|
||||
ds.end_parse();
|
||||
throw_unless(33, msg_seqno == stored_seqno);
|
||||
ifnot (msg_seqno) {
|
||||
|
@ -24,14 +27,15 @@ _ days_passed() inline {
|
|||
set_data(begin_cell()
|
||||
.store_uint(stored_seqno + 1, 32)
|
||||
.store_uint(public_key, 256)
|
||||
.store_uint(start_at, 32)
|
||||
.store_dict(rdict)
|
||||
.end_cell());
|
||||
return ();
|
||||
}
|
||||
throw_unless(34, check_signature(slice_hash(in_msg), signature, public_key));
|
||||
accept_message();
|
||||
var ts = days_passed();
|
||||
var (_, value, found) = rdict.idict_get_preveq?(16, ts);
|
||||
var ts = seconds_passed(start_at, now());
|
||||
var (_, value, found) = rdict.idict_get_preveq?(32, ts);
|
||||
if (found) {
|
||||
raw_reserve(value~load_grams(), 2);
|
||||
}
|
||||
|
@ -45,6 +49,7 @@ _ days_passed() inline {
|
|||
set_data(begin_cell()
|
||||
.store_uint(stored_seqno + 1, 32)
|
||||
.store_uint(public_key, 256)
|
||||
.store_uint(start_at, 32)
|
||||
.store_dict(rdict)
|
||||
.end_cell());
|
||||
}
|
||||
|
@ -61,15 +66,23 @@ int get_public_key() method_id {
|
|||
return cs.preload_uint(256);
|
||||
}
|
||||
|
||||
int balance() method_id {
|
||||
int compute_balance_at(int utime) inline_ref {
|
||||
var ds = get_data().begin_parse().skip_bits(32 + 256);
|
||||
var rdict = ds~load_dict();
|
||||
var (start_at, rdict) = (ds~load_uint(32), ds~load_dict());
|
||||
ds.end_parse();
|
||||
var ts = days_passed();
|
||||
var ts = seconds_passed(start_at, utime);
|
||||
var balance = get_balance().pair_first();
|
||||
var (_, value, found) = rdict.idict_get_preveq?(16, ts);
|
||||
var (_, value, found) = rdict.idict_get_preveq?(32, ts);
|
||||
if (found) {
|
||||
balance = max(balance - value~load_grams(), 0);
|
||||
}
|
||||
return balance;
|
||||
}
|
||||
|
||||
int balance_at(int utime) method_id {
|
||||
return compute_balance_at(utime);
|
||||
}
|
||||
|
||||
int balance() method_id {
|
||||
return compute_balance_at(now());
|
||||
}
|
||||
|
|
103
crypto/smartcont/restricted-wallet3-code.fc
Normal file
103
crypto/smartcont/restricted-wallet3-code.fc
Normal file
|
@ -0,0 +1,103 @@
|
|||
;; Restricted wallet initialized by a third party (a variant of restricted-wallet2-code.fc)
|
||||
;; restricts access to parts of balance until certain dates
|
||||
|
||||
() recv_internal(slice in_msg) impure {
|
||||
;; do nothing for internal messages
|
||||
}
|
||||
|
||||
_ seconds_passed(int start_at, int utime) inline_ref {
|
||||
ifnot (start_at) {
|
||||
var p = config_param(-13);
|
||||
start_at = null?(p) ? 0 : begin_parse(p).preload_uint(32);
|
||||
}
|
||||
return start_at ? utime - start_at : -1;
|
||||
}
|
||||
|
||||
() recv_external(slice in_msg) impure {
|
||||
var signature = in_msg~load_bits(512);
|
||||
var cs = in_msg;
|
||||
var (subwallet_id, valid_until, msg_seqno) = (cs~load_uint(32), cs~load_uint(32), cs~load_uint(32));
|
||||
throw_if(35, valid_until <= now());
|
||||
var ds = get_data().begin_parse();
|
||||
var (stored_seqno, stored_subwallet, public_key) = (ds~load_uint(32), ds~load_uint(32), ds~load_uint(256));
|
||||
throw_unless(33, msg_seqno == stored_seqno);
|
||||
throw_unless(34, subwallet_id == stored_subwallet);
|
||||
throw_unless(36, check_signature(slice_hash(in_msg), signature, public_key));
|
||||
ifnot (msg_seqno) {
|
||||
public_key = ds~load_uint(256); ;; load "final" public key
|
||||
ds.end_parse();
|
||||
cs~touch();
|
||||
var (start_at, rdict) = (cs~load_uint(32), cs~load_dict());
|
||||
cs.end_parse();
|
||||
accept_message();
|
||||
set_data(begin_cell()
|
||||
.store_uint(stored_seqno + 1, 32)
|
||||
.store_uint(stored_subwallet, 32)
|
||||
.store_uint(public_key, 256)
|
||||
.store_uint(start_at, 32)
|
||||
.store_dict(rdict)
|
||||
.end_cell());
|
||||
return ();
|
||||
}
|
||||
var (start_at, rdict) = (ds~load_uint(32), ds~load_dict());
|
||||
ds.end_parse();
|
||||
accept_message();
|
||||
var ts = seconds_passed(start_at, now());
|
||||
var (_, value, found) = rdict.idict_get_preveq?(32, ts);
|
||||
if (found) {
|
||||
raw_reserve(value~load_grams(), 2);
|
||||
}
|
||||
cs~touch();
|
||||
while (cs.slice_refs()) {
|
||||
var mode = cs~load_uint(8);
|
||||
var msg = cs~load_ref();
|
||||
send_raw_message(msg, mode);
|
||||
}
|
||||
cs.end_parse();
|
||||
set_data(begin_cell()
|
||||
.store_uint(stored_seqno + 1, 32)
|
||||
.store_uint(stored_subwallet, 32)
|
||||
.store_uint(public_key, 256)
|
||||
.store_uint(start_at, 32)
|
||||
.store_dict(rdict)
|
||||
.end_cell());
|
||||
}
|
||||
|
||||
;; Get methods
|
||||
|
||||
int seqno() method_id {
|
||||
return get_data().begin_parse().preload_uint(32);
|
||||
}
|
||||
|
||||
int wallet_id() method_id {
|
||||
var ds = get_data().begin_parse();
|
||||
ds~load_uint(32);
|
||||
return ds.preload_uint(32);
|
||||
}
|
||||
|
||||
int get_public_key() method_id {
|
||||
var ds = get_data().begin_parse();
|
||||
ds~load_uint(32 + 32);
|
||||
return ds.preload_uint(256);
|
||||
}
|
||||
|
||||
int compute_balance_at(int utime) inline_ref {
|
||||
var ds = get_data().begin_parse().skip_bits(32 + 32 + 256);
|
||||
var (start_at, rdict) = (ds~load_uint(32), ds~load_dict());
|
||||
ds.end_parse();
|
||||
var ts = seconds_passed(start_at, utime);
|
||||
var balance = get_balance().pair_first();
|
||||
var (_, value, found) = rdict.idict_get_preveq?(32, ts);
|
||||
if (found) {
|
||||
balance = max(balance - value~load_grams(), 0);
|
||||
}
|
||||
return balance;
|
||||
}
|
||||
|
||||
int balance_at(int utime) method_id {
|
||||
return compute_balance_at(utime);
|
||||
}
|
||||
|
||||
int balance() method_id {
|
||||
return compute_balance_at(now());
|
||||
}
|
|
@ -18,5 +18,7 @@ dup Blen { 32 B>i@ } { drop Basechain } cond constant wallet_wc
|
|||
|
||||
file-base +".pk" dup file-exists? {
|
||||
dup file>B dup Blen 32 <> abort"Private key must be exactly 32 bytes long"
|
||||
=: wallet_pk ."Private key available in file " type cr
|
||||
tuck =: wallet_pk ."Private key available in file " type cr
|
||||
priv>pub 256 B>u@
|
||||
dup ."Corresponding public key is " .pubkey ." = " 64X. cr
|
||||
} { ."Private key file " type ." not found" cr } cond
|
||||
|
|
|
@ -21,6 +21,31 @@
|
|||
#include "block/block-auto.h"
|
||||
#include "block/block-parse.h"
|
||||
namespace ton {
|
||||
|
||||
namespace smc {
|
||||
td::Ref<vm::CellSlice> pack_grams(td::uint64 amount) {
|
||||
vm::CellBuilder cb;
|
||||
block::tlb::t_Grams.store_integer_value(cb, td::BigInt256(amount));
|
||||
return vm::load_cell_slice_ref(cb.finalize());
|
||||
}
|
||||
|
||||
bool unpack_grams(td::Ref<vm::CellSlice> cs, td::uint64& amount) {
|
||||
td::RefInt256 got;
|
||||
if (!block::tlb::t_Grams.as_integer_to(cs, got)) {
|
||||
return false;
|
||||
}
|
||||
if (!got->unsigned_fits_bits(63)) {
|
||||
return false;
|
||||
}
|
||||
auto x = got->to_long();
|
||||
if (x < 0) {
|
||||
return false;
|
||||
}
|
||||
amount = x;
|
||||
return true;
|
||||
}
|
||||
} // namespace smc
|
||||
|
||||
td::Ref<vm::Cell> GenericAccount::get_init_state(td::Ref<vm::Cell> code, td::Ref<vm::Cell> data) noexcept {
|
||||
return vm::CellBuilder()
|
||||
.store_zeroes(2)
|
||||
|
@ -47,7 +72,7 @@ void GenericAccount::store_int_message(vm::CellBuilder& cb, const block::StdAddr
|
|||
.store_long(dest_address.workchain, 8)
|
||||
.store_int256(dest_addr, 256);
|
||||
block::tlb::t_Grams.store_integer_value(cb, td::BigInt256(gramms));
|
||||
cb.store_zeroes(9 + 64 + 32 + 1 + 1);
|
||||
cb.store_zeroes(9 + 64 + 32);
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> GenericAccount::create_ext_message(const block::StdAddress& address, td::Ref<vm::Cell> new_state,
|
||||
|
|
|
@ -23,6 +23,10 @@
|
|||
#include "SmartContract.h"
|
||||
|
||||
namespace ton {
|
||||
namespace smc {
|
||||
td::Ref<vm::CellSlice> pack_grams(td::uint64 amount);
|
||||
bool unpack_grams(td::Ref<vm::CellSlice> cs, td::uint64& amount);
|
||||
} // namespace smc
|
||||
class GenericAccount {
|
||||
public:
|
||||
static td::Ref<vm::Cell> get_init_state(td::Ref<vm::Cell> code, td::Ref<vm::Cell> data) noexcept;
|
||||
|
|
|
@ -73,17 +73,11 @@ td::Ref<vm::Cell> HighloadWallet::make_a_gift_message(const td::Ed25519::Private
|
|||
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;
|
||||
if (gift.gramms == -1) {
|
||||
send_mode += 128;
|
||||
}
|
||||
auto message_inner = create_int_message(gift);
|
||||
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);
|
||||
|
|
|
@ -73,18 +73,11 @@ td::Ref<vm::Cell> HighloadWalletV2::make_a_gift_message(const td::Ed25519::Priva
|
|||
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;
|
||||
if (gift.gramms == -1) {
|
||||
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);
|
||||
cb.store_long(send_mode, 8).store_ref(create_int_message(gift));
|
||||
auto key = messages.integer_key(td::make_refint(i), 16, false);
|
||||
messages.set_builder(key.bits(), 16, cb);
|
||||
}
|
||||
|
|
264
crypto/smc-envelope/PaymentChannel.cpp
Normal file
264
crypto/smc-envelope/PaymentChannel.cpp
Normal file
|
@ -0,0 +1,264 @@
|
|||
#include "PaymentChannel.h"
|
||||
#include "GenericAccount.h"
|
||||
#include "vm/cells.h"
|
||||
#include "vm/cellslice.h"
|
||||
#include "Ed25519.h"
|
||||
#include "block/block-auto.h"
|
||||
#include "block/block-parse.h"
|
||||
|
||||
#include "SmartContract.h"
|
||||
#include "SmartContractCode.h"
|
||||
|
||||
namespace ton {
|
||||
using smc::pack_grams;
|
||||
using smc::unpack_grams;
|
||||
namespace pchan {
|
||||
|
||||
td::Ref<vm::Cell> Config::serialize() const {
|
||||
block::gen::ChanConfig::Record rec;
|
||||
|
||||
vm::CellBuilder a_addr_cb;
|
||||
block::tlb::t_MsgAddressInt.store_std_address(a_addr_cb, a_addr);
|
||||
rec.a_addr = a_addr_cb.finalize_novm();
|
||||
|
||||
vm::CellBuilder b_addr_cb;
|
||||
block::tlb::t_MsgAddressInt.store_std_address(b_addr_cb, b_addr);
|
||||
rec.b_addr = b_addr_cb.finalize_novm();
|
||||
|
||||
rec.a_key.as_slice().copy_from(a_key);
|
||||
rec.b_key.as_slice().copy_from(b_key);
|
||||
rec.init_timeout = init_timeout;
|
||||
rec.close_timeout = close_timeout;
|
||||
rec.channel_id = channel_id;
|
||||
|
||||
td::Ref<vm::Cell> res;
|
||||
CHECK(tlb::pack_cell(res, rec));
|
||||
return res;
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> MsgInit::serialize() const {
|
||||
block::gen::ChanMsg::Record_chan_msg_init rec;
|
||||
rec.min_A = pack_grams(min_A);
|
||||
rec.min_B = pack_grams(min_B);
|
||||
rec.inc_A = pack_grams(inc_A);
|
||||
rec.inc_B = pack_grams(inc_B);
|
||||
rec.channel_id = channel_id;
|
||||
|
||||
td::Ref<vm::Cell> res;
|
||||
CHECK(tlb::pack_cell(res, rec));
|
||||
return res;
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> Promise::serialize() const {
|
||||
block::gen::ChanPromise::Record rec;
|
||||
rec.channel_id = channel_id;
|
||||
rec.promise_A = pack_grams(promise_A);
|
||||
rec.promise_B = pack_grams(promise_B);
|
||||
td::Ref<vm::Cell> res;
|
||||
CHECK(tlb::pack_cell(res, rec));
|
||||
return res;
|
||||
}
|
||||
|
||||
td::SecureString sign(const td::Ref<vm::Cell>& msg, const td::Ed25519::PrivateKey* key) {
|
||||
return key->sign(msg->get_hash().as_slice()).move_as_ok();
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> maybe_sign(const td::Ref<vm::Cell>& msg, const td::Ed25519::PrivateKey* key) {
|
||||
if (!key) {
|
||||
return {};
|
||||
}
|
||||
return vm::CellBuilder().store_bytes(sign(msg, key).as_slice()).finalize();
|
||||
}
|
||||
|
||||
td::Ref<vm::CellSlice> maybe_ref(td::Ref<vm::Cell> msg) {
|
||||
vm::CellBuilder cb;
|
||||
CHECK(cb.store_maybe_ref(msg));
|
||||
return vm::load_cell_slice_ref(cb.finalize());
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> MsgClose::serialize() const {
|
||||
block::gen::ChanMsg::Record_chan_msg_close rec;
|
||||
rec.extra_A = pack_grams(extra_A);
|
||||
rec.extra_B = pack_grams(extra_B);
|
||||
rec.promise = signed_promise;
|
||||
|
||||
td::Ref<vm::Cell> res;
|
||||
CHECK(tlb::pack_cell(res, rec));
|
||||
return res;
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> MsgTimeout::serialize() const {
|
||||
block::gen::ChanMsg::Record_chan_msg_timeout rec;
|
||||
td::Ref<vm::Cell> res;
|
||||
CHECK(tlb::pack_cell(res, rec));
|
||||
return res;
|
||||
}
|
||||
|
||||
td::SecureString SignedPromise::signature(const td::Ed25519::PrivateKey* key, const td::Ref<vm::Cell>& promise) {
|
||||
return sign(promise, key);
|
||||
}
|
||||
td::Ref<vm::Cell> SignedPromise::create_and_serialize(td::Slice signature, const td::Ref<vm::Cell>& promise) {
|
||||
block::gen::ChanSignedPromise::Record rec;
|
||||
rec.promise = vm::load_cell_slice_ref(promise);
|
||||
LOG(ERROR) << "signature.size() = " << signature.size();
|
||||
rec.sig = maybe_ref(vm::CellBuilder().store_bytes(signature).finalize());
|
||||
td::Ref<vm::Cell> res;
|
||||
CHECK(tlb::pack_cell(res, rec));
|
||||
return res;
|
||||
}
|
||||
td::Ref<vm::Cell> SignedPromise::create_and_serialize(const td::Ed25519::PrivateKey* key,
|
||||
const td::Ref<vm::Cell>& promise) {
|
||||
block::gen::ChanSignedPromise::Record rec;
|
||||
rec.promise = vm::load_cell_slice_ref(promise);
|
||||
rec.sig = maybe_ref(maybe_sign(promise, key));
|
||||
td::Ref<vm::Cell> res;
|
||||
CHECK(tlb::pack_cell(res, rec));
|
||||
return res;
|
||||
}
|
||||
|
||||
bool SignedPromise::unpack(td::Ref<vm::Cell> cell) {
|
||||
block::gen::ChanSignedPromise::Record rec;
|
||||
if (!tlb::unpack_cell(cell, rec)) {
|
||||
return false;
|
||||
}
|
||||
block::gen::ChanPromise::Record rec_promise;
|
||||
if (!tlb::csr_unpack(rec.promise, rec_promise)) {
|
||||
return false;
|
||||
}
|
||||
promise.channel_id = rec_promise.channel_id;
|
||||
if (!unpack_grams(rec_promise.promise_A, promise.promise_A)) {
|
||||
return false;
|
||||
}
|
||||
if (!unpack_grams(rec_promise.promise_B, promise.promise_B)) {
|
||||
return false;
|
||||
}
|
||||
td::Ref<vm::Cell> sig_cell;
|
||||
if (!rec.sig->prefetch_maybe_ref(sig_cell)) {
|
||||
return false;
|
||||
}
|
||||
td::SecureString signature(64);
|
||||
vm::CellSlice cs = vm::load_cell_slice(sig_cell);
|
||||
if (!cs.prefetch_bytes(signature.as_mutable_slice())) {
|
||||
return false;
|
||||
}
|
||||
o_signature = std::move(signature);
|
||||
return true;
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> StateInit::serialize() const {
|
||||
block::gen::ChanState::Record_chan_state_init rec;
|
||||
rec.expire_at = expire_at;
|
||||
rec.min_A = pack_grams(min_A);
|
||||
rec.min_B = pack_grams(min_B);
|
||||
rec.A = pack_grams(A);
|
||||
rec.B = pack_grams(B);
|
||||
rec.signed_A = signed_A;
|
||||
rec.signed_B = signed_B;
|
||||
td::Ref<vm::Cell> res;
|
||||
CHECK(tlb::pack_cell(res, rec));
|
||||
return res;
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> Data::serialize() const {
|
||||
block::gen::ChanData::Record rec;
|
||||
rec.config = config;
|
||||
rec.state = state;
|
||||
td::Ref<vm::Cell> res;
|
||||
CHECK(block::gen::t_ChanData.cell_pack(res, rec));
|
||||
return res;
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> Data::init_state() {
|
||||
return StateInit().serialize();
|
||||
}
|
||||
} // namespace pchan
|
||||
|
||||
td::Result<PaymentChannel::Info> PaymentChannel::get_info() const {
|
||||
block::gen::ChanData::Record data_rec;
|
||||
if (!tlb::unpack_cell(get_state().data, data_rec)) {
|
||||
return td::Status::Error("Can't unpack data");
|
||||
}
|
||||
block::gen::ChanConfig::Record config_rec;
|
||||
if (!tlb::unpack_cell(data_rec.config, config_rec)) {
|
||||
return td::Status::Error("Can't unpack config");
|
||||
}
|
||||
pchan::Config config;
|
||||
config.a_key = td::SecureString(config_rec.a_key.as_slice());
|
||||
config.b_key = td::SecureString(config_rec.b_key.as_slice());
|
||||
block::tlb::t_MsgAddressInt.extract_std_address(vm::load_cell_slice_ref(config_rec.a_addr), config.a_addr);
|
||||
block::tlb::t_MsgAddressInt.extract_std_address(vm::load_cell_slice_ref(config_rec.b_addr), config.b_addr);
|
||||
config.init_timeout = static_cast<td::int32>(config_rec.init_timeout);
|
||||
config.close_timeout = static_cast<td::int32>(config_rec.close_timeout);
|
||||
config.channel_id = static_cast<td::int64>(config_rec.channel_id);
|
||||
|
||||
auto state_cs = vm::load_cell_slice(data_rec.state);
|
||||
Info res;
|
||||
switch (block::gen::t_ChanState.check_tag(state_cs)) {
|
||||
case block::gen::ChanState::chan_state_init: {
|
||||
pchan::StateInit state;
|
||||
block::gen::ChanState::Record_chan_state_init state_rec;
|
||||
if (!tlb::unpack_cell(data_rec.state, state_rec)) {
|
||||
return td::Status::Error("Can't unpack state");
|
||||
}
|
||||
bool ok = unpack_grams(state_rec.A, state.A) && unpack_grams(state_rec.B, state.B) &&
|
||||
unpack_grams(state_rec.min_A, state.min_A) && unpack_grams(state_rec.min_B, state.min_B);
|
||||
state.expire_at = state_rec.expire_at;
|
||||
state.signed_A = state_rec.signed_A;
|
||||
state.signed_B = state_rec.signed_B;
|
||||
if (!ok) {
|
||||
return td::Status::Error("Can't unpack state");
|
||||
}
|
||||
res.state = std::move(state);
|
||||
break;
|
||||
}
|
||||
case block::gen::ChanState::chan_state_close: {
|
||||
pchan::StateClose state;
|
||||
block::gen::ChanState::Record_chan_state_close state_rec;
|
||||
if (!tlb::unpack_cell(data_rec.state, state_rec)) {
|
||||
return td::Status::Error("Can't unpack state");
|
||||
}
|
||||
bool ok = unpack_grams(state_rec.A, state.A) && unpack_grams(state_rec.B, state.B) &&
|
||||
unpack_grams(state_rec.promise_A, state.promise_A) &&
|
||||
unpack_grams(state_rec.promise_B, state.promise_B);
|
||||
state.expire_at = state_rec.expire_at;
|
||||
state.signed_A = state_rec.signed_A;
|
||||
state.signed_B = state_rec.signed_B;
|
||||
if (!ok) {
|
||||
return td::Status::Error("Can't unpack state");
|
||||
}
|
||||
res.state = std::move(state);
|
||||
break;
|
||||
}
|
||||
case block::gen::ChanState::chan_state_payout: {
|
||||
pchan::StatePayout state;
|
||||
block::gen::ChanState::Record_chan_state_payout state_rec;
|
||||
if (!tlb::unpack_cell(data_rec.state, state_rec)) {
|
||||
return td::Status::Error("Can't unpack state");
|
||||
}
|
||||
bool ok = unpack_grams(state_rec.A, state.A) && unpack_grams(state_rec.B, state.B);
|
||||
if (!ok) {
|
||||
return td::Status::Error("Can't unpack state");
|
||||
}
|
||||
res.state = std::move(state);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return td::Status::Error("Can't unpack state");
|
||||
}
|
||||
|
||||
res.config = std::move(config);
|
||||
res.description = block::gen::t_ChanState.as_string_ref(data_rec.state);
|
||||
|
||||
return std::move(res);
|
||||
} // namespace ton
|
||||
|
||||
td::optional<td::int32> PaymentChannel::guess_revision(const vm::Cell::Hash& code_hash) {
|
||||
for (auto i : ton::SmartContractCode::get_revisions(ton::SmartContractCode::PaymentChannel)) {
|
||||
auto code = SmartContractCode::get_code(SmartContractCode::PaymentChannel, i);
|
||||
if (code->get_hash() == code_hash) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
} // namespace ton
|
251
crypto/smc-envelope/PaymentChannel.h
Normal file
251
crypto/smc-envelope/PaymentChannel.h
Normal file
|
@ -0,0 +1,251 @@
|
|||
#pragma once
|
||||
#include "vm/cells.h"
|
||||
#include "vm/cellslice.h"
|
||||
#include "Ed25519.h"
|
||||
#include "block/block-auto.h"
|
||||
#include "block/block-parse.h"
|
||||
|
||||
#include "td/utils/Variant.h"
|
||||
|
||||
#include "SmartContract.h"
|
||||
#include "SmartContractCode.h"
|
||||
|
||||
namespace ton {
|
||||
namespace pchan {
|
||||
|
||||
//
|
||||
// Payment channels
|
||||
//
|
||||
struct Config {
|
||||
td::uint32 init_timeout{0};
|
||||
td::uint32 close_timeout{0};
|
||||
td::SecureString a_key;
|
||||
td::SecureString b_key;
|
||||
block::StdAddress a_addr;
|
||||
block::StdAddress b_addr;
|
||||
td::uint64 channel_id{0};
|
||||
|
||||
td::Ref<vm::Cell> serialize() const;
|
||||
};
|
||||
|
||||
struct MsgInit {
|
||||
td::uint64 inc_A{0};
|
||||
td::uint64 inc_B{0};
|
||||
td::uint64 min_A{0};
|
||||
td::uint64 min_B{0};
|
||||
td::uint64 channel_id{0};
|
||||
|
||||
td::Ref<vm::Cell> serialize() const;
|
||||
};
|
||||
|
||||
struct Promise {
|
||||
td::uint64 channel_id;
|
||||
td::uint64 promise_A{0};
|
||||
td::uint64 promise_B{0};
|
||||
td::Ref<vm::Cell> serialize() const;
|
||||
};
|
||||
|
||||
td::Ref<vm::Cell> maybe_sign(const td::Ref<vm::Cell>& msg, const td::Ed25519::PrivateKey* key);
|
||||
td::Ref<vm::CellSlice> maybe_ref(td::Ref<vm::Cell> msg);
|
||||
|
||||
struct MsgClose {
|
||||
td::uint64 extra_A{0};
|
||||
td::uint64 extra_B{0};
|
||||
td::Ref<vm::CellSlice> signed_promise;
|
||||
td::Ref<vm::Cell> serialize() const;
|
||||
};
|
||||
|
||||
struct MsgTimeout {
|
||||
td::Ref<vm::Cell> serialize() const;
|
||||
};
|
||||
|
||||
struct SignedPromise {
|
||||
Promise promise;
|
||||
td::optional<td::SecureString> o_signature;
|
||||
|
||||
bool unpack(td::Ref<vm::Cell> cell);
|
||||
static td::SecureString signature(const td::Ed25519::PrivateKey* key, const td::Ref<vm::Cell>& promise);
|
||||
static td::Ref<vm::Cell> create_and_serialize(td::Slice signature, const td::Ref<vm::Cell>& promise);
|
||||
static td::Ref<vm::Cell> create_and_serialize(const td::Ed25519::PrivateKey* key, const td::Ref<vm::Cell>& promise);
|
||||
};
|
||||
|
||||
struct StateInit {
|
||||
bool signed_A{false};
|
||||
bool signed_B{false};
|
||||
td::uint64 min_A{0};
|
||||
td::uint64 min_B{0};
|
||||
td::uint64 A{0};
|
||||
td::uint64 B{0};
|
||||
td::uint32 expire_at{0};
|
||||
|
||||
td::Ref<vm::Cell> serialize() const;
|
||||
};
|
||||
|
||||
struct StateClose {
|
||||
bool signed_A{false};
|
||||
bool signed_B{false};
|
||||
td::uint64 promise_A{0};
|
||||
td::uint64 promise_B{0};
|
||||
td::uint64 A{0};
|
||||
td::uint64 B{0};
|
||||
td::uint32 expire_at{0};
|
||||
};
|
||||
|
||||
struct StatePayout {
|
||||
td::uint64 A{0};
|
||||
td::uint64 B{0};
|
||||
};
|
||||
|
||||
struct Data {
|
||||
td::Ref<vm::Cell> config;
|
||||
td::Ref<vm::Cell> state;
|
||||
|
||||
static td::Ref<vm::Cell> init_state();
|
||||
|
||||
td::Ref<vm::Cell> serialize() const;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct MsgBuilder {
|
||||
td::Ed25519::PrivateKey* a_key{nullptr};
|
||||
td::Ed25519::PrivateKey* b_key{nullptr};
|
||||
|
||||
T&& with_a_key(td::Ed25519::PrivateKey* key) && {
|
||||
a_key = key;
|
||||
return static_cast<T&&>(*this);
|
||||
}
|
||||
T&& with_b_key(td::Ed25519::PrivateKey* key) && {
|
||||
b_key = key;
|
||||
return static_cast<T&&>(*this);
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> finalize() && {
|
||||
block::gen::ChanSignedMsg::Record rec;
|
||||
auto msg = static_cast<T&&>(*this).msg.serialize();
|
||||
rec.msg = vm::load_cell_slice_ref(msg);
|
||||
rec.sig_A = maybe_ref(maybe_sign(msg, a_key));
|
||||
rec.sig_B = maybe_ref(maybe_sign(msg, b_key));
|
||||
td::Ref<vm::Cell> res;
|
||||
CHECK(tlb::pack_cell(res, rec));
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
struct MsgInitBuilder : public MsgBuilder<MsgInitBuilder> {
|
||||
MsgInit msg;
|
||||
|
||||
MsgInitBuilder&& min_A(td::uint64 value) && {
|
||||
msg.min_A = value;
|
||||
return std::move(*this);
|
||||
}
|
||||
MsgInitBuilder&& min_B(td::uint64 value) && {
|
||||
msg.min_B = value;
|
||||
return std::move(*this);
|
||||
}
|
||||
MsgInitBuilder&& inc_A(td::uint64 value) && {
|
||||
msg.inc_A = value;
|
||||
return std::move(*this);
|
||||
}
|
||||
MsgInitBuilder&& inc_B(td::uint64 value) && {
|
||||
msg.inc_B = value;
|
||||
return std::move(*this);
|
||||
}
|
||||
MsgInitBuilder&& channel_id(td::uint64 value) && {
|
||||
msg.channel_id = value;
|
||||
return std::move(*this);
|
||||
}
|
||||
};
|
||||
|
||||
struct MsgTimeoutBuilder : public MsgBuilder<MsgTimeoutBuilder> {
|
||||
MsgTimeout msg;
|
||||
};
|
||||
|
||||
struct MsgCloseBuilder : public MsgBuilder<MsgCloseBuilder> {
|
||||
MsgClose msg;
|
||||
|
||||
MsgCloseBuilder&& extra_A(td::uint64 value) && {
|
||||
msg.extra_A = value;
|
||||
return std::move(*this);
|
||||
}
|
||||
MsgCloseBuilder&& extra_B(td::uint64 value) && {
|
||||
msg.extra_B = value;
|
||||
return std::move(*this);
|
||||
}
|
||||
MsgCloseBuilder&& signed_promise(td::Ref<vm::Cell> signed_promise) && {
|
||||
msg.signed_promise = vm::load_cell_slice_ref(signed_promise);
|
||||
return std::move(*this);
|
||||
}
|
||||
};
|
||||
|
||||
struct SignedPromiseBuilder {
|
||||
Promise promise;
|
||||
td::optional<td::SecureString> o_signature;
|
||||
td::Ed25519::PrivateKey* key{nullptr};
|
||||
|
||||
SignedPromiseBuilder& with_key(td::Ed25519::PrivateKey* key) {
|
||||
this->key = key;
|
||||
return *this;
|
||||
}
|
||||
SignedPromiseBuilder& promise_A(td::uint64 value) {
|
||||
promise.promise_A = value;
|
||||
return *this;
|
||||
}
|
||||
SignedPromiseBuilder& promise_B(td::uint64 value) {
|
||||
promise.promise_B = value;
|
||||
return *this;
|
||||
}
|
||||
SignedPromiseBuilder& channel_id(td::uint64 value) {
|
||||
promise.channel_id = value;
|
||||
return *this;
|
||||
}
|
||||
SignedPromiseBuilder& signature(td::SecureString signature) {
|
||||
o_signature = std::move(signature);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool check_signature(td::Slice signature, const td::Ed25519::PublicKey& pk) {
|
||||
return pk.verify_signature(promise.serialize()->get_hash().as_slice(), signature).is_ok();
|
||||
}
|
||||
td::SecureString calc_signature() {
|
||||
CHECK(key);
|
||||
return SignedPromise::signature(key, promise.serialize());
|
||||
}
|
||||
td::Ref<vm::Cell> finalize() {
|
||||
if (o_signature) {
|
||||
return SignedPromise::create_and_serialize(o_signature.value().copy(), promise.serialize());
|
||||
} else {
|
||||
return SignedPromise::create_and_serialize(key, promise.serialize());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace pchan
|
||||
|
||||
class PaymentChannel : public SmartContract {
|
||||
public:
|
||||
PaymentChannel(State state) : SmartContract(std::move(state)) {
|
||||
}
|
||||
|
||||
struct Info {
|
||||
pchan::Config config;
|
||||
td::Variant<pchan::StateInit, pchan::StateClose, pchan::StatePayout> state;
|
||||
std::string description;
|
||||
};
|
||||
td::Result<Info> get_info() const;
|
||||
|
||||
static td::Ref<PaymentChannel> create(State state) {
|
||||
return td::Ref<PaymentChannel>(true, std::move(state));
|
||||
}
|
||||
static td::optional<td::int32> guess_revision(const vm::Cell::Hash& code_hash);
|
||||
static td::Ref<PaymentChannel> create(const pchan::Config& config, td::int32 revision) {
|
||||
State state;
|
||||
state.code = SmartContractCode::get_code(SmartContractCode::PaymentChannel, revision);
|
||||
pchan::Data data;
|
||||
data.config = config.serialize();
|
||||
pchan::StateInit init;
|
||||
data.state = init.serialize();
|
||||
state.data = data.serialize();
|
||||
return create(std::move(state));
|
||||
}
|
||||
};
|
||||
} // namespace ton
|
|
@ -32,19 +32,19 @@
|
|||
namespace ton {
|
||||
namespace {
|
||||
|
||||
td::Ref<vm::Stack> prepare_vm_stack(td::Ref<vm::CellSlice> body) {
|
||||
td::Ref<vm::Stack> prepare_vm_stack(td::RefInt256 amount, td::Ref<vm::CellSlice> body) {
|
||||
td::Ref<vm::Stack> stack_ref{true};
|
||||
td::RefInt256 acc_addr{true};
|
||||
//CHECK(acc_addr.write().import_bits(account.addr.cbits(), 256));
|
||||
vm::Stack& stack = stack_ref.write();
|
||||
stack.push_int(td::make_refint(10000000000));
|
||||
stack.push_int(td::make_refint(10000000000));
|
||||
stack.push_int(std::move(amount));
|
||||
stack.push_cell(vm::CellBuilder().finalize());
|
||||
stack.push_cellslice(std::move(body));
|
||||
return stack_ref;
|
||||
}
|
||||
|
||||
td::Ref<vm::Tuple> prepare_vm_c7(td::uint32 now) {
|
||||
td::Ref<vm::Tuple> prepare_vm_c7(td::uint32 now, td::uint64 balance) {
|
||||
// TODO: fix initialization of c7
|
||||
td::BitArray<256> rand_seed;
|
||||
rand_seed.as_slice().fill(0);
|
||||
|
@ -58,7 +58,7 @@ td::Ref<vm::Tuple> prepare_vm_c7(td::uint32 now) {
|
|||
td::make_refint(0), // block_lt:Integer
|
||||
td::make_refint(0), // trans_lt:Integer
|
||||
std::move(rand_seed_int), // rand_seed:Integer
|
||||
block::CurrencyCollection(1000000000).as_vm_tuple(), // balance_remaining:[Integer (Maybe Cell)]
|
||||
block::CurrencyCollection(balance).as_vm_tuple(), // balance_remaining:[Integer (Maybe Cell)]
|
||||
vm::load_cell_slice_ref(vm::CellBuilder().finalize()) // myself:MsgAddressInt
|
||||
//vm::StackEntry::maybe(td::Ref<vm::Cell>())
|
||||
); // global_config:(Maybe Cell) ] = SmartContractInfo;
|
||||
|
@ -66,6 +66,15 @@ td::Ref<vm::Tuple> prepare_vm_c7(td::uint32 now) {
|
|||
return vm::make_tuple_ref(std::move(tuple));
|
||||
}
|
||||
|
||||
static int output_actions_count(td::Ref<vm::Cell> list) {
|
||||
int i = -1;
|
||||
do {
|
||||
++i;
|
||||
list = load_cell_slice(std::move(list)).prefetch_ref();
|
||||
} while (list.not_null());
|
||||
return i;
|
||||
}
|
||||
|
||||
SmartContract::Answer run_smartcont(SmartContract::State state, td::Ref<vm::Stack> stack, td::Ref<vm::Tuple> c7,
|
||||
vm::GasLimits gas, bool ignore_chksig) {
|
||||
auto gas_credit = gas.gas_credit;
|
||||
|
@ -123,6 +132,8 @@ SmartContract::Answer run_smartcont(SmartContract::State state, td::Ref<vm::Stac
|
|||
if (res.success) {
|
||||
res.new_state.data = vm.get_c4();
|
||||
res.actions = vm.get_d(5);
|
||||
LOG(DEBUG) << "output actions:\n"
|
||||
<< block::gen::OutList{output_actions_count(res.actions)}.as_string_ref(res.actions);
|
||||
}
|
||||
LOG_IF(ERROR, gas_credit != 0 && (res.accepted && !res.success))
|
||||
<< "Accepted but failed with code " << res.code << "\n"
|
||||
|
@ -171,10 +182,13 @@ SmartContract::Answer SmartContract::run_method(Args args) {
|
|||
now = args.now.unwrap();
|
||||
}
|
||||
if (!args.c7) {
|
||||
args.c7 = prepare_vm_c7(now);
|
||||
args.c7 = prepare_vm_c7(now, args.balance);
|
||||
}
|
||||
if (!args.limits) {
|
||||
args.limits = vm::GasLimits{(long long)0, (long long)1000000, (long long)10000};
|
||||
bool is_internal = args.get_method_id().ok() == 0;
|
||||
|
||||
args.limits = vm::GasLimits{is_internal ? (long long)args.amount * 1000 : (long long)0, (long long)1000000,
|
||||
is_internal ? 0 : (long long)10000};
|
||||
}
|
||||
CHECK(args.stack);
|
||||
CHECK(args.method_id);
|
||||
|
@ -191,7 +205,7 @@ SmartContract::Answer SmartContract::run_get_method(Args args) const {
|
|||
now = args.now.unwrap();
|
||||
}
|
||||
if (!args.c7) {
|
||||
args.c7 = prepare_vm_c7(now);
|
||||
args.c7 = prepare_vm_c7(now, args.balance);
|
||||
}
|
||||
if (!args.limits) {
|
||||
args.limits = vm::GasLimits{1000000};
|
||||
|
@ -209,6 +223,11 @@ SmartContract::Answer SmartContract::run_get_method(td::Slice method, Args args)
|
|||
}
|
||||
|
||||
SmartContract::Answer SmartContract::send_external_message(td::Ref<vm::Cell> cell, Args args) {
|
||||
return run_method(args.set_stack(prepare_vm_stack(vm::load_cell_slice_ref(cell))).set_method_id(-1));
|
||||
return run_method(
|
||||
args.set_stack(prepare_vm_stack(td::make_refint(0), vm::load_cell_slice_ref(cell))).set_method_id(-1));
|
||||
}
|
||||
SmartContract::Answer SmartContract::send_internal_message(td::Ref<vm::Cell> cell, Args args) {
|
||||
return run_method(
|
||||
args.set_stack(prepare_vm_stack(td::make_refint(args.amount), vm::load_cell_slice_ref(cell))).set_method_id(0));
|
||||
}
|
||||
} // namespace ton
|
||||
|
|
|
@ -57,6 +57,8 @@ class SmartContract : public td::CntObject {
|
|||
td::optional<td::Ref<vm::Stack>> stack;
|
||||
td::optional<td::int32> now;
|
||||
bool ignore_chksig{false};
|
||||
td::uint64 amount{0};
|
||||
td::uint64 balance{0};
|
||||
|
||||
Args() {
|
||||
}
|
||||
|
@ -95,6 +97,14 @@ class SmartContract : public td::CntObject {
|
|||
this->ignore_chksig = ignore_chksig;
|
||||
return std::move(*this);
|
||||
}
|
||||
Args&& set_amount(td::uint64 amount) {
|
||||
this->amount = amount;
|
||||
return std::move(*this);
|
||||
}
|
||||
Args&& set_balance(td::uint64 balance) {
|
||||
this->balance = balance;
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
td::Result<td::int32> get_method_id() const {
|
||||
if (!method_id) {
|
||||
|
@ -109,6 +119,7 @@ class SmartContract : public td::CntObject {
|
|||
Answer run_get_method(Args args = {}) const;
|
||||
Answer run_get_method(td::Slice method, Args args = {}) const;
|
||||
Answer send_external_message(td::Ref<vm::Cell> cell, Args args = {});
|
||||
Answer send_internal_message(td::Ref<vm::Cell> cell, Args args = {});
|
||||
|
||||
size_t code_size() const;
|
||||
size_t data_size() const;
|
||||
|
@ -122,6 +133,9 @@ class SmartContract : public td::CntObject {
|
|||
const State& get_state() const {
|
||||
return state_;
|
||||
}
|
||||
CntObject* make_copy() const override {
|
||||
return new SmartContract(state_);
|
||||
}
|
||||
|
||||
protected:
|
||||
State state_;
|
||||
|
|
|
@ -44,6 +44,8 @@ const auto& get_map() {
|
|||
#include "smartcont/auto/highload-wallet-code.cpp"
|
||||
#include "smartcont/auto/highload-wallet-v2-code.cpp"
|
||||
#include "smartcont/auto/dns-manual-code.cpp"
|
||||
#include "smartcont/auto/payment-channel-code.cpp"
|
||||
#include "smartcont/auto/restricted-wallet3-code.cpp"
|
||||
|
||||
with_tvm_code("highload-wallet-r1",
|
||||
"te6ccgEBBgEAhgABFP8A9KQT9KDyyAsBAgEgAgMCAUgEBQC88oMI1xgg0x/TH9Mf+CMTu/Jj7UTQ0x/TH9P/"
|
||||
|
@ -96,6 +98,14 @@ const auto& get_map() {
|
|||
"FwCEMQLTAAHAAZPUAdCY0wUBqgLXGAHiINdJwg/"
|
||||
"ypiB41yLXCwfyaHBTEddJqTYCmNMHAcAAEqEB5DDIywYBzxbJ0FADACBZ9KhvpSCUAvQEMJIybeICACg0A4AQ9FqZECOECUBE8AEBkjAx4gBmM"
|
||||
"SLAFZwy9AQQI4QJUELwAQHgIsAWmDIChAn0czAB4DAyIMAfkzD0BODAIJJtAeDyLG0B");
|
||||
with_tvm_code(
|
||||
"restricted-wallet3-r1",
|
||||
"te6ccgECEgEAAUsAART/APSkE/S88sgLAQIBIAIDAgFIBAUD+PKDCNcYINMf0x/THwL4I7vyY+1E0NMf0x/T/"
|
||||
"1NDuvKhUWK68qIG+QFUEHb5EPKkAY4fMwHT/9EB0x/0BNH4AAOkyMsfFMsfy/8Syx/0AMntVOEC0x/"
|
||||
"0BNH4ACH4I9s8IYAg9HtvpTGW+gAwcvsCkTDiApMg10qK6NECpMgPEBEABNAwAgEgBgcCASAICQIBSAwNAgFuCgsAEbjJftRNDXCx+"
|
||||
"AAXrc52omhpn5jrhf/AABesePaiaGmPmOuFj8ABDbbYHwR7Z5AOAQm1B1tnkA4BTu1E0IEBQNch0x/"
|
||||
"0BNEC2zz4J28QAoAg9HtvpTGX+gAwoXC2CZEw4g8AOiGOETGA8/gzIG6SMHCU0NcLH+IB3yGSAaGSW3/iAAzTB9QC+wAAHssfFMsfEsv/yx/"
|
||||
"0AMntVA==");
|
||||
return map;
|
||||
}();
|
||||
return map;
|
||||
|
@ -103,7 +113,6 @@ const auto& get_map() {
|
|||
} // namespace
|
||||
|
||||
td::Result<td::Ref<vm::Cell>> SmartContractCode::load(td::Slice name) {
|
||||
LOG(ERROR) << "LOAD " << name;
|
||||
auto& map = get_map();
|
||||
auto it = map.find(name);
|
||||
if (it == map.end()) {
|
||||
|
@ -146,6 +155,14 @@ td::Span<int> SmartContractCode::get_revisions(Type type) {
|
|||
static int res[] = {-1, 1};
|
||||
return res;
|
||||
}
|
||||
case Type::PaymentChannel: {
|
||||
static int res[] = {-1};
|
||||
return res;
|
||||
}
|
||||
case Type::RestrictedWallet: {
|
||||
static int res[] = {-1, 1};
|
||||
return res;
|
||||
}
|
||||
}
|
||||
UNREACHABLE();
|
||||
return {};
|
||||
|
@ -190,6 +207,10 @@ td::Ref<vm::Cell> SmartContractCode::get_code(Type type, int ext_revision) {
|
|||
return "multisig";
|
||||
case Type::ManualDns:
|
||||
return "dns-manual";
|
||||
case Type::PaymentChannel:
|
||||
return "payment-channel";
|
||||
case Type::RestrictedWallet:
|
||||
return "restricted-wallet3";
|
||||
}
|
||||
UNREACHABLE();
|
||||
return "";
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
Copyright 2017-2020 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "vm/cells.h"
|
||||
|
||||
#include "td/utils/Span.h"
|
||||
|
@ -25,7 +26,18 @@ class SmartContractCode {
|
|||
public:
|
||||
static td::Result<td::Ref<vm::Cell>> load(td::Slice name);
|
||||
|
||||
enum Type { WalletV1 = 1, WalletV1Ext, WalletV2, WalletV3, HighloadWalletV1, HighloadWalletV2, ManualDns, Multisig };
|
||||
enum Type {
|
||||
WalletV1 = 1,
|
||||
WalletV1Ext,
|
||||
WalletV2,
|
||||
WalletV3,
|
||||
HighloadWalletV1,
|
||||
HighloadWalletV2,
|
||||
ManualDns,
|
||||
Multisig,
|
||||
PaymentChannel,
|
||||
RestrictedWallet
|
||||
};
|
||||
static td::Span<int> get_revisions(Type type);
|
||||
static td::Result<int> validate_revision(Type type, int revision);
|
||||
static td::Ref<vm::Cell> get_code(Type type, int revision = 0);
|
||||
|
|
|
@ -43,12 +43,7 @@ td::Ref<vm::Cell> TestGiver::make_a_gift_message_static(td::uint32 seqno, td::Sp
|
|||
|
||||
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));
|
||||
cb.store_long(send_mode, 8).store_ref(create_int_message(gift));
|
||||
}
|
||||
|
||||
return cb.finalize();
|
||||
|
|
|
@ -46,18 +46,11 @@ td::Ref<vm::Cell> TestWallet::make_a_gift_message_static(const td::Ed25519::Priv
|
|||
|
||||
for (auto& gift : gifts) {
|
||||
td::int32 send_mode = 3;
|
||||
auto gramms = gift.gramms;
|
||||
if (gramms == -1) {
|
||||
gramms = 0;
|
||||
if (gift.gramms == -1) {
|
||||
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));
|
||||
cb.store_long(send_mode, 8).store_ref(create_int_message(gift));
|
||||
}
|
||||
|
||||
auto message_outer = cb.finalize();
|
||||
auto signature = private_key.sign(message_outer->get_hash().as_slice()).move_as_ok();
|
||||
return vm::CellBuilder().store_bytes(signature).append_cellslice(vm::load_cell_slice(message_outer)).finalize();
|
||||
|
|
|
@ -52,16 +52,10 @@ td::Ref<vm::Cell> Wallet::make_a_gift_message(const td::Ed25519::PrivateKey& pri
|
|||
|
||||
for (auto& gift : gifts) {
|
||||
td::int32 send_mode = 3;
|
||||
auto gramms = gift.gramms;
|
||||
if (gramms == -1) {
|
||||
gramms = 0;
|
||||
if (gift.gramms == -1) {
|
||||
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));
|
||||
cb.store_long(send_mode, 8).store_ref(create_int_message(gift));
|
||||
}
|
||||
|
||||
auto message_outer = cb.finalize();
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "vm/cells/CellString.h"
|
||||
|
||||
#include "SmartContract.h"
|
||||
#include "GenericAccount.h"
|
||||
|
||||
namespace ton {
|
||||
class WalletInterface {
|
||||
|
@ -36,6 +37,7 @@ class WalletInterface {
|
|||
std::string message;
|
||||
|
||||
td::Ref<vm::Cell> body;
|
||||
td::Ref<vm::Cell> init_state;
|
||||
};
|
||||
|
||||
virtual ~WalletInterface() {
|
||||
|
@ -48,15 +50,29 @@ class WalletInterface {
|
|||
return td::Status::Error("Unsupported");
|
||||
}
|
||||
|
||||
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()) {
|
||||
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()) const {
|
||||
return make_a_gift_message(private_key, valid_until, {});
|
||||
}
|
||||
static td::Ref<vm::Cell> create_int_message(const Gift &gift) {
|
||||
vm::CellBuilder cbi;
|
||||
GenericAccount::store_int_message(cbi, gift.destination, gift.gramms < 0 ? 0 : gift.gramms);
|
||||
if (gift.init_state.not_null()) {
|
||||
cbi.store_ones(2);
|
||||
cbi.store_ref(gift.init_state);
|
||||
} else {
|
||||
cbi.store_zeroes(1);
|
||||
}
|
||||
cbi.store_zeroes(1);
|
||||
store_gift_message(cbi, gift);
|
||||
return cbi.finalize();
|
||||
}
|
||||
static void store_gift_message(vm::CellBuilder &cb, const Gift &gift) {
|
||||
if (gift.body.not_null()) {
|
||||
auto body = vm::load_cell_slice(gift.body);
|
||||
//TODO: handle error
|
||||
cb.append_cellslice_bool(body);
|
||||
CHECK(cb.append_cellslice_bool(body));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -62,16 +62,10 @@ td::Ref<vm::Cell> WalletV3::make_a_gift_message(const td::Ed25519::PrivateKey& p
|
|||
|
||||
for (auto& gift : gifts) {
|
||||
td::int32 send_mode = 3;
|
||||
auto gramms = gift.gramms;
|
||||
if (gramms == -1) {
|
||||
gramms = 0;
|
||||
if (gift.gramms == -1) {
|
||||
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));
|
||||
cb.store_long(send_mode, 8).store_ref(create_int_message(gift));
|
||||
}
|
||||
|
||||
auto message_outer = cb.finalize();
|
||||
|
|
|
@ -70,3 +70,209 @@ class WalletV3 : public ton::SmartContract, public WalletInterface {
|
|||
td::Result<td::Ed25519::PublicKey> get_public_key_or_throw() const;
|
||||
};
|
||||
} // namespace ton
|
||||
|
||||
#include "smc-envelope/SmartContractCode.h"
|
||||
#include "smc-envelope/GenericAccount.h"
|
||||
#include "block/block-parse.h"
|
||||
#include <algorithm>
|
||||
namespace ton {
|
||||
template <class WalletT, class TraitsT>
|
||||
class WalletBase : public SmartContract, public WalletInterface {
|
||||
public:
|
||||
using Traits = TraitsT;
|
||||
using InitData = typename Traits::InitData;
|
||||
|
||||
explicit WalletBase(State state) : SmartContract(std::move(state)) {
|
||||
}
|
||||
static td::Ref<WalletT> create(State state) {
|
||||
return td::Ref<WalletT>(true, std::move(state));
|
||||
}
|
||||
static td::Ref<vm::Cell> get_init_code(int revision) {
|
||||
return SmartContractCode::get_code(get_code_type(), revision);
|
||||
};
|
||||
size_t get_max_gifts_size() const override {
|
||||
return Traits::max_gifts_size;
|
||||
}
|
||||
static SmartContractCode::Type get_code_type() {
|
||||
return Traits::code_type;
|
||||
}
|
||||
static td::optional<td::int32> guess_revision(const vm::Cell::Hash& code_hash) {
|
||||
for (auto i : ton::SmartContractCode::get_revisions(get_code_type())) {
|
||||
auto code = SmartContractCode::get_code(get_code_type(), i);
|
||||
if (code->get_hash() == code_hash) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static td::Ref<WalletT> create(const InitData& init_data, int revision) {
|
||||
return td::Ref<WalletT>(true, State{get_init_code(revision), WalletT::get_init_data(init_data)});
|
||||
}
|
||||
|
||||
td::Result<td::uint32> get_seqno() const {
|
||||
return TRY_VM([&]() -> td::Result<td::uint32> {
|
||||
Answer answer = this->run_get_method("seqno");
|
||||
if (!answer.success) {
|
||||
return td::Status::Error("seqno get method failed");
|
||||
}
|
||||
return static_cast<td::uint32>(answer.stack.write().pop_long_range(std::numeric_limits<td::uint32>::max()));
|
||||
}());
|
||||
}
|
||||
td::Result<td::uint32> get_wallet_id() const {
|
||||
return TRY_VM([&]() -> td::Result<td::uint32> {
|
||||
Answer answer = this->run_get_method("wallet_id");
|
||||
if (!answer.success) {
|
||||
return td::Status::Error("seqno get method failed");
|
||||
}
|
||||
return static_cast<td::uint32>(answer.stack.write().pop_long_range(std::numeric_limits<td::uint32>::max()));
|
||||
}());
|
||||
}
|
||||
|
||||
td::Result<td::uint64> get_balance(td::uint64 account_balance, td::uint32 now) const {
|
||||
return TRY_VM([&]() -> td::Result<td::uint64> {
|
||||
Answer answer = this->run_get_method(Args().set_method_id("balance").set_balance(account_balance).set_now(now));
|
||||
if (!answer.success) {
|
||||
return td::Status::Error("balance get method failed");
|
||||
}
|
||||
return static_cast<td::uint64>(answer.stack.write().pop_long());
|
||||
}());
|
||||
}
|
||||
|
||||
td::Result<td::Ed25519::PublicKey> get_public_key() const override {
|
||||
return TRY_VM([&]() -> td::Result<td::Ed25519::PublicKey> {
|
||||
Answer answer = this->run_get_method("get_public_key");
|
||||
if (!answer.success) {
|
||||
return td::Status::Error("get_public_key get method failed");
|
||||
}
|
||||
auto key_int = answer.stack.write().pop_int();
|
||||
LOG(ERROR) << key_int->bit_size(false);
|
||||
td::SecureString bytes(32);
|
||||
if (!key_int->export_bytes(bytes.as_mutable_slice().ubegin(), bytes.size(), false)) {
|
||||
return td::Status::Error("not a public key");
|
||||
}
|
||||
return td::Ed25519::PublicKey(std::move(bytes));
|
||||
}());
|
||||
};
|
||||
};
|
||||
|
||||
struct RestrictedWalletTraits {
|
||||
struct InitData {
|
||||
td::SecureString init_key;
|
||||
td::SecureString main_key;
|
||||
td::uint32 wallet_id{0};
|
||||
};
|
||||
|
||||
static constexpr unsigned max_message_size = vm::CellString::max_bytes;
|
||||
static constexpr unsigned max_gifts_size = 4;
|
||||
static constexpr auto code_type = SmartContractCode::RestrictedWallet;
|
||||
};
|
||||
|
||||
class RestrictedWallet : public WalletBase<RestrictedWallet, RestrictedWalletTraits> {
|
||||
public:
|
||||
struct Config {
|
||||
td::uint32 start_at{0};
|
||||
std::vector<std::pair<td::int32, td::uint64>> limits;
|
||||
};
|
||||
|
||||
explicit RestrictedWallet(State state) : WalletBase(std::move(state)) {
|
||||
}
|
||||
|
||||
td::Result<Config> get_config() const {
|
||||
return TRY_VM([this]() -> td::Result<Config> {
|
||||
auto cs = vm::load_cell_slice(get_state().data);
|
||||
Config config;
|
||||
td::Ref<vm::Cell> dict_root;
|
||||
auto ok = cs.advance(32 + 32 + 256) && cs.fetch_uint_to(32, config.start_at) && cs.fetch_maybe_ref(dict_root);
|
||||
vm::Dictionary dict(std::move(dict_root), 32);
|
||||
dict.check_for_each([&](auto cs, auto ptr, auto ptr_bits) {
|
||||
auto r_seconds = td::narrow_cast_safe<td::int32>(dict.key_as_integer(ptr, true)->to_long());
|
||||
if (r_seconds.is_error()) {
|
||||
ok = false;
|
||||
return ok;
|
||||
}
|
||||
td::uint64 value;
|
||||
ok &= smc::unpack_grams(cs, value);
|
||||
config.limits.emplace_back(r_seconds.ok(), value);
|
||||
return ok;
|
||||
});
|
||||
if (!ok) {
|
||||
return td::Status::Error("Can't parse config");
|
||||
}
|
||||
std::sort(config.limits.begin(), config.limits.end());
|
||||
return config;
|
||||
}());
|
||||
}
|
||||
|
||||
static td::Ref<vm::Cell> get_init_data(const InitData& init_data) {
|
||||
vm::CellBuilder cb;
|
||||
cb.store_long(0, 32);
|
||||
cb.store_long(init_data.wallet_id, 32);
|
||||
CHECK(init_data.init_key.size() == 32);
|
||||
CHECK(init_data.main_key.size() == 32);
|
||||
cb.store_bytes(init_data.init_key.as_slice());
|
||||
cb.store_bytes(init_data.main_key.as_slice());
|
||||
return cb.finalize();
|
||||
}
|
||||
|
||||
td::Result<td::Ref<vm::Cell>> get_init_message(const td::Ed25519::PrivateKey& init_private_key,
|
||||
td::uint32 valid_until, const Config& config) const {
|
||||
vm::CellBuilder cb;
|
||||
TRY_RESULT(seqno, get_seqno());
|
||||
TRY_RESULT(wallet_id, get_wallet_id());
|
||||
LOG(ERROR) << "seqno: " << seqno << " wallet_id: " << wallet_id;
|
||||
if (seqno != 0) {
|
||||
return td::Status::Error("Wallet is already inited");
|
||||
}
|
||||
|
||||
cb.store_long(wallet_id, 32);
|
||||
cb.store_long(valid_until, 32);
|
||||
cb.store_long(seqno, 32);
|
||||
|
||||
cb.store_long(config.start_at, 32);
|
||||
vm::Dictionary dict(32);
|
||||
|
||||
auto add = [&](td::int32 till, td::uint64 value) {
|
||||
auto key = dict.integer_key(td::make_refint(till), 32, true);
|
||||
vm::CellBuilder gcb;
|
||||
block::tlb::t_Grams.store_integer_value(gcb, td::BigInt256(value));
|
||||
dict.set_builder(key.bits(), 32, gcb);
|
||||
};
|
||||
for (auto limit : config.limits) {
|
||||
add(limit.first, limit.second);
|
||||
}
|
||||
cb.store_maybe_ref(dict.get_root_cell());
|
||||
|
||||
auto message_outer = cb.finalize();
|
||||
auto signature = init_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::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 {
|
||||
CHECK(gifts.size() <= Traits::max_gifts_size);
|
||||
|
||||
vm::CellBuilder cb;
|
||||
TRY_RESULT(seqno, get_seqno());
|
||||
TRY_RESULT(wallet_id, get_wallet_id());
|
||||
if (seqno == 0) {
|
||||
return td::Status::Error("Wallet is not inited yet");
|
||||
}
|
||||
cb.store_long(wallet_id, 32);
|
||||
cb.store_long(valid_until, 32);
|
||||
cb.store_long(seqno, 32);
|
||||
|
||||
for (auto& gift : gifts) {
|
||||
td::int32 send_mode = 3;
|
||||
if (gift.gramms == -1) {
|
||||
send_mode += 128;
|
||||
}
|
||||
cb.store_long(send_mode, 8).store_ref(create_int_message(gift));
|
||||
}
|
||||
|
||||
auto message_outer = cb.finalize();
|
||||
auto signature = private_key.sign(message_outer->get_hash().as_slice()).move_as_ok();
|
||||
return vm::CellBuilder().store_bytes(signature).append_cellslice(vm::load_cell_slice(message_outer)).finalize();
|
||||
}
|
||||
};
|
||||
} // namespace ton
|
||||
|
|
|
@ -1542,6 +1542,7 @@ template <class DeserializerT>
|
|||
class BenchBocDeserializer : public td::Benchmark {
|
||||
public:
|
||||
BenchBocDeserializer(std::string name, BenchBocDeserializerConfig config) : name_(std::move(name)), config_(config) {
|
||||
td::PerfWarningTimer perf("A", 1);
|
||||
fast_array_ = vm::FastCompactArray(array_size);
|
||||
td::Random::Xorshift128plus rnd{123};
|
||||
for (td::uint32 i = 0; i < array_size; i++) {
|
||||
|
|
|
@ -21,7 +21,9 @@
|
|||
|
||||
#include "Ed25519.h"
|
||||
|
||||
#include "block/block-auto.h"
|
||||
#include "block/block.h"
|
||||
#include "block/block-parse.h"
|
||||
|
||||
#include "fift/Fift.h"
|
||||
#include "fift/words.h"
|
||||
|
@ -38,6 +40,7 @@
|
|||
#include "smc-envelope/WalletV3.h"
|
||||
#include "smc-envelope/HighloadWallet.h"
|
||||
#include "smc-envelope/HighloadWalletV2.h"
|
||||
#include "smc-envelope/PaymentChannel.h"
|
||||
|
||||
#include "td/utils/base64.h"
|
||||
#include "td/utils/crypto.h"
|
||||
|
@ -489,6 +492,99 @@ TEST(Tonlib, TestGiver) {
|
|||
CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == res->get_hash());
|
||||
}
|
||||
|
||||
TEST(Tonlib, RestrictedWallet) {
|
||||
//auto source_lookup = fift::create_mem_source_lookup(load_source("smartcont/new-restricted-wallet2.fif")).move_as_ok();
|
||||
//source_lookup
|
||||
//.write_file("/auto/restricted-wallet2-code.fif", load_source("smartcont/auto/restricted-wallet2-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 priv_key = td::Ed25519::generate_private_key().move_as_ok();
|
||||
//auto pub_key = priv_key.get_public_key().move_as_ok();
|
||||
//auto pub_key_serialized = block::PublicKey::from_bytes(pub_key.as_octet_string()).move_as_ok().serialize(true);
|
||||
|
||||
//std::vector<std::string> args = {"path", pub_key_serialized, std::string("100")};
|
||||
//auto fift_output = fift::mem_run_fift(std::move(source_lookup), args).move_as_ok();
|
||||
|
||||
//ton::RestrictedWallet::InitData init_data;
|
||||
//td::uint64 x = 100 * 1000000000ull;
|
||||
//init_data.key = &pub_key;
|
||||
//init_data.start_at = 0;
|
||||
//init_data.limits = {{-32768, x}, {92, x * 3 / 4}, {183, x * 1 / 2}, {366, x * 1 / 4}, {548, 0}};
|
||||
//auto wallet = ton::RestrictedWallet::create(init_data, -1);
|
||||
|
||||
//ASSERT_EQ(0u, wallet->get_seqno().move_as_ok());
|
||||
//CHECK(pub_key.as_octet_string() == wallet->get_public_key().move_as_ok().as_octet_string());
|
||||
////LOG(ERROR) << wallet->get_balance(x, 60 * 60 * 24 * 400).move_as_ok();
|
||||
|
||||
//auto new_wallet_query = fift_output.source_lookup.read_file("rwallet-query.boc").move_as_ok().data;
|
||||
//auto new_wallet_addr = fift_output.source_lookup.read_file("rwallet.addr").move_as_ok().data;
|
||||
|
||||
//auto address = wallet->get_address(-1);
|
||||
////CHECK(address.addr.as_slice() == td::Slice(new_wallet_addr).substr(0, 32));
|
||||
//address.bounceable = false;
|
||||
//auto res = ton::GenericAccount::create_ext_message(address, wallet->get_init_state(),
|
||||
//wallet->get_init_message(priv_key).move_as_ok());
|
||||
//LOG(ERROR) << "-------";
|
||||
//vm::load_cell_slice(res).print_rec(std::cerr);
|
||||
//LOG(ERROR) << "-------";
|
||||
//vm::load_cell_slice(vm::std_boc_deserialize(new_wallet_query).move_as_ok()).print_rec(std::cerr);
|
||||
//CHECK(vm::std_boc_deserialize(new_wallet_query).move_as_ok()->get_hash() == res->get_hash());
|
||||
|
||||
//auto dest = block::StdAddress::parse("Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX").move_as_ok();
|
||||
//fift_output.source_lookup.write_file("/main.fif", load_source("smartcont/wallet-v2.fif")).ensure();
|
||||
//fift_output.source_lookup.write_file("rwallet.pk", priv_key.as_octet_string().as_slice()).ensure();
|
||||
//fift_output = fift::mem_run_fift(
|
||||
//std::move(fift_output.source_lookup),
|
||||
//{"aba", "rwallet", "-C", "TESTv2", "Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX", "0", "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;
|
||||
////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, {}, 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) << "-------";
|
||||
//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, RestrictedWallet3) {
|
||||
auto init_priv_key = td::Ed25519::generate_private_key().move_as_ok();
|
||||
auto init_pub_key = init_priv_key.get_public_key().move_as_ok();
|
||||
auto priv_key = td::Ed25519::generate_private_key().move_as_ok();
|
||||
auto pub_key = priv_key.get_public_key().move_as_ok();
|
||||
|
||||
ton::RestrictedWallet::InitData init_data;
|
||||
init_data.init_key = init_pub_key.as_octet_string();
|
||||
init_data.main_key = pub_key.as_octet_string();
|
||||
init_data.wallet_id = 123;
|
||||
auto wallet = ton::RestrictedWallet::create(init_data, 1);
|
||||
|
||||
auto address = wallet->get_address();
|
||||
|
||||
td::uint64 x = 100 * 1000000000ull;
|
||||
ton::RestrictedWallet::Config config;
|
||||
config.start_at = 1;
|
||||
config.limits = {{-32768, x}, {92, x * 3 / 4}, {183, x * 1 / 2}, {366, x * 1 / 4}, {548, 0}};
|
||||
CHECK(wallet.write().send_external_message(wallet->get_init_message(init_priv_key, 10, config).move_as_ok()).success);
|
||||
CHECK(wallet->get_seqno().move_as_ok() == 1);
|
||||
|
||||
ton::WalletInterface::Gift gift;
|
||||
gift.destination = address;
|
||||
gift.message = "hello";
|
||||
CHECK(wallet.write().send_external_message(wallet->make_a_gift_message(priv_key, 10, {gift}).move_as_ok()).success);
|
||||
CHECK(wallet->get_seqno().move_as_ok() == 2);
|
||||
}
|
||||
|
||||
class SimpleWallet : public ton::SmartContract {
|
||||
public:
|
||||
SimpleWallet(State state) : SmartContract(std::move(state)) {
|
||||
|
@ -1286,3 +1382,397 @@ TEST(Smartcont, DnsManual) {
|
|||
// TODO: rethink semantic of creating an empty dictionary
|
||||
do_dns_test(CheckedDns(true, true));
|
||||
}
|
||||
|
||||
using namespace ton::pchan;
|
||||
|
||||
template <class T>
|
||||
struct ValidateState {
|
||||
T& self() {
|
||||
return static_cast<T&>(*this);
|
||||
}
|
||||
|
||||
void init(td::Ref<vm::Cell> state) {
|
||||
state_ = state;
|
||||
block::gen::ChanData::Record data_rec;
|
||||
if (!tlb::unpack_cell(state, data_rec)) {
|
||||
on_fatal_error(td::Status::Error("Expected Data"));
|
||||
return;
|
||||
}
|
||||
if (!tlb::unpack_cell(data_rec.state, self().rec)) {
|
||||
on_fatal_error(td::Status::Error("Expected StatePayout"));
|
||||
return;
|
||||
}
|
||||
CHECK(self().rec.A.not_null());
|
||||
}
|
||||
|
||||
T& expect_grams(td::Ref<vm::CellSlice> cs, td::uint64 expected, td::Slice name) {
|
||||
if (has_fatal_error_) {
|
||||
return self();
|
||||
}
|
||||
td::RefInt256 got;
|
||||
CHECK(cs.not_null());
|
||||
CHECK(block::tlb::t_Grams.as_integer_to(cs, got));
|
||||
if (got->cmp(expected) != 0) {
|
||||
on_error(td::Status::Error(PSLICE() << name << ": expected " << expected << ", got " << got->to_dec_string()));
|
||||
}
|
||||
return self();
|
||||
}
|
||||
template <class S>
|
||||
T& expect_eq(S a, S expected, td::Slice name) {
|
||||
if (has_fatal_error_) {
|
||||
return self();
|
||||
}
|
||||
if (!(a == expected)) {
|
||||
on_error(td::Status::Error(PSLICE() << name << ": expected " << expected << ", got " << a));
|
||||
}
|
||||
return self();
|
||||
}
|
||||
|
||||
td::Status finish() {
|
||||
if (errors_.empty()) {
|
||||
return td::Status::OK();
|
||||
}
|
||||
std::stringstream ss;
|
||||
block::gen::t_ChanData.print_ref(ss, state_);
|
||||
td::StringBuilder sb;
|
||||
for (auto& error : errors_) {
|
||||
sb << error << "\n";
|
||||
}
|
||||
sb << ss.str();
|
||||
return td::Status::Error(sb.as_cslice());
|
||||
}
|
||||
|
||||
void on_fatal_error(td::Status error) {
|
||||
CHECK(!has_fatal_error_);
|
||||
has_fatal_error_ = true;
|
||||
on_error(std::move(error));
|
||||
}
|
||||
void on_error(td::Status error) {
|
||||
CHECK(error.is_error());
|
||||
errors_.push_back(std::move(error));
|
||||
}
|
||||
|
||||
public:
|
||||
td::Ref<vm::Cell> state_;
|
||||
bool has_fatal_error_{false};
|
||||
std::vector<td::Status> errors_;
|
||||
};
|
||||
|
||||
struct ValidateStatePayout : public ValidateState<ValidateStatePayout> {
|
||||
ValidateStatePayout& expect_A(td::uint64 a) {
|
||||
expect_grams(rec.A, a, "A");
|
||||
return *this;
|
||||
}
|
||||
ValidateStatePayout& expect_B(td::uint64 b) {
|
||||
expect_grams(rec.B, b, "B");
|
||||
return *this;
|
||||
}
|
||||
|
||||
ValidateStatePayout(td::Ref<vm::Cell> state) {
|
||||
init(std::move(state));
|
||||
}
|
||||
|
||||
block::gen::ChanState::Record_chan_state_payout rec;
|
||||
};
|
||||
|
||||
struct ValidateStateInit : public ValidateState<ValidateStateInit> {
|
||||
ValidateStateInit& expect_A(td::uint64 a) {
|
||||
expect_grams(rec.A, a, "A");
|
||||
return *this;
|
||||
}
|
||||
ValidateStateInit& expect_B(td::uint64 b) {
|
||||
expect_grams(rec.B, b, "B");
|
||||
return *this;
|
||||
}
|
||||
ValidateStateInit& expect_min_A(td::uint64 a) {
|
||||
expect_grams(rec.min_A, a, "min_A");
|
||||
return *this;
|
||||
}
|
||||
ValidateStateInit& expect_min_B(td::uint64 b) {
|
||||
expect_grams(rec.min_B, b, "min_B");
|
||||
return *this;
|
||||
}
|
||||
ValidateStateInit& expect_expire_at(td::uint32 b) {
|
||||
expect_eq(rec.expire_at, b, "expire_at");
|
||||
return *this;
|
||||
}
|
||||
ValidateStateInit& expect_signed_A(bool x) {
|
||||
expect_eq(rec.signed_A, x, "signed_A");
|
||||
return *this;
|
||||
}
|
||||
ValidateStateInit& expect_signed_B(bool x) {
|
||||
expect_eq(rec.signed_B, x, "signed_B");
|
||||
return *this;
|
||||
}
|
||||
|
||||
ValidateStateInit(td::Ref<vm::Cell> state) {
|
||||
init(std::move(state));
|
||||
}
|
||||
|
||||
block::gen::ChanState::Record_chan_state_init rec;
|
||||
};
|
||||
|
||||
struct ValidateStateClose : public ValidateState<ValidateStateClose> {
|
||||
ValidateStateClose& expect_A(td::uint64 a) {
|
||||
expect_grams(rec.A, a, "A");
|
||||
return *this;
|
||||
}
|
||||
ValidateStateClose& expect_B(td::uint64 b) {
|
||||
expect_grams(rec.B, b, "B");
|
||||
return *this;
|
||||
}
|
||||
ValidateStateClose& expect_promise_A(td::uint64 a) {
|
||||
expect_grams(rec.promise_A, a, "promise_A");
|
||||
return *this;
|
||||
}
|
||||
ValidateStateClose& expect_promise_B(td::uint64 b) {
|
||||
expect_grams(rec.promise_B, b, "promise_B");
|
||||
return *this;
|
||||
}
|
||||
ValidateStateClose& expect_expire_at(td::uint32 b) {
|
||||
expect_eq(rec.expire_at, b, "expire_at");
|
||||
return *this;
|
||||
}
|
||||
ValidateStateClose& expect_signed_A(bool x) {
|
||||
expect_eq(rec.signed_A, x, "signed_A");
|
||||
return *this;
|
||||
}
|
||||
ValidateStateClose& expect_signed_B(bool x) {
|
||||
expect_eq(rec.signed_B, x, "signed_B");
|
||||
return *this;
|
||||
}
|
||||
|
||||
ValidateStateClose(td::Ref<vm::Cell> state) {
|
||||
init(std::move(state));
|
||||
}
|
||||
|
||||
block::gen::ChanState::Record_chan_state_close rec;
|
||||
};
|
||||
|
||||
// config$_ initTimeout:int exitTimeout:int a_key:int256 b_key:int256 a_addr b_addr channel_id:int256 = Config;
|
||||
TEST(Smarcont, Channel) {
|
||||
auto code = ton::SmartContractCode::get_code(ton::SmartContractCode::PaymentChannel);
|
||||
Config config;
|
||||
auto a_pkey = td::Ed25519::generate_private_key().move_as_ok();
|
||||
auto b_pkey = td::Ed25519::generate_private_key().move_as_ok();
|
||||
config.init_timeout = 20;
|
||||
config.close_timeout = 40;
|
||||
auto dest = block::StdAddress::parse("Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX").move_as_ok();
|
||||
config.a_addr = dest;
|
||||
config.b_addr = dest;
|
||||
config.a_key = a_pkey.get_public_key().ok().as_octet_string();
|
||||
config.b_key = b_pkey.get_public_key().ok().as_octet_string();
|
||||
config.channel_id = 123;
|
||||
|
||||
Data data;
|
||||
data.config = config.serialize();
|
||||
data.state = data.init_state();
|
||||
auto data_cell = data.serialize();
|
||||
|
||||
auto channel = ton::SmartContract::create(ton::SmartContract::State{code, data_cell});
|
||||
ValidateStateInit(channel->get_state().data)
|
||||
.expect_A(0)
|
||||
.expect_B(0)
|
||||
.expect_min_A(0)
|
||||
.expect_min_B(0)
|
||||
.expect_signed_A(false)
|
||||
.expect_signed_B(false)
|
||||
.expect_expire_at(0)
|
||||
.finish()
|
||||
.ensure();
|
||||
|
||||
enum err {
|
||||
ok = 0,
|
||||
wrong_a_signature = 31,
|
||||
wrong_b_signature,
|
||||
msg_value_too_small,
|
||||
replay_protection,
|
||||
no_timeout,
|
||||
expected_init,
|
||||
expected_close,
|
||||
no_promise_signature,
|
||||
wrong_channel_id
|
||||
};
|
||||
|
||||
#define expect_code(description, expected_code, e) \
|
||||
{ \
|
||||
auto res = e; \
|
||||
LOG_IF(FATAL, expected_code != res.code) << " res.code=" << res.code << " " << description << "\n" << #e; \
|
||||
}
|
||||
#define expect_ok(description, e) expect_code(description, 0, e)
|
||||
|
||||
expect_code("Trying to invoke a timeout while channel is empty", no_timeout,
|
||||
channel.write().send_external_message(MsgTimeoutBuilder().finalize(),
|
||||
ton::SmartContract::Args().set_now(1000000)));
|
||||
|
||||
expect_code("External init message with no signatures", replay_protection,
|
||||
channel.write().send_external_message(MsgInitBuilder().channel_id(config.channel_id).finalize()));
|
||||
expect_code("Internal init message with not enough value", msg_value_too_small,
|
||||
channel.write().send_internal_message(
|
||||
MsgInitBuilder().channel_id(config.channel_id).inc_A(1000).min_B(2000).with_a_key(&a_pkey).finalize(),
|
||||
ton::SmartContract::Args().set_amount(100)));
|
||||
expect_code(
|
||||
"Internal init message with wrong channel_id", wrong_channel_id,
|
||||
channel.write().send_internal_message(MsgInitBuilder().inc_A(1000).min_B(2000).with_a_key(&a_pkey).finalize(),
|
||||
ton::SmartContract::Args().set_amount(1000)));
|
||||
expect_ok("A init with (inc_A = 1000, min_A = 1, min_B = 2000)",
|
||||
channel.write().send_internal_message(MsgInitBuilder()
|
||||
.channel_id(config.channel_id)
|
||||
.inc_A(1000)
|
||||
.min_A(1)
|
||||
.min_B(2000)
|
||||
.with_a_key(&a_pkey)
|
||||
.finalize(),
|
||||
ton::SmartContract::Args().set_amount(1000)));
|
||||
ValidateStateInit(channel->get_state().data)
|
||||
.expect_A(1000)
|
||||
.expect_B(0)
|
||||
.expect_min_A(1)
|
||||
.expect_min_B(2000)
|
||||
.expect_signed_A(true)
|
||||
.expect_signed_B(false)
|
||||
.expect_expire_at(config.init_timeout)
|
||||
.finish()
|
||||
.ensure();
|
||||
|
||||
expect_code("Repeated init of A init with (inc_A = 100, min_B = 5000). Must be ignored", replay_protection,
|
||||
channel.write().send_internal_message(
|
||||
MsgInitBuilder().channel_id(config.channel_id).inc_A(100).min_B(5000).with_a_key(&a_pkey).finalize(),
|
||||
ton::SmartContract::Args().set_amount(1000)));
|
||||
expect_code(
|
||||
"Trying to invoke a timeout too early", no_timeout,
|
||||
channel.write().send_external_message(MsgTimeoutBuilder().finalize(), ton::SmartContract::Args().set_now(0)));
|
||||
|
||||
{
|
||||
auto channel_copy = channel;
|
||||
expect_ok("Invoke a timeout", channel_copy.write().send_external_message(MsgTimeoutBuilder().finalize(),
|
||||
ton::SmartContract::Args().set_now(21)));
|
||||
ValidateStatePayout(channel_copy->get_state().data).expect_A(1000).expect_B(0).finish().ensure();
|
||||
}
|
||||
{
|
||||
auto channel_copy = channel;
|
||||
expect_ok("B init with inc_B < min_B. Leads to immediate payout",
|
||||
channel_copy.write().send_internal_message(
|
||||
MsgInitBuilder().channel_id(config.channel_id).inc_B(1500).with_b_key(&b_pkey).finalize(),
|
||||
ton::SmartContract::Args().set_amount(1500)));
|
||||
ValidateStatePayout(channel_copy->get_state().data).expect_A(1000).expect_B(1500).finish().ensure();
|
||||
}
|
||||
|
||||
expect_ok("B init with (inc_B = 2000, min_A = 1, min_A = 1000)",
|
||||
channel.write().send_internal_message(
|
||||
MsgInitBuilder().channel_id(config.channel_id).inc_B(2000).min_A(1000).with_b_key(&b_pkey).finalize(),
|
||||
ton::SmartContract::Args().set_amount(2000)));
|
||||
ValidateStateClose(channel->get_state().data)
|
||||
.expect_A(1000)
|
||||
.expect_B(2000)
|
||||
.expect_promise_A(0)
|
||||
.expect_promise_B(0)
|
||||
.expect_signed_A(false)
|
||||
.expect_signed_B(false)
|
||||
.expect_expire_at(0)
|
||||
.finish()
|
||||
.ensure();
|
||||
|
||||
{
|
||||
auto channel_copy = channel;
|
||||
expect_ok("A&B send Promise(1000000, 1000000 + 10) signed by nobody",
|
||||
channel_copy.write().send_external_message(MsgCloseBuilder()
|
||||
.signed_promise(SignedPromiseBuilder()
|
||||
.promise_A(1000000)
|
||||
.promise_B(1000000 + 10)
|
||||
.channel_id(config.channel_id)
|
||||
.finalize())
|
||||
.with_a_key(&a_pkey)
|
||||
.with_b_key(&b_pkey)
|
||||
.finalize(),
|
||||
ton::SmartContract::Args().set_now(21)));
|
||||
ValidateStatePayout(channel_copy->get_state().data).expect_A(1000 + 10).expect_B(2000 - 10).finish().ensure();
|
||||
}
|
||||
{
|
||||
auto channel_copy = channel;
|
||||
expect_ok("A&B send Promise(1000000, 1000000 + 10) signed by A",
|
||||
channel_copy.write().send_external_message(MsgCloseBuilder()
|
||||
.signed_promise(SignedPromiseBuilder()
|
||||
.promise_A(1000000)
|
||||
.promise_B(1000000 + 10)
|
||||
.with_key(&a_pkey)
|
||||
.channel_id(config.channel_id)
|
||||
.finalize())
|
||||
.with_a_key(&a_pkey)
|
||||
.with_b_key(&b_pkey)
|
||||
.finalize(),
|
||||
ton::SmartContract::Args().set_now(21)));
|
||||
ValidateStatePayout(channel_copy->get_state().data).expect_A(1000 + 10).expect_B(2000 - 10).finish().ensure();
|
||||
}
|
||||
|
||||
expect_code(
|
||||
"A sends Promise(1000000, 0) signed by A", wrong_b_signature,
|
||||
channel.write().send_external_message(
|
||||
MsgCloseBuilder()
|
||||
.signed_promise(
|
||||
SignedPromiseBuilder().promise_A(1000000).with_key(&a_pkey).channel_id(config.channel_id).finalize())
|
||||
.with_a_key(&a_pkey)
|
||||
.finalize(),
|
||||
ton::SmartContract::Args().set_now(21)));
|
||||
expect_code(
|
||||
"B sends Promise(1000000, 0) signed by B", wrong_a_signature,
|
||||
channel.write().send_external_message(
|
||||
MsgCloseBuilder()
|
||||
.signed_promise(
|
||||
SignedPromiseBuilder().promise_A(1000000).with_key(&b_pkey).channel_id(config.channel_id).finalize())
|
||||
.with_b_key(&b_pkey)
|
||||
.finalize(),
|
||||
ton::SmartContract::Args().set_now(21)));
|
||||
expect_code("B sends Promise(1000000, 0) signed by A with wrong channel_id", wrong_channel_id,
|
||||
channel.write().send_external_message(MsgCloseBuilder()
|
||||
.signed_promise(SignedPromiseBuilder()
|
||||
.promise_A(1000000)
|
||||
.with_key(&a_pkey)
|
||||
.channel_id(config.channel_id + 1)
|
||||
.finalize())
|
||||
.with_b_key(&b_pkey)
|
||||
.finalize(),
|
||||
ton::SmartContract::Args().set_now(21)));
|
||||
expect_code(
|
||||
"B sends unsigned Promise(1000000, 0)", no_promise_signature,
|
||||
channel.write().send_external_message(
|
||||
MsgCloseBuilder()
|
||||
.signed_promise(SignedPromiseBuilder().promise_A(1000000).channel_id(config.channel_id).finalize())
|
||||
.with_b_key(&b_pkey)
|
||||
.finalize(),
|
||||
ton::SmartContract::Args().set_now(21)));
|
||||
|
||||
expect_ok(
|
||||
"B sends Promise(1000000, 0) signed by A",
|
||||
channel.write().send_external_message(
|
||||
MsgCloseBuilder()
|
||||
.signed_promise(
|
||||
SignedPromiseBuilder().promise_A(1000000).with_key(&a_pkey).channel_id(config.channel_id).finalize())
|
||||
.with_b_key(&b_pkey)
|
||||
.finalize(),
|
||||
ton::SmartContract::Args().set_now(21)));
|
||||
ValidateStateClose(channel->get_state().data)
|
||||
.expect_A(1000)
|
||||
.expect_B(2000)
|
||||
.expect_promise_A(1000000)
|
||||
.expect_promise_B(0)
|
||||
.expect_signed_A(false)
|
||||
.expect_signed_B(true)
|
||||
.expect_expire_at(21 + config.close_timeout)
|
||||
.finish()
|
||||
.ensure();
|
||||
|
||||
expect_ok("B sends Promise(0, 1000000 + 10) signed by A",
|
||||
channel.write().send_external_message(MsgCloseBuilder()
|
||||
.signed_promise(SignedPromiseBuilder()
|
||||
.promise_B(1000000 + 10)
|
||||
.with_key(&b_pkey)
|
||||
.channel_id(config.channel_id)
|
||||
.finalize())
|
||||
.with_a_key(&a_pkey)
|
||||
.finalize(),
|
||||
ton::SmartContract::Args().set_now(21)));
|
||||
ValidateStatePayout(channel->get_state().data).expect_A(1000 + 10).expect_B(2000 - 10).finish().ensure();
|
||||
#undef expect_ok
|
||||
#undef expect_code
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "vm/cells/CellSlice.h"
|
||||
#include "vm/excno.hpp"
|
||||
#include "td/utils/bits.h"
|
||||
#include "td/utils/misc.h"
|
||||
|
||||
namespace vm {
|
||||
|
||||
|
@ -719,6 +720,10 @@ bool CellSlice::prefetch_bytes(unsigned char* buffer, unsigned bytes) const {
|
|||
}
|
||||
}
|
||||
|
||||
bool CellSlice::fetch_bytes(td::MutableSlice slice) {
|
||||
return fetch_bytes(slice.ubegin(), td::narrow_cast<unsigned>(slice.size()));
|
||||
}
|
||||
|
||||
bool CellSlice::fetch_bytes(unsigned char* buffer, unsigned bytes) {
|
||||
if (prefetch_bytes(buffer, bytes)) {
|
||||
advance(bytes * 8);
|
||||
|
@ -728,6 +733,10 @@ bool CellSlice::fetch_bytes(unsigned char* buffer, unsigned bytes) {
|
|||
}
|
||||
}
|
||||
|
||||
bool CellSlice::prefetch_bytes(td::MutableSlice slice) const {
|
||||
return prefetch_bytes(slice.ubegin(), td::narrow_cast<unsigned>(slice.size()));
|
||||
}
|
||||
|
||||
Ref<Cell> CellSlice::prefetch_ref(unsigned offset) const {
|
||||
if (offset < size_refs()) {
|
||||
auto ref_id = refs_st + offset;
|
||||
|
|
|
@ -218,7 +218,9 @@ class CellSlice : public td::CntObject {
|
|||
return prefetch_bits_to(buffer.bits(), n);
|
||||
}
|
||||
bool fetch_bytes(unsigned char* buffer, unsigned bytes);
|
||||
bool fetch_bytes(td::MutableSlice slice);
|
||||
bool prefetch_bytes(unsigned char* buffer, unsigned bytes) const;
|
||||
bool prefetch_bytes(td::MutableSlice slice) const;
|
||||
td::BitSlice as_bitslice() const {
|
||||
return prefetch_bits(size());
|
||||
}
|
||||
|
|
|
@ -148,6 +148,11 @@ class MerkleProofCombineFast {
|
|||
MerkleProofCombineFast(Ref<Cell> a, Ref<Cell> b) : a_(std::move(a)), b_(std::move(b)) {
|
||||
}
|
||||
td::Result<Ref<Cell>> run() {
|
||||
if (a_.is_null()) {
|
||||
return b_;
|
||||
} else if (b_.is_null()) {
|
||||
return a_;
|
||||
}
|
||||
TRY_RESULT_ASSIGN(a_, unpack_proof(a_));
|
||||
TRY_RESULT_ASSIGN(b_, unpack_proof(b_));
|
||||
TRY_RESULT(res, run_raw());
|
||||
|
@ -204,6 +209,11 @@ class MerkleProofCombine {
|
|||
MerkleProofCombine(Ref<Cell> a, Ref<Cell> b) : a_(std::move(a)), b_(std::move(b)) {
|
||||
}
|
||||
td::Result<Ref<Cell>> run() {
|
||||
if (a_.is_null()) {
|
||||
return b_;
|
||||
} else if (b_.is_null()) {
|
||||
return a_;
|
||||
}
|
||||
TRY_RESULT_ASSIGN(a_, unpack_proof(a_));
|
||||
TRY_RESULT_ASSIGN(b_, unpack_proof(b_));
|
||||
TRY_RESULT(res, run_raw());
|
||||
|
@ -323,6 +333,10 @@ Ref<Cell> MerkleProof::combine(Ref<Cell> a, Ref<Cell> b) {
|
|||
return res.move_as_ok();
|
||||
}
|
||||
|
||||
td::Result<Ref<Cell>> MerkleProof::combine_status(Ref<Cell> a, Ref<Cell> b) {
|
||||
return MerkleProofCombine(std::move(a), std::move(b)).run();
|
||||
}
|
||||
|
||||
Ref<Cell> MerkleProof::combine_fast(Ref<Cell> a, Ref<Cell> b) {
|
||||
auto res = MerkleProofCombineFast(std::move(a), std::move(b)).run();
|
||||
if (res.is_error()) {
|
||||
|
@ -331,6 +345,10 @@ Ref<Cell> MerkleProof::combine_fast(Ref<Cell> a, Ref<Cell> b) {
|
|||
return res.move_as_ok();
|
||||
}
|
||||
|
||||
td::Result<Ref<Cell>> MerkleProof::combine_fast_status(Ref<Cell> a, Ref<Cell> b) {
|
||||
return MerkleProofCombineFast(std::move(a), std::move(b)).run();
|
||||
}
|
||||
|
||||
Ref<Cell> MerkleProof::combine_raw(Ref<Cell> a, Ref<Cell> b) {
|
||||
auto res = MerkleProofCombine(std::move(a), std::move(b)).run_raw();
|
||||
if (res.is_error()) {
|
||||
|
|
|
@ -38,7 +38,9 @@ class MerkleProof {
|
|||
static Ref<Cell> virtualize(Ref<Cell> cell, int virtualization);
|
||||
|
||||
static Ref<Cell> combine(Ref<Cell> a, Ref<Cell> b);
|
||||
static td::Result<Ref<Cell>> combine_status(Ref<Cell> a, Ref<Cell> b);
|
||||
static Ref<Cell> combine_fast(Ref<Cell> a, Ref<Cell> b);
|
||||
static td::Result<Ref<Cell>> combine_fast_status(Ref<Cell> a, Ref<Cell> b);
|
||||
|
||||
// works with upwrapped proofs
|
||||
// works fine with cell of non-zero level, but this is not supported (yet?) in MerkeProof special cell
|
||||
|
|
|
@ -255,15 +255,15 @@ TonDbTransactionImpl::TonDbTransactionImpl(std::shared_ptr<KeyValue> kv) : kv_(s
|
|||
}
|
||||
|
||||
void TonDbTransactionImpl::begin() {
|
||||
kv_->begin_transaction();
|
||||
kv_->begin_write_batch();
|
||||
generation_++;
|
||||
}
|
||||
void TonDbTransactionImpl::commit() {
|
||||
kv_->commit_transaction();
|
||||
kv_->commit_write_batch();
|
||||
reader_.reset(kv_->snapshot().release());
|
||||
}
|
||||
void TonDbTransactionImpl::abort() {
|
||||
kv_->abort_transaction();
|
||||
kv_->abort_write_batch();
|
||||
}
|
||||
void TonDbTransactionImpl::clear_cache() {
|
||||
contracts_ = {};
|
||||
|
|
|
@ -77,6 +77,13 @@ class VmError {
|
|||
long long get_arg() const {
|
||||
return arg;
|
||||
}
|
||||
td::Status as_status() const {
|
||||
return td::Status::Error(td::Slice{get_msg()});
|
||||
}
|
||||
template <typename T>
|
||||
td::Status as_status(T pfx) const {
|
||||
return td::Status::Error(PSLICE() << pfx << get_msg());
|
||||
}
|
||||
};
|
||||
|
||||
struct VmNoGas {
|
||||
|
@ -90,6 +97,13 @@ struct VmNoGas {
|
|||
operator VmError() const {
|
||||
return VmError{Excno::out_of_gas, "out of gas"};
|
||||
}
|
||||
td::Status as_status() const {
|
||||
return td::Status::Error(td::Slice{get_msg()});
|
||||
}
|
||||
template <typename T>
|
||||
td::Status as_status(T pfx) const {
|
||||
return td::Status::Error(PSLICE() << pfx << get_msg());
|
||||
}
|
||||
};
|
||||
|
||||
struct VmVirtError {
|
||||
|
@ -106,6 +120,13 @@ struct VmVirtError {
|
|||
operator VmError() const {
|
||||
return VmError{Excno::virt_err, "prunned branch", virtualization};
|
||||
}
|
||||
td::Status as_status() const {
|
||||
return td::Status::Error(td::Slice{get_msg()});
|
||||
}
|
||||
template <typename T>
|
||||
td::Status as_status(T pfx) const {
|
||||
return td::Status::Error(PSLICE() << pfx << get_msg());
|
||||
}
|
||||
};
|
||||
|
||||
struct VmFatal {};
|
||||
|
@ -114,12 +135,12 @@ template <class F>
|
|||
auto try_f(F&& f) noexcept -> decltype(f()) {
|
||||
try {
|
||||
return f();
|
||||
} catch (vm::VmError error) {
|
||||
return td::Status::Error(PSLICE() << "Got a vm exception: " << error.get_msg());
|
||||
} catch (vm::VmVirtError error) {
|
||||
return td::Status::Error(PSLICE() << "Got a vm virtualization exception: " << error.get_msg());
|
||||
} catch (vm::VmNoGas error) {
|
||||
return td::Status::Error(PSLICE() << "Got a vm no gas exception: " << error.get_msg());
|
||||
} catch (vm::VmError& error) {
|
||||
return error.as_status("Got a vm exception: ");
|
||||
} catch (vm::VmVirtError& error) {
|
||||
return error.as_status("Got a vm virtualization exception: ");
|
||||
} catch (vm::VmNoGas& error) {
|
||||
return error.as_status("Got a vm no gas exception: ");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue