Ton
1189 строк · 42.5 Кб
1;; Elector smartcontract
2
3#include "stdlib.fc";
4
5;; cur_elect credits past_elections grams active_id active_hash
6(cell, cell, cell, int, int, int) load_data() inline_ref {
7var cs = get_data().begin_parse();
8var res = (cs~load_dict(), cs~load_dict(), cs~load_dict(), cs~load_grams(), cs~load_uint(32), cs~load_uint(256));
9cs.end_parse();
10return res;
11}
12
13;; cur_elect credits past_elections grams active_id active_hash
14() store_data(elect, credits, past_elections, grams, active_id, active_hash) impure inline_ref {
15set_data(begin_cell()
16.store_dict(elect)
17.store_dict(credits)
18.store_dict(past_elections)
19.store_grams(grams)
20.store_uint(active_id, 32)
21.store_uint(active_hash, 256)
22.end_cell());
23}
24
25;; elect -> elect_at elect_close min_stake total_stake members failed finished
26_ unpack_elect(elect) inline_ref {
27var es = elect.begin_parse();
28var 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));
29es.end_parse();
30return res;
31}
32
33cell pack_elect(elect_at, elect_close, min_stake, total_stake, members, failed, finished) inline_ref {
34return begin_cell()
35.store_uint(elect_at, 32)
36.store_uint(elect_close, 32)
37.store_grams(min_stake)
38.store_grams(total_stake)
39.store_dict(members)
40.store_int(failed, 1)
41.store_int(finished, 1)
42.end_cell();
43}
44
45;; slice -> unfreeze_at stake_held vset_hash frozen_dict total_stake bonuses complaints
46_ unpack_past_election(slice fs) inline_ref {
47var 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());
48fs.end_parse();
49return res;
50}
51
52builder pack_past_election(int unfreeze_at, int stake_held, int vset_hash, cell frozen_dict, int total_stake, int bonuses, cell complaints) inline_ref {
53return begin_cell()
54.store_uint(unfreeze_at, 32)
55.store_uint(stake_held, 32)
56.store_uint(vset_hash, 256)
57.store_dict(frozen_dict)
58.store_grams(total_stake)
59.store_grams(bonuses)
60.store_dict(complaints);
61}
62
63;; complaint_status#2d complaint:^ValidatorComplaint voters:(HashmapE 16 True)
64;; vset_id:uint256 weight_remaining:int64 = ValidatorComplaintStatus;
65_ unpack_complaint_status(slice cs) inline_ref {
66throw_unless(9, cs~load_uint(8) == 0x2d);
67var res = (cs~load_ref(), cs~load_dict(), cs~load_uint(256), cs~load_int(64));
68cs.end_parse();
69return res;
70}
71
72builder pack_complaint_status(cell complaint, cell voters, int vset_id, int weight_remaining) inline_ref {
73return begin_cell()
74.store_uint(0x2d, 8)
75.store_ref(complaint)
76.store_dict(voters)
77.store_uint(vset_id, 256)
78.store_int(weight_remaining, 64);
79}
80
81;; validator_complaint#bc validator_pubkey:uint256 description:^ComplaintDescr
82;; created_at:uint32 severity:uint8 reward_addr:uint256 paid:Grams suggested_fine:Grams
83;; suggested_fine_part:uint32 = ValidatorComplaint;
84_ unpack_complaint(slice cs) inline_ref {
85throw_unless(9, cs~load_int(8) == 0xbc - 0x100);
86var 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));
87cs.end_parse();
88return res;
89}
90
91builder 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 {
92return begin_cell()
93.store_int(0xbc - 0x100, 8)
94.store_uint(validator_pubkey, 256)
95.store_ref(description)
96.store_uint(created_at, 32)
97.store_uint(severity, 8)
98.store_uint(reward_addr, 256)
99.store_grams(paid)
100.store_grams(suggested_fine)
101.store_uint(suggested_fine_part, 32);
102}
103
104;; complaint_prices#1a deposit:Grams bit_price:Grams cell_price:Grams = ComplaintPricing;
105(int, int, int) parse_complaint_prices(cell info) inline {
106var cs = info.begin_parse();
107throw_unless(9, cs~load_uint(8) == 0x1a);
108var res = (cs~load_grams(), cs~load_grams(), cs~load_grams());
109cs.end_parse();
110return res;
111}
112
113;; deposit bit_price cell_price
114(int, int, int) get_complaint_prices() inline_ref {
115var info = config_param(13);
116return info.null?() ? (1 << 36, 1, 512) : info.parse_complaint_prices();
117}
118
119;; elected_for elections_begin_before elections_end_before stake_held_for
120(int, int, int, int) get_validator_conf() {
121var cs = config_param(15).begin_parse();
122return (cs~load_int(32), cs~load_int(32), cs~load_int(32), cs.preload_int(32));
123}
124
125;; next three functions return information about current validator set (config param #34)
126;; they are borrowed from config-code.fc
127(cell, int, cell) get_current_vset() inline_ref {
128var vset = config_param(34);
129var cs = begin_parse(vset);
130;; validators_ext#12 utime_since:uint32 utime_until:uint32
131;; total:(## 16) main:(## 16) { main <= total } { main >= 1 }
132;; total_weight:uint64
133throw_unless(40, cs~load_uint(8) == 0x12);
134cs~skip_bits(32 + 32 + 16 + 16);
135var (total_weight, dict) = (cs~load_uint(64), cs~load_dict());
136cs.end_parse();
137return (vset, total_weight, dict);
138}
139
140(slice, int) get_validator_descr(int idx) inline_ref {
141var (vset, total_weight, dict) = get_current_vset();
142var (value, _) = dict.udict_get?(16, idx);
143return (value, total_weight);
144}
145
146(int, int) unpack_validator_descr(slice cs) inline {
147;; ed25519_pubkey#8e81278a pubkey:bits256 = SigPubKey;
148;; validator#53 public_key:SigPubKey weight:uint64 = ValidatorDescr;
149;; validator_addr#73 public_key:SigPubKey weight:uint64 adnl_addr:bits256 = ValidatorDescr;
150throw_unless(41, (cs~load_uint(8) & ~ 0x20) == 0x53);
151throw_unless(41, cs~load_uint(32) == 0x8e81278a);
152return (cs~load_uint(256), cs~load_uint(64));
153}
154
155() send_message_back(addr, ans_tag, query_id, body, grams, mode) impure inline_ref {
156;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000
157var msg = begin_cell()
158.store_uint(0x18, 6)
159.store_slice(addr)
160.store_grams(grams)
161.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
162.store_uint(ans_tag, 32)
163.store_uint(query_id, 64);
164if (body >= 0) {
165msg~store_uint(body, 32);
166}
167send_raw_message(msg.end_cell(), mode);
168}
169
170() return_stake(addr, query_id, reason) impure inline_ref {
171return send_message_back(addr, 0xee6f454c, query_id, reason, 0, 64);
172}
173
174() send_confirmation(addr, query_id, comment) impure inline_ref {
175return send_message_back(addr, 0xf374484c, query_id, comment, 1000000000, 2);
176}
177
178() send_validator_set_to_config(config_addr, vset, query_id) impure inline_ref {
179var msg = begin_cell()
180.store_uint(0xc4ff, 17) ;; 0 11000100 0xff
181.store_uint(config_addr, 256)
182.store_grams(1 << 30) ;; ~1 gram of value to process and obtain answer
183.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
184.store_uint(0x4e565354, 32)
185.store_uint(query_id, 64)
186.store_ref(vset);
187send_raw_message(msg.end_cell(), 1);
188}
189
190;; credits 'amount' to 'addr' inside credit dictionary 'credits'
191_ ~credit_to(credits, addr, amount) inline_ref {
192var (val, f) = credits.udict_get?(256, addr);
193if (f) {
194amount += val~load_grams();
195}
196credits~udict_set_builder(256, addr, begin_cell().store_grams(amount));
197return (credits, ());
198}
199
200() process_new_stake(s_addr, msg_value, cs, query_id) impure inline_ref {
201var (src_wc, src_addr) = parse_std_addr(s_addr);
202var ds = get_data().begin_parse();
203var elect = ds~load_dict();
204if (elect.null?() | (src_wc + 1)) {
205;; no elections active, or source is not in masterchain
206;; bounce message
207return return_stake(s_addr, query_id, 0);
208}
209;; parse the remainder of new stake message
210var validator_pubkey = cs~load_uint(256);
211var stake_at = cs~load_uint(32);
212var max_factor = cs~load_uint(32);
213var adnl_addr = cs~load_uint(256);
214var signature = cs~load_ref().begin_parse().preload_bits(512);
215cs.end_parse();
216ifnot (check_data_signature(begin_cell()
217.store_uint(0x654c5074, 32)
218.store_uint(stake_at, 32)
219.store_uint(max_factor, 32)
220.store_uint(src_addr, 256)
221.store_uint(adnl_addr, 256)
222.end_cell().begin_parse(), signature, validator_pubkey)) {
223;; incorrect signature, return stake
224return return_stake(s_addr, query_id, 1);
225}
226if (max_factor < 0x10000) {
227;; factor must be >= 1. = 65536/65536
228return return_stake(s_addr, query_id, 6);
229}
230;; parse current election data
231var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect();
232;; elect_at~dump();
233msg_value -= 1000000000; ;; deduct GR$1 for sending confirmation
234if ((msg_value << 12) < total_stake) {
235;; stake smaller than 1/4096 of the total accumulated stakes, return
236return return_stake(s_addr, query_id, 2);
237}
238total_stake += msg_value; ;; (provisionally) increase total stake
239if (stake_at != elect_at) {
240;; stake for some other elections, return
241return return_stake(s_addr, query_id, 3);
242}
243if (finished) {
244;; elections already finished, return stake
245return return_stake(s_addr, query_id, 0);
246}
247var (mem, found) = members.udict_get?(256, validator_pubkey);
248if (found) {
249;; entry found, merge stakes
250msg_value += mem~load_grams();
251mem~load_uint(64); ;; skip timestamp and max_factor
252found = (src_addr != mem~load_uint(256));
253}
254if (found) {
255;; can make stakes for a public key from one address only
256return return_stake(s_addr, query_id, 4);
257}
258if (msg_value < min_stake) {
259;; stake too small, return it
260return return_stake(s_addr, query_id, 5);
261}
262throw_unless(44, msg_value);
263accept_message();
264;; store stake in the dictionary
265members~udict_set_builder(256, validator_pubkey, begin_cell()
266.store_grams(msg_value)
267.store_uint(now(), 32)
268.store_uint(max_factor, 32)
269.store_uint(src_addr, 256)
270.store_uint(adnl_addr, 256));
271;; gather and save election data
272elect = pack_elect(elect_at, elect_close, min_stake, total_stake, members, false, false);
273set_data(begin_cell().store_dict(elect).store_slice(ds).end_cell());
274;; return confirmation message
275if (query_id) {
276return send_confirmation(s_addr, query_id, 0);
277}
278return ();
279}
280
281(cell, int) unfreeze_without_bonuses(credits, freeze_dict, tot_stakes) inline_ref {
282var total = var recovered = 0;
283var pubkey = -1;
284do {
285(pubkey, var cs, var f) = freeze_dict.udict_get_next?(256, pubkey);
286if (f) {
287var (addr, weight, stake, banned) = (cs~load_uint(256), cs~load_uint(64), cs~load_grams(), cs~load_int(1));
288cs.end_parse();
289if (banned) {
290recovered += stake;
291} else {
292credits~credit_to(addr, stake);
293}
294total += stake;
295}
296} until (~ f);
297throw_unless(59, total == tot_stakes);
298return (credits, recovered);
299}
300
301(cell, int) unfreeze_with_bonuses(credits, freeze_dict, tot_stakes, tot_bonuses) inline_ref {
302var total = var recovered = var returned_bonuses = 0;
303var pubkey = -1;
304do {
305(pubkey, var cs, var f) = freeze_dict.udict_get_next?(256, pubkey);
306if (f) {
307var (addr, weight, stake, banned) = (cs~load_uint(256), cs~load_uint(64), cs~load_grams(), cs~load_int(1));
308cs.end_parse();
309if (banned) {
310recovered += stake;
311} else {
312var bonus = muldiv(tot_bonuses, stake, tot_stakes);
313returned_bonuses += bonus;
314credits~credit_to(addr, stake + bonus);
315}
316total += stake;
317}
318} until (~ f);
319throw_unless(59, (total == tot_stakes) & (returned_bonuses <= tot_bonuses));
320return (credits, recovered + tot_bonuses - returned_bonuses);
321}
322
323int stakes_sum(frozen_dict) inline_ref {
324var total = 0;
325var pubkey = -1;
326do {
327(pubkey, var cs, var f) = frozen_dict.udict_get_next?(256, pubkey);
328if (f) {
329cs~skip_bits(256 + 64);
330total += cs~load_grams();
331}
332} until (~ f);
333return total;
334}
335
336_ unfreeze_all(credits, past_elections, elect_id) inline_ref {
337var (fs, f) = past_elections~udict_delete_get?(32, elect_id);
338ifnot (f) {
339;; no elections with this id
340return (credits, past_elections, 0);
341}
342var (unfreeze_at, stake_held, vset_hash, fdict, tot_stakes, bonuses, complaints) = fs.unpack_past_election();
343;; tot_stakes = fdict.stakes_sum(); ;; TEMP BUGFIX
344var unused_prizes = (bonuses > 0) ?
345credits~unfreeze_with_bonuses(fdict, tot_stakes, bonuses) :
346credits~unfreeze_without_bonuses(fdict, tot_stakes);
347return (credits, past_elections, unused_prizes);
348}
349
350() config_set_confirmed(s_addr, cs, query_id, ok) impure inline_ref {
351var (src_wc, src_addr) = parse_std_addr(s_addr);
352var config_addr = config_param(0).begin_parse().preload_uint(256);
353var ds = get_data().begin_parse();
354var elect = ds~load_dict();
355if ((src_wc + 1) | (src_addr != config_addr) | elect.null?()) {
356;; not from config smc, somebody's joke?
357;; or no elections active (or just completed)
358return ();
359}
360var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect();
361if ((elect_at != query_id) | ~ finished) {
362;; not these elections, or elections not finished yet
363return ();
364}
365accept_message();
366ifnot (ok) {
367;; cancel elections, return stakes
368var (credits, past_elections, grams) = (ds~load_dict(), ds~load_dict(), ds~load_grams());
369(credits, past_elections, var unused_prizes) = unfreeze_all(credits, past_elections, elect_at);
370set_data(begin_cell()
371.store_int(false, 1)
372.store_dict(credits)
373.store_dict(past_elections)
374.store_grams(grams + unused_prizes)
375.store_slice(ds)
376.end_cell());
377}
378;; ... do not remove elect until we see this set as the next elected validator set
379}
380
381() process_simple_transfer(s_addr, msg_value) impure inline_ref {
382var (elect, credits, past_elections, grams, active_id, active_hash) = load_data();
383(int src_wc, int src_addr) = parse_std_addr(s_addr);
384if (src_addr | (src_wc + 1) | (active_id == 0)) {
385;; simple transfer to us (credit "nobody's" account)
386;; (or no known active validator set)
387grams += msg_value;
388return store_data(elect, credits, past_elections, grams, active_id, active_hash);
389}
390;; zero source address -1:00..00 (collecting validator fees)
391var (fs, f) = past_elections.udict_get?(32, active_id);
392ifnot (f) {
393;; active validator set not found (?)
394grams += msg_value;
395} else {
396;; credit active validator set bonuses
397var (unfreeze_at, stake_held, hash, dict, total_stake, bonuses, complaints) = fs.unpack_past_election();
398bonuses += msg_value;
399past_elections~udict_set_builder(32, active_id,
400pack_past_election(unfreeze_at, stake_held, hash, dict, total_stake, bonuses, complaints));
401}
402return store_data(elect, credits, past_elections, grams, active_id, active_hash);
403}
404
405() recover_stake(op, s_addr, cs, query_id) impure inline_ref {
406(int src_wc, int src_addr) = parse_std_addr(s_addr);
407if (src_wc + 1) {
408;; not from masterchain, return error
409return send_message_back(s_addr, 0xfffffffe, query_id, op, 0, 64);
410}
411var ds = get_data().begin_parse();
412var (elect, credits) = (ds~load_dict(), ds~load_dict());
413var (cs, f) = credits~udict_delete_get?(256, src_addr);
414ifnot (f) {
415;; no credit for sender, return error
416return send_message_back(s_addr, 0xfffffffe, query_id, op, 0, 64);
417}
418var amount = cs~load_grams();
419cs.end_parse();
420;; save data
421set_data(begin_cell().store_dict(elect).store_dict(credits).store_slice(ds).end_cell());
422;; send amount to sender in a new message
423send_raw_message(begin_cell()
424.store_uint(0x18, 6)
425.store_slice(s_addr)
426.store_grams(amount)
427.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
428.store_uint(0xf96f7324, 32)
429.store_uint(query_id, 64)
430.end_cell(), 64);
431}
432
433() after_code_upgrade(slice s_addr, slice cs, int query_id) impure method_id(1666) {
434var op = 0x4e436f64;
435return send_message_back(s_addr, 0xce436f64, query_id, op, 0, 64);
436}
437
438int upgrade_code(s_addr, cs, query_id) inline_ref {
439var c_addr = config_param(0);
440if (c_addr.null?()) {
441;; no configuration smart contract known
442return false;
443}
444var config_addr = c_addr.begin_parse().preload_uint(256);
445var (src_wc, src_addr) = parse_std_addr(s_addr);
446if ((src_wc + 1) | (src_addr != config_addr)) {
447;; not from configuration smart contract, return error
448return false;
449}
450accept_message();
451var code = cs~load_ref();
452set_code(code);
453ifnot(cs.slice_empty?()) {
454set_c3(code.begin_parse().bless());
455after_code_upgrade(s_addr, cs, query_id);
456throw(0);
457}
458return true;
459}
460
461int register_complaint(s_addr, complaint, msg_value) {
462var (src_wc, src_addr) = parse_std_addr(s_addr);
463if (src_wc + 1) { ;; not from masterchain, return error
464return -1;
465}
466if (complaint.slice_depth() >= 128) {
467return -3; ;; invalid complaint
468}
469var (elect, credits, past_elections, grams, active_id, active_hash) = load_data();
470var election_id = complaint~load_uint(32);
471var (fs, f) = past_elections.udict_get?(32, election_id);
472ifnot (f) { ;; election not found
473return -2;
474}
475var expire_in = fs.preload_uint(32) - now();
476if (expire_in <= 0) { ;; already expired
477return -4;
478}
479var (validator_pubkey, description, created_at, severity, reward_addr, paid, suggested_fine, suggested_fine_part) = unpack_complaint(complaint);
480reward_addr = src_addr;
481created_at = now();
482;; compute complaint storage/creation price
483var (deposit, bit_price, cell_price) = get_complaint_prices();
484var (_, bits, refs) = slice_compute_data_size(complaint, 4096);
485var pps = (bits + 1024) * bit_price + (refs + 2) * cell_price;
486paid = pps * expire_in + deposit;
487if (msg_value < paid + (1 << 30)) { ;; not enough money
488return -5;
489}
490;; re-pack modified complaint
491cell complaint = pack_complaint(validator_pubkey, description, created_at, severity, reward_addr, paid, suggested_fine, suggested_fine_part).end_cell();
492var (unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints) = unpack_past_election(fs);
493var (fs, f) = frozen_dict.udict_get?(256, validator_pubkey);
494ifnot (f) { ;; no such validator, cannot complain
495return -6;
496}
497fs~skip_bits(256 + 64); ;; addr weight
498var validator_stake = fs~load_grams();
499int fine = suggested_fine + muldiv(validator_stake, suggested_fine_part, 1 << 32);
500if (fine > validator_stake) { ;; validator's stake is less than suggested fine
501return -7;
502}
503if (fine <= paid) { ;; fine is less than the money paid for creating complaint
504return -8;
505}
506;; create complaint status
507var cstatus = pack_complaint_status(complaint, null(), 0, 0);
508;; save complaint status into complaints
509var cpl_id = complaint.cell_hash();
510ifnot (complaints~udict_add_builder?(256, cpl_id, cstatus)) {
511return -9; ;; complaint already exists
512}
513;; pack past election info
514past_elections~udict_set_builder(32, election_id, pack_past_election(unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints));
515;; pack persistent data
516;; next line can be commented, but it saves a lot of stack manipulations
517var (elect, credits, _, grams, active_id, active_hash) = load_data();
518store_data(elect, credits, past_elections, grams, active_id, active_hash);
519return paid;
520}
521
522(cell, cell, int, int) punish(credits, frozen, complaint) inline_ref {
523var (validator_pubkey, description, created_at, severity, reward_addr, paid, suggested_fine, suggested_fine_part) = complaint.begin_parse().unpack_complaint();
524var (cs, f) = frozen.udict_get?(256, validator_pubkey);
525ifnot (f) {
526;; no validator to punish
527return (credits, frozen, 0, 0);
528}
529var (addr, weight, stake, banned) = (cs~load_uint(256), cs~load_uint(64), cs~load_grams(), cs~load_int(1));
530cs.end_parse();
531int fine = min(stake, suggested_fine + muldiv(stake, suggested_fine_part, 1 << 32));
532stake -= fine;
533frozen~udict_set_builder(256, validator_pubkey, begin_cell()
534.store_uint(addr, 256)
535.store_uint(weight, 64)
536.store_grams(stake)
537.store_int(banned, 1));
538int reward = min(fine >> 3, paid * 8);
539credits~credit_to(reward_addr, reward);
540return (credits, frozen, fine - reward, fine);
541}
542
543(cell, cell, int) register_vote(complaints, chash, idx, weight) inline_ref {
544var (cstatus, found?) = complaints.udict_get?(256, chash);
545ifnot (found?) {
546;; complaint not found
547return (complaints, null(), -1);
548}
549var (cur_vset, total_weight, _) = get_current_vset();
550int cur_vset_id = cur_vset.cell_hash();
551var (complaint, voters, vset_id, weight_remaining) = unpack_complaint_status(cstatus);
552int vset_old? = (vset_id != cur_vset_id);
553if ((weight_remaining < 0) & vset_old?) {
554;; previous validator set already collected 2/3 votes, skip new votes
555return (complaints, null(), -3);
556}
557if (vset_old?) {
558;; complaint votes belong to a previous validator set, reset voting
559vset_id = cur_vset_id;
560voters = null();
561weight_remaining = muldiv(total_weight, 2, 3);
562}
563var (_, found?) = voters.udict_get?(16, idx);
564if (found?) {
565;; already voted for this proposal, ignore vote
566return (complaints, null(), 0);
567}
568;; register vote
569voters~udict_set_builder(16, idx, begin_cell().store_uint(now(), 32));
570int old_wr = weight_remaining;
571weight_remaining -= weight;
572old_wr ^= weight_remaining;
573;; save voters and weight_remaining
574complaints~udict_set_builder(256, chash, pack_complaint_status(complaint, voters, vset_id, weight_remaining));
575if (old_wr >= 0) {
576;; not enough votes or already accepted
577return (complaints, null(), 1);
578}
579;; complaint wins, prepare punishment
580return (complaints, complaint, 2);
581}
582
583int proceed_register_vote(election_id, chash, idx, weight) impure inline_ref {
584var (elect, credits, past_elections, grams, active_id, active_hash) = load_data();
585var (fs, f) = past_elections.udict_get?(32, election_id);
586ifnot (f) { ;; election not found
587return -2;
588}
589var (unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints) = unpack_past_election(fs);
590(complaints, var accepted_complaint, var status) = register_vote(complaints, chash, idx, weight);
591if (status <= 0) {
592return status;
593}
594ifnot (accepted_complaint.null?()) {
595(credits, frozen_dict, int fine_unalloc, int fine_collected) = punish(credits, frozen_dict, accepted_complaint);
596grams += fine_unalloc;
597total_stake -= fine_collected;
598}
599past_elections~udict_set_builder(32, election_id, pack_past_election(unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints));
600store_data(elect, credits, past_elections, grams, active_id, active_hash);
601return status;
602}
603
604() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure {
605;; do nothing for internal messages
606var cs = in_msg_cell.begin_parse();
607var flags = cs~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool
608if (flags & 1) {
609;; ignore all bounced messages
610return ();
611}
612var s_addr = cs~load_msg_addr();
613if (in_msg.slice_empty?()) {
614;; inbound message has empty body
615return process_simple_transfer(s_addr, msg_value);
616}
617int op = in_msg~load_uint(32);
618if (op == 0) {
619;; simple transfer with comment, return
620return process_simple_transfer(s_addr, msg_value);
621}
622int query_id = in_msg~load_uint(64);
623if (op == 0x4e73744b) {
624;; new stake message
625return process_new_stake(s_addr, msg_value, in_msg, query_id);
626}
627if (op == 0x47657424) {
628;; recover stake request
629return recover_stake(op, s_addr, in_msg, query_id);
630}
631if (op == 0x4e436f64) {
632;; upgrade code (accepted only from configuration smart contract)
633var ok = upgrade_code(s_addr, in_msg, query_id);
634return send_message_back(s_addr, ok ? 0xce436f64 : 0xffffffff, query_id, op, 0, 64);
635}
636var cfg_ok = (op == 0xee764f4b);
637if (cfg_ok | (op == 0xee764f6f)) {
638;; confirmation from configuration smart contract
639return config_set_confirmed(s_addr, in_msg, query_id, cfg_ok);
640}
641if (op == 0x52674370) {
642;; new complaint
643var price = register_complaint(s_addr, in_msg, msg_value);
644int mode = 64;
645int ans_tag = - price;
646if (price >= 0) {
647;; ok, debit price
648raw_reserve(price, 4);
649ans_tag = 0;
650mode = 128;
651}
652return send_message_back(s_addr, ans_tag + 0xf2676350, query_id, op, 0, mode);
653}
654if (op == 0x56744370) {
655;; vote for a complaint
656var signature = in_msg~load_bits(512);
657var msg_body = in_msg;
658var (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));
659in_msg.end_parse();
660throw_unless(37, sign_tag == 0x56744350);
661var (vdescr, total_weight) = get_validator_descr(idx);
662var (val_pubkey, weight) = unpack_validator_descr(vdescr);
663throw_unless(34, check_data_signature(msg_body, signature, val_pubkey));
664int res = proceed_register_vote(elect_id, chash, idx, weight);
665return send_message_back(s_addr, res + 0xd6745240, query_id, op, 0, 64);
666}
667
668ifnot (op & (1 << 31)) {
669;; unknown query, return error
670return send_message_back(s_addr, 0xffffffff, query_id, op, 0, 64);
671}
672;; unknown answer, ignore
673return ();
674}
675
676int postpone_elections() impure {
677return false;
678}
679
680;; computes the total stake out of the first n entries of list l
681_ compute_total_stake(l, n, m_stake) inline_ref {
682int tot_stake = 0;
683repeat (n) {
684(var h, l) = uncons(l);
685var stake = h.at(0);
686var max_f = h.at(1);
687stake = min(stake, (max_f * m_stake) >> 16);
688tot_stake += stake;
689}
690return tot_stake;
691}
692
693(cell, cell, int, cell, int, int) try_elect(credits, members, min_stake, max_stake, min_total_stake, max_stake_factor) {
694var cs = 16.config_param().begin_parse();
695var (max_validators, _, min_validators) = (cs~load_uint(16), cs~load_uint(16), cs~load_uint(16));
696cs.end_parse();
697min_validators = max(min_validators, 1);
698int n = 0;
699var sdict = new_dict();
700var pubkey = -1;
701do {
702(pubkey, var cs, var f) = members.udict_get_next?(256, pubkey);
703if (f) {
704var (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));
705cs.end_parse();
706var key = begin_cell()
707.store_uint(stake, 128)
708.store_int(- time, 32)
709.store_uint(pubkey, 256)
710.end_cell().begin_parse();
711sdict~dict_set_builder(128 + 32 + 256, key, begin_cell()
712.store_uint(min(max_factor, max_stake_factor), 32)
713.store_uint(addr, 256)
714.store_uint(adnl_addr, 256));
715n += 1;
716}
717} until (~ f);
718n = min(n, max_validators);
719if (n < min_validators) {
720return (credits, new_dict(), 0, new_dict(), 0, 0);
721}
722var l = nil;
723do {
724var (key, cs, f) = sdict~dict::delete_get_min(128 + 32 + 256);
725if (f) {
726var (stake, _, pubkey) = (min(key~load_uint(128), max_stake), key~load_uint(32), key.preload_uint(256));
727var (max_f, _, adnl_addr) = (cs~load_uint(32), cs~load_uint(256), cs.preload_uint(256));
728l = cons([stake, max_f, pubkey, adnl_addr], l);
729}
730} until (~ f);
731;; l is the list of all stakes in decreasing order
732int i = min_validators - 1;
733var l1 = l;
734repeat (i) {
735l1 = cdr(l1);
736}
737var (best_stake, m) = (0, 0);
738do {
739var stake = l1~list_next().at(0);
740i += 1;
741if (stake >= min_stake) {
742var tot_stake = compute_total_stake(l, i, stake);
743if (tot_stake > best_stake) {
744(best_stake, m) = (tot_stake, i);
745}
746}
747} until (i >= n);
748if ((m == 0) | (best_stake < min_total_stake)) {
749return (credits, new_dict(), 0, new_dict(), 0, 0);
750}
751;; we have to select first m validators from list l
752l1 = touch(l);
753;; l1~dump(); ;; DEBUG
754repeat (m - 1) {
755l1 = cdr(l1);
756}
757var m_stake = car(l1).at(0); ;; minimal stake
758;; create both the new validator set and the refund set
759int i = 0;
760var tot_stake = 0;
761var tot_weight = 0;
762var vset = new_dict();
763var frozen = new_dict();
764do {
765var [stake, max_f, pubkey, adnl_addr] = l~list_next();
766;; lookup source address first
767var (val, f) = members.udict_get?(256, pubkey);
768throw_unless(61, f);
769(_, _, var src_addr) = (val~load_grams(), val~load_uint(64), val.preload_uint(256));
770if (i < m) {
771;; one of the first m members, include into validator set
772var true_stake = min(stake, (max_f * m_stake) >> 16);
773stake -= true_stake;
774;; ed25519_pubkey#8e81278a pubkey:bits256 = SigPubKey; // 288 bits
775;; validator_addr#73 public_key:SigPubKey weight:uint64 adnl_addr:bits256 = ValidatorDescr;
776var weight = (true_stake << 60) / best_stake;
777tot_stake += true_stake;
778tot_weight += weight;
779var vinfo = begin_cell()
780.store_uint(adnl_addr ? 0x73 : 0x53, 8) ;; validator_addr#73 or validator#53
781.store_uint(0x8e81278a, 32) ;; ed25519_pubkey#8e81278a
782.store_uint(pubkey, 256) ;; pubkey:bits256
783.store_uint(weight, 64); ;; weight:uint64
784if (adnl_addr) {
785vinfo~store_uint(adnl_addr, 256); ;; adnl_addr:bits256
786}
787vset~udict_set_builder(16, i, vinfo);
788frozen~udict_set_builder(256, pubkey, begin_cell()
789.store_uint(src_addr, 256)
790.store_uint(weight, 64)
791.store_grams(true_stake)
792.store_int(false, 1));
793}
794if (stake) {
795;; non-zero unused part of the stake, credit to the source address
796credits~credit_to(src_addr, stake);
797}
798i += 1;
799} until (l.null?());
800throw_unless(49, tot_stake == best_stake);
801return (credits, vset, tot_weight, frozen, tot_stake, m);
802}
803
804int conduct_elections(ds, elect, credits) impure {
805var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect();
806if (now() < elect_close) {
807;; elections not finished yet
808return false;
809}
810if (config_param(0).null?()) {
811;; no configuration smart contract to send result to
812return postpone_elections();
813}
814var cs = config_param(17).begin_parse();
815min_stake = cs~load_grams();
816var max_stake = cs~load_grams();
817var min_total_stake = cs~load_grams();
818var max_stake_factor = cs~load_uint(32);
819cs.end_parse();
820if (total_stake < min_total_stake) {
821;; insufficient total stake, postpone elections
822return postpone_elections();
823}
824if (failed) {
825;; do not retry failed elections until new stakes arrive
826return postpone_elections();
827}
828if (finished) {
829;; elections finished
830return false;
831}
832(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);
833;; pack elections; if cnt==0, set failed=true, finished=false.
834failed = (cnt == 0);
835finished = ~ failed;
836elect = pack_elect(elect_at, elect_close, min_stake, total_stake, members, failed, finished);
837ifnot (cnt) {
838;; elections failed, set elect_failed to true
839set_data(begin_cell().store_dict(elect).store_dict(credits).store_slice(ds).end_cell());
840return postpone_elections();
841}
842;; serialize a query to the configuration smart contract
843;; to install the computed validator set as the next validator set
844var (elect_for, elect_begin_before, elect_end_before, stake_held) = get_validator_conf();
845var start = max(now() + elect_end_before - 60, elect_at);
846var main_validators = config_param(16).begin_parse().skip_bits(16).preload_uint(16);
847var vset = begin_cell()
848.store_uint(0x12, 8) ;; validators_ext#12
849.store_uint(start, 32) ;; utime_since:uint32
850.store_uint(start + elect_for, 32) ;; utime_until:uint32
851.store_uint(cnt, 16) ;; total:(## 16)
852.store_uint(min(cnt, main_validators), 16) ;; main:(## 16)
853.store_uint(total_weight, 64) ;; total_weight:uint64
854.store_dict(vdict) ;; list:(HashmapE 16 ValidatorDescr)
855.end_cell();
856var config_addr = config_param(0).begin_parse().preload_uint(256);
857send_validator_set_to_config(config_addr, vset, elect_at);
858;; add frozen to the dictionary of past elections
859var past_elections = ds~load_dict();
860past_elections~udict_set_builder(32, elect_at, pack_past_election(
861start + elect_for + stake_held, stake_held, vset.cell_hash(),
862frozen, total_stakes, 0, null()));
863;; store credits and frozen until end
864set_data(begin_cell()
865.store_dict(elect)
866.store_dict(credits)
867.store_dict(past_elections)
868.store_slice(ds)
869.end_cell());
870return true;
871}
872
873int update_active_vset_id() impure {
874var (elect, credits, past_elections, grams, active_id, active_hash) = load_data();
875var cur_hash = config_param(34).cell_hash();
876if (cur_hash == active_hash) {
877;; validator set unchanged
878return false;
879}
880if (active_id) {
881;; active_id becomes inactive
882var (fs, f) = past_elections.udict_get?(32, active_id);
883if (f) {
884;; adjust unfreeze time of this validator set
885var unfreeze_time = fs~load_uint(32);
886var fs0 = fs;
887var (stake_held, hash) = (fs~load_uint(32), fs~load_uint(256));
888throw_unless(57, hash == active_hash);
889unfreeze_time = now() + stake_held;
890past_elections~udict_set_builder(32, active_id, begin_cell()
891.store_uint(unfreeze_time, 32)
892.store_slice(fs0));
893}
894}
895;; look up new active_id by hash
896var id = -1;
897do {
898(id, var fs, var f) = past_elections.udict_get_next?(32, id);
899if (f) {
900var (tm, hash) = (fs~load_uint(64), fs~load_uint(256));
901if (hash == cur_hash) {
902;; parse more of this record
903var (dict, total_stake, bonuses) = (fs~load_dict(), fs~load_grams(), fs~load_grams());
904;; transfer 1/8 of accumulated everybody's grams to this validator set as bonuses
905var amount = (grams >> 3);
906grams -= amount;
907bonuses += amount;
908;; serialize back
909past_elections~udict_set_builder(32, id, begin_cell()
910.store_uint(tm, 64)
911.store_uint(hash, 256)
912.store_dict(dict)
913.store_grams(total_stake)
914.store_grams(bonuses)
915.store_slice(fs));
916;; found
917f = false;
918}
919}
920} until (~ f);
921active_id = (id.null?() ? 0 : id);
922active_hash = cur_hash;
923store_data(elect, credits, past_elections, grams, active_id, active_hash);
924return true;
925}
926
927int cell_hash_eq?(cell vset, int expected_vset_hash) inline_ref {
928return vset.null?() ? false : cell_hash(vset) == expected_vset_hash;
929}
930
931int validator_set_installed(ds, elect, credits) impure {
932var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect();
933ifnot (finished) {
934;; elections not finished yet
935return false;
936}
937var past_elections = ds~load_dict();
938var (fs, f) = past_elections.udict_get?(32, elect_at);
939ifnot (f) {
940;; no election data in dictionary
941return false;
942}
943;; recover validator set hash
944var vset_hash = fs.skip_bits(64).preload_uint(256);
945if (config_param(34).cell_hash_eq?(vset_hash) | config_param(36).cell_hash_eq?(vset_hash)) {
946;; this validator set has been installed, forget elections
947set_data(begin_cell()
948.store_int(false, 1) ;; forget current elections
949.store_dict(credits)
950.store_dict(past_elections)
951.store_slice(ds)
952.end_cell());
953update_active_vset_id();
954return true;
955}
956return false;
957}
958
959int check_unfreeze() impure {
960var (elect, credits, past_elections, grams, active_id, active_hash) = load_data();
961int id = -1;
962do {
963(id, var fs, var f) = past_elections.udict_get_next?(32, id);
964if (f) {
965var unfreeze_at = fs~load_uint(32);
966if ((unfreeze_at <= now()) & (id != active_id)) {
967;; unfreeze!
968(credits, past_elections, var unused_prizes) = unfreeze_all(credits, past_elections, id);
969grams += unused_prizes;
970;; unfreeze only one at time, exit loop
971store_data(elect, credits, past_elections, grams, active_id, active_hash);
972;; exit loop
973f = false;
974}
975}
976} until (~ f);
977return ~ id.null?();
978}
979
980int announce_new_elections(ds, elect, credits) {
981var next_vset = config_param(36); ;; next validator set
982ifnot (next_vset.null?()) {
983;; next validator set exists, no elections needed
984return false;
985}
986var elector_addr = config_param(1).begin_parse().preload_uint(256);
987var (my_wc, my_addr) = my_address().parse_std_addr();
988if ((my_wc + 1) | (my_addr != elector_addr)) {
989;; this smart contract is not the elections smart contract anymore, no new elections
990return false;
991}
992var cur_vset = config_param(34); ;; current validator set
993if (cur_vset.null?()) {
994return false;
995}
996var (elect_for, elect_begin_before, elect_end_before, stake_held) = get_validator_conf();
997var cur_valid_until = cur_vset.begin_parse().skip_bits(8 + 32).preload_uint(32);
998var t = now();
999var t0 = cur_valid_until - elect_begin_before;
1000if (t < t0) {
1001;; too early for the next elections
1002return false;
1003}
1004;; less than elect_before_begin seconds left, create new elections
1005if (t - t0 < 60) {
1006;; pretend that the elections started at t0
1007t = t0;
1008}
1009;; get stake parameters
1010(_, var min_stake) = config_param(17).begin_parse().load_grams();
1011;; announce new elections
1012var elect_at = t + elect_begin_before;
1013;; elect_at~dump();
1014var elect_close = elect_at - elect_end_before;
1015elect = pack_elect(elect_at, elect_close, min_stake, 0, new_dict(), false, false);
1016set_data(begin_cell().store_dict(elect).store_dict(credits).store_slice(ds).end_cell());
1017return true;
1018}
1019
1020() run_ticktock(int is_tock) impure {
1021;; check whether an election is being conducted
1022var ds = get_data().begin_parse();
1023var (elect, credits) = (ds~load_dict(), ds~load_dict());
1024ifnot (elect.null?()) {
1025;; have an active election
1026throw_if(0, conduct_elections(ds, elect, credits)); ;; elections conducted, exit
1027throw_if(0, validator_set_installed(ds, elect, credits)); ;; validator set installed, current elections removed
1028} else {
1029throw_if(0, announce_new_elections(ds, elect, credits)); ;; new elections announced, exit
1030}
1031throw_if(0, update_active_vset_id()); ;; active validator set id updated, exit
1032check_unfreeze();
1033}
1034
1035;; Get methods
1036
1037;; returns active election id or 0
1038int active_election_id() method_id {
1039var elect = get_data().begin_parse().preload_dict();
1040return elect.null?() ? 0 : elect.begin_parse().preload_uint(32);
1041}
1042
1043;; checks whether a public key participates in current elections
1044int participates_in(int validator_pubkey) method_id {
1045var elect = get_data().begin_parse().preload_dict();
1046if (elect.null?()) {
1047return 0;
1048}
1049var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect();
1050var (mem, found) = members.udict_get?(256, validator_pubkey);
1051return found ? mem~load_grams() : 0;
1052}
1053
1054;; returns the list of all participants of current elections with their stakes
1055_ participant_list() method_id {
1056var elect = get_data().begin_parse().preload_dict();
1057if (elect.null?()) {
1058return nil;
1059}
1060var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect();
1061var l = nil;
1062var id = (1 << 255) + ((1 << 255) - 1);
1063do {
1064(id, var fs, var f) = members.udict_get_prev?(256, id);
1065if (f) {
1066l = cons([id, fs~load_grams()], l);
1067}
1068} until (~ f);
1069return l;
1070}
1071
1072;; returns the list of all participants of current elections with their data
1073_ participant_list_extended() method_id {
1074var elect = get_data().begin_parse().preload_dict();
1075if (elect.null?()) {
1076return (0, 0, 0, 0, nil, 0, 0);
1077}
1078var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect();
1079var l = nil;
1080var id = (1 << 255) + ((1 << 255) - 1);
1081do {
1082(id, var cs, var f) = members.udict_get_prev?(256, id);
1083if (f) {
1084var (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));
1085cs.end_parse();
1086l = cons([id, [stake, max_factor, addr, adnl_addr]], l);
1087}
1088} until (~ f);
1089return (elect_at, elect_close, min_stake, total_stake, l, failed, finished);
1090}
1091
1092;; computes the return stake
1093int compute_returned_stake(int wallet_addr) method_id {
1094var cs = get_data().begin_parse();
1095(_, var credits) = (cs~load_dict(), cs~load_dict());
1096var (val, f) = credits.udict_get?(256, wallet_addr);
1097return f ? val~load_grams() : 0;
1098}
1099
1100;; returns the list of past election ids
1101tuple past_election_ids() method_id {
1102var (elect, credits, past_elections, grams, active_id, active_hash) = load_data();
1103var id = (1 << 32);
1104var list = null();
1105do {
1106(id, var fs, var f) = past_elections.udict_get_prev?(32, id);
1107if (f) {
1108list = cons(id, list);
1109}
1110} until (~ f);
1111return list;
1112}
1113
1114tuple past_elections() method_id {
1115var (elect, credits, past_elections, grams, active_id, active_hash) = load_data();
1116var id = (1 << 32);
1117var list = null();
1118do {
1119(id, var fs, var found) = past_elections.udict_get_prev?(32, id);
1120if (found) {
1121list = cons([id, unpack_past_election(fs)], list);
1122}
1123} until (~ found);
1124return list;
1125}
1126
1127tuple past_elections_list() method_id {
1128var (elect, credits, past_elections, grams, active_id, active_hash) = load_data();
1129var id = (1 << 32);
1130var list = null();
1131do {
1132(id, var fs, var found) = past_elections.udict_get_prev?(32, id);
1133if (found) {
1134var (unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints) = unpack_past_election(fs);
1135list = cons([id, unfreeze_at, vset_hash, stake_held], list);
1136}
1137} until (~ found);
1138return list;
1139}
1140
1141_ complete_unpack_complaint(slice cs) inline_ref {
1142var (complaint, voters, vset_id, weight_remaining) = cs.unpack_complaint_status();
1143var voters_list = null();
1144var voter_id = (1 << 32);
1145do {
1146(voter_id, _, var f) = voters.udict_get_prev?(16, voter_id);
1147if (f) {
1148voters_list = cons(voter_id, voters_list);
1149}
1150} until (~ f);
1151return [[complaint.begin_parse().unpack_complaint()], voters_list, vset_id, weight_remaining];
1152}
1153
1154cell get_past_complaints(int election_id) inline_ref method_id {
1155var (elect, credits, past_elections, grams, active_id, active_hash) = load_data();
1156var (fs, found?) = past_elections.udict_get?(32, election_id);
1157ifnot (found?) {
1158return null();
1159}
1160var (unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints) = unpack_past_election(fs);
1161return complaints;
1162}
1163
1164_ show_complaint(int election_id, int chash) method_id {
1165var complaints = get_past_complaints(election_id);
1166var (cs, found) = complaints.udict_get?(256, chash);
1167return found ? complete_unpack_complaint(cs) : null();
1168}
1169
1170tuple list_complaints(int election_id) method_id {
1171var complaints = get_past_complaints(election_id);
1172int id = (1 << 255) + ((1 << 255) - 1);
1173var list = null();
1174do {
1175(id, var cs, var found?) = complaints.udict_get_prev?(256, id);
1176if (found?) {
1177list = cons(pair(id, complete_unpack_complaint(cs)), list);
1178}
1179} until (~ found?);
1180return list;
1181}
1182
1183int complaint_storage_price(int bits, int refs, int expire_in) method_id {
1184;; compute complaint storage/creation price
1185var (deposit, bit_price, cell_price) = get_complaint_prices();
1186var pps = (bits + 1024) * bit_price + (refs + 2) * cell_price;
1187var paid = pps * expire_in + deposit;
1188return paid + (1 << 30);
1189}
1190