mirror of
				https://github.com/ton-blockchain/ton
				synced 2025-03-09 15:40:10 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			953 lines
		
	
	
	
		
			34 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
			
		
		
	
	
			953 lines
		
	
	
	
		
			34 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#ba validator_pubkey:uint256 description:^ComplaintDescr
 | |
| ;;   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) == 0xba - 0x100);
 | |
|   var res = (cs~load_uint(256), cs~load_ref(), 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 severity, int reward_addr, int paid, int suggested_fine, int suggested_fine_part) inline_ref {
 | |
|   return begin_cell()
 | |
|     .store_int(0xba - 0x100, 8)
 | |
|     .store_uint(validator_pubkey, 256)
 | |
|     .store_ref(description)
 | |
|     .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));
 | |
| }
 | |
| 
 | |
| () 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);
 | |
| }
 | |
| 
 | |
| _ 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();
 | |
|   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, severity, reward_addr, paid, suggested_fine, suggested_fine_part) = unpack_complaint(complaint);
 | |
|   reward_addr = src_addr;
 | |
|   ;; 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 (paid + (1 << 30) < msg_value) { ;; not enough money
 | |
|     return -5;
 | |
|   }
 | |
|   ;; re-pack modified complaint
 | |
|   cell complaint = pack_complaint(validator_pubkey, description, 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;
 | |
|   }
 | |
|   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 0;
 | |
| }
 | |
| 
 | |
| () 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);
 | |
|   }
 | |
|   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;
 | |
| }
 |