mirror of
https://github.com/ton-blockchain/ton
synced 2025-02-12 19:22:37 +00:00
1187 lines
42 KiB
Text
1187 lines
42 KiB
Text
;; Elector smartcontract
|
|
|
|
;; cur_elect credits past_elections grams active_id active_hash
|
|
(cell, cell, cell, int, int, int) load_data() inline_ref {
|
|
var cs = get_data().begin_parse();
|
|
var res = (cs~load_dict(), cs~load_dict(), cs~load_dict(), cs~load_grams(), cs~load_uint(32), cs~load_uint(256));
|
|
cs.end_parse();
|
|
return res;
|
|
}
|
|
|
|
;; cur_elect credits past_elections grams active_id active_hash
|
|
() store_data(elect, credits, past_elections, grams, active_id, active_hash) impure inline_ref {
|
|
set_data(begin_cell()
|
|
.store_dict(elect)
|
|
.store_dict(credits)
|
|
.store_dict(past_elections)
|
|
.store_grams(grams)
|
|
.store_uint(active_id, 32)
|
|
.store_uint(active_hash, 256)
|
|
.end_cell());
|
|
}
|
|
|
|
;; elect -> elect_at elect_close min_stake total_stake members failed finished
|
|
_ unpack_elect(elect) inline_ref {
|
|
var es = elect.begin_parse();
|
|
var res = (es~load_uint(32), es~load_uint(32), es~load_grams(), es~load_grams(), es~load_dict(), es~load_int(1), es~load_int(1));
|
|
es.end_parse();
|
|
return res;
|
|
}
|
|
|
|
cell pack_elect(elect_at, elect_close, min_stake, total_stake, members, failed, finished) inline_ref {
|
|
return begin_cell()
|
|
.store_uint(elect_at, 32)
|
|
.store_uint(elect_close, 32)
|
|
.store_grams(min_stake)
|
|
.store_grams(total_stake)
|
|
.store_dict(members)
|
|
.store_int(failed, 1)
|
|
.store_int(finished, 1)
|
|
.end_cell();
|
|
}
|
|
|
|
;; slice -> unfreeze_at stake_held vset_hash frozen_dict total_stake bonuses complaints
|
|
_ unpack_past_election(slice fs) inline_ref {
|
|
var res = (fs~load_uint(32), fs~load_uint(32), fs~load_uint(256), fs~load_dict(), fs~load_grams(), fs~load_grams(), fs~load_dict());
|
|
fs.end_parse();
|
|
return res;
|
|
}
|
|
|
|
builder pack_past_election(int unfreeze_at, int stake_held, int vset_hash, cell frozen_dict, int total_stake, int bonuses, cell complaints) inline_ref {
|
|
return begin_cell()
|
|
.store_uint(unfreeze_at, 32)
|
|
.store_uint(stake_held, 32)
|
|
.store_uint(vset_hash, 256)
|
|
.store_dict(frozen_dict)
|
|
.store_grams(total_stake)
|
|
.store_grams(bonuses)
|
|
.store_dict(complaints);
|
|
}
|
|
|
|
;; complaint_status#2d complaint:^ValidatorComplaint voters:(HashmapE 16 True)
|
|
;; vset_id:uint256 weight_remaining:int64 = ValidatorComplaintStatus;
|
|
_ unpack_complaint_status(slice cs) inline_ref {
|
|
throw_unless(9, cs~load_uint(8) == 0x2d);
|
|
var res = (cs~load_ref(), cs~load_dict(), cs~load_uint(256), cs~load_int(64));
|
|
cs.end_parse();
|
|
return res;
|
|
}
|
|
|
|
builder pack_complaint_status(cell complaint, cell voters, int vset_id, int weight_remaining) inline_ref {
|
|
return begin_cell()
|
|
.store_uint(0x2d, 8)
|
|
.store_ref(complaint)
|
|
.store_dict(voters)
|
|
.store_uint(vset_id, 256)
|
|
.store_int(weight_remaining, 64);
|
|
}
|
|
|
|
;; validator_complaint#bc validator_pubkey:uint256 description:^ComplaintDescr
|
|
;; created_at:uint32 severity:uint8 reward_addr:uint256 paid:Grams suggested_fine:Grams
|
|
;; suggested_fine_part:uint32 = ValidatorComplaint;
|
|
_ unpack_complaint(slice cs) inline_ref {
|
|
throw_unless(9, cs~load_int(8) == 0xbc - 0x100);
|
|
var res = (cs~load_uint(256), cs~load_ref(), cs~load_uint(32), cs~load_uint(8), cs~load_uint(256), cs~load_grams(), cs~load_grams(), cs~load_uint(32));
|
|
cs.end_parse();
|
|
return res;
|
|
}
|
|
|
|
builder pack_complaint(int validator_pubkey, cell description, int created_at, int severity, int reward_addr, int paid, int suggested_fine, int suggested_fine_part) inline_ref {
|
|
return begin_cell()
|
|
.store_int(0xbc - 0x100, 8)
|
|
.store_uint(validator_pubkey, 256)
|
|
.store_ref(description)
|
|
.store_uint(created_at, 32)
|
|
.store_uint(severity, 8)
|
|
.store_uint(reward_addr, 256)
|
|
.store_grams(paid)
|
|
.store_grams(suggested_fine)
|
|
.store_uint(suggested_fine_part, 32);
|
|
}
|
|
|
|
;; complaint_prices#1a deposit:Grams bit_price:Grams cell_price:Grams = ComplaintPricing;
|
|
(int, int, int) parse_complaint_prices(cell info) inline {
|
|
var cs = info.begin_parse();
|
|
throw_unless(9, cs~load_uint(8) == 0x1a);
|
|
var res = (cs~load_grams(), cs~load_grams(), cs~load_grams());
|
|
cs.end_parse();
|
|
return res;
|
|
}
|
|
|
|
;; deposit bit_price cell_price
|
|
(int, int, int) get_complaint_prices() inline_ref {
|
|
var info = config_param(13);
|
|
return info.null?() ? (1 << 36, 1, 512) : info.parse_complaint_prices();
|
|
}
|
|
|
|
;; elected_for elections_begin_before elections_end_before stake_held_for
|
|
(int, int, int, int) get_validator_conf() {
|
|
var cs = config_param(15).begin_parse();
|
|
return (cs~load_int(32), cs~load_int(32), cs~load_int(32), cs.preload_int(32));
|
|
}
|
|
|
|
;; next three functions return information about current validator set (config param #34)
|
|
;; they are borrowed from config-code.fc
|
|
(cell, int, cell) get_current_vset() inline_ref {
|
|
var vset = config_param(34);
|
|
var cs = begin_parse(vset);
|
|
;; validators_ext#12 utime_since:uint32 utime_until:uint32
|
|
;; total:(## 16) main:(## 16) { main <= total } { main >= 1 }
|
|
;; total_weight:uint64
|
|
throw_unless(40, cs~load_uint(8) == 0x12);
|
|
cs~skip_bits(32 + 32 + 16 + 16);
|
|
var (total_weight, dict) = (cs~load_uint(64), cs~load_dict());
|
|
cs.end_parse();
|
|
return (vset, total_weight, dict);
|
|
}
|
|
|
|
(slice, int) get_validator_descr(int idx) inline_ref {
|
|
var (vset, total_weight, dict) = get_current_vset();
|
|
var (value, _) = dict.udict_get?(16, idx);
|
|
return (value, total_weight);
|
|
}
|
|
|
|
(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));
|
|
}
|
|
|
|
() send_message_back(addr, ans_tag, query_id, body, grams, mode) impure inline_ref {
|
|
;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000
|
|
var msg = begin_cell()
|
|
.store_uint(0x18, 6)
|
|
.store_slice(addr)
|
|
.store_grams(grams)
|
|
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
|
|
.store_uint(ans_tag, 32)
|
|
.store_uint(query_id, 64);
|
|
if (body >= 0) {
|
|
msg~store_uint(body, 32);
|
|
}
|
|
send_raw_message(msg.end_cell(), mode);
|
|
}
|
|
|
|
() return_stake(addr, query_id, reason) impure inline_ref {
|
|
return send_message_back(addr, 0xee6f454c, query_id, reason, 0, 64);
|
|
}
|
|
|
|
() send_confirmation(addr, query_id, comment) impure inline_ref {
|
|
return send_message_back(addr, 0xf374484c, query_id, comment, 1000000000, 2);
|
|
}
|
|
|
|
() send_validator_set_to_config(config_addr, vset, query_id) impure inline_ref {
|
|
var msg = begin_cell()
|
|
.store_uint(0xc4ff, 17) ;; 0 11000100 0xff
|
|
.store_uint(config_addr, 256)
|
|
.store_grams(1 << 30) ;; ~1 gram of value to process and obtain answer
|
|
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
|
|
.store_uint(0x4e565354, 32)
|
|
.store_uint(query_id, 64)
|
|
.store_ref(vset);
|
|
send_raw_message(msg.end_cell(), 1);
|
|
}
|
|
|
|
;; credits 'amount' to 'addr' inside credit dictionary 'credits'
|
|
_ ~credit_to(credits, addr, amount) inline_ref {
|
|
var (val, f) = credits.udict_get?(256, addr);
|
|
if (f) {
|
|
amount += val~load_grams();
|
|
}
|
|
credits~udict_set_builder(256, addr, begin_cell().store_grams(amount));
|
|
return (credits, ());
|
|
}
|
|
|
|
() process_new_stake(s_addr, msg_value, cs, query_id) impure inline_ref {
|
|
var (src_wc, src_addr) = parse_std_addr(s_addr);
|
|
var ds = get_data().begin_parse();
|
|
var elect = ds~load_dict();
|
|
if (elect.null?() | (src_wc + 1)) {
|
|
;; no elections active, or source is not in masterchain
|
|
;; bounce message
|
|
return return_stake(s_addr, query_id, 0);
|
|
}
|
|
;; parse the remainder of new stake message
|
|
var validator_pubkey = cs~load_uint(256);
|
|
var stake_at = cs~load_uint(32);
|
|
var max_factor = cs~load_uint(32);
|
|
var adnl_addr = cs~load_uint(256);
|
|
var signature = cs~load_ref().begin_parse().preload_bits(512);
|
|
cs.end_parse();
|
|
ifnot (check_data_signature(begin_cell()
|
|
.store_uint(0x654c5074, 32)
|
|
.store_uint(stake_at, 32)
|
|
.store_uint(max_factor, 32)
|
|
.store_uint(src_addr, 256)
|
|
.store_uint(adnl_addr, 256)
|
|
.end_cell().begin_parse(), signature, validator_pubkey)) {
|
|
;; incorrect signature, return stake
|
|
return return_stake(s_addr, query_id, 1);
|
|
}
|
|
if (max_factor < 0x10000) {
|
|
;; factor must be >= 1. = 65536/65536
|
|
return return_stake(s_addr, query_id, 6);
|
|
}
|
|
;; parse current election data
|
|
var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect();
|
|
;; elect_at~dump();
|
|
msg_value -= 1000000000; ;; deduct GR$1 for sending confirmation
|
|
if ((msg_value << 12) < total_stake) {
|
|
;; stake smaller than 1/4096 of the total accumulated stakes, return
|
|
return return_stake(s_addr, query_id, 2);
|
|
}
|
|
total_stake += msg_value; ;; (provisionally) increase total stake
|
|
if (stake_at != elect_at) {
|
|
;; stake for some other elections, return
|
|
return return_stake(s_addr, query_id, 3);
|
|
}
|
|
if (finished) {
|
|
;; elections already finished, return stake
|
|
return return_stake(s_addr, query_id, 0);
|
|
}
|
|
var (mem, found) = members.udict_get?(256, validator_pubkey);
|
|
if (found) {
|
|
;; entry found, merge stakes
|
|
msg_value += mem~load_grams();
|
|
mem~load_uint(64); ;; skip timestamp and max_factor
|
|
found = (src_addr != mem~load_uint(256));
|
|
}
|
|
if (found) {
|
|
;; can make stakes for a public key from one address only
|
|
return return_stake(s_addr, query_id, 4);
|
|
}
|
|
if (msg_value < min_stake) {
|
|
;; stake too small, return it
|
|
return return_stake(s_addr, query_id, 5);
|
|
}
|
|
throw_unless(44, msg_value);
|
|
accept_message();
|
|
;; store stake in the dictionary
|
|
members~udict_set_builder(256, validator_pubkey, begin_cell()
|
|
.store_grams(msg_value)
|
|
.store_uint(now(), 32)
|
|
.store_uint(max_factor, 32)
|
|
.store_uint(src_addr, 256)
|
|
.store_uint(adnl_addr, 256));
|
|
;; gather and save election data
|
|
elect = pack_elect(elect_at, elect_close, min_stake, total_stake, members, false, false);
|
|
set_data(begin_cell().store_dict(elect).store_slice(ds).end_cell());
|
|
;; return confirmation message
|
|
if (query_id) {
|
|
return send_confirmation(s_addr, query_id, 0);
|
|
}
|
|
return ();
|
|
}
|
|
|
|
(cell, int) unfreeze_without_bonuses(credits, freeze_dict, tot_stakes) inline_ref {
|
|
var total = var recovered = 0;
|
|
var pubkey = -1;
|
|
do {
|
|
(pubkey, var cs, var f) = freeze_dict.udict_get_next?(256, pubkey);
|
|
if (f) {
|
|
var (addr, weight, stake, banned) = (cs~load_uint(256), cs~load_uint(64), cs~load_grams(), cs~load_int(1));
|
|
cs.end_parse();
|
|
if (banned) {
|
|
recovered += stake;
|
|
} else {
|
|
credits~credit_to(addr, stake);
|
|
}
|
|
total += stake;
|
|
}
|
|
} until (~ f);
|
|
throw_unless(59, total == tot_stakes);
|
|
return (credits, recovered);
|
|
}
|
|
|
|
(cell, int) unfreeze_with_bonuses(credits, freeze_dict, tot_stakes, tot_bonuses) inline_ref {
|
|
var total = var recovered = var returned_bonuses = 0;
|
|
var pubkey = -1;
|
|
do {
|
|
(pubkey, var cs, var f) = freeze_dict.udict_get_next?(256, pubkey);
|
|
if (f) {
|
|
var (addr, weight, stake, banned) = (cs~load_uint(256), cs~load_uint(64), cs~load_grams(), cs~load_int(1));
|
|
cs.end_parse();
|
|
if (banned) {
|
|
recovered += stake;
|
|
} else {
|
|
var bonus = muldiv(tot_bonuses, stake, tot_stakes);
|
|
returned_bonuses += bonus;
|
|
credits~credit_to(addr, stake + bonus);
|
|
}
|
|
total += stake;
|
|
}
|
|
} until (~ f);
|
|
throw_unless(59, (total == tot_stakes) & (returned_bonuses <= tot_bonuses));
|
|
return (credits, recovered + tot_bonuses - returned_bonuses);
|
|
}
|
|
|
|
int stakes_sum(frozen_dict) inline_ref {
|
|
var total = 0;
|
|
var pubkey = -1;
|
|
do {
|
|
(pubkey, var cs, var f) = frozen_dict.udict_get_next?(256, pubkey);
|
|
if (f) {
|
|
cs~skip_bits(256 + 64);
|
|
total += cs~load_grams();
|
|
}
|
|
} until (~ f);
|
|
return total;
|
|
}
|
|
|
|
_ unfreeze_all(credits, past_elections, elect_id) inline_ref {
|
|
var (fs, f) = past_elections~udict_delete_get?(32, elect_id);
|
|
ifnot (f) {
|
|
;; no elections with this id
|
|
return (credits, past_elections, 0);
|
|
}
|
|
var (unfreeze_at, stake_held, vset_hash, fdict, tot_stakes, bonuses, complaints) = fs.unpack_past_election();
|
|
;; tot_stakes = fdict.stakes_sum(); ;; TEMP BUGFIX
|
|
var unused_prizes = (bonuses > 0) ?
|
|
credits~unfreeze_with_bonuses(fdict, tot_stakes, bonuses) :
|
|
credits~unfreeze_without_bonuses(fdict, tot_stakes);
|
|
return (credits, past_elections, unused_prizes);
|
|
}
|
|
|
|
() config_set_confirmed(s_addr, cs, query_id, ok) impure inline_ref {
|
|
var (src_wc, src_addr) = parse_std_addr(s_addr);
|
|
var config_addr = config_param(0).begin_parse().preload_uint(256);
|
|
var ds = get_data().begin_parse();
|
|
var elect = ds~load_dict();
|
|
if ((src_wc + 1) | (src_addr != config_addr) | elect.null?()) {
|
|
;; not from config smc, somebody's joke?
|
|
;; or no elections active (or just completed)
|
|
return ();
|
|
}
|
|
var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect();
|
|
if ((elect_at != query_id) | ~ finished) {
|
|
;; not these elections, or elections not finished yet
|
|
return ();
|
|
}
|
|
accept_message();
|
|
ifnot (ok) {
|
|
;; cancel elections, return stakes
|
|
var (credits, past_elections, grams) = (ds~load_dict(), ds~load_dict(), ds~load_grams());
|
|
(credits, past_elections, var unused_prizes) = unfreeze_all(credits, past_elections, elect_at);
|
|
set_data(begin_cell()
|
|
.store_int(false, 1)
|
|
.store_dict(credits)
|
|
.store_dict(past_elections)
|
|
.store_grams(grams + unused_prizes)
|
|
.store_slice(ds)
|
|
.end_cell());
|
|
}
|
|
;; ... do not remove elect until we see this set as the next elected validator set
|
|
}
|
|
|
|
() process_simple_transfer(s_addr, msg_value) impure inline_ref {
|
|
var (elect, credits, past_elections, grams, active_id, active_hash) = load_data();
|
|
(int src_wc, int src_addr) = parse_std_addr(s_addr);
|
|
if (src_addr | (src_wc + 1) | (active_id == 0)) {
|
|
;; simple transfer to us (credit "nobody's" account)
|
|
;; (or no known active validator set)
|
|
grams += msg_value;
|
|
return store_data(elect, credits, past_elections, grams, active_id, active_hash);
|
|
}
|
|
;; zero source address -1:00..00 (collecting validator fees)
|
|
var (fs, f) = past_elections.udict_get?(32, active_id);
|
|
ifnot (f) {
|
|
;; active validator set not found (?)
|
|
grams += msg_value;
|
|
} else {
|
|
;; credit active validator set bonuses
|
|
var (unfreeze_at, stake_held, hash, dict, total_stake, bonuses, complaints) = fs.unpack_past_election();
|
|
bonuses += msg_value;
|
|
past_elections~udict_set_builder(32, active_id,
|
|
pack_past_election(unfreeze_at, stake_held, hash, dict, total_stake, bonuses, complaints));
|
|
}
|
|
return store_data(elect, credits, past_elections, grams, active_id, active_hash);
|
|
}
|
|
|
|
() recover_stake(op, s_addr, cs, query_id) impure inline_ref {
|
|
(int src_wc, int src_addr) = parse_std_addr(s_addr);
|
|
if (src_wc + 1) {
|
|
;; not from masterchain, return error
|
|
return send_message_back(s_addr, 0xfffffffe, query_id, op, 0, 64);
|
|
}
|
|
var ds = get_data().begin_parse();
|
|
var (elect, credits) = (ds~load_dict(), ds~load_dict());
|
|
var (cs, f) = credits~udict_delete_get?(256, src_addr);
|
|
ifnot (f) {
|
|
;; no credit for sender, return error
|
|
return send_message_back(s_addr, 0xfffffffe, query_id, op, 0, 64);
|
|
}
|
|
var amount = cs~load_grams();
|
|
cs.end_parse();
|
|
;; save data
|
|
set_data(begin_cell().store_dict(elect).store_dict(credits).store_slice(ds).end_cell());
|
|
;; send amount to sender in a new message
|
|
send_raw_message(begin_cell()
|
|
.store_uint(0x18, 6)
|
|
.store_slice(s_addr)
|
|
.store_grams(amount)
|
|
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
|
|
.store_uint(0xf96f7324, 32)
|
|
.store_uint(query_id, 64)
|
|
.end_cell(), 64);
|
|
}
|
|
|
|
() after_code_upgrade(slice s_addr, slice cs, int query_id) impure method_id(1666) {
|
|
var op = 0x4e436f64;
|
|
return send_message_back(s_addr, 0xce436f64, query_id, op, 0, 64);
|
|
}
|
|
|
|
int upgrade_code(s_addr, cs, query_id) inline_ref {
|
|
var c_addr = config_param(0);
|
|
if (c_addr.null?()) {
|
|
;; no configuration smart contract known
|
|
return false;
|
|
}
|
|
var config_addr = c_addr.begin_parse().preload_uint(256);
|
|
var (src_wc, src_addr) = parse_std_addr(s_addr);
|
|
if ((src_wc + 1) | (src_addr != config_addr)) {
|
|
;; not from configuration smart contract, return error
|
|
return false;
|
|
}
|
|
accept_message();
|
|
var code = cs~load_ref();
|
|
set_code(code);
|
|
ifnot(cs.slice_empty?()) {
|
|
set_c3(code.begin_parse().bless());
|
|
after_code_upgrade(s_addr, cs, query_id);
|
|
throw(0);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int register_complaint(s_addr, complaint, msg_value) {
|
|
var (src_wc, src_addr) = parse_std_addr(s_addr);
|
|
if (src_wc + 1) { ;; not from masterchain, return error
|
|
return -1;
|
|
}
|
|
if (complaint.slice_depth() >= 128) {
|
|
return -3; ;; invalid complaint
|
|
}
|
|
var (elect, credits, past_elections, grams, active_id, active_hash) = load_data();
|
|
var election_id = complaint~load_uint(32);
|
|
var (fs, f) = past_elections.udict_get?(32, election_id);
|
|
ifnot (f) { ;; election not found
|
|
return -2;
|
|
}
|
|
var expire_in = fs.preload_uint(32) - now();
|
|
if (expire_in <= 0) { ;; already expired
|
|
return -4;
|
|
}
|
|
var (validator_pubkey, description, created_at, severity, reward_addr, paid, suggested_fine, suggested_fine_part) = unpack_complaint(complaint);
|
|
reward_addr = src_addr;
|
|
created_at = now();
|
|
;; compute complaint storage/creation price
|
|
var (deposit, bit_price, cell_price) = get_complaint_prices();
|
|
var (_, bits, refs) = slice_compute_data_size(complaint, 4096);
|
|
var pps = (bits + 1024) * bit_price + (refs + 2) * cell_price;
|
|
paid = pps * expire_in + deposit;
|
|
if (msg_value < paid + (1 << 30)) { ;; not enough money
|
|
return -5;
|
|
}
|
|
;; re-pack modified complaint
|
|
cell complaint = pack_complaint(validator_pubkey, description, created_at, severity, reward_addr, paid, suggested_fine, suggested_fine_part).end_cell();
|
|
var (unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints) = unpack_past_election(fs);
|
|
var (fs, f) = frozen_dict.udict_get?(256, validator_pubkey);
|
|
ifnot (f) { ;; no such validator, cannot complain
|
|
return -6;
|
|
}
|
|
fs~skip_bits(256 + 64); ;; addr weight
|
|
var validator_stake = fs~load_grams();
|
|
int fine = suggested_fine + muldiv(validator_stake, suggested_fine_part, 1 << 32);
|
|
if (fine > validator_stake) { ;; validator's stake is less than suggested fine
|
|
return -7;
|
|
}
|
|
if (fine <= paid) { ;; fine is less than the money paid for creating complaint
|
|
return -8;
|
|
}
|
|
;; create complaint status
|
|
var cstatus = pack_complaint_status(complaint, null(), 0, 0);
|
|
;; save complaint status into complaints
|
|
var cpl_id = complaint.cell_hash();
|
|
ifnot (complaints~udict_add_builder?(256, cpl_id, cstatus)) {
|
|
return -9; ;; complaint already exists
|
|
}
|
|
;; pack past election info
|
|
past_elections~udict_set_builder(32, election_id, pack_past_election(unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints));
|
|
;; pack persistent data
|
|
;; next line can be commented, but it saves a lot of stack manipulations
|
|
var (elect, credits, _, grams, active_id, active_hash) = load_data();
|
|
store_data(elect, credits, past_elections, grams, active_id, active_hash);
|
|
return paid;
|
|
}
|
|
|
|
(cell, cell, int, int) punish(credits, frozen, complaint) inline_ref {
|
|
var (validator_pubkey, description, created_at, severity, reward_addr, paid, suggested_fine, suggested_fine_part) = complaint.begin_parse().unpack_complaint();
|
|
var (cs, f) = frozen.udict_get?(256, validator_pubkey);
|
|
ifnot (f) {
|
|
;; no validator to punish
|
|
return (credits, frozen, 0, 0);
|
|
}
|
|
var (addr, weight, stake, banned) = (cs~load_uint(256), cs~load_uint(64), cs~load_grams(), cs~load_int(1));
|
|
cs.end_parse();
|
|
int fine = min(stake, suggested_fine + muldiv(stake, suggested_fine_part, 1 << 32));
|
|
stake -= fine;
|
|
frozen~udict_set_builder(256, validator_pubkey, begin_cell()
|
|
.store_uint(addr, 256)
|
|
.store_uint(weight, 64)
|
|
.store_grams(stake)
|
|
.store_int(banned, 1));
|
|
int reward = min(fine >> 3, paid * 8);
|
|
credits~credit_to(reward_addr, reward);
|
|
return (credits, frozen, fine - reward, fine);
|
|
}
|
|
|
|
(cell, cell, int) register_vote(complaints, chash, idx, weight) inline_ref {
|
|
var (cstatus, found?) = complaints.udict_get?(256, chash);
|
|
ifnot (found?) {
|
|
;; complaint not found
|
|
return (complaints, null(), -1);
|
|
}
|
|
var (cur_vset, total_weight, _) = get_current_vset();
|
|
int cur_vset_id = cur_vset.cell_hash();
|
|
var (complaint, voters, vset_id, weight_remaining) = unpack_complaint_status(cstatus);
|
|
int vset_old? = (vset_id != cur_vset_id);
|
|
if ((weight_remaining < 0) & vset_old?) {
|
|
;; previous validator set already collected 2/3 votes, skip new votes
|
|
return (complaints, null(), -3);
|
|
}
|
|
if (vset_old?) {
|
|
;; complaint votes belong to a previous validator set, reset voting
|
|
vset_id = cur_vset_id;
|
|
voters = null();
|
|
weight_remaining = muldiv(total_weight, 2, 3);
|
|
}
|
|
var (_, found?) = voters.udict_get?(16, idx);
|
|
if (found?) {
|
|
;; already voted for this proposal, ignore vote
|
|
return (complaints, null(), 0);
|
|
}
|
|
;; register vote
|
|
voters~udict_set_builder(16, idx, begin_cell().store_uint(now(), 32));
|
|
int old_wr = weight_remaining;
|
|
weight_remaining -= weight;
|
|
old_wr ^= weight_remaining;
|
|
;; save voters and weight_remaining
|
|
complaints~udict_set_builder(256, chash, pack_complaint_status(complaint, voters, vset_id, weight_remaining));
|
|
if (old_wr >= 0) {
|
|
;; not enough votes or already accepted
|
|
return (complaints, null(), 1);
|
|
}
|
|
;; complaint wins, prepare punishment
|
|
return (complaints, complaint, 2);
|
|
}
|
|
|
|
int proceed_register_vote(election_id, chash, idx, weight) impure inline_ref {
|
|
var (elect, credits, past_elections, grams, active_id, active_hash) = load_data();
|
|
var (fs, f) = past_elections.udict_get?(32, election_id);
|
|
ifnot (f) { ;; election not found
|
|
return -2;
|
|
}
|
|
var (unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints) = unpack_past_election(fs);
|
|
(complaints, var accepted_complaint, var status) = register_vote(complaints, chash, idx, weight);
|
|
if (status <= 0) {
|
|
return status;
|
|
}
|
|
ifnot (accepted_complaint.null?()) {
|
|
(credits, frozen_dict, int fine_unalloc, int fine_collected) = punish(credits, frozen_dict, accepted_complaint);
|
|
grams += fine_unalloc;
|
|
total_stake -= fine_collected;
|
|
}
|
|
past_elections~udict_set_builder(32, election_id, pack_past_election(unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints));
|
|
store_data(elect, credits, past_elections, grams, active_id, active_hash);
|
|
return status;
|
|
}
|
|
|
|
() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure {
|
|
;; do nothing for internal messages
|
|
var cs = in_msg_cell.begin_parse();
|
|
var flags = cs~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool
|
|
if (flags & 1) {
|
|
;; ignore all bounced messages
|
|
return ();
|
|
}
|
|
var s_addr = cs~load_msg_addr();
|
|
if (in_msg.slice_empty?()) {
|
|
;; inbound message has empty body
|
|
return process_simple_transfer(s_addr, msg_value);
|
|
}
|
|
int op = in_msg~load_uint(32);
|
|
if (op == 0) {
|
|
;; simple transfer with comment, return
|
|
return process_simple_transfer(s_addr, msg_value);
|
|
}
|
|
int query_id = in_msg~load_uint(64);
|
|
if (op == 0x4e73744b) {
|
|
;; new stake message
|
|
return process_new_stake(s_addr, msg_value, in_msg, query_id);
|
|
}
|
|
if (op == 0x47657424) {
|
|
;; recover stake request
|
|
return recover_stake(op, s_addr, in_msg, query_id);
|
|
}
|
|
if (op == 0x4e436f64) {
|
|
;; upgrade code (accepted only from configuration smart contract)
|
|
var ok = upgrade_code(s_addr, in_msg, query_id);
|
|
return send_message_back(s_addr, ok ? 0xce436f64 : 0xffffffff, query_id, op, 0, 64);
|
|
}
|
|
var cfg_ok = (op == 0xee764f4b);
|
|
if (cfg_ok | (op == 0xee764f6f)) {
|
|
;; confirmation from configuration smart contract
|
|
return config_set_confirmed(s_addr, in_msg, query_id, cfg_ok);
|
|
}
|
|
if (op == 0x52674370) {
|
|
;; new complaint
|
|
var price = register_complaint(s_addr, in_msg, msg_value);
|
|
int mode = 64;
|
|
int ans_tag = - price;
|
|
if (price >= 0) {
|
|
;; ok, debit price
|
|
raw_reserve(price, 4);
|
|
ans_tag = 0;
|
|
mode = 128;
|
|
}
|
|
return send_message_back(s_addr, ans_tag + 0xf2676350, query_id, op, 0, mode);
|
|
}
|
|
if (op == 0x56744370) {
|
|
;; vote for a complaint
|
|
var signature = in_msg~load_bits(512);
|
|
var msg_body = in_msg;
|
|
var (sign_tag, idx, elect_id, chash) = (in_msg~load_uint(32), in_msg~load_uint(16), in_msg~load_uint(32), in_msg~load_uint(256));
|
|
in_msg.end_parse();
|
|
throw_unless(37, sign_tag == 0x56744350);
|
|
var (vdescr, total_weight) = get_validator_descr(idx);
|
|
var (val_pubkey, weight) = unpack_validator_descr(vdescr);
|
|
throw_unless(34, check_data_signature(msg_body, signature, val_pubkey));
|
|
int res = proceed_register_vote(elect_id, chash, idx, weight);
|
|
return send_message_back(s_addr, res + 0xd6745240, query_id, op, 0, 64);
|
|
}
|
|
|
|
ifnot (op & (1 << 31)) {
|
|
;; unknown query, return error
|
|
return send_message_back(s_addr, 0xffffffff, query_id, op, 0, 64);
|
|
}
|
|
;; unknown answer, ignore
|
|
return ();
|
|
}
|
|
|
|
int postpone_elections() impure {
|
|
return false;
|
|
}
|
|
|
|
;; computes the total stake out of the first n entries of list l
|
|
_ compute_total_stake(l, n, m_stake) inline_ref {
|
|
int tot_stake = 0;
|
|
repeat (n) {
|
|
(var h, l) = uncons(l);
|
|
var stake = h.at(0);
|
|
var max_f = h.at(1);
|
|
stake = min(stake, (max_f * m_stake) >> 16);
|
|
tot_stake += stake;
|
|
}
|
|
return tot_stake;
|
|
}
|
|
|
|
(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();
|
|
min_validators = max(min_validators, 1);
|
|
int n = 0;
|
|
var sdict = new_dict();
|
|
var pubkey = -1;
|
|
do {
|
|
(pubkey, var cs, var f) = members.udict_get_next?(256, pubkey);
|
|
if (f) {
|
|
var (stake, time, max_factor, addr, adnl_addr) = (cs~load_grams(), cs~load_uint(32), cs~load_uint(32), cs~load_uint(256), cs~load_uint(256));
|
|
cs.end_parse();
|
|
var key = begin_cell()
|
|
.store_uint(stake, 128)
|
|
.store_int(- time, 32)
|
|
.store_uint(pubkey, 256)
|
|
.end_cell().begin_parse();
|
|
sdict~dict_set_builder(128 + 32 + 256, key, begin_cell()
|
|
.store_uint(min(max_factor, max_stake_factor), 32)
|
|
.store_uint(addr, 256)
|
|
.store_uint(adnl_addr, 256));
|
|
n += 1;
|
|
}
|
|
} until (~ f);
|
|
n = min(n, max_validators);
|
|
if (n < min_validators) {
|
|
return (credits, new_dict(), 0, new_dict(), 0, 0);
|
|
}
|
|
var l = nil;
|
|
do {
|
|
var (key, cs, f) = sdict~dict::delete_get_min(128 + 32 + 256);
|
|
if (f) {
|
|
var (stake, _, pubkey) = (min(key~load_uint(128), max_stake), key~load_uint(32), key.preload_uint(256));
|
|
var (max_f, _, adnl_addr) = (cs~load_uint(32), cs~load_uint(256), cs.preload_uint(256));
|
|
l = cons([stake, max_f, pubkey, adnl_addr], l);
|
|
}
|
|
} until (~ f);
|
|
;; l is the list of all stakes in decreasing order
|
|
int i = min_validators - 1;
|
|
var l1 = l;
|
|
repeat (i) {
|
|
l1 = cdr(l1);
|
|
}
|
|
var (best_stake, m) = (0, 0);
|
|
do {
|
|
var stake = l1~list_next().at(0);
|
|
i += 1;
|
|
if (stake >= min_stake) {
|
|
var tot_stake = compute_total_stake(l, i, stake);
|
|
if (tot_stake > best_stake) {
|
|
(best_stake, m) = (tot_stake, i);
|
|
}
|
|
}
|
|
} until (i >= n);
|
|
if ((m == 0) | (best_stake < min_total_stake)) {
|
|
return (credits, new_dict(), 0, new_dict(), 0, 0);
|
|
}
|
|
;; we have to select first m validators from list l
|
|
l1 = touch(l);
|
|
;; l1~dump(); ;; DEBUG
|
|
repeat (m - 1) {
|
|
l1 = cdr(l1);
|
|
}
|
|
var m_stake = car(l1).at(0); ;; minimal 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 {
|
|
var [stake, max_f, pubkey, adnl_addr] = l~list_next();
|
|
;; lookup source address first
|
|
var (val, f) = members.udict_get?(256, pubkey);
|
|
throw_unless(61, f);
|
|
(_, _, var src_addr) = (val~load_grams(), val~load_uint(64), val.preload_uint(256));
|
|
if (i < m) {
|
|
;; one of the first m members, include into validator set
|
|
var true_stake = min(stake, (max_f * m_stake) >> 16);
|
|
stake -= true_stake;
|
|
;; ed25519_pubkey#8e81278a pubkey:bits256 = SigPubKey; // 288 bits
|
|
;; 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
|
|
.store_uint(pubkey, 256) ;; pubkey:bits256
|
|
.store_uint(weight, 64); ;; weight:uint64
|
|
if (adnl_addr) {
|
|
vinfo~store_uint(adnl_addr, 256); ;; adnl_addr:bits256
|
|
}
|
|
vset~udict_set_builder(16, i, vinfo);
|
|
frozen~udict_set_builder(256, pubkey, begin_cell()
|
|
.store_uint(src_addr, 256)
|
|
.store_uint(weight, 64)
|
|
.store_grams(true_stake)
|
|
.store_int(false, 1));
|
|
}
|
|
if (stake) {
|
|
;; non-zero unused part of the stake, credit to the source address
|
|
credits~credit_to(src_addr, stake);
|
|
}
|
|
i += 1;
|
|
} until (l.null?());
|
|
throw_unless(49, tot_stake == best_stake);
|
|
return (credits, vset, tot_weight, frozen, tot_stake, m);
|
|
}
|
|
|
|
int conduct_elections(ds, elect, credits) impure {
|
|
var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect();
|
|
if (now() < elect_close) {
|
|
;; elections not finished yet
|
|
return false;
|
|
}
|
|
if (config_param(0).null?()) {
|
|
;; no configuration smart contract to send result to
|
|
return postpone_elections();
|
|
}
|
|
var cs = config_param(17).begin_parse();
|
|
min_stake = cs~load_grams();
|
|
var max_stake = cs~load_grams();
|
|
var min_total_stake = cs~load_grams();
|
|
var max_stake_factor = cs~load_uint(32);
|
|
cs.end_parse();
|
|
if (total_stake < min_total_stake) {
|
|
;; insufficient total stake, postpone elections
|
|
return postpone_elections();
|
|
}
|
|
if (failed) {
|
|
;; do not retry failed elections until new stakes arrive
|
|
return postpone_elections();
|
|
}
|
|
if (finished) {
|
|
;; elections finished
|
|
return false;
|
|
}
|
|
(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;
|
|
elect = pack_elect(elect_at, elect_close, min_stake, total_stake, members, failed, finished);
|
|
ifnot (cnt) {
|
|
;; elections failed, set elect_failed to true
|
|
set_data(begin_cell().store_dict(elect).store_dict(credits).store_slice(ds).end_cell());
|
|
return postpone_elections();
|
|
}
|
|
;; serialize a query to the configuration smart contract
|
|
;; to install the computed validator set as the next validator set
|
|
var (elect_for, elect_begin_before, elect_end_before, stake_held) = get_validator_conf();
|
|
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(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_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);
|
|
;; add frozen to the dictionary of past elections
|
|
var past_elections = ds~load_dict();
|
|
past_elections~udict_set_builder(32, elect_at, pack_past_election(
|
|
start + elect_for + stake_held, stake_held, vset.cell_hash(),
|
|
frozen, total_stakes, 0, null()));
|
|
;; store credits and frozen until end
|
|
set_data(begin_cell()
|
|
.store_dict(elect)
|
|
.store_dict(credits)
|
|
.store_dict(past_elections)
|
|
.store_slice(ds)
|
|
.end_cell());
|
|
return true;
|
|
}
|
|
|
|
int update_active_vset_id() impure {
|
|
var (elect, credits, past_elections, grams, active_id, active_hash) = load_data();
|
|
var cur_hash = config_param(34).cell_hash();
|
|
if (cur_hash == active_hash) {
|
|
;; validator set unchanged
|
|
return false;
|
|
}
|
|
if (active_id) {
|
|
;; active_id becomes inactive
|
|
var (fs, f) = past_elections.udict_get?(32, active_id);
|
|
if (f) {
|
|
;; adjust unfreeze time of this validator set
|
|
var unfreeze_time = fs~load_uint(32);
|
|
var fs0 = fs;
|
|
var (stake_held, hash) = (fs~load_uint(32), fs~load_uint(256));
|
|
throw_unless(57, hash == active_hash);
|
|
unfreeze_time = now() + stake_held;
|
|
past_elections~udict_set_builder(32, active_id, begin_cell()
|
|
.store_uint(unfreeze_time, 32)
|
|
.store_slice(fs0));
|
|
}
|
|
}
|
|
;; look up new active_id by hash
|
|
var id = -1;
|
|
do {
|
|
(id, var fs, var f) = past_elections.udict_get_next?(32, id);
|
|
if (f) {
|
|
var (tm, hash) = (fs~load_uint(64), fs~load_uint(256));
|
|
if (hash == cur_hash) {
|
|
;; parse more of this record
|
|
var (dict, total_stake, bonuses) = (fs~load_dict(), fs~load_grams(), fs~load_grams());
|
|
;; transfer 1/8 of accumulated everybody's grams to this validator set as bonuses
|
|
var amount = (grams >> 3);
|
|
grams -= amount;
|
|
bonuses += amount;
|
|
;; serialize back
|
|
past_elections~udict_set_builder(32, id, begin_cell()
|
|
.store_uint(tm, 64)
|
|
.store_uint(hash, 256)
|
|
.store_dict(dict)
|
|
.store_grams(total_stake)
|
|
.store_grams(bonuses)
|
|
.store_slice(fs));
|
|
;; found
|
|
f = false;
|
|
}
|
|
}
|
|
} until (~ f);
|
|
active_id = (id.null?() ? 0 : id);
|
|
active_hash = cur_hash;
|
|
store_data(elect, credits, past_elections, grams, active_id, active_hash);
|
|
return true;
|
|
}
|
|
|
|
int cell_hash_eq?(cell vset, int expected_vset_hash) inline_ref {
|
|
return vset.null?() ? false : cell_hash(vset) == expected_vset_hash;
|
|
}
|
|
|
|
int validator_set_installed(ds, elect, credits) impure {
|
|
var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect();
|
|
ifnot (finished) {
|
|
;; elections not finished yet
|
|
return false;
|
|
}
|
|
var past_elections = ds~load_dict();
|
|
var (fs, f) = past_elections.udict_get?(32, elect_at);
|
|
ifnot (f) {
|
|
;; no election data in dictionary
|
|
return false;
|
|
}
|
|
;; recover validator set hash
|
|
var vset_hash = fs.skip_bits(64).preload_uint(256);
|
|
if (config_param(34).cell_hash_eq?(vset_hash) | config_param(36).cell_hash_eq?(vset_hash)) {
|
|
;; this validator set has been installed, forget elections
|
|
set_data(begin_cell()
|
|
.store_int(false, 1) ;; forget current elections
|
|
.store_dict(credits)
|
|
.store_dict(past_elections)
|
|
.store_slice(ds)
|
|
.end_cell());
|
|
update_active_vset_id();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int check_unfreeze() impure {
|
|
var (elect, credits, past_elections, grams, active_id, active_hash) = load_data();
|
|
int id = -1;
|
|
do {
|
|
(id, var fs, var f) = past_elections.udict_get_next?(32, id);
|
|
if (f) {
|
|
var unfreeze_at = fs~load_uint(32);
|
|
if ((unfreeze_at <= now()) & (id != active_id)) {
|
|
;; unfreeze!
|
|
(credits, past_elections, var unused_prizes) = unfreeze_all(credits, past_elections, id);
|
|
grams += unused_prizes;
|
|
;; unfreeze only one at time, exit loop
|
|
store_data(elect, credits, past_elections, grams, active_id, active_hash);
|
|
;; exit loop
|
|
f = false;
|
|
}
|
|
}
|
|
} until (~ f);
|
|
return ~ id.null?();
|
|
}
|
|
|
|
int announce_new_elections(ds, elect, credits) {
|
|
var next_vset = config_param(36); ;; next validator set
|
|
ifnot (next_vset.null?()) {
|
|
;; next validator set exists, no elections needed
|
|
return false;
|
|
}
|
|
var elector_addr = config_param(1).begin_parse().preload_uint(256);
|
|
var (my_wc, my_addr) = my_address().parse_std_addr();
|
|
if ((my_wc + 1) | (my_addr != elector_addr)) {
|
|
;; this smart contract is not the elections smart contract anymore, no new elections
|
|
return false;
|
|
}
|
|
var cur_vset = config_param(34); ;; current validator set
|
|
if (cur_vset.null?()) {
|
|
return false;
|
|
}
|
|
var (elect_for, elect_begin_before, elect_end_before, stake_held) = get_validator_conf();
|
|
var cur_valid_until = cur_vset.begin_parse().skip_bits(8 + 32).preload_uint(32);
|
|
var t = now();
|
|
var t0 = cur_valid_until - elect_begin_before;
|
|
if (t < t0) {
|
|
;; too early for the next elections
|
|
return false;
|
|
}
|
|
;; less than elect_before_begin seconds left, create new elections
|
|
if (t - t0 < 60) {
|
|
;; pretend that the elections started at t0
|
|
t = t0;
|
|
}
|
|
;; get stake parameters
|
|
(_, var min_stake) = config_param(17).begin_parse().load_grams();
|
|
;; announce new elections
|
|
var elect_at = t + elect_begin_before;
|
|
;; elect_at~dump();
|
|
var elect_close = elect_at - elect_end_before;
|
|
elect = pack_elect(elect_at, elect_close, min_stake, 0, new_dict(), false, false);
|
|
set_data(begin_cell().store_dict(elect).store_dict(credits).store_slice(ds).end_cell());
|
|
return true;
|
|
}
|
|
|
|
() run_ticktock(int is_tock) impure {
|
|
;; check whether an election is being conducted
|
|
var ds = get_data().begin_parse();
|
|
var (elect, credits) = (ds~load_dict(), ds~load_dict());
|
|
ifnot (elect.null?()) {
|
|
;; have an active election
|
|
throw_if(0, conduct_elections(ds, elect, credits)); ;; elections conducted, exit
|
|
throw_if(0, validator_set_installed(ds, elect, credits)); ;; validator set installed, current elections removed
|
|
} else {
|
|
throw_if(0, announce_new_elections(ds, elect, credits)); ;; new elections announced, exit
|
|
}
|
|
throw_if(0, update_active_vset_id()); ;; active validator set id updated, exit
|
|
check_unfreeze();
|
|
}
|
|
|
|
;; Get methods
|
|
|
|
;; returns active election id or 0
|
|
int active_election_id() method_id {
|
|
var elect = get_data().begin_parse().preload_dict();
|
|
return elect.null?() ? 0 : elect.begin_parse().preload_uint(32);
|
|
}
|
|
|
|
;; checks whether a public key participates in current elections
|
|
int participates_in(int validator_pubkey) method_id {
|
|
var elect = get_data().begin_parse().preload_dict();
|
|
if (elect.null?()) {
|
|
return 0;
|
|
}
|
|
var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect();
|
|
var (mem, found) = members.udict_get?(256, validator_pubkey);
|
|
return found ? mem~load_grams() : 0;
|
|
}
|
|
|
|
;; returns the list of all participants of current elections with their stakes
|
|
_ participant_list() method_id {
|
|
var elect = get_data().begin_parse().preload_dict();
|
|
if (elect.null?()) {
|
|
return nil;
|
|
}
|
|
var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect();
|
|
var l = nil;
|
|
var id = (1 << 255) + ((1 << 255) - 1);
|
|
do {
|
|
(id, var fs, var f) = members.udict_get_prev?(256, id);
|
|
if (f) {
|
|
l = cons([id, fs~load_grams()], l);
|
|
}
|
|
} until (~ f);
|
|
return l;
|
|
}
|
|
|
|
;; returns the list of all participants of current elections with their data
|
|
_ participant_list_extended() method_id {
|
|
var elect = get_data().begin_parse().preload_dict();
|
|
if (elect.null?()) {
|
|
return (0, 0, 0, 0, nil, 0, 0);
|
|
}
|
|
var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect();
|
|
var l = nil;
|
|
var id = (1 << 255) + ((1 << 255) - 1);
|
|
do {
|
|
(id, var cs, var f) = members.udict_get_prev?(256, id);
|
|
if (f) {
|
|
var (stake, time, max_factor, addr, adnl_addr) = (cs~load_grams(), cs~load_uint(32), cs~load_uint(32), cs~load_uint(256), cs~load_uint(256));
|
|
cs.end_parse();
|
|
l = cons([id, [stake, max_factor, addr, adnl_addr]], l);
|
|
}
|
|
} until (~ f);
|
|
return (elect_at, elect_close, min_stake, total_stake, l, failed, finished);
|
|
}
|
|
|
|
;; computes the return stake
|
|
int compute_returned_stake(int wallet_addr) method_id {
|
|
var cs = get_data().begin_parse();
|
|
(_, var credits) = (cs~load_dict(), cs~load_dict());
|
|
var (val, f) = credits.udict_get?(256, wallet_addr);
|
|
return f ? val~load_grams() : 0;
|
|
}
|
|
|
|
;; returns the list of past election ids
|
|
tuple past_election_ids() method_id {
|
|
var (elect, credits, past_elections, grams, active_id, active_hash) = load_data();
|
|
var id = (1 << 32);
|
|
var list = null();
|
|
do {
|
|
(id, var fs, var f) = past_elections.udict_get_prev?(32, id);
|
|
if (f) {
|
|
list = cons(id, list);
|
|
}
|
|
} until (~ f);
|
|
return list;
|
|
}
|
|
|
|
tuple past_elections() method_id {
|
|
var (elect, credits, past_elections, grams, active_id, active_hash) = load_data();
|
|
var id = (1 << 32);
|
|
var list = null();
|
|
do {
|
|
(id, var fs, var found) = past_elections.udict_get_prev?(32, id);
|
|
if (found) {
|
|
list = cons([id, unpack_past_election(fs)], list);
|
|
}
|
|
} until (~ found);
|
|
return list;
|
|
}
|
|
|
|
tuple past_elections_list() method_id {
|
|
var (elect, credits, past_elections, grams, active_id, active_hash) = load_data();
|
|
var id = (1 << 32);
|
|
var list = null();
|
|
do {
|
|
(id, var fs, var found) = past_elections.udict_get_prev?(32, id);
|
|
if (found) {
|
|
var (unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints) = unpack_past_election(fs);
|
|
list = cons([id, unfreeze_at, vset_hash, stake_held], list);
|
|
}
|
|
} until (~ found);
|
|
return list;
|
|
}
|
|
|
|
_ complete_unpack_complaint(slice cs) inline_ref {
|
|
var (complaint, voters, vset_id, weight_remaining) = cs.unpack_complaint_status();
|
|
var voters_list = null();
|
|
var voter_id = (1 << 32);
|
|
do {
|
|
(voter_id, _, var f) = voters.udict_get_prev?(16, voter_id);
|
|
if (f) {
|
|
voters_list = cons(voter_id, voters_list);
|
|
}
|
|
} until (~ f);
|
|
return [[complaint.begin_parse().unpack_complaint()], voters_list, vset_id, weight_remaining];
|
|
}
|
|
|
|
cell get_past_complaints(int election_id) inline_ref method_id {
|
|
var (elect, credits, past_elections, grams, active_id, active_hash) = load_data();
|
|
var (fs, found?) = past_elections.udict_get?(32, election_id);
|
|
ifnot (found?) {
|
|
return null();
|
|
}
|
|
var (unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints) = unpack_past_election(fs);
|
|
return complaints;
|
|
}
|
|
|
|
_ show_complaint(int election_id, int chash) method_id {
|
|
var complaints = get_past_complaints(election_id);
|
|
var (cs, found) = complaints.udict_get?(256, chash);
|
|
return found ? complete_unpack_complaint(cs) : null();
|
|
}
|
|
|
|
tuple list_complaints(int election_id) method_id {
|
|
var complaints = get_past_complaints(election_id);
|
|
int id = (1 << 255) + ((1 << 255) - 1);
|
|
var list = null();
|
|
do {
|
|
(id, var cs, var found?) = complaints.udict_get_prev?(256, id);
|
|
if (found?) {
|
|
list = cons(pair(id, complete_unpack_complaint(cs)), list);
|
|
}
|
|
} until (~ found?);
|
|
return list;
|
|
}
|
|
|
|
int complaint_storage_price(int bits, int refs, int expire_in) method_id {
|
|
;; compute complaint storage/creation price
|
|
var (deposit, bit_price, cell_price) = get_complaint_prices();
|
|
var pps = (bits + 1024) * bit_price + (refs + 2) * cell_price;
|
|
var paid = pps * expire_in + deposit;
|
|
return paid + (1 << 30);
|
|
}
|