mirror of
https://github.com/ton-blockchain/ton
synced 2025-03-09 15:40:10 +00:00
updated smartcontracts
- updated smartcontracts - updated fullnode database layout - fixed memory leak in blockchain-explorer - updated tonlib
This commit is contained in:
parent
9c9248a9ae
commit
c860ce3d1e
104 changed files with 7309 additions and 1335 deletions
|
@ -88,6 +88,7 @@ set(TON_CRYPTO_SOURCE
|
|||
vm/cells/CellBuilder.cpp
|
||||
vm/cells/CellHash.cpp
|
||||
vm/cells/CellSlice.cpp
|
||||
vm/cells/CellString.cpp
|
||||
vm/cells/CellTraits.cpp
|
||||
vm/cells/CellUsageTree.cpp
|
||||
vm/cells/DataCell.cpp
|
||||
|
@ -99,6 +100,7 @@ set(TON_CRYPTO_SOURCE
|
|||
vm/cells/CellBuilder.h
|
||||
vm/cells/CellHash.h
|
||||
vm/cells/CellSlice.h
|
||||
vm/cells/CellString.h
|
||||
vm/cells/CellTraits.h
|
||||
vm/cells/CellUsageTree.h
|
||||
vm/cells/CellWithStorage.h
|
||||
|
@ -197,6 +199,24 @@ set(BLOCK_SOURCE
|
|||
block/transaction.h
|
||||
)
|
||||
|
||||
set(SMC_ENVELOPE_SOURCE
|
||||
smc-envelope/GenericAccount.cpp
|
||||
smc-envelope/MultisigWallet.cpp
|
||||
smc-envelope/SmartContract.cpp
|
||||
smc-envelope/SmartContractCode.cpp
|
||||
smc-envelope/TestGiver.cpp
|
||||
smc-envelope/TestWallet.cpp
|
||||
smc-envelope/Wallet.cpp
|
||||
|
||||
smc-envelope/GenericAccount.h
|
||||
smc-envelope/MultisigWallet.h
|
||||
smc-envelope/SmartContract.h
|
||||
smc-envelope/SmartContractCode.h
|
||||
smc-envelope/TestGiver.h
|
||||
smc-envelope/TestWallet.h
|
||||
smc-envelope/Wallet.h
|
||||
)
|
||||
|
||||
set(ED25519_TEST_SOURCE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/Ed25519.cpp
|
||||
PARENT_SCOPE
|
||||
|
@ -217,6 +237,11 @@ set(TONVM_TEST_SOURCE
|
|||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
set(SMARTCONT_TEST_SOURCE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/test-smartcont.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
set(FIFT_TEST_SOURCE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/fift.cpp
|
||||
PARENT_SCOPE
|
||||
|
@ -329,10 +354,18 @@ if (NOT CMAKE_CROSSCOMPILING)
|
|||
GenFif(DEST smartcont/auto/highload-wallet-code SOURCE smartcont/highload-wallet-code.fc NAME highload-wallet)
|
||||
GenFif(DEST smartcont/auto/highload-wallet-v2-code SOURCE smartcont/highload-wallet-v2-code.fc NAME highoad-wallet-v2)
|
||||
GenFif(DEST smartcont/auto/elector-code SOURCE smartcont/elector-code.fc NAME elector-code)
|
||||
GenFif(DEST smartcont/auto/multisig-code SOURCE smartcont/multisig-code.fc NAME multisig)
|
||||
GenFif(DEST smartcont/auto/restricted-wallet-code SOURCE smartcont/restricted-wallet-code.fc NAME restricted-wallet)
|
||||
GenFif(DEST smartcont/auto/restricted-wallet2-code SOURCE smartcont/restricted-wallet2-code.fc NAME restricted-wallet2)
|
||||
|
||||
GenFif(DEST smartcont/auto/simple-wallet-ext-code SOURCE smartcont/simple-wallet-ext-code.fc NAME simple-wallet-ext)
|
||||
endif()
|
||||
|
||||
add_library(smc-envelope ${SMC_ENVELOPE_SOURCE})
|
||||
target_include_directories(smc-envelope PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
|
||||
target_link_libraries(smc-envelope PUBLIC ton_crypto PRIVATE tdutils ton_block)
|
||||
add_dependencies(smc-envelope gen_fif)
|
||||
|
||||
add_executable(create-state block/create-state.cpp)
|
||||
target_include_directories(create-state PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/..>)
|
||||
|
|
|
@ -551,6 +551,9 @@ validator_addr#73 public_key:SigPubKey weight:uint64 adnl_addr:bits256 = Validat
|
|||
validators#11 utime_since:uint32 utime_until:uint32
|
||||
total:(## 16) main:(## 16) { main <= total } { main >= 1 }
|
||||
list:(Hashmap 16 ValidatorDescr) = ValidatorSet;
|
||||
validators_ext#12 utime_since:uint32 utime_until:uint32
|
||||
total:(## 16) main:(## 16) { main <= total } { main >= 1 }
|
||||
total_weight:uint64 list:(HashmapE 16 ValidatorDescr) = ValidatorSet;
|
||||
|
||||
_ config_addr:bits256 = ConfigParam 0;
|
||||
_ elector_addr:bits256 = ConfigParam 1;
|
||||
|
|
|
@ -221,8 +221,6 @@ td::Status check_account_proof(td::Slice proof, ton::BlockIdExt shard_blk, const
|
|||
td::Result<AccountState::Info> AccountState::validate(ton::BlockIdExt ref_blk, block::StdAddress addr) const {
|
||||
TRY_RESULT_PREFIX(root, vm::std_boc_deserialize(state.as_slice(), true), "cannot deserialize account state");
|
||||
|
||||
LOG(INFO) << "got account state for " << addr << " with respect to blocks " << blk.to_str()
|
||||
<< (shard_blk == blk ? "" : std::string{" and "} + shard_blk.to_str());
|
||||
if (blk != ref_blk && ref_blk.id.seqno != ~0U) {
|
||||
return td::Status::Error(PSLICE() << "obtained getAccountState() for a different reference block " << blk.to_str()
|
||||
<< " instead of requested " << ref_blk.to_str());
|
||||
|
|
|
@ -387,11 +387,25 @@ td::Result<std::unique_ptr<ValidatorSet>> Config::unpack_validator_set(Ref<vm::C
|
|||
if (vset_root.is_null()) {
|
||||
return td::Status::Error("validator set is absent");
|
||||
}
|
||||
gen::ValidatorSet::Record rec;
|
||||
if (!tlb::unpack_cell(std::move(vset_root), rec)) {
|
||||
return td::Status::Error("validator set is invalid");
|
||||
gen::ValidatorSet::Record_validators_ext rec;
|
||||
Ref<vm::Cell> dict_root;
|
||||
if (!tlb::unpack_cell(vset_root, rec)) {
|
||||
gen::ValidatorSet::Record_validators rec0;
|
||||
if (!tlb::unpack_cell(std::move(vset_root), rec0)) {
|
||||
return td::Status::Error("validator set is invalid");
|
||||
}
|
||||
rec.utime_since = rec0.utime_since;
|
||||
rec.utime_until = rec0.utime_until;
|
||||
rec.total = rec0.total;
|
||||
rec.main = rec0.main;
|
||||
dict_root = vm::Dictionary::construct_root_from(*rec0.list);
|
||||
rec.total_weight = 0;
|
||||
} else if (rec.total_weight) {
|
||||
dict_root = rec.list->prefetch_ref();
|
||||
} else {
|
||||
return td::Status::Error("validator set cannot have zero total weight");
|
||||
}
|
||||
vm::Dictionary dict{vm::Dictionary::construct_root_from(*rec.list), 16};
|
||||
vm::Dictionary dict{std::move(dict_root), 16};
|
||||
td::BitArray<16> key_buffer;
|
||||
auto last = dict.get_minmax_key(key_buffer.bits(), 16, true);
|
||||
if (last.is_null() || (int)key_buffer.to_ulong() != rec.total - 1) {
|
||||
|
@ -428,6 +442,9 @@ td::Result<std::unique_ptr<ValidatorSet>> Config::unpack_validator_set(Ref<vm::C
|
|||
ptr->list.emplace_back(sig_pubkey.pubkey, descr.weight, ptr->total_weight, descr.adnl_addr);
|
||||
ptr->total_weight += descr.weight;
|
||||
}
|
||||
if (rec.total_weight && rec.total_weight != ptr->total_weight) {
|
||||
return td::Status::Error("validator set declares incorrect total weight");
|
||||
}
|
||||
return std::move(ptr);
|
||||
}
|
||||
|
||||
|
@ -517,6 +534,58 @@ td::Result<std::vector<StoragePrices>> Config::get_storage_prices() const {
|
|||
return std::move(res);
|
||||
}
|
||||
|
||||
td::Result<GasLimitsPrices> Config::get_gas_limits_prices(bool is_masterchain) const {
|
||||
GasLimitsPrices res;
|
||||
auto id = is_masterchain ? 20 : 21;
|
||||
auto cell = get_config_param(id);
|
||||
if (cell.is_null()) {
|
||||
return td::Status::Error(PSLICE() << "configuration parameter " << id << " with gas prices is absent");
|
||||
}
|
||||
auto cs = vm::load_cell_slice(std::move(cell));
|
||||
block::gen::GasLimitsPrices::Record_gas_flat_pfx flat;
|
||||
if (tlb::unpack(cs, flat)) {
|
||||
cs = *flat.other;
|
||||
res.flat_gas_limit = flat.flat_gas_limit;
|
||||
res.flat_gas_price = flat.flat_gas_price;
|
||||
}
|
||||
auto f = [&](const auto& r, td::uint64 spec_limit) {
|
||||
res.gas_limit = r.gas_limit;
|
||||
res.special_gas_limit = spec_limit;
|
||||
res.gas_credit = r.gas_credit;
|
||||
res.gas_price = r.gas_price;
|
||||
res.freeze_due_limit = r.freeze_due_limit;
|
||||
res.delete_due_limit = r.delete_due_limit;
|
||||
};
|
||||
block::gen::GasLimitsPrices::Record_gas_prices_ext rec;
|
||||
if (tlb::unpack(cs, rec)) {
|
||||
f(rec, rec.special_gas_limit);
|
||||
} else {
|
||||
block::gen::GasLimitsPrices::Record_gas_prices rec0;
|
||||
if (tlb::unpack(cs, rec0)) {
|
||||
f(rec0, rec0.gas_limit);
|
||||
} else {
|
||||
return td::Status::Error(PSLICE() << "configuration parameter " << id
|
||||
<< " with gas prices is invalid - can't parse");
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
td::Result<MsgPrices> Config::get_msg_prices(bool is_masterchain) const {
|
||||
auto id = is_masterchain ? 24 : 25;
|
||||
auto cell = get_config_param(id);
|
||||
if (cell.is_null()) {
|
||||
return td::Status::Error(PSLICE() << "configuration parameter " << id << " with msg prices is absent");
|
||||
}
|
||||
auto cs = vm::load_cell_slice(std::move(cell));
|
||||
block::gen::MsgForwardPrices::Record rec;
|
||||
if (!tlb::unpack(cs, rec)) {
|
||||
return td::Status::Error(PSLICE() << "configuration parameter " << id
|
||||
<< " with msg prices is invalid - can't parse");
|
||||
}
|
||||
return MsgPrices(rec.lump_price, rec.bit_price, rec.cell_price, rec.ihr_price_factor, rec.first_frac, rec.next_frac);
|
||||
}
|
||||
|
||||
CatchainValidatorsConfig Config::unpack_catchain_validators_config(Ref<vm::Cell> cell) {
|
||||
block::gen::CatchainConfig::Record cfg;
|
||||
if (cell.is_null() || !tlb::unpack_cell(std::move(cell), cfg)) {
|
||||
|
|
|
@ -50,7 +50,7 @@ struct ValidatorDescr {
|
|||
: pubkey(_pubkey), weight(_weight), cum_weight(_cum_weight) {
|
||||
adnl_addr.set_zero();
|
||||
}
|
||||
bool operator<(td::uint64 wt_pos) const & {
|
||||
bool operator<(td::uint64 wt_pos) const& {
|
||||
return cum_weight < wt_pos;
|
||||
}
|
||||
};
|
||||
|
@ -327,6 +327,46 @@ struct StoragePrices {
|
|||
, mc_bit_price(_mc_bprice)
|
||||
, mc_cell_price(_mc_cprice) {
|
||||
}
|
||||
static td::RefInt256 compute_storage_fees(ton::UnixTime now, const std::vector<block::StoragePrices>& pricing,
|
||||
const vm::CellStorageStat& storage_stat, ton::UnixTime last_paid,
|
||||
bool is_special, bool is_masterchain);
|
||||
};
|
||||
|
||||
struct GasLimitsPrices {
|
||||
td::uint64 flat_gas_limit{0};
|
||||
td::uint64 flat_gas_price{0};
|
||||
td::uint64 gas_price{0};
|
||||
td::uint64 special_gas_limit{0};
|
||||
td::uint64 gas_limit{0};
|
||||
td::uint64 gas_credit{0};
|
||||
td::uint64 block_gas_limit{0};
|
||||
td::uint64 freeze_due_limit{0};
|
||||
td::uint64 delete_due_limit{0};
|
||||
|
||||
td::RefInt256 compute_gas_price(td::uint64 gas_used) const;
|
||||
};
|
||||
|
||||
// msg_fwd_fees = (lump_price + ceil((bit_price * msg.bits + cell_price * msg.cells)/2^16)) nanograms
|
||||
// ihr_fwd_fees = ceil((msg_fwd_fees * ihr_price_factor)/2^16) nanograms
|
||||
// bits in the root cell of a message are not included in msg.bits (lump_price pays for them)
|
||||
|
||||
struct MsgPrices {
|
||||
td::uint64 lump_price;
|
||||
td::uint64 bit_price;
|
||||
td::uint64 cell_price;
|
||||
td::uint32 ihr_factor;
|
||||
td::uint32 first_frac;
|
||||
td::uint32 next_frac;
|
||||
td::uint64 compute_fwd_fees(td::uint64 cells, td::uint64 bits) const;
|
||||
std::pair<td::uint64, td::uint64> compute_fwd_ihr_fees(td::uint64 cells, td::uint64 bits,
|
||||
bool ihr_disabled = false) const;
|
||||
MsgPrices() = default;
|
||||
MsgPrices(td::uint64 lump, td::uint64 bitp, td::uint64 cellp, td::uint32 ihrf, td::uint32 firstf, td::uint32 nextf)
|
||||
: lump_price(lump), bit_price(bitp), cell_price(cellp), ihr_factor(ihrf), first_frac(firstf), next_frac(nextf) {
|
||||
}
|
||||
td::RefInt256 get_first_part(td::RefInt256 total) const;
|
||||
td::uint64 get_first_part(td::uint64 total) const;
|
||||
td::RefInt256 get_next_part(td::RefInt256 total) const;
|
||||
};
|
||||
|
||||
struct CatchainValidatorsConfig {
|
||||
|
@ -499,6 +539,8 @@ class Config {
|
|||
bool is_special_smartcontract(const ton::StdSmcAddress& addr) const;
|
||||
static td::Result<std::unique_ptr<ValidatorSet>> unpack_validator_set(Ref<vm::Cell> valset_root);
|
||||
td::Result<std::vector<StoragePrices>> get_storage_prices() const;
|
||||
td::Result<GasLimitsPrices> get_gas_limits_prices(bool is_masterchain = false) const;
|
||||
td::Result<MsgPrices> get_msg_prices(bool is_masterchain = false) const;
|
||||
static CatchainValidatorsConfig unpack_catchain_validators_config(Ref<vm::Cell> cell);
|
||||
CatchainValidatorsConfig get_catchain_validators_config() const;
|
||||
td::Status visit_validator_params() const;
|
||||
|
|
|
@ -421,7 +421,9 @@ void add_partial_storage_payment(td::BigInt256& payment, ton::UnixTime delta, co
|
|||
payment += b;
|
||||
}
|
||||
|
||||
td::RefInt256 Account::compute_storage_fees(ton::UnixTime now, const std::vector<block::StoragePrices>& pricing) const {
|
||||
td::RefInt256 StoragePrices::compute_storage_fees(ton::UnixTime now, const std::vector<block::StoragePrices>& pricing,
|
||||
const vm::CellStorageStat& storage_stat, ton::UnixTime last_paid,
|
||||
bool is_special, bool is_masterchain) {
|
||||
if (now <= last_paid || !last_paid || is_special || pricing.empty() || now <= pricing[0].valid_since) {
|
||||
return {};
|
||||
}
|
||||
|
@ -438,7 +440,7 @@ td::RefInt256 Account::compute_storage_fees(ton::UnixTime now, const std::vector
|
|||
ton::UnixTime valid_until = (i < n - 1 ? std::min(now, pricing[i + 1].valid_since) : now);
|
||||
if (upto < valid_until) {
|
||||
assert(upto >= pricing[i].valid_since);
|
||||
add_partial_storage_payment(total.unique_write(), valid_until - upto, pricing[i], storage_stat, is_masterchain());
|
||||
add_partial_storage_payment(total.unique_write(), valid_until - upto, pricing[i], storage_stat, is_masterchain);
|
||||
}
|
||||
upto = valid_until;
|
||||
}
|
||||
|
@ -446,6 +448,10 @@ td::RefInt256 Account::compute_storage_fees(ton::UnixTime now, const std::vector
|
|||
return total;
|
||||
}
|
||||
|
||||
td::RefInt256 Account::compute_storage_fees(ton::UnixTime now, const std::vector<block::StoragePrices>& pricing) const {
|
||||
return StoragePrices::compute_storage_fees(now, pricing, storage_stat, last_paid, is_special, is_masterchain());
|
||||
}
|
||||
|
||||
Transaction::Transaction(const Account& _account, int ttype, ton::LogicalTime req_start_lt, ton::UnixTime _now,
|
||||
Ref<vm::Cell> _inmsg)
|
||||
: trans_type(ttype)
|
||||
|
@ -969,7 +975,7 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) {
|
|||
gas = vm.get_gas_limits();
|
||||
cp.gas_used = std::min<long long>(gas.gas_consumed(), gas.gas_limit);
|
||||
cp.accepted = (gas.gas_credit == 0);
|
||||
cp.success = (cp.accepted && (unsigned)cp.exit_code <= 1);
|
||||
cp.success = (cp.accepted && vm.committed());
|
||||
if (cp.accepted & use_msg_state) {
|
||||
was_activated = true;
|
||||
acc_status = Account::acc_active;
|
||||
|
@ -978,8 +984,8 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) {
|
|||
<< ", limit=" << gas.gas_limit << ", credit=" << gas.gas_credit;
|
||||
LOG(INFO) << "out_of_gas=" << cp.out_of_gas << ", accepted=" << cp.accepted << ", success=" << cp.success;
|
||||
if (cp.success) {
|
||||
cp.new_data = vm.get_c4(); // c4 -> persistent data
|
||||
cp.actions = vm.get_d(5); // c5 -> action list
|
||||
cp.new_data = vm.get_committed_state().c4; // c4 -> persistent data
|
||||
cp.actions = vm.get_committed_state().c5; // c5 -> action list
|
||||
int out_act_num = output_actions_count(cp.actions);
|
||||
if (verbosity > 2) {
|
||||
std::cerr << "new smart contract data: ";
|
||||
|
|
|
@ -65,10 +65,10 @@ struct NewOutMsg {
|
|||
NewOutMsg(ton::LogicalTime _lt, Ref<vm::Cell> _msg, Ref<vm::Cell> _trans)
|
||||
: lt(_lt), msg(std::move(_msg)), trans(std::move(_trans)) {
|
||||
}
|
||||
bool operator<(const NewOutMsg& other) const & {
|
||||
bool operator<(const NewOutMsg& other) const& {
|
||||
return lt < other.lt || (lt == other.lt && msg->get_hash() < other.msg->get_hash());
|
||||
}
|
||||
bool operator>(const NewOutMsg& other) const & {
|
||||
bool operator>(const NewOutMsg& other) const& {
|
||||
return lt > other.lt || (lt == other.lt && other.msg->get_hash() < msg->get_hash());
|
||||
}
|
||||
};
|
||||
|
@ -132,29 +132,6 @@ struct ComputePhaseConfig {
|
|||
bool parse_GasLimitsPrices(Ref<vm::Cell> cell, td::RefInt256& freeze_due_limit, td::RefInt256& delete_due_limit);
|
||||
};
|
||||
|
||||
// msg_fwd_fees = (lump_price + ceil((bit_price * msg.bits + cell_price * msg.cells)/2^16)) nanograms
|
||||
// ihr_fwd_fees = ceil((msg_fwd_fees * ihr_price_factor)/2^16) nanograms
|
||||
// bits in the root cell of a message are not included in msg.bits (lump_price pays for them)
|
||||
|
||||
struct MsgPrices {
|
||||
td::uint64 lump_price;
|
||||
td::uint64 bit_price;
|
||||
td::uint64 cell_price;
|
||||
td::uint32 ihr_factor;
|
||||
td::uint32 first_frac;
|
||||
td::uint32 next_frac;
|
||||
td::uint64 compute_fwd_fees(td::uint64 cells, td::uint64 bits) const;
|
||||
std::pair<td::uint64, td::uint64> compute_fwd_ihr_fees(td::uint64 cells, td::uint64 bits,
|
||||
bool ihr_disabled = false) const;
|
||||
MsgPrices() = default;
|
||||
MsgPrices(td::uint64 lump, td::uint64 bitp, td::uint64 cellp, td::uint32 ihrf, td::uint32 firstf, td::uint32 nextf)
|
||||
: lump_price(lump), bit_price(bitp), cell_price(cellp), ihr_factor(ihrf), first_frac(firstf), next_frac(nextf) {
|
||||
}
|
||||
td::RefInt256 get_first_part(td::RefInt256 total) const;
|
||||
td::uint64 get_first_part(td::uint64 total) const;
|
||||
td::RefInt256 get_next_part(td::RefInt256 total) const;
|
||||
};
|
||||
|
||||
struct ActionPhaseConfig {
|
||||
int max_actions{255};
|
||||
MsgPrices fwd_std;
|
||||
|
|
|
@ -224,6 +224,8 @@ class BitSliceGen {
|
|||
BitSliceGen(BitSliceGen&& bs, unsigned _offs, unsigned _len);
|
||||
BitSliceGen(Pt* _ptr, unsigned _len) : ref(), ptr(_ptr), offs(0), len(_len) {
|
||||
}
|
||||
explicit BitSliceGen(Slice slice) : BitSliceGen(slice.data(), slice.size() * 8) {
|
||||
}
|
||||
~BitSliceGen() {
|
||||
}
|
||||
Pt* get_ptr() const {
|
||||
|
|
|
@ -970,6 +970,7 @@ x{F4B7} @Defop SUBDICTURPGET
|
|||
|
||||
x{F800} @Defop ACCEPT
|
||||
x{F801} @Defop SETGASLIMIT
|
||||
x{F80F} @Defop COMMIT
|
||||
|
||||
x{F82} @Defop(4u) GETPARAM
|
||||
x{F823} @Defop NOW
|
||||
|
|
|
@ -139,7 +139,7 @@ recursive append-long-bytes {
|
|||
|
||||
// ( S -- x ) parse public key
|
||||
{ dup $len 48 <> abort"public key must be 48 characters long"
|
||||
base64>B dup Blen 36 <> abort"public key must be 48 characters long"
|
||||
base64url>B dup Blen 36 <> abort"public key must be 48 characters long"
|
||||
34 B| 16 B>u@ over crc16 <> abort"crc16 mismatch in public key"
|
||||
16 B>u@+ 0x3ee6 <> abort"invalid tag in public key"
|
||||
256 B>u@
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"Asm.fif" include
|
||||
"TonUtil.fif" include
|
||||
|
||||
31 -1<< constant wc_undef
|
||||
0 constant wc_base
|
||||
|
@ -187,6 +188,7 @@ dictnew constant special-dict
|
|||
|
||||
|
||||
// restricted wallet creation
|
||||
"auto/wallet-code.fif" include =: WCode0
|
||||
"auto/restricted-wallet-code.fif" include =: RWCode1
|
||||
"auto/restricted-wallet2-code.fif" include =: RWCode2
|
||||
|
||||
|
@ -200,7 +202,7 @@ dictnew constant special-dict
|
|||
0 // ticktock
|
||||
2 // mode: create
|
||||
register_smc
|
||||
Masterchain 6 .Addr cr
|
||||
Masterchain swap 6 .Addr cr
|
||||
} : create-wallet1
|
||||
|
||||
// D x t -- D'
|
||||
|
@ -225,5 +227,18 @@ dictnew constant special-dict
|
|||
0 // ticktock
|
||||
2 // mode: create
|
||||
register_smc
|
||||
Masterchain 6 .Addr cr
|
||||
Masterchain swap 6 .Addr cr
|
||||
} : create-wallet2
|
||||
|
||||
// pubkey amount
|
||||
{ over ."Key " pubkey>$ type ." -> "
|
||||
WCode0 // code
|
||||
<b 1 32 u, 3 roll 256 u, b> // data
|
||||
empty_cell // libs
|
||||
3 roll // balance
|
||||
0 // split_depth
|
||||
0 // ticktock
|
||||
2 // mode: create
|
||||
register_smc
|
||||
Masterchain swap 6 .Addr cr
|
||||
} : create-wallet0
|
||||
|
|
|
@ -1,15 +1,32 @@
|
|||
;; Simple configuration smart contract
|
||||
|
||||
() set_conf_param(int index, cell value) impure {
|
||||
var cs = begin_parse(get_data());
|
||||
var cs = get_data().begin_parse();
|
||||
var cfg_dict = cs~load_ref();
|
||||
cfg_dict~idict_set_ref(32, index, value);
|
||||
set_data(begin_cell().store_ref(cfg_dict).store_slice(cs).end_cell());
|
||||
}
|
||||
|
||||
(cell, int, int, cell) load_data() inline {
|
||||
var cs = get_data().begin_parse();
|
||||
var (cfg_dict, stored_seqno, public_key) = (cs~load_ref(), cs~load_uint(32), cs~load_uint(256));
|
||||
var vote_dict = cs.slice_empty?() ? new_dict() : cs~load_dict();
|
||||
cs.end_parse();
|
||||
return (cfg_dict, stored_seqno, public_key, vote_dict);
|
||||
}
|
||||
|
||||
() store_data(cfg_dict, stored_seqno, public_key, vote_dict) impure inline {
|
||||
set_data(begin_cell()
|
||||
.store_ref(cfg_dict)
|
||||
.store_uint(stored_seqno, 32)
|
||||
.store_uint(public_key, 256)
|
||||
.store_dict(vote_dict)
|
||||
.end_cell());
|
||||
}
|
||||
|
||||
(int, int) check_validator_set(cell vset) {
|
||||
var cs = vset.begin_parse();
|
||||
throw_unless(9, cs~load_uint(8) == 0x11); ;; validators#11
|
||||
throw_if(9, (cs~load_uint(8) - 0x11) & -2); ;; validators#11 or validators_ext#12
|
||||
int utime_since = cs~load_uint(32);
|
||||
int utime_until = cs~load_uint(32);
|
||||
int total = cs~load_uint(16);
|
||||
|
@ -86,6 +103,83 @@
|
|||
.end_cell(), 0);
|
||||
}
|
||||
|
||||
() after_code_upgrade(slice param, cell old_code) impure method_id(1666) {
|
||||
}
|
||||
|
||||
_ perform_action(cfg_dict, public_key, action, cs) {
|
||||
if (action == 0x43665021) {
|
||||
;; change one configuration parameter
|
||||
var param_index = cs~load_uint(32);
|
||||
var param_value = cs~load_ref();
|
||||
cs.end_parse();
|
||||
cfg_dict~idict_set_ref(32, param_index, param_value);
|
||||
return (cfg_dict, public_key);
|
||||
} elseif (action == 0x4e436f64) {
|
||||
;; change configuration smart contract code
|
||||
var new_code = cs~load_ref();
|
||||
set_code(new_code);
|
||||
var old_code = get_c3();
|
||||
set_c3(new_code);
|
||||
after_code_upgrade(cs, old_code);
|
||||
throw(0);
|
||||
return (cfg_dict, public_key);
|
||||
} elseif (action == 0x50624b21) {
|
||||
;; change configuration master public key
|
||||
public_key = cs~load_uint(256);
|
||||
cs.end_parse();
|
||||
return (cfg_dict, public_key);
|
||||
} elseif (action == 0x4e43ef05) {
|
||||
;; change election smart contract code
|
||||
change_elector_code(cs);
|
||||
return (cfg_dict, public_key);
|
||||
} else {
|
||||
throw_if(32, action);
|
||||
return (cfg_dict, public_key);
|
||||
}
|
||||
}
|
||||
|
||||
slice get_validator_descr(int idx) inline_ref {
|
||||
var vset = config_param(34);
|
||||
if (vset.null?()) {
|
||||
return null();
|
||||
}
|
||||
var cs = begin_parse(vset);
|
||||
cs~skip_bits(8 + 32 + 32 + 16 + 16);
|
||||
var dict = begin_cell().store_slice(cs).end_cell();
|
||||
var (value, _) = dict.udict_get?(16, idx);
|
||||
return value;
|
||||
}
|
||||
|
||||
(int, int) unpack_validator_descr(slice cs) inline {
|
||||
;; ed25519_pubkey#8e81278a pubkey:bits256 = SigPubKey;
|
||||
;; validator#53 public_key:SigPubKey weight:uint64 = ValidatorDescr;
|
||||
;; validator_addr#73 public_key:SigPubKey weight:uint64 adnl_addr:bits256 = ValidatorDescr;
|
||||
throw_unless(41, (cs~load_uint(8) & ~ 0x20) == 0x53);
|
||||
throw_unless(41, cs~load_uint(32) == 0x8e81278a);
|
||||
return (cs~load_uint(256), cs~load_uint(64));
|
||||
}
|
||||
|
||||
slice create_new_entry(cs) inline {
|
||||
return begin_cell().store_int(false, 1).store_uint(0, 64).store_uint(0, 256).store_slice(cs).end_cell().begin_parse();
|
||||
}
|
||||
|
||||
cell register_vote(vote_dict, action, cs, idx, weight) {
|
||||
int hash = 0;
|
||||
var entry = null();
|
||||
if (action & 1) {
|
||||
hash = slice_hash(cs);
|
||||
(entry, var found?) = vote_dict.udict_get?(256, hash);
|
||||
ifnot (found?) {
|
||||
entry = create_new_entry(cs);
|
||||
}
|
||||
} else {
|
||||
hash = cs.preload_uint(256);
|
||||
(entry, var found?) = vote_dict.udict_get?(256, hash);
|
||||
throw_unless(42, found?);
|
||||
}
|
||||
return vote_dict;
|
||||
}
|
||||
|
||||
() recv_external(slice in_msg) impure {
|
||||
var signature = in_msg~load_bits(512);
|
||||
var cs = in_msg;
|
||||
|
@ -93,36 +187,28 @@
|
|||
int msg_seqno = cs~load_uint(32);
|
||||
var valid_until = cs~load_uint(32);
|
||||
throw_if(35, valid_until < now());
|
||||
var cs2 = begin_parse(get_data());
|
||||
var cfg_dict = cs2~load_ref();
|
||||
var stored_seqno = cs2~load_uint(32);
|
||||
var public_key = cs2~load_uint(256);
|
||||
cs2.end_parse();
|
||||
var (cfg_dict, stored_seqno, public_key, vote_dict) = load_data();
|
||||
throw_unless(33, msg_seqno == stored_seqno);
|
||||
ifnot ((action - 0x566f7465) & -2) {
|
||||
var idx = cs~load_uint(16);
|
||||
var vdescr = get_validator_descr(idx);
|
||||
var (val_pubkey, weight) = unpack_validator_descr(vdescr);
|
||||
throw_unless(34, check_signature(slice_hash(in_msg), signature, val_pubkey));
|
||||
accept_message();
|
||||
stored_seqno += 1;
|
||||
store_data(cfg_dict, stored_seqno, public_key, vote_dict);
|
||||
commit();
|
||||
vote_dict = register_vote(vote_dict, action, cs, idx, weight);
|
||||
store_data(cfg_dict, stored_seqno, public_key, vote_dict);
|
||||
return ();
|
||||
}
|
||||
throw_unless(34, check_signature(slice_hash(in_msg), signature, public_key));
|
||||
accept_message();
|
||||
if (action == 0x43665021) {
|
||||
;; change one configuration parameter
|
||||
var param_index = cs~load_uint(32);
|
||||
var param_value = cs~load_ref();
|
||||
cs.end_parse();
|
||||
cfg_dict~idict_set_ref(32, param_index, param_value);
|
||||
} elseif (action == 0x4e436f64) {
|
||||
;; change configuration smart contract code
|
||||
var new_code = cs~load_ref();
|
||||
cs.end_parse();
|
||||
set_code(new_code);
|
||||
} elseif (action == 0x50624b21) {
|
||||
;; change configuration master public key
|
||||
public_key = cs~load_uint(256);
|
||||
cs.end_parse();
|
||||
} elseif (action == 0x4e43ef05) {
|
||||
;; change election smart contract code
|
||||
change_elector_code(cs);
|
||||
} else {
|
||||
throw_if(32, action);
|
||||
}
|
||||
set_data(begin_cell().store_ref(cfg_dict).store_uint(stored_seqno + 1, 32).store_uint(public_key, 256).end_cell());
|
||||
stored_seqno += 1;
|
||||
store_data(cfg_dict, stored_seqno, public_key, vote_dict);
|
||||
commit();
|
||||
(cfg_dict, public_key) = perform_action(cfg_dict, public_key, action, cs);
|
||||
store_data(cfg_dict, stored_seqno, public_key, vote_dict);
|
||||
}
|
||||
|
||||
() run_ticktock(int is_tock) impure {
|
||||
|
|
|
@ -408,7 +408,7 @@ _ compute_total_stake(l, n, m_stake) {
|
|||
return tot_stake;
|
||||
}
|
||||
|
||||
(cell, cell, cell, int, int) try_elect(credits, members, min_stake, max_stake, min_total_stake, max_stake_factor) {
|
||||
(cell, cell, int, cell, int, int) try_elect(credits, members, min_stake, max_stake, min_total_stake, max_stake_factor) {
|
||||
var cs = 16.config_param().begin_parse();
|
||||
var (max_validators, _, min_validators) = (cs~load_uint(16), cs~load_uint(16), cs~load_uint(16));
|
||||
cs.end_parse();
|
||||
|
@ -435,7 +435,7 @@ _ compute_total_stake(l, n, m_stake) {
|
|||
} until (~ f);
|
||||
n = min(n, max_validators);
|
||||
if (n < min_validators) {
|
||||
return (credits, new_dict(), new_dict(), 0, 0);
|
||||
return (credits, new_dict(), 0, new_dict(), 0, 0);
|
||||
}
|
||||
var l = nil;
|
||||
do {
|
||||
|
@ -464,7 +464,7 @@ _ compute_total_stake(l, n, m_stake) {
|
|||
}
|
||||
} until (i >= n);
|
||||
if ((m == 0) | (best_stake < min_total_stake)) {
|
||||
return (credits, new_dict(), new_dict(), 0, 0);
|
||||
return (credits, new_dict(), 0, new_dict(), 0, 0);
|
||||
}
|
||||
;; we have to select first m validators from list l
|
||||
l1 = touch(l);
|
||||
|
@ -476,6 +476,7 @@ _ compute_total_stake(l, n, m_stake) {
|
|||
;; create both the new validator set and the refund set
|
||||
int i = 0;
|
||||
var tot_stake = 0;
|
||||
var tot_weight = 0;
|
||||
var vset = new_dict();
|
||||
var frozen = new_dict();
|
||||
do {
|
||||
|
@ -492,6 +493,7 @@ _ compute_total_stake(l, n, m_stake) {
|
|||
;; validator_addr#73 public_key:SigPubKey weight:uint64 adnl_addr:bits256 = ValidatorDescr;
|
||||
var weight = (true_stake << 60) / best_stake;
|
||||
tot_stake += true_stake;
|
||||
tot_weight += weight;
|
||||
var vinfo = begin_cell()
|
||||
.store_uint(adnl_addr ? 0x73 : 0x53, 8) ;; validator_addr#73 or validator#53
|
||||
.store_uint(0x8e81278a, 32) ;; ed25519_pubkey#8e81278a
|
||||
|
@ -514,7 +516,7 @@ _ compute_total_stake(l, n, m_stake) {
|
|||
i += 1;
|
||||
} until (l.null?());
|
||||
throw_unless(49, tot_stake == best_stake);
|
||||
return (credits, vset, frozen, tot_stake, m);
|
||||
return (credits, vset, tot_weight, frozen, tot_stake, m);
|
||||
}
|
||||
|
||||
int conduct_elections(ds, elect, credits) impure {
|
||||
|
@ -545,7 +547,7 @@ int conduct_elections(ds, elect, credits) impure {
|
|||
;; elections finished
|
||||
return false;
|
||||
}
|
||||
(credits, var vdict, var frozen, var total_stakes, var cnt) = try_elect(credits, members, min_stake, max_stake, min_total_stake, max_stake_factor);
|
||||
(credits, var vdict, var total_weight, var frozen, var total_stakes, var cnt) = try_elect(credits, members, min_stake, max_stake, min_total_stake, max_stake_factor);
|
||||
;; pack elections; if cnt==0, set failed=true, finished=false.
|
||||
failed = (cnt == 0);
|
||||
finished = ~ failed;
|
||||
|
@ -561,12 +563,13 @@ int conduct_elections(ds, elect, credits) impure {
|
|||
var start = max(now() + elect_end_before - 60, elect_at);
|
||||
var main_validators = config_param(16).begin_parse().skip_bits(16).preload_uint(16);
|
||||
var vset = begin_cell()
|
||||
.store_uint(0x11, 8) ;; validators#11
|
||||
.store_uint(0x12, 8) ;; validators_ext#12
|
||||
.store_uint(start, 32) ;; utime_since:uint32
|
||||
.store_uint(start + elect_for, 32) ;; utime_until:uint32
|
||||
.store_uint(cnt, 16) ;; total:(## 16)
|
||||
.store_uint(min(cnt, main_validators), 16) ;; main:(## 16)
|
||||
.store_slice(vdict.begin_parse()) ;; list:(Hashmap 16 ValidatorDescr)
|
||||
.store_uint(min(cnt, main_validators), 16) ;; main:(## 16)
|
||||
.store_uint(total_weight, 64) ;; total_weight:uint64
|
||||
.store_dict(vdict) ;; list:(HashmapE 16 ValidatorDescr)
|
||||
.end_cell();
|
||||
var config_addr = config_param(0).begin_parse().preload_uint(256);
|
||||
send_validator_set_to_config(config_addr, vset, elect_at);
|
||||
|
|
|
@ -225,6 +225,22 @@ Masterchain swap
|
|||
"config-master" +suffix +".addr" save-address-verbose
|
||||
// Other data
|
||||
|
||||
/*
|
||||
*
|
||||
* Initial wallets (test)
|
||||
*
|
||||
*/
|
||||
|
||||
// pubkey amount `create-wallet1` or pubkey amount `create-wallet2`
|
||||
PK'PuZPPXK5Rff9SvtoS7Y9lUuEixvy-J6aishYFj3Qn6P0pJMb GR$100000000 create-wallet1
|
||||
PK'PuYiB1zAWzr4p8j6I681+sGUrRGcn6Ylf7vXl0xaUl/w6Xfg GR$1700000000 create-wallet0
|
||||
|
||||
/*
|
||||
*
|
||||
* Create state
|
||||
*
|
||||
*/
|
||||
|
||||
create_state
|
||||
cr cr ."new state is:" cr dup <s csr. cr
|
||||
dup 31 boc+>B dup Bx. cr
|
||||
|
|
265
crypto/smartcont/multisig-code.fc
Normal file
265
crypto/smartcont/multisig-code.fc
Normal file
|
@ -0,0 +1,265 @@
|
|||
;; Simple wallet smart contract
|
||||
|
||||
_ unpack_state() inline_ref {
|
||||
var ds = begin_parse(get_data());
|
||||
var res = (ds~load_uint(8), ds~load_uint(8), ds~load_uint(64), ds~load_dict(), ds~load_dict());
|
||||
ds.end_parse();
|
||||
return res;
|
||||
}
|
||||
|
||||
_ pack_state(cell pending_queries, cell public_keys, int last_cleaned, int k, int n) inline_ref {
|
||||
return begin_cell()
|
||||
.store_uint(n, 8)
|
||||
.store_uint(k, 8)
|
||||
.store_uint(last_cleaned, 64)
|
||||
.store_dict(public_keys)
|
||||
.store_dict(pending_queries)
|
||||
.end_cell();
|
||||
}
|
||||
|
||||
(int, int) check_signatures(cell public_keys, cell signatures, int hash, int cnt_bits) inline_ref {
|
||||
int cnt = 0;
|
||||
|
||||
do {
|
||||
slice cs = signatures.begin_parse();
|
||||
slice signature = cs~load_bits(512);
|
||||
|
||||
int i = cs~load_uint(8);
|
||||
signatures = cs~load_dict();
|
||||
|
||||
(slice public_key, var found?) = public_keys.udict_get?(8, i);
|
||||
throw_unless(37, found?);
|
||||
throw_unless(38, check_signature(hash, signature, public_key.preload_uint(256)));
|
||||
|
||||
int mask = (1 << i);
|
||||
int old_cnt_bits = cnt_bits;
|
||||
cnt_bits |= mask;
|
||||
int should_check = cnt_bits != old_cnt_bits;
|
||||
cnt -= should_check;
|
||||
} until (cell_null?(signatures));
|
||||
|
||||
return (cnt, cnt_bits);
|
||||
}
|
||||
|
||||
|
||||
() recv_internal(slice in_msg) impure {
|
||||
;; do nothing for internal messages
|
||||
}
|
||||
|
||||
(int, int, slice) unpack_query_data(slice in_msg, int n, slice query, var found?) inline_ref {
|
||||
if (found?) {
|
||||
throw_unless(35, query~load_int(1));
|
||||
(int cnt, int cnt_bits, slice msg) = (query~load_uint(8), query~load_uint(n), query);
|
||||
throw_unless(36, slice_hash(msg) == slice_hash(in_msg));
|
||||
return (cnt, cnt_bits, msg);
|
||||
}
|
||||
return (0, 0, in_msg);
|
||||
}
|
||||
|
||||
() try_init() impure inline_ref {
|
||||
;; first query without signatures is always accepted
|
||||
(int n, int k, int last_cleaned, cell public_keys, cell pending_queries) = unpack_state();
|
||||
throw_if(37, last_cleaned);
|
||||
accept_message();
|
||||
set_data(pack_state(pending_queries, public_keys, 1, k, n));
|
||||
}
|
||||
|
||||
cell update_pending_queries(cell pending_queries, slice msg, int query_id, int cnt, int cnt_bits, int n, int k) impure inline_ref {
|
||||
if (cnt >= k) {
|
||||
while (msg.slice_refs()) {
|
||||
var mode = msg~load_uint(8);
|
||||
send_raw_message(msg~load_ref(), mode);
|
||||
}
|
||||
pending_queries~udict_set_builder(64, query_id, begin_cell().store_int(0, 1));
|
||||
} else {
|
||||
pending_queries~udict_set_builder(64, query_id, begin_cell()
|
||||
.store_uint(1, 1)
|
||||
.store_uint(cnt, 8)
|
||||
.store_uint(cnt_bits, n)
|
||||
.store_slice(msg));
|
||||
}
|
||||
return pending_queries;
|
||||
}
|
||||
|
||||
() recv_external(slice in_msg) impure {
|
||||
;; empty message triggers init
|
||||
if (slice_empty?(in_msg)) {
|
||||
return try_init();
|
||||
}
|
||||
|
||||
;; Check root signature
|
||||
slice root_signature = in_msg~load_bits(512);
|
||||
int root_hash = slice_hash(in_msg);
|
||||
int root_i = in_msg~load_uint(8);
|
||||
|
||||
(int n, int k, int last_cleaned, cell public_keys, cell pending_queries) = unpack_state();
|
||||
last_cleaned -= last_cleaned == 0;
|
||||
|
||||
(slice public_key, var found?) = public_keys.udict_get?(8, root_i);
|
||||
throw_unless(31, found?);
|
||||
throw_unless(32, check_signature(root_hash, root_signature, public_key.preload_uint(256)));
|
||||
|
||||
cell signatures = in_msg~load_dict();
|
||||
|
||||
var hash = slice_hash(in_msg);
|
||||
int query_id = in_msg~load_uint(64);
|
||||
|
||||
var bound = (now() << 32);
|
||||
throw_if(33, query_id < bound);
|
||||
|
||||
(slice query, var found?) = pending_queries.udict_get?(64, query_id);
|
||||
(int cnt, int cnt_bits, slice msg) = unpack_query_data(in_msg, n, query, found?);
|
||||
int mask = 1 << root_i;
|
||||
throw_if(34, cnt_bits & mask);
|
||||
cnt_bits |= mask;
|
||||
cnt += 1;
|
||||
|
||||
;; TODO: reserve some gas or FAIL
|
||||
accept_message();
|
||||
|
||||
pending_queries = update_pending_queries(pending_queries, msg, query_id, cnt, cnt_bits, n, k);
|
||||
set_data(pack_state(pending_queries, public_keys, last_cleaned, k, n));
|
||||
|
||||
commit();
|
||||
|
||||
int need_save = 0;
|
||||
ifnot (cell_null?(signatures) | (cnt >= k)) {
|
||||
(int new_cnt, cnt_bits) = check_signatures(public_keys, signatures, hash, cnt_bits);
|
||||
cnt += new_cnt;
|
||||
pending_queries = update_pending_queries(pending_queries, msg, query_id, cnt, cnt_bits, n, k);
|
||||
need_save = -1;
|
||||
}
|
||||
|
||||
bound -= (64 << 32); ;; clean up records expired more than 64 seconds ago
|
||||
int old_last_cleaned = last_cleaned;
|
||||
do {
|
||||
var (pending_queries', i, _, f) = pending_queries.udict_delete_get_min(64);
|
||||
f~touch();
|
||||
if (f) {
|
||||
f = (i < bound);
|
||||
}
|
||||
if (f) {
|
||||
pending_queries = pending_queries';
|
||||
last_cleaned = i;
|
||||
need_save = -1;
|
||||
}
|
||||
} until (~ f);
|
||||
|
||||
if (need_save) {
|
||||
set_data(pack_state(pending_queries, public_keys, last_cleaned, k, n));
|
||||
}
|
||||
}
|
||||
|
||||
;; Get methods
|
||||
;; returns -1 for processed queries, 0 for unprocessed, 1 for unknown (forgotten)
|
||||
(int, int) get_query_state(int query_id) method_id {
|
||||
(int n, _, int last_cleaned, _, cell pending_queries) = unpack_state();
|
||||
(slice cs, var found) = pending_queries.udict_get?(64, query_id);
|
||||
if (found) {
|
||||
if (cs~load_int(1)) {
|
||||
cs~load_uint(8);
|
||||
return (0, cs~load_uint(n));
|
||||
} else {
|
||||
return (-1, 0);
|
||||
}
|
||||
} else {
|
||||
return (-(query_id <= last_cleaned), 0);
|
||||
}
|
||||
}
|
||||
|
||||
int processed?(int query_id) method_id {
|
||||
(int x, _) = get_query_state(query_id);
|
||||
return x;
|
||||
}
|
||||
|
||||
cell create_init_state(int n, int k, cell public_keys) method_id {
|
||||
return pack_state(new_dict(), public_keys, 0, k, n);
|
||||
}
|
||||
|
||||
cell merge_list(cell a, cell b) {
|
||||
if (cell_null?(a)) {
|
||||
return b;
|
||||
}
|
||||
if (cell_null?(b)) {
|
||||
return a;
|
||||
}
|
||||
slice as = a.begin_parse();
|
||||
if (as.slice_refs() != 0) {
|
||||
cell tail = merge_list(as~load_ref(), b);
|
||||
return begin_cell().store_slice(as).store_ref(tail).end_cell();
|
||||
}
|
||||
|
||||
as~skip_last_bits(1);
|
||||
;; as~skip_bits(1);
|
||||
return begin_cell().store_slice(as).store_dict(b).end_cell();
|
||||
|
||||
}
|
||||
|
||||
cell get_public_keys() method_id {
|
||||
(_, _, _, cell public_keys, _) = unpack_state();
|
||||
return public_keys;
|
||||
}
|
||||
|
||||
(int, int) check_query_signatures(cell query) method_id {
|
||||
slice cs = query.begin_parse();
|
||||
slice root_signature = cs~load_bits(512);
|
||||
int root_hash = slice_hash(cs);
|
||||
int root_i = cs~load_uint(8);
|
||||
|
||||
cell public_keys = get_public_keys();
|
||||
(slice public_key, var found?) = public_keys.udict_get?(8, root_i);
|
||||
throw_unless(31, found?);
|
||||
throw_unless(32, check_signature(root_hash, root_signature, public_key.preload_uint(256)));
|
||||
|
||||
int mask = 1 << root_i;
|
||||
|
||||
cell signatures = cs~load_dict();
|
||||
if (cell_null?(signatures)) {
|
||||
return (1, mask);
|
||||
}
|
||||
(int cnt, mask) = check_signatures(public_keys, signatures, slice_hash(cs), mask);
|
||||
return (cnt + 1, mask);
|
||||
}
|
||||
|
||||
cell messages_by_mask(int mask) method_id {
|
||||
(int n, _, _, _, cell pending_queries) = unpack_state();
|
||||
int i = -1;
|
||||
cell a = new_dict();
|
||||
do {
|
||||
(i, var cs, var f) = pending_queries.udict_get_next?(64, i);
|
||||
if (f) {
|
||||
if (cs~load_int(1)) {
|
||||
int cnt_bits = cs.skip_bits(8).preload_uint(n);
|
||||
if (cnt_bits & mask) {
|
||||
a~udict_set_builder(64, i, begin_cell().store_slice(cs));
|
||||
}
|
||||
}
|
||||
}
|
||||
} until (~ f);
|
||||
return a;
|
||||
}
|
||||
|
||||
cell get_messages_unsigned_by_id(int id) method_id {
|
||||
return messages_by_mask(1 << id);
|
||||
}
|
||||
|
||||
cell get_messages_unsigned() method_id {
|
||||
return messages_by_mask(~ 0);
|
||||
}
|
||||
|
||||
(int, int) get_n_k() method_id {
|
||||
(int n, int k, _, _, _) = unpack_state();
|
||||
return (n, k);
|
||||
}
|
||||
|
||||
cell merge_inner_queries(cell a, cell b) method_id {
|
||||
slice ca = a.begin_parse();
|
||||
slice cb = b.begin_parse();
|
||||
cell list_a = ca~load_dict();
|
||||
cell list_b = cb~load_dict();
|
||||
throw_unless(31, slice_hash(ca) == slice_hash(cb));
|
||||
return begin_cell()
|
||||
.store_dict(merge_list(list_a, list_b))
|
||||
.store_slice(ca)
|
||||
.end_cell();
|
||||
}
|
67
crypto/smartcont/simple-wallet-ext-code.fc
Normal file
67
crypto/smartcont/simple-wallet-ext-code.fc
Normal file
|
@ -0,0 +1,67 @@
|
|||
;; Simple wallet smart contract
|
||||
|
||||
cell create_state(int seqno, int public_key) {
|
||||
return begin_cell().store_uint(seqno, 32).store_uint(public_key, 256).end_cell();
|
||||
}
|
||||
|
||||
(int, int) load_state() {
|
||||
var cs2 = begin_parse(get_data());
|
||||
return (cs2~load_uint(32), cs2~load_uint(256));
|
||||
}
|
||||
|
||||
() save_state(int seqno, int public_key) impure {
|
||||
set_data(create_state(seqno, public_key));
|
||||
}
|
||||
|
||||
() recv_internal(slice in_msg) impure {
|
||||
;; do nothing for internal messages
|
||||
}
|
||||
|
||||
slice do_verify_message(slice in_msg, int seqno, int public_key) {
|
||||
var signature = in_msg~load_bits(512);
|
||||
var cs = in_msg;
|
||||
int msg_seqno = cs~load_uint(32);
|
||||
throw_unless(33, msg_seqno == seqno);
|
||||
throw_unless(34, check_signature(slice_hash(in_msg), signature, public_key));
|
||||
return cs;
|
||||
}
|
||||
|
||||
() recv_external(slice in_msg) impure {
|
||||
(int stored_seqno, int public_key) = load_state();
|
||||
var cs = do_verify_message(in_msg, stored_seqno, public_key);
|
||||
accept_message();
|
||||
cs~touch_slice();
|
||||
if (cs.slice_refs()) {
|
||||
var mode = cs~load_uint(8);
|
||||
send_raw_message(cs~load_ref(), mode);
|
||||
}
|
||||
cs.end_parse();
|
||||
save_state(stored_seqno + 1, public_key);
|
||||
}
|
||||
|
||||
;; Get methods
|
||||
|
||||
int seqno() method_id {
|
||||
return get_data().begin_parse().preload_uint(32);
|
||||
}
|
||||
|
||||
cell create_init_state(int public_key) method_id {
|
||||
return create_state(0, public_key);
|
||||
}
|
||||
|
||||
cell prepare_send_message_with_seqno(int mode, cell msg, int seqno) method_id {
|
||||
return begin_cell().store_uint(seqno, 32).store_uint(mode, 8).store_ref(msg).end_cell();
|
||||
}
|
||||
|
||||
cell prepare_send_message(int mode, cell msg) method_id {
|
||||
return prepare_send_message_with_seqno(mode, msg, seqno());
|
||||
}
|
||||
|
||||
|
||||
slice verify_message(slice msg) method_id {
|
||||
var (stored_seqno, public_key) = load_state();
|
||||
return do_verify_message(msg, stored_seqno, public_key);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -16,6 +16,7 @@ forall X -> X first(tuple t) asm "FIRST";
|
|||
forall X -> X second(tuple t) asm "SECOND";
|
||||
forall X -> X third(tuple t) asm "THIRD";
|
||||
forall X -> X fourth(tuple t) asm "3 INDEX";
|
||||
forall X -> X null() asm "PUSHNULL";
|
||||
|
||||
int now() asm "NOW";
|
||||
slice my_address() asm "MYADDR";
|
||||
|
@ -34,8 +35,10 @@ int check_data_signature(slice data, slice signature, int public_key) asm "CHKSI
|
|||
|
||||
cell get_data() asm "c4 PUSH";
|
||||
() set_data(cell c) impure asm "c4 POP";
|
||||
cell get_c3() impure asm "c3 PUSH";
|
||||
() set_c3(cell c) impure asm "c3 POP";
|
||||
() accept_message() impure asm "ACCEPT";
|
||||
() commit() impure asm "COMMIT";
|
||||
|
||||
int min(int x, int y) asm "MIN";
|
||||
int max(int x, int y) asm "MAX";
|
||||
|
@ -52,7 +55,10 @@ cell preload_ref(slice s) asm "PLDREF";
|
|||
;; slice preload_bits(slice s, int len) asm "PLDSLICEX";
|
||||
(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS";
|
||||
slice skip_bits(slice s, int len) asm "SDSKIPFIRST";
|
||||
(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST";
|
||||
slice first_bits(slice s, int len) asm "SDCUTFIRST";
|
||||
slice skip_last_bits(slice s, int len) asm "SDSKIPLAST";
|
||||
(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST";
|
||||
(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT";
|
||||
cell preload_dict(slice s) asm "PLDDICT";
|
||||
slice skip_dict(slice s) asm "SKIPDICT";
|
||||
|
@ -94,6 +100,16 @@ cell udict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "D
|
|||
(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT";
|
||||
(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT";
|
||||
(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT";
|
||||
cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET";
|
||||
(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET";
|
||||
cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET";
|
||||
(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET";
|
||||
cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET";
|
||||
(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET";
|
||||
(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD";
|
||||
(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE";
|
||||
(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD";
|
||||
(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE";
|
||||
cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB";
|
||||
(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB";
|
||||
cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB";
|
||||
|
|
99
crypto/smc-envelope/GenericAccount.cpp
Normal file
99
crypto/smc-envelope/GenericAccount.cpp
Normal file
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "GenericAccount.h"
|
||||
|
||||
#include "block/block-auto.h"
|
||||
#include "block/block-parse.h"
|
||||
namespace ton {
|
||||
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)
|
||||
.store_ones(2)
|
||||
.store_zeroes(1)
|
||||
.store_ref(std::move(code))
|
||||
.store_ref(std::move(data))
|
||||
.finalize();
|
||||
}
|
||||
block::StdAddress GenericAccount::get_address(ton::WorkchainId workchain_id,
|
||||
const td::Ref<vm::Cell>& init_state) noexcept {
|
||||
return block::StdAddress(workchain_id, init_state->get_hash().bits(), true /*bounce*/);
|
||||
}
|
||||
|
||||
void GenericAccount::store_int_message(vm::CellBuilder& cb, const block::StdAddress& dest_address, td::int64 gramms) {
|
||||
td::BigInt256 dest_addr;
|
||||
dest_addr.import_bits(dest_address.addr.as_bitslice());
|
||||
cb.store_zeroes(1)
|
||||
.store_ones(1)
|
||||
.store_long(dest_address.bounceable, 1)
|
||||
.store_zeroes(3)
|
||||
.store_ones(1)
|
||||
.store_zeroes(2)
|
||||
.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);
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> GenericAccount::create_ext_message(const block::StdAddress& address, td::Ref<vm::Cell> new_state,
|
||||
td::Ref<vm::Cell> body) noexcept {
|
||||
block::gen::Message::Record message;
|
||||
/*info*/ {
|
||||
block::gen::CommonMsgInfo::Record_ext_in_msg_info info;
|
||||
/* src */
|
||||
tlb::csr_pack(info.src, block::gen::MsgAddressExt::Record_addr_none{});
|
||||
/* dest */ {
|
||||
block::gen::MsgAddressInt::Record_addr_std dest;
|
||||
dest.anycast = vm::CellBuilder().store_zeroes(1).as_cellslice_ref();
|
||||
dest.workchain_id = address.workchain;
|
||||
dest.address = address.addr;
|
||||
|
||||
tlb::csr_pack(info.dest, dest);
|
||||
}
|
||||
/* import_fee */ {
|
||||
vm::CellBuilder cb;
|
||||
block::tlb::t_Grams.store_integer_value(cb, td::BigInt256(0));
|
||||
info.import_fee = cb.as_cellslice_ref();
|
||||
}
|
||||
|
||||
tlb::csr_pack(message.info, info);
|
||||
}
|
||||
/* init */ {
|
||||
if (new_state.not_null()) {
|
||||
// Just(Left(new_state))
|
||||
message.init = vm::CellBuilder()
|
||||
.store_ones(1)
|
||||
.store_zeroes(1)
|
||||
.append_cellslice(vm::load_cell_slice(new_state))
|
||||
.as_cellslice_ref();
|
||||
} else {
|
||||
message.init = vm::CellBuilder().store_zeroes(1).as_cellslice_ref();
|
||||
CHECK(message.init.not_null());
|
||||
}
|
||||
}
|
||||
/* body */ {
|
||||
message.body = vm::CellBuilder().store_zeroes(1).append_cellslice(vm::load_cell_slice_ref(body)).as_cellslice_ref();
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> res;
|
||||
tlb::type_pack_cell(res, block::gen::t_Message_Any, message);
|
||||
CHECK(res.not_null());
|
||||
|
||||
return res;
|
||||
}
|
||||
} // namespace ton
|
31
crypto/smc-envelope/GenericAccount.h
Normal file
31
crypto/smc-envelope/GenericAccount.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "vm/cells.h"
|
||||
#include "block/block.h"
|
||||
namespace ton {
|
||||
class GenericAccount {
|
||||
public:
|
||||
static td::Ref<vm::Cell> get_init_state(td::Ref<vm::Cell> code, td::Ref<vm::Cell> data) noexcept;
|
||||
static block::StdAddress get_address(ton::WorkchainId workchain_id, const td::Ref<vm::Cell>& init_state) noexcept;
|
||||
static td::Ref<vm::Cell> create_ext_message(const block::StdAddress& address, td::Ref<vm::Cell> new_state,
|
||||
td::Ref<vm::Cell> body) noexcept;
|
||||
static void store_int_message(vm::CellBuilder& cb, const block::StdAddress& dest_address, td::int64 gramms);
|
||||
};
|
||||
} // namespace ton
|
171
crypto/smc-envelope/MultisigWallet.cpp
Normal file
171
crypto/smc-envelope/MultisigWallet.cpp
Normal file
|
@ -0,0 +1,171 @@
|
|||
#include "MultisigWallet.h"
|
||||
|
||||
#include "SmartContractCode.h"
|
||||
|
||||
#include "vm/dict.h"
|
||||
|
||||
#include "td/utils/misc.h"
|
||||
|
||||
namespace ton {
|
||||
|
||||
MultisigWallet::QueryBuilder::QueryBuilder(td::int64 query_id, td::Ref<vm::Cell> msg, int mode) {
|
||||
msg_ = vm::CellBuilder().store_long(query_id, 64).store_long(mode, 8).store_ref(std::move(msg)).finalize();
|
||||
}
|
||||
void MultisigWallet::QueryBuilder::sign(td::int32 id, td::Ed25519::PrivateKey& pk) {
|
||||
CHECK(id < td::narrow_cast<td::int32>(mask_.size()));
|
||||
auto signature = pk.sign(msg_->get_hash().as_slice()).move_as_ok();
|
||||
mask_.set(id);
|
||||
vm::CellBuilder cb;
|
||||
cb.store_bytes(signature.as_slice());
|
||||
cb.store_long(id, 8);
|
||||
cb.ensure_throw(cb.store_maybe_ref(std::move(dict_)));
|
||||
dict_ = cb.finalize();
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> MultisigWallet::QueryBuilder::create_inner() const {
|
||||
vm::CellBuilder cb;
|
||||
cb.ensure_throw(cb.store_maybe_ref(dict_));
|
||||
return cb.append_cellslice(vm::load_cell_slice(msg_)).finalize();
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> MultisigWallet::QueryBuilder::create(td::int32 id, td::Ed25519::PrivateKey& pk) const {
|
||||
auto cell = create_inner();
|
||||
vm::CellBuilder cb;
|
||||
cb.store_long(id, 8);
|
||||
cb.append_cellslice(vm::load_cell_slice(cell));
|
||||
cell = cb.finalize();
|
||||
|
||||
auto signature = pk.sign(cell->get_hash().as_slice()).move_as_ok();
|
||||
vm::CellBuilder cb2;
|
||||
cb2.store_bytes(signature.as_slice());
|
||||
cb2.append_cellslice(vm::load_cell_slice(cell));
|
||||
return cb2.finalize();
|
||||
}
|
||||
|
||||
td::Ref<MultisigWallet> MultisigWallet::create(td::Ref<vm::Cell> data) {
|
||||
return td::Ref<MultisigWallet>(true, State{ton::SmartContractCode::multisig(), std::move(data)});
|
||||
}
|
||||
|
||||
int MultisigWallet::processed(td::uint64 query_id) const {
|
||||
auto res = run_get_method("processed?", {td::make_refint(query_id)});
|
||||
return res.stack.write().pop_smallint_range(1, -1);
|
||||
}
|
||||
|
||||
MultisigWallet::QueryState MultisigWallet::get_query_state(td::uint64 query_id) const {
|
||||
auto ans = run_get_method("get_query_state", {td::make_refint(query_id)});
|
||||
|
||||
auto mask = ans.stack.write().pop_int();
|
||||
auto state = ans.stack.write().pop_smallint_range(1, -1);
|
||||
|
||||
QueryState res;
|
||||
if (state == 1) {
|
||||
res.state = QueryState::Unknown;
|
||||
} else if (state == 0) {
|
||||
res.state = QueryState::NotReady;
|
||||
for (size_t i = 0; i < res.mask.size(); i++) {
|
||||
if (mask->get_bit(static_cast<int>(i))) {
|
||||
res.mask.set(i);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
res.state = QueryState::Sent;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<td::SecureString> MultisigWallet::get_public_keys() const {
|
||||
auto ans = run_get_method("get_public_keys");
|
||||
auto dict_root = ans.stack.write().pop_cell();
|
||||
vm::Dictionary dict(std::move(dict_root), 8);
|
||||
std::vector<td::SecureString> res;
|
||||
dict.check_for_each([&](auto cs, auto x, auto y) {
|
||||
td::SecureString key(32);
|
||||
cs->prefetch_bytes(key.as_mutable_slice().ubegin(), td::narrow_cast<int>(key.size()));
|
||||
res.push_back(std::move(key));
|
||||
return true;
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> MultisigWallet::create_init_data(std::vector<td::SecureString> public_keys, int k) const {
|
||||
vm::Dictionary pk(8);
|
||||
for (size_t i = 0; i < public_keys.size(); i++) {
|
||||
auto key = pk.integer_key(td::make_refint(i), 8, false);
|
||||
pk.set_builder(key.bits(), 8, vm::CellBuilder().store_bytes(public_keys[i].as_slice()));
|
||||
}
|
||||
auto res = run_get_method("create_init_state",
|
||||
{td::make_refint(public_keys.size()), td::make_refint(k), pk.get_root_cell()});
|
||||
CHECK(res.code == 0);
|
||||
return res.stack.write().pop_cell();
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> MultisigWallet::create_init_data_fast(std::vector<td::SecureString> public_keys, int k) {
|
||||
vm::Dictionary pk(8);
|
||||
for (size_t i = 0; i < public_keys.size(); i++) {
|
||||
auto key = pk.integer_key(td::make_refint(i), 8, false);
|
||||
pk.set_builder(key.bits(), 8, vm::CellBuilder().store_bytes(public_keys[i].as_slice()));
|
||||
}
|
||||
|
||||
vm::CellBuilder cb;
|
||||
cb.store_long(public_keys.size(), 8).store_long(k, 8).store_long(0, 64);
|
||||
cb.ensure_throw(cb.store_maybe_ref(pk.get_root_cell()));
|
||||
cb.ensure_throw(cb.store_maybe_ref({}));
|
||||
return cb.finalize();
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> MultisigWallet::merge_queries(td::Ref<vm::Cell> a, td::Ref<vm::Cell> b) const {
|
||||
auto res = run_get_method("merge_queries", {a, b});
|
||||
return res.stack.write().pop_cell();
|
||||
}
|
||||
|
||||
MultisigWallet::Mask MultisigWallet::to_mask(td::RefInt256 mask) const {
|
||||
Mask res_mask;
|
||||
for (size_t i = 0; i < res_mask.size(); i++) {
|
||||
if (mask->get_bit(static_cast<int>(i))) {
|
||||
res_mask.set(i);
|
||||
}
|
||||
}
|
||||
return res_mask;
|
||||
}
|
||||
|
||||
std::pair<int, MultisigWallet::Mask> MultisigWallet::check_query_signatures(td::Ref<vm::Cell> a) const {
|
||||
auto ans = run_get_method("check_query_signatures", {a});
|
||||
|
||||
auto mask = ans.stack.write().pop_int();
|
||||
auto cnt = ans.stack.write().pop_smallint_range(128);
|
||||
return std::make_pair(cnt, to_mask(mask));
|
||||
}
|
||||
|
||||
std::pair<int, int> MultisigWallet::get_n_k() const {
|
||||
auto ans = run_get_method("get_n_k");
|
||||
auto k = ans.stack.write().pop_smallint_range(128);
|
||||
auto n = ans.stack.write().pop_smallint_range(128);
|
||||
return std::make_pair(n, k);
|
||||
}
|
||||
|
||||
std::vector<MultisigWallet::Message> MultisigWallet::get_unsigned_messaged(int id) const {
|
||||
SmartContract::Answer ans;
|
||||
if (id == -1) {
|
||||
ans = run_get_method("get_messages_unsigned");
|
||||
} else {
|
||||
ans = run_get_method("get_messages_unsigned_by_id", {td::make_refint(id)});
|
||||
}
|
||||
auto n_k = get_n_k();
|
||||
|
||||
auto cell = ans.stack.write().pop_maybe_cell();
|
||||
vm::Dictionary dict(std::move(cell), 64);
|
||||
std::vector<Message> res;
|
||||
dict.check_for_each([&](auto cs, auto ptr, auto ptr_bits) {
|
||||
cs.write().skip_first(8);
|
||||
Message message;
|
||||
td::BigInt256 query_id;
|
||||
query_id.import_bits(ptr, ptr_bits, false);
|
||||
message.query_id = static_cast<td::uint64>(query_id.to_long());
|
||||
message.signed_by = to_mask(cs.write().fetch_int256(n_k.first, false));
|
||||
message.message = cs.write().fetch_ref();
|
||||
res.push_back(std::move(message));
|
||||
return true;
|
||||
});
|
||||
return res;
|
||||
}
|
||||
} // namespace ton
|
64
crypto/smc-envelope/MultisigWallet.h
Normal file
64
crypto/smc-envelope/MultisigWallet.h
Normal file
|
@ -0,0 +1,64 @@
|
|||
#pragma once
|
||||
#include "vm/cells.h"
|
||||
|
||||
#include "SmartContract.h"
|
||||
#include "Ed25519.h"
|
||||
|
||||
#include <bitset>
|
||||
|
||||
namespace ton {
|
||||
class MultisigWallet : public ton::SmartContract {
|
||||
public:
|
||||
MultisigWallet(State state) : SmartContract(std::move(state)) {
|
||||
}
|
||||
|
||||
using Mask = std::bitset<128>;
|
||||
struct QueryState {
|
||||
enum State { Unknown, NotReady, Sent } state = Unknown;
|
||||
Mask mask;
|
||||
};
|
||||
|
||||
class QueryBuilder {
|
||||
public:
|
||||
QueryBuilder(td::int64 query_id, td::Ref<vm::Cell> msg, int mode = 3);
|
||||
void sign(td::int32 id, td::Ed25519::PrivateKey& pk);
|
||||
|
||||
td::Ref<vm::Cell> create_inner() const;
|
||||
td::Ref<vm::Cell> create(td::int32 id, td::Ed25519::PrivateKey& pk) const;
|
||||
Mask get_mask() const {
|
||||
return mask_;
|
||||
}
|
||||
|
||||
private:
|
||||
vm::Ref<vm::Cell> dict_;
|
||||
td::Ref<vm::Cell> msg_;
|
||||
Mask mask_;
|
||||
};
|
||||
|
||||
MultisigWallet* make_copy() const override {
|
||||
return new MultisigWallet{state_};
|
||||
}
|
||||
|
||||
// creation
|
||||
static td::Ref<MultisigWallet> create(td::Ref<vm::Cell> data = {});
|
||||
|
||||
td::Ref<vm::Cell> create_init_data(std::vector<td::SecureString> public_keys, int k) const;
|
||||
static td::Ref<vm::Cell> create_init_data_fast(std::vector<td::SecureString> public_keys, int k);
|
||||
|
||||
// get methods
|
||||
int processed(td::uint64 query_id) const;
|
||||
QueryState get_query_state(td::uint64 query_id) const;
|
||||
std::vector<td::SecureString> get_public_keys() const;
|
||||
td::Ref<vm::Cell> merge_queries(td::Ref<vm::Cell> a, td::Ref<vm::Cell> b) const;
|
||||
std::pair<int, Mask> check_query_signatures(td::Ref<vm::Cell> a) const;
|
||||
std::pair<int, int> get_n_k() const;
|
||||
Mask to_mask(td::RefInt256 mask) const;
|
||||
|
||||
struct Message {
|
||||
td::uint64 query_id;
|
||||
Mask signed_by;
|
||||
td::Ref<vm::Cell> message;
|
||||
};
|
||||
std::vector<Message> get_unsigned_messaged(int id = -1) const;
|
||||
};
|
||||
} // namespace ton
|
188
crypto/smc-envelope/SmartContract.cpp
Normal file
188
crypto/smc-envelope/SmartContract.cpp
Normal file
|
@ -0,0 +1,188 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "SmartContract.h"
|
||||
|
||||
#include "GenericAccount.h"
|
||||
|
||||
#include "block/block.h"
|
||||
#include "block/block-auto.h"
|
||||
#include "vm/cellslice.h"
|
||||
#include "vm/cp0.h"
|
||||
#include "vm/continuation.h"
|
||||
|
||||
#include "td/utils/crypto.h"
|
||||
|
||||
namespace ton {
|
||||
namespace {
|
||||
td::int32 get_method_id(td::Slice method_name) {
|
||||
unsigned crc = td::crc16(method_name);
|
||||
return (crc & 0xffff) | 0x10000;
|
||||
}
|
||||
td::Ref<vm::Stack> prepare_vm_stack(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::RefInt256{true, 10000000000});
|
||||
stack.push_int(td::RefInt256{true, 10000000000});
|
||||
stack.push_cell(vm::CellBuilder().finalize());
|
||||
stack.push_cellslice(std::move(body));
|
||||
return stack_ref;
|
||||
}
|
||||
|
||||
td::Ref<vm::Tuple> prepare_vm_c7() {
|
||||
// TODO: fix initialization of c7
|
||||
td::BitArray<256> rand_seed;
|
||||
rand_seed.as_slice().fill(0);
|
||||
td::RefInt256 rand_seed_int{true};
|
||||
rand_seed_int.unique_write().import_bits(rand_seed.cbits(), 256, false);
|
||||
auto tuple = vm::make_tuple_ref(
|
||||
td::make_refint(0x076ef1ea), // [ magic:0x076ef1ea
|
||||
td::make_refint(0), // actions:Integer
|
||||
td::make_refint(0), // msgs_sent:Integer
|
||||
td::make_refint(0), // unixtime:Integer
|
||||
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)]
|
||||
vm::load_cell_slice_ref(vm::CellBuilder().finalize()) // myself:MsgAddressInt
|
||||
//vm::StackEntry::maybe(td::Ref<vm::Cell>())
|
||||
); // global_config:(Maybe Cell) ] = SmartContractInfo;
|
||||
//LOG(DEBUG) << "SmartContractInfo initialized with " << vm::StackEntry(tuple).to_string();
|
||||
return vm::make_tuple_ref(std::move(tuple));
|
||||
}
|
||||
|
||||
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;
|
||||
vm::init_op_cp0();
|
||||
vm::DictionaryBase::get_empty_dictionary();
|
||||
|
||||
class Logger : public td::LogInterface {
|
||||
public:
|
||||
void append(td::CSlice slice) override {
|
||||
res.append(slice.data(), slice.size());
|
||||
}
|
||||
std::string res;
|
||||
};
|
||||
Logger logger;
|
||||
vm::VmLog log{&logger, td::LogOptions::plain()};
|
||||
|
||||
if (GET_VERBOSITY_LEVEL() >= VERBOSITY_NAME(DEBUG)) {
|
||||
log.log_options.level = 4;
|
||||
log.log_options.fix_newlines = true;
|
||||
log.log_mask |= vm::VmLog::DumpStack;
|
||||
} else {
|
||||
log.log_options.level = 0;
|
||||
log.log_mask = 0;
|
||||
}
|
||||
|
||||
SmartContract::Answer res;
|
||||
vm::VmState vm{state.code, std::move(stack), gas, 1, state.data, log};
|
||||
vm.set_c7(std::move(c7));
|
||||
vm.set_chksig_always_succeed(ignore_chksig);
|
||||
try {
|
||||
res.code = ~vm.run();
|
||||
} catch (...) {
|
||||
LOG(FATAL) << "catch unhandled exception";
|
||||
}
|
||||
res.new_state = std::move(state);
|
||||
res.stack = vm.get_stack_ref();
|
||||
gas = vm.get_gas_limits();
|
||||
res.gas_used = gas.gas_consumed();
|
||||
res.accepted = gas.gas_credit == 0;
|
||||
res.success = (res.accepted && (unsigned)res.code <= 1);
|
||||
if (GET_VERBOSITY_LEVEL() >= VERBOSITY_NAME(DEBUG)) {
|
||||
LOG(DEBUG) << "VM log\n" << logger.res;
|
||||
std::ostringstream os;
|
||||
res.stack->dump(os);
|
||||
LOG(DEBUG) << "VM stack:\n" << os.str();
|
||||
LOG(DEBUG) << "VM exit code: " << res.code;
|
||||
LOG(DEBUG) << "VM accepted: " << res.accepted;
|
||||
LOG(DEBUG) << "VM success: " << res.success;
|
||||
}
|
||||
if (res.success) {
|
||||
res.new_state.data = vm.get_c4();
|
||||
res.actions = vm.get_d(5);
|
||||
}
|
||||
LOG_IF(ERROR, gas_credit != 0 && (res.accepted && !res.success))
|
||||
<< "Accepted but failed with code " << res.code << "\n"
|
||||
<< res.gas_used << "\n";
|
||||
return res;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
td::Ref<vm::CellSlice> SmartContract::empty_slice() {
|
||||
return vm::load_cell_slice_ref(vm::CellBuilder().finalize());
|
||||
}
|
||||
|
||||
size_t SmartContract::code_size() const {
|
||||
return vm::std_boc_serialize(state_.code).ok().size();
|
||||
}
|
||||
size_t SmartContract::data_size() const {
|
||||
return vm::std_boc_serialize(state_.data).ok().size();
|
||||
}
|
||||
|
||||
block::StdAddress SmartContract::get_address(WorkchainId workchain_id) const {
|
||||
return GenericAccount::get_address(workchain_id, get_init_state());
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> SmartContract::get_init_state() const {
|
||||
return GenericAccount::get_init_state(get_state().code, get_state().data);
|
||||
}
|
||||
|
||||
SmartContract::Answer SmartContract::run_method(Args args) {
|
||||
if (!args.c7) {
|
||||
args.c7 = prepare_vm_c7();
|
||||
}
|
||||
if (!args.limits) {
|
||||
args.limits = vm::GasLimits{(long long)0, (long long)1000000, (long long)10000};
|
||||
}
|
||||
CHECK(args.stack);
|
||||
CHECK(args.method_id);
|
||||
args.stack.value().write().push_smallint(args.method_id.unwrap());
|
||||
auto res =
|
||||
run_smartcont(get_state(), args.stack.unwrap(), args.c7.unwrap(), args.limits.unwrap(), args.ignore_chksig);
|
||||
state_ = res.new_state;
|
||||
return res;
|
||||
}
|
||||
|
||||
SmartContract::Answer SmartContract::run_get_method(Args args) const {
|
||||
if (!args.c7) {
|
||||
args.c7 = prepare_vm_c7();
|
||||
}
|
||||
if (!args.limits) {
|
||||
args.limits = vm::GasLimits{1000000};
|
||||
}
|
||||
if (!args.stack) {
|
||||
args.stack = td::Ref<vm::Stack>(true);
|
||||
}
|
||||
CHECK(args.method_id);
|
||||
args.stack.value().write().push_smallint(args.method_id.unwrap());
|
||||
return run_smartcont(get_state(), args.stack.unwrap(), args.c7.unwrap(), args.limits.unwrap(), args.ignore_chksig);
|
||||
}
|
||||
|
||||
SmartContract::Answer SmartContract::run_get_method(td::Slice method, Args args) const {
|
||||
return run_get_method(args.set_method_id(method));
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
} // namespace ton
|
116
crypto/smc-envelope/SmartContract.h
Normal file
116
crypto/smc-envelope/SmartContract.h
Normal file
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "vm/cells.h"
|
||||
#include "vm/stack.hpp"
|
||||
#include "vm/continuation.h"
|
||||
|
||||
#include "td/utils/optional.h"
|
||||
#include "td/utils/crypto.h"
|
||||
|
||||
#include "block/block.h"
|
||||
|
||||
namespace ton {
|
||||
class SmartContract : public td::CntObject {
|
||||
static td::Ref<vm::CellSlice> empty_slice();
|
||||
|
||||
public:
|
||||
struct State {
|
||||
td::Ref<vm::Cell> code;
|
||||
td::Ref<vm::Cell> data;
|
||||
};
|
||||
|
||||
SmartContract(State state) : state_(std::move(state)) {
|
||||
}
|
||||
|
||||
struct Answer {
|
||||
SmartContract::State new_state;
|
||||
bool accepted;
|
||||
bool success;
|
||||
td::Ref<vm::Stack> stack;
|
||||
td::Ref<vm::Cell> actions;
|
||||
td::int32 code;
|
||||
td::int64 gas_used;
|
||||
};
|
||||
|
||||
struct Args {
|
||||
td::optional<td::int32> method_id;
|
||||
td::optional<vm::GasLimits> limits;
|
||||
td::optional<td::Ref<vm::Tuple>> c7;
|
||||
td::optional<td::Ref<vm::Stack>> stack;
|
||||
bool ignore_chksig{false};
|
||||
|
||||
Args() {
|
||||
}
|
||||
Args(std::initializer_list<vm::StackEntry> stack)
|
||||
: stack(td::Ref<vm::Stack>(true, std::vector<vm::StackEntry>(std::move(stack)))) {
|
||||
}
|
||||
Args&& set_method_id(td::Slice method_name) {
|
||||
unsigned crc = td::crc16(method_name);
|
||||
return set_method_id((crc & 0xffff) | 0x10000);
|
||||
}
|
||||
Args&& set_method_id(td::int32 method_id) {
|
||||
this->method_id = method_id;
|
||||
return std::move(*this);
|
||||
}
|
||||
Args&& set_limits(vm::GasLimits limits) {
|
||||
this->limits = std::move(limits);
|
||||
return std::move(*this);
|
||||
}
|
||||
Args&& set_c7(td::Ref<vm::Tuple> c7) {
|
||||
this->c7 = std::move(c7);
|
||||
return std::move(*this);
|
||||
}
|
||||
Args&& set_stack(std::vector<vm::StackEntry> stack) {
|
||||
this->stack = td::Ref<vm::Stack>(true, std::move(stack));
|
||||
return std::move(*this);
|
||||
}
|
||||
Args&& set_stack(td::Ref<vm::Stack> stack) {
|
||||
this->stack = std::move(stack);
|
||||
return std::move(*this);
|
||||
}
|
||||
Args&& set_ignore_chksig(bool ignore_chksig) {
|
||||
this->ignore_chksig = ignore_chksig;
|
||||
return std::move(*this);
|
||||
}
|
||||
};
|
||||
|
||||
Answer run_method(Args args = {});
|
||||
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 = {});
|
||||
|
||||
size_t code_size() const;
|
||||
size_t data_size() const;
|
||||
static td::Ref<SmartContract> create(State state) {
|
||||
return td::Ref<SmartContract>{true, std::move(state)};
|
||||
}
|
||||
|
||||
block::StdAddress get_address(WorkchainId workchain_id = basechainId) const;
|
||||
td::Ref<vm::Cell> get_init_state() const;
|
||||
|
||||
const State& get_state() const {
|
||||
return state_;
|
||||
}
|
||||
|
||||
protected:
|
||||
State state_;
|
||||
};
|
||||
} // namespace ton
|
72
crypto/smc-envelope/SmartContractCode.cpp
Normal file
72
crypto/smc-envelope/SmartContractCode.cpp
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "SmartContractCode.h"
|
||||
|
||||
#include "vm/boc.h"
|
||||
#include <map>
|
||||
|
||||
#include "td/utils/base64.h"
|
||||
|
||||
namespace ton {
|
||||
namespace {
|
||||
const auto& get_map() {
|
||||
static auto map = [] {
|
||||
class Cmp : public std::less<> {
|
||||
public:
|
||||
using is_transparent = void;
|
||||
};
|
||||
std::map<std::string, td::Ref<vm::Cell>, Cmp> map;
|
||||
auto with_tvm_code = [&](auto name, td::Slice code_str) {
|
||||
map[name] = vm::std_boc_deserialize(td::base64_decode(code_str).move_as_ok()).move_as_ok();
|
||||
};
|
||||
#include "smartcont/auto/multisig-code.cpp"
|
||||
#include "smartcont/auto/simple-wallet-ext-code.cpp"
|
||||
#include "smartcont/auto/simple-wallet-code.cpp"
|
||||
#include "smartcont/auto/wallet-code.cpp"
|
||||
return map;
|
||||
}();
|
||||
return map;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
td::Result<td::Ref<vm::Cell>> SmartContractCode::load(td::Slice name) {
|
||||
auto& map = get_map();
|
||||
auto it = map.find(name);
|
||||
if (it == map.end()) {
|
||||
return td::Status::Error(PSLICE() << "Can't load td::ref<vm::cell " << name);
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
td::Ref<vm::Cell> SmartContractCode::multisig() {
|
||||
auto res = load("multisig").move_as_ok();
|
||||
return res;
|
||||
}
|
||||
td::Ref<vm::Cell> SmartContractCode::wallet() {
|
||||
auto res = load("wallet").move_as_ok();
|
||||
return res;
|
||||
}
|
||||
td::Ref<vm::Cell> SmartContractCode::simple_wallet() {
|
||||
auto res = load("simple-wallet").move_as_ok();
|
||||
return res;
|
||||
}
|
||||
td::Ref<vm::Cell> SmartContractCode::simple_wallet_ext() {
|
||||
static auto res = load("simple-wallet-ext").move_as_ok();
|
||||
return res;
|
||||
}
|
||||
} // namespace ton
|
30
crypto/smc-envelope/SmartContractCode.h
Normal file
30
crypto/smc-envelope/SmartContractCode.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "vm/cells.h"
|
||||
|
||||
namespace ton {
|
||||
class SmartContractCode {
|
||||
public:
|
||||
static td::Result<td::Ref<vm::Cell>> load(td::Slice name);
|
||||
static td::Ref<vm::Cell> multisig();
|
||||
static td::Ref<vm::Cell> wallet();
|
||||
static td::Ref<vm::Cell> simple_wallet();
|
||||
static td::Ref<vm::Cell> simple_wallet_ext();
|
||||
};
|
||||
} // namespace ton
|
62
crypto/smc-envelope/TestGiver.cpp
Normal file
62
crypto/smc-envelope/TestGiver.cpp
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "TestGiver.h"
|
||||
#include "GenericAccount.h"
|
||||
|
||||
#include "td/utils/base64.h"
|
||||
|
||||
namespace ton {
|
||||
const block::StdAddress& TestGiver::address() noexcept {
|
||||
static block::StdAddress res =
|
||||
block::StdAddress::parse("kf_8uRo6OBbQ97jCx2EIuKm8Wmt6Vb15-KsQHFLbKSMiYIny").move_as_ok();
|
||||
//static block::StdAddress res =
|
||||
//block::StdAddress::parse("kf9tswzQaryeJ4aAYLy_phLhx4afF1aEvpUVak-2BuA0CmZi").move_as_ok();
|
||||
return res;
|
||||
}
|
||||
|
||||
vm::CellHash TestGiver::get_init_code_hash() noexcept {
|
||||
return vm::CellHash::from_slice(td::base64_decode("wDkZp0yR4xo+9+BnuAPfGVjBzK6FPzqdv2DwRq3z3KE=").move_as_ok());
|
||||
//return vm::CellHash::from_slice(td::base64_decode("YV/IANhoI22HVeatFh6S5LbCHp+5OilARfzW+VQPZgQ=").move_as_ok());
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> TestGiver::make_a_gift_message(td::uint32 seqno, td::uint64 gramms, td::Slice message,
|
||||
const block::StdAddress& dest_address) noexcept {
|
||||
vm::CellBuilder cb;
|
||||
GenericAccount::store_int_message(cb, dest_address, gramms);
|
||||
cb.store_bytes("\0\0\0\0", 4);
|
||||
vm::CellString::store(cb, message, 35 * 8).ensure();
|
||||
auto message_inner = cb.finalize();
|
||||
return vm::CellBuilder().store_long(seqno, 32).store_long(1, 8).store_ref(message_inner).finalize();
|
||||
}
|
||||
|
||||
td::Result<td::uint32> TestGiver::get_seqno() const {
|
||||
return TRY_VM(get_seqno_or_throw());
|
||||
}
|
||||
|
||||
td::Result<td::uint32> TestGiver::get_seqno_or_throw() const {
|
||||
if (state_.data.is_null()) {
|
||||
return 0;
|
||||
}
|
||||
auto seqno = vm::load_cell_slice(state_.data).fetch_ulong(32);
|
||||
if (seqno == vm::CellSlice::fetch_ulong_eof) {
|
||||
return td::Status::Error("Failed to parse seq_no");
|
||||
}
|
||||
return static_cast<td::uint32>(seqno);
|
||||
}
|
||||
} // namespace ton
|
39
crypto/smc-envelope/TestGiver.h
Normal file
39
crypto/smc-envelope/TestGiver.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "SmartContract.h"
|
||||
#include "block/block.h"
|
||||
#include "vm/cells/CellString.h"
|
||||
namespace ton {
|
||||
class TestGiver : public SmartContract {
|
||||
public:
|
||||
explicit TestGiver(State state) : ton::SmartContract(std::move(state)) {
|
||||
}
|
||||
static constexpr unsigned max_message_size = vm::CellString::max_bytes;
|
||||
static const block::StdAddress& address() noexcept;
|
||||
static vm::CellHash get_init_code_hash() noexcept;
|
||||
static td::Ref<vm::Cell> make_a_gift_message(td::uint32 seqno, td::uint64 gramms, td::Slice message,
|
||||
const block::StdAddress& dest_address) noexcept;
|
||||
|
||||
td::Result<td::uint32> get_seqno() const;
|
||||
|
||||
private:
|
||||
td::Result<td::uint32> get_seqno_or_throw() const;
|
||||
};
|
||||
} // namespace ton
|
91
crypto/smc-envelope/TestWallet.cpp
Normal file
91
crypto/smc-envelope/TestWallet.cpp
Normal file
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "TestWallet.h"
|
||||
#include "GenericAccount.h"
|
||||
|
||||
#include "vm/boc.h"
|
||||
#include "td/utils/base64.h"
|
||||
|
||||
namespace ton {
|
||||
td::Ref<vm::Cell> TestWallet::get_init_state(const td::Ed25519::PublicKey& public_key) noexcept {
|
||||
auto code = get_init_code();
|
||||
auto data = get_init_data(public_key);
|
||||
return GenericAccount::get_init_state(std::move(code), std::move(data));
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> TestWallet::get_init_message(const td::Ed25519::PrivateKey& private_key) noexcept {
|
||||
std::string seq_no(4, 0);
|
||||
auto signature =
|
||||
private_key.sign(vm::CellBuilder().store_bytes(seq_no).finalize()->get_hash().as_slice()).move_as_ok();
|
||||
return vm::CellBuilder().store_bytes(signature).store_bytes(seq_no).finalize();
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> TestWallet::make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 seqno,
|
||||
td::int64 gramms, td::Slice message,
|
||||
const block::StdAddress& dest_address) noexcept {
|
||||
td::int32 send_mode = 3;
|
||||
if (gramms == -1) {
|
||||
gramms = 0;
|
||||
send_mode += 128;
|
||||
}
|
||||
vm::CellBuilder cb;
|
||||
GenericAccount::store_int_message(cb, dest_address, gramms);
|
||||
cb.store_bytes("\0\0\0\0", 4);
|
||||
vm::CellString::store(cb, message, 35 * 8).ensure();
|
||||
auto message_inner = cb.finalize();
|
||||
auto message_outer =
|
||||
vm::CellBuilder().store_long(seqno, 32).store_long(send_mode, 8).store_ref(message_inner).finalize();
|
||||
auto signature = private_key.sign(message_outer->get_hash().as_slice()).move_as_ok();
|
||||
return vm::CellBuilder().store_bytes(signature).append_cellslice(vm::load_cell_slice(message_outer)).finalize();
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> TestWallet::get_init_code() noexcept {
|
||||
static auto res = [] {
|
||||
auto serialized_code = td::base64_decode(
|
||||
"te6ccgEEAQEAAAAAUwAAov8AIN0gggFMl7qXMO1E0NcLH+Ck8mCBAgDXGCDXCx/tRNDTH9P/"
|
||||
"0VESuvKhIvkBVBBE+RDyovgAAdMfMSDXSpbTB9QC+wDe0aTIyx/L/8ntVA==")
|
||||
.move_as_ok();
|
||||
return vm::std_boc_deserialize(serialized_code).move_as_ok();
|
||||
}();
|
||||
return res;
|
||||
}
|
||||
|
||||
vm::CellHash TestWallet::get_init_code_hash() noexcept {
|
||||
return get_init_code()->get_hash();
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> TestWallet::get_init_data(const td::Ed25519::PublicKey& public_key) noexcept {
|
||||
return vm::CellBuilder().store_long(0, 32).store_bytes(public_key.as_octet_string()).finalize();
|
||||
}
|
||||
|
||||
td::Result<td::uint32> TestWallet::get_seqno() const {
|
||||
return TRY_VM(get_seqno_or_throw());
|
||||
}
|
||||
|
||||
td::Result<td::uint32> TestWallet::get_seqno_or_throw() const {
|
||||
if (state_.data.is_null()) {
|
||||
return 0;
|
||||
}
|
||||
auto seqno = vm::load_cell_slice(state_.data).fetch_ulong(32);
|
||||
if (seqno == vm::CellSlice::fetch_ulong_eof) {
|
||||
return td::Status::Error("Failed to parse seq_no");
|
||||
}
|
||||
return static_cast<td::uint32>(seqno);
|
||||
}
|
||||
|
||||
} // namespace ton
|
48
crypto/smc-envelope/TestWallet.h
Normal file
48
crypto/smc-envelope/TestWallet.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "smc-envelope/SmartContract.h"
|
||||
#include "vm/cells.h"
|
||||
#include "Ed25519.h"
|
||||
#include "block/block.h"
|
||||
#include "vm/cells/CellString.h"
|
||||
|
||||
namespace ton {
|
||||
class TestWallet : public ton::SmartContract {
|
||||
public:
|
||||
explicit TestWallet(State state) : ton::SmartContract(std::move(state)) {
|
||||
}
|
||||
static constexpr unsigned max_message_size = vm::CellString::max_bytes;
|
||||
static td::Ref<vm::Cell> get_init_state(const td::Ed25519::PublicKey& public_key) noexcept;
|
||||
static td::Ref<vm::Cell> get_init_message(const td::Ed25519::PrivateKey& private_key) noexcept;
|
||||
static td::Ref<vm::Cell> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 seqno,
|
||||
td::int64 gramms, td::Slice message,
|
||||
const block::StdAddress& dest_address) noexcept;
|
||||
|
||||
static td::Ref<vm::Cell> get_init_code() noexcept;
|
||||
static vm::CellHash get_init_code_hash() noexcept;
|
||||
static td::Ref<vm::Cell> get_init_data(const td::Ed25519::PublicKey& public_key) noexcept;
|
||||
|
||||
td::Result<td::uint32> get_seqno() const;
|
||||
|
||||
private:
|
||||
td::Result<td::uint32> get_seqno_or_throw() const;
|
||||
};
|
||||
} // namespace ton
|
100
crypto/smc-envelope/Wallet.cpp
Normal file
100
crypto/smc-envelope/Wallet.cpp
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "Wallet.h"
|
||||
#include "GenericAccount.h"
|
||||
|
||||
#include "vm/boc.h"
|
||||
#include "vm/cells/CellString.h"
|
||||
#include "td/utils/base64.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace ton {
|
||||
td::Ref<vm::Cell> Wallet::get_init_state(const td::Ed25519::PublicKey& public_key) noexcept {
|
||||
auto code = get_init_code();
|
||||
auto data = get_init_data(public_key);
|
||||
return GenericAccount::get_init_state(std::move(code), std::move(data));
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> Wallet::get_init_message(const td::Ed25519::PrivateKey& private_key) noexcept {
|
||||
td::uint32 seqno = 0;
|
||||
td::uint32 valid_until = std::numeric_limits<td::uint32>::max();
|
||||
auto signature =
|
||||
private_key
|
||||
.sign(vm::CellBuilder().store_long(seqno, 32).store_long(valid_until, 32).finalize()->get_hash().as_slice())
|
||||
.move_as_ok();
|
||||
return vm::CellBuilder().store_bytes(signature).store_long(seqno, 32).store_long(valid_until, 32).finalize();
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> Wallet::make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 seqno,
|
||||
td::uint32 valid_until, td::int64 gramms, td::Slice message,
|
||||
const block::StdAddress& dest_address) noexcept {
|
||||
td::int32 send_mode = 3;
|
||||
if (gramms == -1) {
|
||||
gramms = 0;
|
||||
send_mode += 128;
|
||||
}
|
||||
vm::CellBuilder cb;
|
||||
GenericAccount::store_int_message(cb, dest_address, gramms);
|
||||
cb.store_bytes("\0\0\0\0", 4);
|
||||
vm::CellString::store(cb, message, 35 * 8).ensure();
|
||||
auto message_inner = cb.finalize();
|
||||
|
||||
auto message_outer = vm::CellBuilder()
|
||||
.store_long(seqno, 32)
|
||||
.store_long(valid_until, 32)
|
||||
.store_long(send_mode, 8)
|
||||
.store_ref(message_inner)
|
||||
.finalize();
|
||||
auto signature = private_key.sign(message_outer->get_hash().as_slice()).move_as_ok();
|
||||
return vm::CellBuilder().store_bytes(signature).append_cellslice(vm::load_cell_slice(message_outer)).finalize();
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> Wallet::get_init_code() noexcept {
|
||||
static auto res = [] {
|
||||
auto serialized_code = td::base64_decode(
|
||||
"te6ccgEEAQEAAAAAVwAAqv8AIN0gggFMl7qXMO1E0NcLH+Ck8mCDCNcYINMf0x8B+CO78mPtRNDTH9P/"
|
||||
"0VExuvKhA/kBVBBC+RDyovgAApMg10qW0wfUAvsA6NGkyMsfy//J7VQ=")
|
||||
.move_as_ok();
|
||||
return vm::std_boc_deserialize(serialized_code).move_as_ok();
|
||||
}();
|
||||
return res;
|
||||
}
|
||||
|
||||
vm::CellHash Wallet::get_init_code_hash() noexcept {
|
||||
return get_init_code()->get_hash();
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> Wallet::get_init_data(const td::Ed25519::PublicKey& public_key) noexcept {
|
||||
return vm::CellBuilder().store_long(0, 32).store_bytes(public_key.as_octet_string()).finalize();
|
||||
}
|
||||
|
||||
td::Result<td::uint32> Wallet::get_seqno() const {
|
||||
return TRY_VM(get_seqno_or_throw());
|
||||
}
|
||||
|
||||
td::Result<td::uint32> Wallet::get_seqno_or_throw() const {
|
||||
if (state_.data.is_null()) {
|
||||
return 0;
|
||||
}
|
||||
//FIXME use get method
|
||||
return static_cast<td::uint32>(vm::load_cell_slice(state_.data).fetch_ulong(32));
|
||||
}
|
||||
|
||||
} // namespace ton
|
48
crypto/smc-envelope/Wallet.h
Normal file
48
crypto/smc-envelope/Wallet.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "smc-envelope/SmartContract.h"
|
||||
#include "vm/cells.h"
|
||||
#include "Ed25519.h"
|
||||
#include "block/block.h"
|
||||
#include "vm/cells/CellString.h"
|
||||
|
||||
namespace ton {
|
||||
class Wallet : ton::SmartContract {
|
||||
public:
|
||||
explicit Wallet(State state) : ton::SmartContract(std::move(state)) {
|
||||
}
|
||||
static constexpr unsigned max_message_size = vm::CellString::max_bytes;
|
||||
static td::Ref<vm::Cell> get_init_state(const td::Ed25519::PublicKey& public_key) noexcept;
|
||||
static td::Ref<vm::Cell> get_init_message(const td::Ed25519::PrivateKey& private_key) noexcept;
|
||||
static td::Ref<vm::Cell> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 seqno,
|
||||
td::uint32 valid_until, td::int64 gramms, td::Slice message,
|
||||
const block::StdAddress& dest_address) noexcept;
|
||||
|
||||
static td::Ref<vm::Cell> get_init_code() noexcept;
|
||||
static vm::CellHash get_init_code_hash() noexcept;
|
||||
static td::Ref<vm::Cell> get_init_data(const td::Ed25519::PublicKey& public_key) noexcept;
|
||||
|
||||
td::Result<td::uint32> get_seqno() const;
|
||||
|
||||
private:
|
||||
td::Result<td::uint32> get_seqno_or_throw() const;
|
||||
};
|
||||
} // namespace ton
|
|
@ -21,8 +21,12 @@
|
|||
#include "td/utils/misc.h"
|
||||
#include "td/utils/Slice.h"
|
||||
#include "td/utils/tests.h"
|
||||
#include "td/utils/JsonBuilder.h"
|
||||
|
||||
#include "wycheproof.h"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
unsigned char fixed_privkey[32] = "abacabadabacabaeabacabadabacaba";
|
||||
unsigned char fixed_pubkey[32] = {0x6f, 0x9e, 0x5b, 0xde, 0xce, 0x87, 0x21, 0xeb, 0x57, 0x37, 0xfb,
|
||||
|
@ -142,3 +146,74 @@ TEST(Crypto, ed25519) {
|
|||
assert(!std::memcmp(secret12, secret21, 32));
|
||||
*/
|
||||
}
|
||||
|
||||
TEST(Crypto, wycheproof) {
|
||||
std::vector<std::pair<std::string, std::string>> bad_tests;
|
||||
auto json_str = wycheproof_ed25519();
|
||||
auto value = td::json_decode(json_str).move_as_ok();
|
||||
auto &root = value.get_object();
|
||||
auto test_groups_o = get_json_object_field(root, "testGroups", td::JsonValue::Type::Array, false).move_as_ok();
|
||||
auto &test_groups = test_groups_o.get_array();
|
||||
auto from_hexc = [](char c) {
|
||||
if (c >= '0' && c <= '9') {
|
||||
return c - '0';
|
||||
}
|
||||
return c - 'a' + 10;
|
||||
};
|
||||
auto from_hex = [&](td::Slice s) {
|
||||
CHECK(s.size() % 2 == 0);
|
||||
std::string res(s.size() / 2, 0);
|
||||
for (size_t i = 0; i < s.size(); i += 2) {
|
||||
res[i / 2] = char(from_hexc(s[i]) * 16 + from_hexc(s[i + 1]));
|
||||
}
|
||||
return res;
|
||||
};
|
||||
for (auto &test_o : test_groups) {
|
||||
auto &test = test_o.get_object();
|
||||
auto key_o = get_json_object_field(test, "key", td::JsonValue::Type::Object, false).move_as_ok();
|
||||
auto sk_str = td::get_json_object_string_field(key_o.get_object(), "sk", false).move_as_ok();
|
||||
auto pk_str = td::get_json_object_string_field(key_o.get_object(), "pk", false).move_as_ok();
|
||||
auto pk = td::Ed25519::PublicKey(td::SecureString(from_hex(pk_str)));
|
||||
auto sk = td::Ed25519::PrivateKey(td::SecureString(from_hex(sk_str)));
|
||||
CHECK(sk.get_public_key().move_as_ok().as_octet_string().as_slice() == pk.as_octet_string().as_slice());
|
||||
|
||||
//auto key =
|
||||
//td::Ed25519::PrivateKey::from_pem(
|
||||
//td::SecureString(td::get_json_object_string_field(test, "keyPem", false).move_as_ok()), td::SecureString())
|
||||
//.move_as_ok();
|
||||
|
||||
auto tests_o = get_json_object_field(test, "tests", td::JsonValue::Type::Array, false).move_as_ok();
|
||||
auto &tests = tests_o.get_array();
|
||||
for (auto &test_o : tests) {
|
||||
auto &test = test_o.get_object();
|
||||
auto id = td::get_json_object_string_field(test, "tcId", false).move_as_ok();
|
||||
auto comment = td::get_json_object_string_field(test, "comment", false).move_as_ok();
|
||||
auto sig = from_hex(td::get_json_object_string_field(test, "sig", false).move_as_ok());
|
||||
auto msg = from_hex(td::get_json_object_string_field(test, "msg", false).move_as_ok());
|
||||
auto result = td::get_json_object_string_field(test, "result", false).move_as_ok();
|
||||
auto has_result = pk.verify_signature(msg, sig).is_ok() ? "valid" : "invalid";
|
||||
if (result != has_result) {
|
||||
bad_tests.push_back({id, comment});
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bad_tests.empty()) {
|
||||
return;
|
||||
}
|
||||
LOG(ERROR) << "FAILED: " << td::format::as_array(bad_tests);
|
||||
}
|
||||
|
||||
TEST(Crypto, almost_zero) {
|
||||
td::SecureString pub(32);
|
||||
td::SecureString sig(64);
|
||||
td::SecureString msg(1);
|
||||
|
||||
pub.as_mutable_slice().ubegin()[31] = static_cast<unsigned char>(128);
|
||||
for (td::int32 j = 0; j < 256; j++) {
|
||||
msg.as_mutable_slice()[0] = (char)j;
|
||||
if (td::Ed25519::PublicKey(pub.copy()).verify_signature(msg, sig).is_ok()) {
|
||||
LOG(ERROR) << "FAILED: " << j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,34 +51,6 @@
|
|||
|
||||
#include <openssl/sha.h>
|
||||
|
||||
struct Step {
|
||||
std::function<void()> func;
|
||||
td::uint32 weight;
|
||||
};
|
||||
class RandomSteps {
|
||||
public:
|
||||
RandomSteps(std::vector<Step> steps) : steps_(std::move(steps)) {
|
||||
for (auto &step : steps_) {
|
||||
steps_sum_ += step.weight;
|
||||
}
|
||||
}
|
||||
template <class Random>
|
||||
void step(Random &rnd) {
|
||||
auto w = rnd() % steps_sum_;
|
||||
for (auto &step : steps_) {
|
||||
if (w < step.weight) {
|
||||
step.func();
|
||||
break;
|
||||
}
|
||||
w -= step.weight;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Step> steps_;
|
||||
td::int32 steps_sum_ = 0;
|
||||
};
|
||||
|
||||
namespace vm {
|
||||
|
||||
std::vector<int> do_get_serialization_modes() {
|
||||
|
@ -879,7 +851,7 @@ TEST(TonDb, DynamicBoc2) {
|
|||
VLOG(boc) << " OK";
|
||||
};
|
||||
|
||||
RandomSteps steps({{new_root, 10}, {delete_root, 9}, {commit, 2}, {reset, 1}});
|
||||
td::RandomSteps steps({{new_root, 10}, {delete_root, 9}, {commit, 2}, {reset, 1}});
|
||||
while (first_root_id != total_roots) {
|
||||
VLOG(boc) << first_root_id << " " << last_root_id << " " << kv->count("").ok();
|
||||
steps.step(rnd);
|
||||
|
@ -1732,7 +1704,7 @@ TEST(TonDb, CompactArray) {
|
|||
fast_array = vm::FastCompactArray(size);
|
||||
};
|
||||
|
||||
RandomSteps steps({{reset_array, 1}, {set_value, 1000}, {validate, 10}, {validate_full, 2}, {flush_to_db, 1}});
|
||||
td::RandomSteps steps({{reset_array, 1}, {set_value, 1000}, {validate, 10}, {validate_full, 2}, {flush_to_db, 1}});
|
||||
for (size_t t = 0; t < 100000; t++) {
|
||||
if (t % 10000 == 0) {
|
||||
LOG(ERROR) << t;
|
||||
|
|
524
crypto/test/test-smartcont.cpp
Normal file
524
crypto/test/test-smartcont.cpp
Normal file
|
@ -0,0 +1,524 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "vm/dict.h"
|
||||
#include "common/bigint.hpp"
|
||||
|
||||
#include "Ed25519.h"
|
||||
|
||||
#include "block/block.h"
|
||||
|
||||
#include "fift/Fift.h"
|
||||
#include "fift/words.h"
|
||||
#include "fift/utils.h"
|
||||
|
||||
#include "smc-envelope/GenericAccount.h"
|
||||
#include "smc-envelope/MultisigWallet.h"
|
||||
#include "smc-envelope/SmartContract.h"
|
||||
#include "smc-envelope/SmartContractCode.h"
|
||||
#include "smc-envelope/TestGiver.h"
|
||||
#include "smc-envelope/TestWallet.h"
|
||||
#include "smc-envelope/Wallet.h"
|
||||
|
||||
#include "td/utils/base64.h"
|
||||
#include "td/utils/crypto.h"
|
||||
#include "td/utils/Random.h"
|
||||
#include "td/utils/tests.h"
|
||||
#include "td/utils/ScopeGuard.h"
|
||||
#include "td/utils/StringBuilder.h"
|
||||
#include "td/utils/Timer.h"
|
||||
#include "td/utils/PathView.h"
|
||||
#include "td/utils/filesystem.h"
|
||||
#include "td/utils/port/path.h"
|
||||
|
||||
#include <bitset>
|
||||
#include <set>
|
||||
#include <tuple>
|
||||
|
||||
std::string current_dir() {
|
||||
return td::PathView(td::realpath(__FILE__).move_as_ok()).parent_dir().str();
|
||||
}
|
||||
|
||||
std::string load_source(std::string name) {
|
||||
return td::read_file_str(current_dir() + "../../crypto/" + name).move_as_ok();
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> get_test_wallet_source() {
|
||||
std::string code = R"ABCD(
|
||||
SETCP0 DUP IFNOTRET // return if recv_internal
|
||||
DUP 85143 INT EQUAL IFJMP:<{ // "seqno" get-method
|
||||
DROP c4 PUSHCTR CTOS 32 PLDU // cnt
|
||||
}>
|
||||
INC 32 THROWIF // fail unless recv_external
|
||||
512 INT LDSLICEX DUP 32 PLDU // sign cs cnt
|
||||
c4 PUSHCTR CTOS 32 LDU 256 LDU ENDS // sign cs cnt cnt' pubk
|
||||
s1 s2 XCPU // sign cs cnt pubk cnt' cnt
|
||||
EQUAL 33 THROWIFNOT // ( seqno mismatch? )
|
||||
s2 PUSH HASHSU // sign cs cnt pubk hash
|
||||
s0 s4 s4 XC2PU // pubk cs cnt hash sign pubk
|
||||
CHKSIGNU // pubk cs cnt ?
|
||||
34 THROWIFNOT // signature mismatch
|
||||
ACCEPT
|
||||
SWAP 32 LDU NIP
|
||||
DUP SREFS IF:<{
|
||||
// 3 INT 35 LSHIFT# 3 INT RAWRESERVE // reserve all but 103 Grams from the balance
|
||||
8 LDU LDREF // pubk cnt mode msg cs
|
||||
s0 s2 XCHG SENDRAWMSG // pubk cnt cs ; ( message sent )
|
||||
}>
|
||||
ENDS
|
||||
INC NEWC 32 STU 256 STU ENDC c4 POPCTR
|
||||
)ABCD";
|
||||
return fift::compile_asm(code).move_as_ok();
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> get_wallet_source() {
|
||||
std::string code = R"ABCD(
|
||||
SETCP0 DUP IFNOTRET // return if recv_internal
|
||||
DUP 85143 INT EQUAL IFJMP:<{ // "seqno" get-method
|
||||
DROP c4 PUSHCTR CTOS 32 PLDU // cnt
|
||||
}>
|
||||
INC 32 THROWIF // fail unless recv_external
|
||||
9 PUSHPOW2 LDSLICEX DUP 32 LDU 32 LDU // signature in_msg msg_seqno valid_until cs
|
||||
SWAP NOW LEQ 35 THROWIF // signature in_msg msg_seqno cs
|
||||
c4 PUSH CTOS 32 LDU 256 LDU ENDS // signature in_msg msg_seqno cs stored_seqno public_key
|
||||
s3 s1 XCPU // signature in_msg public_key cs stored_seqno msg_seqno stored_seqno
|
||||
EQUAL 33 THROWIFNOT // signature in_msg public_key cs stored_seqno
|
||||
s0 s3 XCHG HASHSU // signature stored_seqno public_key cs hash
|
||||
s0 s4 s2 XC2PU CHKSIGNU 34 THROWIFNOT // cs stored_seqno public_key
|
||||
ACCEPT
|
||||
s0 s2 XCHG // public_key stored_seqno cs
|
||||
WHILE:<{
|
||||
DUP SREFS // public_key stored_seqno cs _40
|
||||
}>DO<{ // public_key stored_seqno cs
|
||||
// 3 INT 35 LSHIFT# 3 INT RAWRESERVE // reserve all but 103 Grams from the balance
|
||||
8 LDU LDREF s0 s2 XCHG // public_key stored_seqno cs _45 mode
|
||||
SENDRAWMSG // public_key stored_seqno cs
|
||||
}>
|
||||
ENDS INC // public_key seqno'
|
||||
NEWC 32 STU 256 STU ENDC c4 POP
|
||||
)ABCD";
|
||||
return fift::compile_asm(code).move_as_ok();
|
||||
}
|
||||
|
||||
TEST(Tonlib, TestWallet) {
|
||||
LOG(ERROR) << td::base64_encode(std_boc_serialize(get_test_wallet_source()).move_as_ok());
|
||||
CHECK(get_test_wallet_source()->get_hash() == ton::TestWallet::get_init_code()->get_hash());
|
||||
auto fift_output = fift::mem_run_fift(load_source("smartcont/new-wallet.fif"), {"aba", "0"}).move_as_ok();
|
||||
|
||||
auto new_wallet_pk = fift_output.source_lookup.read_file("new-wallet.pk").move_as_ok().data;
|
||||
auto new_wallet_query = fift_output.source_lookup.read_file("new-wallet-query.boc").move_as_ok().data;
|
||||
auto new_wallet_addr = fift_output.source_lookup.read_file("new-wallet.addr").move_as_ok().data;
|
||||
|
||||
td::Ed25519::PrivateKey priv_key{td::SecureString{new_wallet_pk}};
|
||||
auto pub_key = priv_key.get_public_key().move_as_ok();
|
||||
auto init_state = ton::TestWallet::get_init_state(pub_key);
|
||||
auto init_message = ton::TestWallet::get_init_message(priv_key);
|
||||
auto address = ton::GenericAccount::get_address(0, init_state);
|
||||
|
||||
CHECK(address.addr.as_slice() == td::Slice(new_wallet_addr).substr(0, 32));
|
||||
|
||||
td::Ref<vm::Cell> res = ton::GenericAccount::create_ext_message(address, init_state, init_message);
|
||||
|
||||
LOG(ERROR) << "-------";
|
||||
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());
|
||||
|
||||
fift_output.source_lookup.write_file("/main.fif", load_source("smartcont/wallet.fif")).ensure();
|
||||
auto dest = block::StdAddress::parse("Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX").move_as_ok();
|
||||
fift_output = fift::mem_run_fift(std::move(fift_output.source_lookup),
|
||||
{"aba", "new-wallet", "Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX", "123",
|
||||
"321", "-C", "TEST"})
|
||||
.move_as_ok();
|
||||
auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data;
|
||||
auto gift_message = ton::GenericAccount::create_ext_message(
|
||||
address, {}, ton::TestWallet::make_a_gift_message(priv_key, 123, 321000000000ll, "TEST", dest));
|
||||
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());
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> get_wallet_source_fc() {
|
||||
return fift::compile_asm(load_source("smartcont/wallet-code.fif"), "", false).move_as_ok();
|
||||
}
|
||||
|
||||
TEST(Tonlib, Wallet) {
|
||||
LOG(ERROR) << td::base64_encode(std_boc_serialize(get_wallet_source()).move_as_ok());
|
||||
CHECK(get_wallet_source()->get_hash() == ton::Wallet::get_init_code()->get_hash());
|
||||
|
||||
auto fift_output = fift::mem_run_fift(load_source("smartcont/new-wallet-v2.fif"), {"aba", "0"}).move_as_ok();
|
||||
|
||||
auto new_wallet_pk = fift_output.source_lookup.read_file("new-wallet.pk").move_as_ok().data;
|
||||
auto new_wallet_query = fift_output.source_lookup.read_file("new-wallet-query.boc").move_as_ok().data;
|
||||
auto new_wallet_addr = fift_output.source_lookup.read_file("new-wallet.addr").move_as_ok().data;
|
||||
|
||||
td::Ed25519::PrivateKey priv_key{td::SecureString{new_wallet_pk}};
|
||||
auto pub_key = priv_key.get_public_key().move_as_ok();
|
||||
auto init_state = ton::Wallet::get_init_state(pub_key);
|
||||
auto init_message = ton::Wallet::get_init_message(priv_key);
|
||||
auto address = ton::GenericAccount::get_address(0, init_state);
|
||||
|
||||
CHECK(address.addr.as_slice() == td::Slice(new_wallet_addr).substr(0, 32));
|
||||
|
||||
td::Ref<vm::Cell> res = ton::GenericAccount::create_ext_message(address, init_state, init_message);
|
||||
|
||||
LOG(ERROR) << "-------";
|
||||
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());
|
||||
|
||||
fift_output.source_lookup.write_file("/main.fif", load_source("smartcont/wallet-v2.fif")).ensure();
|
||||
class ZeroOsTime : public fift::OsTime {
|
||||
public:
|
||||
td::uint32 now() override {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
fift_output.source_lookup.set_os_time(std::make_unique<ZeroOsTime>());
|
||||
auto dest = block::StdAddress::parse("Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX").move_as_ok();
|
||||
fift_output =
|
||||
fift::mem_run_fift(std::move(fift_output.source_lookup),
|
||||
{"aba", "new-wallet", "Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX", "123", "321"})
|
||||
.move_as_ok();
|
||||
auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data;
|
||||
auto gift_message = ton::GenericAccount::create_ext_message(
|
||||
address, {}, ton::Wallet::make_a_gift_message(priv_key, 123, 60, 321000000000ll, "TESTv2", dest));
|
||||
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, TestGiver) {
|
||||
auto address =
|
||||
block::StdAddress::parse("-1:60c04141c6a7b96d68615e7a91d265ad0f3a9a922e9ae9c901d4fa83f5d3c0d0").move_as_ok();
|
||||
LOG(ERROR) << address.bounceable;
|
||||
auto fift_output = fift::mem_run_fift(load_source("smartcont/testgiver.fif"),
|
||||
{"aba", address.rserialize(), "0", "6.666", "wallet-query"})
|
||||
.move_as_ok();
|
||||
LOG(ERROR) << fift_output.output;
|
||||
|
||||
auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data;
|
||||
|
||||
auto res = ton::GenericAccount::create_ext_message(
|
||||
ton::TestGiver::address(), {},
|
||||
ton::TestGiver::make_a_gift_message(0, 1000000000ll * 6666 / 1000, "GIFT", address));
|
||||
vm::CellSlice(vm::NoVm(), res).print_rec(std::cerr);
|
||||
CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == res->get_hash());
|
||||
}
|
||||
|
||||
class SimpleWallet : public ton::SmartContract {
|
||||
public:
|
||||
SimpleWallet(State state) : SmartContract(std::move(state)) {
|
||||
}
|
||||
|
||||
const State& get_state() const {
|
||||
return state_;
|
||||
}
|
||||
SimpleWallet* make_copy() const override {
|
||||
return new SimpleWallet{state_};
|
||||
}
|
||||
|
||||
static td::Ref<SimpleWallet> create_empty() {
|
||||
return td::Ref<SimpleWallet>(true, State{ton::SmartContractCode::simple_wallet_ext(), {}});
|
||||
}
|
||||
static td::Ref<SimpleWallet> create(td::Ref<vm::Cell> data) {
|
||||
return td::Ref<SimpleWallet>(true, State{ton::SmartContractCode::simple_wallet_ext(), std::move(data)});
|
||||
}
|
||||
static td::Ref<SimpleWallet> create_fast(td::Ref<vm::Cell> data) {
|
||||
return td::Ref<SimpleWallet>(true, State{ton::SmartContractCode::simple_wallet(), std::move(data)});
|
||||
}
|
||||
|
||||
td::int32 seqno() const {
|
||||
auto res = run_get_method("seqno");
|
||||
return res.stack.write().pop_smallint_range(1000000000);
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> create_init_state(td::Slice public_key) const {
|
||||
td::RefInt256 pk{true};
|
||||
pk.write().import_bytes(public_key.ubegin(), public_key.size(), false);
|
||||
auto res = run_get_method("create_init_state", {pk});
|
||||
return res.stack.write().pop_cell();
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> prepare_send_message(td::Ref<vm::Cell> msg, td::int8 mode = 3) const {
|
||||
auto res = run_get_method("prepare_send_message", {td::make_refint(mode), msg});
|
||||
return res.stack.write().pop_cell();
|
||||
}
|
||||
|
||||
static td::Ref<vm::Cell> sign_message(vm::Ref<vm::Cell> body, const td::Ed25519::PrivateKey& pk) {
|
||||
auto signature = pk.sign(body->get_hash().as_slice()).move_as_ok();
|
||||
return vm::CellBuilder().store_bytes(signature.as_slice()).append_cellslice(vm::load_cell_slice(body)).finalize();
|
||||
}
|
||||
};
|
||||
|
||||
TEST(Smartcon, Simple) {
|
||||
auto private_key = td::Ed25519::generate_private_key().move_as_ok();
|
||||
auto public_key = private_key.get_public_key().move_as_ok().as_octet_string();
|
||||
|
||||
auto w_lib = SimpleWallet::create_empty();
|
||||
auto init_data = w_lib->create_init_state(public_key);
|
||||
|
||||
auto w = SimpleWallet::create(init_data);
|
||||
LOG(ERROR) << w->code_size();
|
||||
auto fw = SimpleWallet::create_fast(init_data);
|
||||
LOG(ERROR) << fw->code_size();
|
||||
LOG(ERROR) << w->seqno();
|
||||
|
||||
for (int i = 0; i < 20; i++) {
|
||||
auto msg = w->sign_message(w->prepare_send_message(vm::CellBuilder().finalize()), private_key);
|
||||
w.write().send_external_message(msg);
|
||||
fw.write().send_external_message(msg);
|
||||
}
|
||||
ASSERT_EQ(20, w->seqno());
|
||||
CHECK(w->get_state().data->get_hash() == fw->get_state().data->get_hash());
|
||||
}
|
||||
|
||||
namespace std { // ouch
|
||||
bool operator<(const ton::MultisigWallet::Mask& a, const ton::MultisigWallet::Mask& b) {
|
||||
for (size_t i = 0; i < a.size(); i++) {
|
||||
if (a[i] != b[i]) {
|
||||
return a[i] < b[i];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} // namespace std
|
||||
|
||||
TEST(Smartcon, Multisig) {
|
||||
auto ms_lib = ton::MultisigWallet::create();
|
||||
|
||||
int n = 100;
|
||||
int k = 99;
|
||||
std::vector<td::Ed25519::PrivateKey> keys;
|
||||
for (int i = 0; i < n; i++) {
|
||||
keys.push_back(td::Ed25519::generate_private_key().move_as_ok());
|
||||
}
|
||||
auto init_state = ms_lib->create_init_data(
|
||||
td::transform(keys, [](auto& key) { return key.get_public_key().ok().as_octet_string(); }), k);
|
||||
auto ms = ton::MultisigWallet::create(init_state);
|
||||
|
||||
td::uint64 query_id = 123;
|
||||
ton::MultisigWallet::QueryBuilder qb(query_id, vm::CellBuilder().finalize());
|
||||
// first empty query (init)
|
||||
CHECK(ms.write().send_external_message(vm::CellBuilder().finalize()).code == 0);
|
||||
// first empty query
|
||||
CHECK(ms.write().send_external_message(vm::CellBuilder().finalize()).code > 0);
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
auto query = qb.create(i, keys[i]);
|
||||
auto ans = ms.write().send_external_message(query);
|
||||
LOG(INFO) << "CODE: " << ans.code;
|
||||
LOG(INFO) << "GAS: " << ans.gas_used;
|
||||
}
|
||||
for (int i = 0; i + 1 < 50; i++) {
|
||||
qb.sign(i, keys[i]);
|
||||
}
|
||||
auto query = qb.create(49, keys[49]);
|
||||
|
||||
CHECK(ms->get_n_k() == std::make_pair(n, k));
|
||||
auto ans = ms.write().send_external_message(query);
|
||||
LOG(INFO) << "CODE: " << ans.code;
|
||||
LOG(INFO) << "GAS: " << ans.gas_used;
|
||||
CHECK(ans.success);
|
||||
ASSERT_EQ(0, ms->processed(query_id));
|
||||
CHECK(ms.write().send_external_message(query).code > 0);
|
||||
ASSERT_EQ(0, ms->processed(query_id));
|
||||
|
||||
{
|
||||
ton::MultisigWallet::QueryBuilder qb(query_id, vm::CellBuilder().finalize());
|
||||
for (int i = 50; i + 1 < 100; i++) {
|
||||
qb.sign(i, keys[i]);
|
||||
}
|
||||
query = qb.create(99, keys[99]);
|
||||
}
|
||||
|
||||
ans = ms.write().send_external_message(query);
|
||||
LOG(INFO) << "CODE: " << ans.code;
|
||||
LOG(INFO) << "GAS: " << ans.gas_used;
|
||||
ASSERT_EQ(-1, ms->processed(query_id));
|
||||
}
|
||||
|
||||
TEST(Smartcont, MultisigStress) {
|
||||
int n = 10;
|
||||
int k = 5;
|
||||
|
||||
std::vector<td::Ed25519::PrivateKey> keys;
|
||||
for (int i = 0; i < n; i++) {
|
||||
keys.push_back(td::Ed25519::generate_private_key().move_as_ok());
|
||||
}
|
||||
auto public_keys = td::transform(keys, [](auto& key) { return key.get_public_key().ok().as_octet_string(); });
|
||||
auto ms_lib = ton::MultisigWallet::create();
|
||||
auto init_state_old =
|
||||
ms_lib->create_init_data_fast(td::transform(public_keys, [](auto& key) { return key.copy(); }), k);
|
||||
auto init_state = ms_lib->create_init_data(td::transform(public_keys, [](auto& key) { return key.copy(); }), k);
|
||||
CHECK(init_state_old->get_hash() == init_state->get_hash());
|
||||
auto ms = ton::MultisigWallet::create(init_state);
|
||||
CHECK(ms->get_public_keys() == public_keys);
|
||||
|
||||
td::int32 now = 0;
|
||||
td::int32 qid = 1;
|
||||
using Mask = std::bitset<128>;
|
||||
struct Query {
|
||||
td::int64 id;
|
||||
td::Ref<vm::Cell> message;
|
||||
Mask signed_mask;
|
||||
};
|
||||
|
||||
std::vector<Query> queries;
|
||||
int max_queries = 300;
|
||||
|
||||
td::Random::Xorshift128plus rnd(123);
|
||||
|
||||
auto new_query = [&] {
|
||||
if (qid > max_queries) {
|
||||
return;
|
||||
}
|
||||
Query query;
|
||||
query.id = (static_cast<td::int64>(now) << 32) | qid++;
|
||||
query.message = vm::CellBuilder().store_bytes(td::rand_string('a', 'z', rnd.fast(0, 100))).finalize();
|
||||
queries.push_back(std::move(query));
|
||||
};
|
||||
|
||||
auto verify = [&] {
|
||||
auto messages = ms->get_unsigned_messaged();
|
||||
std::set<std::tuple<td::uint64, ton::MultisigWallet::Mask, std::string>> s;
|
||||
std::set<std::tuple<td::uint64, ton::MultisigWallet::Mask, std::string>> t;
|
||||
|
||||
for (auto& m : messages) {
|
||||
auto x = std::make_tuple(m.query_id, m.signed_by, m.message->get_hash().as_slice().str());
|
||||
s.insert(std::move(x));
|
||||
}
|
||||
|
||||
for (auto& q : queries) {
|
||||
if (q.signed_mask.none()) {
|
||||
continue;
|
||||
}
|
||||
t.insert(std::make_tuple(q.id, q.signed_mask, q.message->get_hash().as_slice().str()));
|
||||
}
|
||||
ASSERT_EQ(t.size(), s.size());
|
||||
CHECK(s == t);
|
||||
};
|
||||
|
||||
auto sign_query = [&](Query& query, Mask mask) {
|
||||
auto qb = ton::MultisigWallet::QueryBuilder(query.id, query.message);
|
||||
int first_i = -1;
|
||||
for (int i = 0; i < (int)mask.size(); i++) {
|
||||
if (mask.test(i)) {
|
||||
if (first_i == -1) {
|
||||
first_i = i;
|
||||
} else {
|
||||
qb.sign(i, keys[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return qb.create(first_i, keys[first_i]);
|
||||
};
|
||||
|
||||
auto send_signature = [&](td::Ref<vm::Cell> query) {
|
||||
auto ans = ms.write().send_external_message(query);
|
||||
LOG(ERROR) << "GAS: " << ans.gas_used;
|
||||
return ans.code == 0;
|
||||
};
|
||||
|
||||
auto is_ready = [&](Query& query) { return ms->processed(query.id) == -1; };
|
||||
|
||||
auto gen_query = [&](Query& query) {
|
||||
auto x = rnd.fast(1, n);
|
||||
Mask mask;
|
||||
for (int t = 0; t < x; t++) {
|
||||
mask.set(rnd() % n);
|
||||
}
|
||||
|
||||
auto signature = sign_query(query, mask);
|
||||
return std::make_pair(signature, mask);
|
||||
};
|
||||
|
||||
auto rand_sign = [&] {
|
||||
if (queries.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t query_i = rnd() % queries.size();
|
||||
auto& query = queries[query_i];
|
||||
|
||||
Mask mask;
|
||||
td::Ref<vm::Cell> signature;
|
||||
std::tie(signature, mask) = gen_query(query);
|
||||
if (false && rnd() % 6 == 0) {
|
||||
Mask mask2;
|
||||
td::Ref<vm::Cell> signature2;
|
||||
std::tie(signature2, mask2) = gen_query(query);
|
||||
for (int i = 0; i < (int)keys.size(); i++) {
|
||||
if (mask[i]) {
|
||||
signature = ms->merge_queries(std::move(signature), std::move(signature2));
|
||||
break;
|
||||
}
|
||||
if (mask2[i]) {
|
||||
signature = ms->merge_queries(std::move(signature2), std::move(signature));
|
||||
break;
|
||||
}
|
||||
}
|
||||
//signature = ms->merge_queries(std::move(signature), std::move(signature2));
|
||||
mask |= mask2;
|
||||
}
|
||||
|
||||
int got_cnt;
|
||||
Mask got_cnt_bits;
|
||||
std::tie(got_cnt, got_cnt_bits) = ms->check_query_signatures(signature);
|
||||
CHECK(mask == got_cnt_bits);
|
||||
|
||||
bool expect_ok = true;
|
||||
{
|
||||
auto new_mask = mask & ~query.signed_mask;
|
||||
expect_ok &= new_mask.any();
|
||||
for (size_t i = 0; i < mask.size(); i++) {
|
||||
if (mask[i]) {
|
||||
expect_ok &= new_mask[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_EQ(expect_ok, send_signature(std::move(signature)));
|
||||
if (expect_ok) {
|
||||
query.signed_mask |= mask;
|
||||
}
|
||||
auto expect_is_ready = query.signed_mask.count() >= (size_t)k;
|
||||
auto state = ms->get_query_state(query.id);
|
||||
ASSERT_EQ(expect_is_ready, (state.state == ton::MultisigWallet::QueryState::Sent));
|
||||
CHECK(expect_is_ready || state.mask == query.signed_mask);
|
||||
ASSERT_EQ(expect_is_ready, is_ready(query));
|
||||
if (expect_is_ready) {
|
||||
queries.erase(queries.begin() + query_i);
|
||||
}
|
||||
verify();
|
||||
};
|
||||
td::RandomSteps steps({{rand_sign, 2}, {new_query, 1}});
|
||||
while (!queries.empty() || qid <= max_queries) {
|
||||
steps.step(rnd);
|
||||
//LOG(ERROR) << ms->data_size();
|
||||
}
|
||||
LOG(INFO) << "Final code size: " << ms->code_size();
|
||||
LOG(INFO) << "Final data size: " << ms->data_size();
|
||||
}
|
1160
crypto/test/wycheproof.h
Normal file
1160
crypto/test/wycheproof.h
Normal file
File diff suppressed because it is too large
Load diff
64
crypto/vm/cells/CellString.cpp
Normal file
64
crypto/vm/cells/CellString.cpp
Normal file
|
@ -0,0 +1,64 @@
|
|||
#include "CellString.h"
|
||||
#include "td/utils/misc.h"
|
||||
|
||||
#include "vm/cells/CellSlice.h"
|
||||
|
||||
namespace vm {
|
||||
td::Status CellString::store(CellBuilder &cb, td::Slice slice, unsigned int top_bits) {
|
||||
td::uint32 size = td::narrow_cast<td::uint32>(slice.size() * 8);
|
||||
return store(cb, td::BitSlice(slice.ubegin(), size), top_bits);
|
||||
}
|
||||
|
||||
td::Status CellString::store(CellBuilder &cb, td::BitSlice slice, unsigned int top_bits) {
|
||||
if (slice.size() > max_bytes * 8) {
|
||||
return td::Status::Error("String is too long (1)");
|
||||
}
|
||||
unsigned int head = td::min(slice.size(), td::min(cb.remaining_bits(), top_bits)) / 8 * 8;
|
||||
auto max_bits = vm::Cell::max_bits / 8 * 8;
|
||||
auto depth = 1 + (slice.size() - head + max_bits - 1) / max_bits;
|
||||
if (depth > max_chain_length) {
|
||||
return td::Status::Error("String is too long (2)");
|
||||
}
|
||||
cb.append_bitslice(slice.subslice(0, head));
|
||||
slice.advance(head);
|
||||
if (slice.size() == 0) {
|
||||
return td::Status::OK();
|
||||
}
|
||||
CellBuilder child_cb;
|
||||
store(child_cb, std::move(slice));
|
||||
cb.store_ref(child_cb.finalize());
|
||||
return td::Status::OK();
|
||||
}
|
||||
|
||||
template <class F>
|
||||
void CellString::for_each(F &&f, CellSlice &cs, unsigned int top_bits) {
|
||||
unsigned int head = td::min(cs.size(), top_bits);
|
||||
f(cs.prefetch_bits(head));
|
||||
if (!cs.have_refs()) {
|
||||
return;
|
||||
}
|
||||
auto ref = cs.prefetch_ref();
|
||||
while (true) {
|
||||
auto cs = vm::load_cell_slice(ref);
|
||||
f(cs.prefetch_bits(cs.size()));
|
||||
if (!cs.have_refs()) {
|
||||
return;
|
||||
}
|
||||
ref = cs.prefetch_ref();
|
||||
}
|
||||
}
|
||||
|
||||
td::Result<td::string> CellString::load(CellSlice &cs, unsigned int top_bits) {
|
||||
unsigned int size = 0;
|
||||
for_each([&](auto slice) { size += slice.size(); }, cs, top_bits);
|
||||
if (size % 8 != 0) {
|
||||
return td::Status::Error("Size is not divisible by 8");
|
||||
}
|
||||
std::string res(size / 8, 0);
|
||||
|
||||
td::BitPtr to(td::MutableSlice(res).ubegin());
|
||||
for_each([&](auto slice) { to.concat(slice); }, cs, top_bits);
|
||||
CHECK(to.offs == (int)size);
|
||||
return res;
|
||||
}
|
||||
} // namespace vm
|
22
crypto/vm/cells/CellString.h
Normal file
22
crypto/vm/cells/CellString.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include "td/utils/Status.h"
|
||||
|
||||
#include "vm/cells/CellBuilder.h"
|
||||
|
||||
namespace vm {
|
||||
class CellString {
|
||||
public:
|
||||
static constexpr unsigned int max_bytes = 1024;
|
||||
static constexpr unsigned int max_chain_length = 16;
|
||||
|
||||
static td::Status store(CellBuilder &cb, td::Slice slice, unsigned int top_bits = Cell::max_bits);
|
||||
static td::Status store(CellBuilder &cb, td::BitSlice slice, unsigned int top_bits = Cell::max_bits);
|
||||
static td::Result<td::string> load(CellSlice &cs, unsigned int top_bits = Cell::max_bits);
|
||||
|
||||
private:
|
||||
template <class F>
|
||||
static void for_each(F &&f, CellSlice &cs, unsigned int top_bits = Cell::max_bits);
|
||||
};
|
||||
|
||||
} // namespace vm
|
|
@ -744,6 +744,9 @@ int VmState::run() {
|
|||
}
|
||||
} while (!res);
|
||||
// LOG(INFO) << "[EN] data cells: " << DataCell::get_total_data_cells();
|
||||
if ((res | 1) == -1) {
|
||||
commit();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
|
|
@ -154,7 +154,7 @@ struct ControlData {
|
|||
|
||||
class Continuation : public td::CntObject {
|
||||
public:
|
||||
virtual int jump(VmState* st) const & = 0;
|
||||
virtual int jump(VmState* st) const& = 0;
|
||||
virtual int jump_w(VmState* st) &;
|
||||
virtual ControlData* get_cdata() {
|
||||
return 0;
|
||||
|
@ -184,7 +184,7 @@ class QuitCont : public Continuation {
|
|||
QuitCont(int _code = 0) : exit_code(_code) {
|
||||
}
|
||||
~QuitCont() override = default;
|
||||
int jump(VmState* st) const & override {
|
||||
int jump(VmState* st) const& override {
|
||||
return ~exit_code;
|
||||
}
|
||||
};
|
||||
|
@ -193,7 +193,7 @@ class ExcQuitCont : public Continuation {
|
|||
public:
|
||||
ExcQuitCont() = default;
|
||||
~ExcQuitCont() override = default;
|
||||
int jump(VmState* st) const & override;
|
||||
int jump(VmState* st) const& override;
|
||||
};
|
||||
|
||||
class PushIntCont : public Continuation {
|
||||
|
@ -204,7 +204,7 @@ class PushIntCont : public Continuation {
|
|||
PushIntCont(int val, Ref<Continuation> _next) : push_val(val), next(_next) {
|
||||
}
|
||||
~PushIntCont() override = default;
|
||||
int jump(VmState* st) const & override;
|
||||
int jump(VmState* st) const& override;
|
||||
int jump_w(VmState* st) & override;
|
||||
};
|
||||
|
||||
|
@ -217,7 +217,7 @@ class RepeatCont : public Continuation {
|
|||
: body(std::move(_body)), after(std::move(_after)), count(_count) {
|
||||
}
|
||||
~RepeatCont() override = default;
|
||||
int jump(VmState* st) const & override;
|
||||
int jump(VmState* st) const& override;
|
||||
int jump_w(VmState* st) & override;
|
||||
};
|
||||
|
||||
|
@ -228,7 +228,7 @@ class AgainCont : public Continuation {
|
|||
AgainCont(Ref<Continuation> _body) : body(std::move(_body)) {
|
||||
}
|
||||
~AgainCont() override = default;
|
||||
int jump(VmState* st) const & override;
|
||||
int jump(VmState* st) const& override;
|
||||
int jump_w(VmState* st) & override;
|
||||
};
|
||||
|
||||
|
@ -239,7 +239,7 @@ class UntilCont : public Continuation {
|
|||
UntilCont(Ref<Continuation> _body, Ref<Continuation> _after) : body(std::move(_body)), after(std::move(_after)) {
|
||||
}
|
||||
~UntilCont() override = default;
|
||||
int jump(VmState* st) const & override;
|
||||
int jump(VmState* st) const& override;
|
||||
int jump_w(VmState* st) & override;
|
||||
};
|
||||
|
||||
|
@ -252,7 +252,7 @@ class WhileCont : public Continuation {
|
|||
: cond(std::move(_cond)), body(std::move(_body)), after(std::move(_after)), chkcond(_chk) {
|
||||
}
|
||||
~WhileCont() override = default;
|
||||
int jump(VmState* st) const & override;
|
||||
int jump(VmState* st) const& override;
|
||||
int jump_w(VmState* st) & override;
|
||||
};
|
||||
|
||||
|
@ -268,7 +268,7 @@ class ArgContExt : public Continuation {
|
|||
ArgContExt(const ArgContExt&) = default;
|
||||
ArgContExt(ArgContExt&&) = default;
|
||||
~ArgContExt() override = default;
|
||||
int jump(VmState* st) const & override;
|
||||
int jump(VmState* st) const& override;
|
||||
int jump_w(VmState* st) & override;
|
||||
ControlData* get_cdata() override {
|
||||
return &data;
|
||||
|
@ -303,7 +303,7 @@ class OrdCont : public Continuation {
|
|||
td::CntObject* make_copy() const override {
|
||||
return new OrdCont{*this};
|
||||
}
|
||||
int jump(VmState* st) const & override;
|
||||
int jump(VmState* st) const& override;
|
||||
int jump_w(VmState* st) & override;
|
||||
|
||||
ControlData* get_cdata() override {
|
||||
|
@ -321,7 +321,7 @@ class OrdCont : public Continuation {
|
|||
Ref<Stack> get_stack_ref() const {
|
||||
return data.stack;
|
||||
}
|
||||
Ref<OrdCont> copy_ord() const & {
|
||||
Ref<OrdCont> copy_ord() const& {
|
||||
return Ref<OrdCont>{true, *this};
|
||||
}
|
||||
Ref<OrdCont> copy_ord() && {
|
||||
|
@ -375,10 +375,16 @@ struct GasLimits {
|
|||
}
|
||||
};
|
||||
|
||||
struct CommittedState {
|
||||
Ref<vm::Cell> c4, c5;
|
||||
bool committed{false};
|
||||
};
|
||||
|
||||
class VmState final : public VmStateInterface {
|
||||
Ref<CellSlice> code;
|
||||
Ref<Stack> stack;
|
||||
ControlRegs cr;
|
||||
CommittedState cstate;
|
||||
int cp;
|
||||
long long steps{0};
|
||||
const DispatchTable* dispatch;
|
||||
|
@ -388,6 +394,8 @@ class VmState final : public VmStateInterface {
|
|||
std::vector<Ref<Cell>> libraries;
|
||||
int stack_trace{0}, debug_off{0};
|
||||
|
||||
bool chksig_always_succeed{false};
|
||||
|
||||
public:
|
||||
static constexpr unsigned cell_load_gas_price = 100, cell_create_gas_price = 500, exception_gas_price = 50,
|
||||
tuple_entry_gas_price = 1;
|
||||
|
@ -401,6 +409,10 @@ class VmState final : public VmStateInterface {
|
|||
VmState(Ref<Cell> code_cell, Args&&... args)
|
||||
: VmState(convert_code_cell(std::move(code_cell)), std::forward<Args>(args)...) {
|
||||
}
|
||||
VmState(const VmState&) = delete;
|
||||
VmState(VmState&&) = delete;
|
||||
VmState& operator=(const VmState&) = delete;
|
||||
VmState& operator=(VmState&&) = delete;
|
||||
bool set_gas_limits(long long _max, long long _limit, long long _credit = 0);
|
||||
bool final_gas_ok() const {
|
||||
return gas.final_ok();
|
||||
|
@ -408,6 +420,12 @@ class VmState final : public VmStateInterface {
|
|||
long long gas_consumed() const {
|
||||
return gas.gas_consumed();
|
||||
}
|
||||
bool committed() const {
|
||||
return cstate.committed;
|
||||
}
|
||||
const CommittedState& get_committed_state() const {
|
||||
return cstate;
|
||||
}
|
||||
void consume_gas(long long amount) {
|
||||
gas.consume(amount);
|
||||
}
|
||||
|
@ -567,6 +585,18 @@ class VmState final : public VmStateInterface {
|
|||
return cont->is_unique() ? cont.unique_write().jump_w(this) : cont->jump(this);
|
||||
}
|
||||
static Ref<CellSlice> convert_code_cell(Ref<Cell> code_cell);
|
||||
void commit() {
|
||||
cstate.c4 = cr.d[0];
|
||||
cstate.c5 = cr.d[1];
|
||||
cstate.committed = true;
|
||||
}
|
||||
|
||||
void set_chksig_always_succeed(bool flag) {
|
||||
chksig_always_succeed = flag;
|
||||
}
|
||||
bool get_chksig_always_succeed() const {
|
||||
return chksig_always_succeed;
|
||||
}
|
||||
|
||||
private:
|
||||
void init_cregs(bool same_c3 = false, bool push_0 = true);
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
#include "td/utils/Status.h"
|
||||
|
||||
namespace vm {
|
||||
|
||||
enum class Excno : int {
|
||||
|
@ -95,4 +97,19 @@ struct VmVirtError {
|
|||
|
||||
struct VmFatal {};
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
#define TRY_VM(f) ::vm::try_f([&] { return f; })
|
||||
|
||||
} // namespace vm
|
||||
|
|
|
@ -75,10 +75,17 @@ int exec_set_gas_limit(VmState* st) {
|
|||
return exec_set_gas_generic(st, gas);
|
||||
}
|
||||
|
||||
int exec_commit(VmState* st) {
|
||||
VM_LOG(st) << "execute COMMIT";
|
||||
st->commit();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void register_basic_gas_ops(OpcodeTable& cp0) {
|
||||
using namespace std::placeholders;
|
||||
cp0.insert(OpcodeInstr::mksimple(0xf800, 16, "ACCEPT", exec_accept))
|
||||
.insert(OpcodeInstr::mksimple(0xf801, 16, "SETGASLIMIT", exec_set_gas_limit));
|
||||
.insert(OpcodeInstr::mksimple(0xf801, 16, "SETGASLIMIT", exec_set_gas_limit))
|
||||
.insert(OpcodeInstr::mksimple(0xf80f, 16, "COMMIT", exec_commit));
|
||||
}
|
||||
|
||||
void register_ton_gas_ops(OpcodeTable& cp0) {
|
||||
|
@ -268,7 +275,7 @@ int exec_ed25519_check_signature(VmState* st, bool from_slice) {
|
|||
}
|
||||
td::Ed25519::PublicKey pub_key{td::SecureString(td::Slice{key, 32})};
|
||||
auto res = pub_key.verify_signature(td::Slice{data, data_len}, td::Slice{signature, 64});
|
||||
stack.push_bool(res.is_ok());
|
||||
stack.push_bool(res.is_ok() || st->get_chksig_always_succeed());
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue