1
0
Fork 0
mirror of https://github.com/ton-blockchain/ton synced 2025-02-12 19:22:37 +00:00
ton/crypto/smartcont/elector-code.fc
2020-05-07 10:35:23 +04:00

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