1
0
Fork 0
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:
ton 2020-04-27 16:01:46 +04:00
parent 16a4566091
commit 9f008b129f
129 changed files with 8438 additions and 879 deletions

View file

@ -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)

View file

@ -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 {

View file

@ -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;

View file

@ -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};
}
}

View file

@ -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);

View file

@ -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;

View file

@ -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();

View file

@ -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,

View file

@ -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,

View file

@ -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)

View 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;
}

View file

@ -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

View file

@ -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>

View 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

View file

@ -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) {

View file

@ -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);

View file

@ -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#

View file

@ -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

View 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

View 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

View file

@ -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);
}

View 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

View file

@ -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

View 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);
}

View file

@ -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());
}

View 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());
}

View file

@ -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

View file

@ -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,

View file

@ -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;

View file

@ -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);

View file

@ -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);
}

View 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

View 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

View file

@ -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

View file

@ -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_;

View file

@ -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 "";

View file

@ -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);

View file

@ -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();

View file

@ -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();

View file

@ -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();

View file

@ -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;
}

View file

@ -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();

View file

@ -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

View file

@ -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++) {

View file

@ -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
}

View file

@ -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;

View file

@ -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());
}

View file

@ -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()) {

View file

@ -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

View file

@ -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_ = {};

View file

@ -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: ");
}
}