mirror of
				https://github.com/ton-blockchain/ton
				synced 2025-03-09 15:40:10 +00:00 
			
		
		
		
	* Add legacy_tester for existing funC contracts * Add storage-contracts and pragma options
		
			
				
	
	
		
			643 lines
		
	
	
	
		
			23 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
			
		
		
	
	
			643 lines
		
	
	
	
		
			23 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
| ;; Simple configuration smart contract
 | |
| 
 | |
| #include "stdlib.fc";
 | |
| 
 | |
| () set_conf_param(int index, cell value) impure {
 | |
|   var cs = get_data().begin_parse();
 | |
|   var cfg_dict = cs~load_ref();
 | |
|   cfg_dict~idict_set_ref(32, index, value);
 | |
|   set_data(begin_cell().store_ref(cfg_dict).store_slice(cs).end_cell());
 | |
| }
 | |
| 
 | |
| (cell, int, int, cell) load_data() inline {
 | |
|   var cs = get_data().begin_parse();
 | |
|   var res = (cs~load_ref(), cs~load_uint(32), cs~load_uint(256), cs~load_dict());
 | |
|   cs.end_parse();
 | |
|   return res;
 | |
| }
 | |
| 
 | |
| () store_data(cfg_dict, stored_seqno, public_key, vote_dict) impure inline {
 | |
|   set_data(begin_cell()
 | |
|     .store_ref(cfg_dict)
 | |
|     .store_uint(stored_seqno, 32)
 | |
|     .store_uint(public_key, 256)
 | |
|     .store_dict(vote_dict)
 | |
|   .end_cell());
 | |
| }
 | |
| 
 | |
| ;; (min_tot_rounds, max_tot_rounds, min_wins, max_losses, min_store_sec, max_store_sec, bit_price, cell_price)
 | |
| _ parse_vote_config(cell c) inline {
 | |
|   var cs = c.begin_parse();
 | |
|   throw_unless(44, cs~load_uint(8) == 0x36);
 | |
|   var res = (cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), cs~load_uint(8), cs~load_uint(32), cs~load_uint(32), cs~load_uint(32), cs~load_uint(32));
 | |
|   cs.end_parse();
 | |
|   return res;
 | |
| }
 | |
| 
 | |
| ;; cfg_vote_setup#91 normal_params:^ConfigProposalSetup critical_params:^ConfigProposalSetup = ConfigVotingSetup;
 | |
| _ get_vote_config_internal(int critical?, cell cparam11) inline_ref {
 | |
|   var cs = cparam11.begin_parse();
 | |
|   throw_unless(44, cs~load_uint(8) == 0x91);
 | |
|   if (critical?) {
 | |
|     cs~load_ref();
 | |
|   }
 | |
|   return parse_vote_config(cs.preload_ref());
 | |
| }
 | |
| 
 | |
| _ get_vote_config(int critical?) inline {
 | |
|   return get_vote_config_internal(critical?, config_param(11));
 | |
| }
 | |
| 
 | |
| (int, int) check_validator_set(cell vset) {
 | |
|   var cs = vset.begin_parse();
 | |
|   throw_unless(9, cs~load_uint(8) == 0x12);  ;; validators_ext#12 only
 | |
|   int utime_since = cs~load_uint(32);
 | |
|   int utime_until = cs~load_uint(32);
 | |
|   int total = cs~load_uint(16);
 | |
|   int main = cs~load_uint(16);
 | |
|   throw_unless(9, main > 0);
 | |
|   throw_unless(9, total >= main);
 | |
|   return (utime_since, utime_until);
 | |
| }
 | |
| 
 | |
| () send_answer(addr, query_id, ans_tag, mode) impure {
 | |
|   ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000
 | |
|   send_raw_message(begin_cell()
 | |
|     .store_uint(0x18, 6)
 | |
|     .store_slice(addr)
 | |
|     .store_uint(0, 5 + 4 + 4 + 64 + 32 + 1 + 1)
 | |
|     .store_uint(ans_tag, 32)
 | |
|     .store_uint(query_id, 64)
 | |
|   .end_cell(), mode);
 | |
| }
 | |
| 
 | |
| () send_confirmation(addr, query_id, ans_tag) impure inline {
 | |
|   return send_answer(addr, query_id, ans_tag, 64);
 | |
| }
 | |
| 
 | |
| () send_error(addr, query_id, ans_tag) impure inline {
 | |
|   return send_answer(addr, query_id, ans_tag, 64);
 | |
| }
 | |
| 
 | |
| ;; forward a message to elector smart contract to make it upgrade its code
 | |
| () change_elector_code(slice cs) impure {
 | |
|   var dest_addr = config_param(1).begin_parse().preload_uint(256);
 | |
|   var query_id = now();
 | |
|   send_raw_message(begin_cell()
 | |
|    .store_uint(0xc4ff, 17)
 | |
|    .store_uint(dest_addr, 256)
 | |
|    .store_grams(1 << 30)         ;; ~ 1 Gram (will be returned back)
 | |
|    .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
 | |
|    .store_uint(0x4e436f64, 32)   ;; action
 | |
|    .store_uint(query_id, 64)
 | |
|    .store_slice(cs)
 | |
|   .end_cell(), 0);
 | |
| }
 | |
| 
 | |
| () after_code_upgrade(slice param, cont old_code) impure method_id(1666) {
 | |
| }
 | |
| 
 | |
| _ perform_action(cfg_dict, public_key, action, cs) inline_ref {
 | |
|   if (action == 0x43665021) {
 | |
|     ;; change one configuration parameter
 | |
|     var param_index = cs~load_int(32);
 | |
|     var param_value = cs~load_ref();
 | |
|     cs.end_parse();
 | |
|     cfg_dict~idict_set_ref(32, param_index, param_value);
 | |
|     return (cfg_dict, public_key);
 | |
|   } elseif (action == 0x4e436f64) {
 | |
|     ;; change configuration smart contract code
 | |
|     var new_code = cs~load_ref();
 | |
|     set_code(new_code);
 | |
|     var old_code = get_c3();
 | |
|     set_c3(new_code.begin_parse().bless());
 | |
|     after_code_upgrade(cs, old_code);
 | |
|     throw(0);
 | |
|     return (cfg_dict, public_key);
 | |
|   } elseif (action == 0x50624b21) {
 | |
|     ;; change configuration master public key
 | |
|     public_key = cs~load_uint(256);
 | |
|     cs.end_parse();
 | |
|     return (cfg_dict, public_key);
 | |
|   } elseif (action == 0x4e43ef05) {
 | |
|     ;; change election smart contract code
 | |
|     change_elector_code(cs);
 | |
|     return (cfg_dict, public_key);
 | |
|   } else {
 | |
|     throw_if(32, action);
 | |
|     return (cfg_dict, public_key);
 | |
|   }
 | |
| }
 | |
| 
 | |
| (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));
 | |
| }
 | |
| 
 | |
| ;; cfg_proposal#f3 param_id:int32 param_value:(Maybe ^Cell) if_hash_equal:(Maybe uint256)
 | |
| ;; c -> (param-id param-cell maybe-hash)
 | |
| (int, cell, int) parse_config_proposal(cell c) inline_ref {
 | |
|   var cs = c.begin_parse();
 | |
|   throw_unless(44, cs~load_int(8) == 0xf3 - 0x100);
 | |
|   var (id, val, hash) = (cs~load_int(32), cs~load_maybe_ref(), cs~load_int(1));
 | |
|   if (hash) {
 | |
|     hash = cs~load_uint(256);
 | |
|   } else {
 | |
|     hash = -1;
 | |
|   }
 | |
|   cs.end_parse();
 | |
|   return (id, val, hash);
 | |
| }
 | |
| 
 | |
| (cell, int, cell) accept_proposal(cell cfg_dict, cell proposal, int critical?) inline_ref {
 | |
|   var (param_id, param_val, req_hash) = parse_config_proposal(proposal);
 | |
|   cell cur_val = cfg_dict.idict_get_ref(32, param_id);
 | |
|   int cur_hash = null?(cur_val) ? 0 : cell_hash(cur_val);
 | |
|   if ((cur_hash != req_hash) & (req_hash >= 0)) {
 | |
|     ;; current value has incorrect hash, do not apply changes
 | |
|     return (cfg_dict, 0, null());
 | |
|   }
 | |
|   cell mparams = cfg_dict.idict_get_ref(32, 9);  ;; mandatory parameters
 | |
|   var (_, found?) = mparams.idict_get?(32, param_id);
 | |
|   if (found? & param_val.null?()) {
 | |
|     ;; cannot set a mandatory parameter to (null)
 | |
|     return (cfg_dict, 0, null());
 | |
|   }
 | |
|   cell cparams = cfg_dict.idict_get_ref(32, 10);  ;; critical parameters
 | |
|   (_, found?) = cparams.idict_get?(32, param_id);
 | |
|   if (found? < critical?) {
 | |
|     ;; trying to set a critical parameter after a non-critical voting
 | |
|     return (cfg_dict, 0, null());
 | |
|   }
 | |
|   ;; CHANGE ONE CONFIGURATION PARAMETER (!)
 | |
|   cfg_dict~idict_set_ref(32, param_id, param_val);
 | |
|   return (cfg_dict, param_id, param_val);
 | |
| }
 | |
| 
 | |
| (cell, int) perform_proposed_action(cell cfg_dict, int public_key, int param_id, cell param_val) inline_ref {
 | |
|   if (param_id == -999) {
 | |
|     ;; appoint or depose dictator
 | |
|     return (cfg_dict, param_val.null?() ? 0 : param_val.begin_parse().preload_uint(256));
 | |
|   }
 | |
|   if (param_val.null?()) {
 | |
|     return (cfg_dict, public_key);
 | |
|   }
 | |
|   if (param_id == -1000) {
 | |
|     ;; upgrade code
 | |
|     var cs = param_val.begin_parse();
 | |
|     var new_code = cs~load_ref();
 | |
|     set_code(new_code);
 | |
|     var old_code = get_c3();
 | |
|     set_c3(new_code.begin_parse().bless());
 | |
|     after_code_upgrade(cs, old_code);
 | |
|     throw(0);
 | |
|     return (cfg_dict, public_key);
 | |
|   }
 | |
|   if (param_id == -1001) {
 | |
|     ;; update elector code
 | |
|     var cs = param_val.begin_parse();
 | |
|     change_elector_code(cs);
 | |
|   }
 | |
|   return (cfg_dict, public_key);
 | |
| }
 | |
| 
 | |
| ;; cfg_proposal_status#ce expires:uint32 proposal:^ConfigProposal is_critical:Bool
 | |
| ;;  voters:(HashmapE 16 True) remaining_weight:int64 validator_set_id:uint256
 | |
| ;;  rounds_remaining:uint8 wins:uint8 losses:uint8 = ConfigProposalStatus;
 | |
| (int, cell, int, cell, int, int, slice) unpack_proposal_status(slice cs) inline_ref {
 | |
|   throw_unless(44, cs~load_int(8) == 0xce - 0x100);
 | |
|   return (cs~load_uint(32), cs~load_ref(), cs~load_int(1), cs~load_dict(), cs~load_int(64), cs~load_uint(256), cs);
 | |
| }
 | |
| 
 | |
| slice update_proposal_status(slice rest, int weight_remaining, int critical?) inline_ref {
 | |
|   var (min_tot_rounds, max_tot_rounds, min_wins, max_losses, _, _, _, _) = get_vote_config(critical?);
 | |
|   var (rounds_remaining, wins, losses) = (rest~load_uint(8), rest~load_uint(8), rest~load_uint(8));
 | |
|   losses -= (weight_remaining >= 0);
 | |
|   if (losses > max_losses) {
 | |
|     ;; lost too many times
 | |
|     return null();
 | |
|   }
 | |
|   rounds_remaining -= 1;
 | |
|   if (rounds_remaining < 0) {
 | |
|     ;; existed for too many rounds
 | |
|     return null();
 | |
|   }
 | |
|   return begin_cell()
 | |
|     .store_uint(rounds_remaining, 8)
 | |
|     .store_uint(wins, 8)
 | |
|     .store_uint(losses, 8)
 | |
|   .end_cell().begin_parse();
 | |
| }
 | |
| 
 | |
| builder begin_pack_proposal_status(int expires, cell proposal, int critical?, cell voters, int weight_remaining, int vset_id) inline {
 | |
|   return begin_cell()
 | |
|     .store_int(0xce - 0x100, 8)
 | |
|     .store_uint(expires, 32)
 | |
|     .store_ref(proposal)
 | |
|     .store_int(critical?, 1)
 | |
|     .store_dict(voters)
 | |
|     .store_int(weight_remaining, 64)
 | |
|     .store_uint(vset_id, 256);
 | |
| }
 | |
| 
 | |
| (cell, cell, int) register_vote(vote_dict, phash, idx, weight) inline_ref {
 | |
|   var (pstatus, found?) = vote_dict.udict_get?(256, phash);
 | |
|   ifnot (found?) {
 | |
|     ;; config proposal not found
 | |
|     return (vote_dict, null(), -1);
 | |
|   }
 | |
|   var (cur_vset, total_weight, _) = get_current_vset();
 | |
|   int cur_vset_id = cur_vset.cell_hash();
 | |
|   var (expires, proposal, critical?, voters, weight_remaining, vset_id, rest) = unpack_proposal_status(pstatus);
 | |
|   if (expires <= now()) {
 | |
|     ;; config proposal expired, delete and report not found
 | |
|     vote_dict~udict_delete?(256, phash);
 | |
|     return (vote_dict, null(), -1);
 | |
|   }
 | |
|   if (vset_id != cur_vset_id) {
 | |
|     ;; config proposal belongs to a previous validator set
 | |
|     vset_id = cur_vset_id;
 | |
|     rest = update_proposal_status(rest, weight_remaining, critical?);
 | |
|     voters = null();
 | |
|     weight_remaining = muldiv(total_weight, 3, 4);
 | |
|   }
 | |
|   if (rest.null?()) {
 | |
|     ;; discard proposal (existed for too many rounds, or too many losses)
 | |
|     vote_dict~udict_delete?(256, phash);
 | |
|     return (vote_dict, null(), -1);
 | |
|   }
 | |
|   var (_, found?) = voters.udict_get?(16, idx);
 | |
|   if (found?) {
 | |
|     ;; already voted for this proposal, ignore vote
 | |
|     return (vote_dict, null(), -2);
 | |
|   }
 | |
|   ;; register vote
 | |
|   voters~udict_set_builder(16, idx, begin_cell().store_uint(now(), 32));
 | |
|   int old_wr = weight_remaining;
 | |
|   weight_remaining -= weight;
 | |
|   if ((weight_remaining ^ old_wr) >= 0) {
 | |
|     ;; not enough votes, or proposal already accepted in this round
 | |
|     ;; simply update weight_remaining
 | |
|     vote_dict~udict_set_builder(256, phash, begin_pack_proposal_status(expires, proposal, critical?, voters, weight_remaining, vset_id).store_slice(rest));
 | |
|     return (vote_dict, null(), 2);
 | |
|   }
 | |
|   ;; proposal wins in this round
 | |
|   var (min_tot_rounds, max_tot_rounds, min_wins, max_losses, _, _, _, _) = get_vote_config(critical?);
 | |
|   var (rounds_remaining, wins, losses) = (rest~load_uint(8), rest~load_uint(8), rest~load_uint(8));
 | |
|   wins += 1;
 | |
|   if (wins >= min_wins) {
 | |
|     ;; proposal is accepted, remove and process
 | |
|     vote_dict~udict_delete?(256, phash);
 | |
|     return (vote_dict, proposal, 6 - critical?);
 | |
|   }
 | |
|   ;; update proposal info
 | |
|   vote_dict~udict_set_builder(256, phash,
 | |
|     begin_pack_proposal_status(expires, proposal, critical?, voters, weight_remaining, vset_id)
 | |
|     .store_uint(rounds_remaining, 8)
 | |
|     .store_uint(wins, 8)
 | |
|     .store_uint(losses, 8));
 | |
|   return (vote_dict, null(), 2);
 | |
| }
 | |
| 
 | |
| int proceed_register_vote(phash, idx, weight) impure inline_ref {
 | |
|   var (cfg_dict, stored_seqno, public_key, vote_dict) = load_data();
 | |
|   (vote_dict, var accepted_proposal, var status) = register_vote(vote_dict, phash, idx, weight);
 | |
|   store_data(cfg_dict, stored_seqno, public_key, vote_dict);
 | |
|   ifnot (accepted_proposal.null?()) {
 | |
|     var critical? = 6 - status;
 | |
|     (cfg_dict, var param_id, var param_val) = accept_proposal(cfg_dict, accepted_proposal, critical?);
 | |
|     store_data(cfg_dict, stored_seqno, public_key, vote_dict);
 | |
|     if (param_id) {
 | |
|       commit();
 | |
|       (cfg_dict, public_key) = perform_proposed_action(cfg_dict, public_key, param_id, param_val);
 | |
|       store_data(cfg_dict, stored_seqno, public_key, vote_dict);
 | |
|     }
 | |
|   }
 | |
|   return status;
 | |
| }
 | |
| 
 | |
| (slice, int) scan_proposal(int phash, slice pstatus) inline_ref {
 | |
|   var (cur_vset, total_weight, _) = get_current_vset();
 | |
|   int cur_vset_id = cur_vset.cell_hash();
 | |
|   var (expires, proposal, critical?, voters, weight_remaining, vset_id, rest) = unpack_proposal_status(pstatus);
 | |
|   if (expires <= now()) {
 | |
|     ;; config proposal expired, delete
 | |
|     return (null(), true);
 | |
|   }
 | |
|   if (vset_id == cur_vset_id) {
 | |
|     ;; config proposal already processed or voted for in this round, change nothing
 | |
|     return (pstatus, false);
 | |
|   }
 | |
|   ;; config proposal belongs to a previous validator set
 | |
|   vset_id = cur_vset_id;
 | |
|   rest = update_proposal_status(rest, weight_remaining, critical?);
 | |
|   voters = null();
 | |
|   weight_remaining = muldiv(total_weight, 3, 4);
 | |
|   if (rest.null?()) {
 | |
|     ;; discard proposal (existed for too many rounds, or too many losses)
 | |
|     return (null(), true);
 | |
|   }
 | |
|   ;; return updated proposal
 | |
|   return (begin_pack_proposal_status(expires, proposal, critical?, voters, weight_remaining, vset_id).store_slice(rest).end_cell().begin_parse(), true);
 | |
| }
 | |
| 
 | |
| cell scan_random_proposal(cell vote_dict) inline_ref {
 | |
|   var (phash, pstatus, found?) = vote_dict.udict_get_nexteq?(256, random());
 | |
|   ifnot (found?) {
 | |
|     return vote_dict;
 | |
|   }
 | |
|   (pstatus, var changed?) = scan_proposal(phash, pstatus);
 | |
|   if (changed?) {
 | |
|     if (pstatus.null?()) {
 | |
|       vote_dict~udict_delete?(256, phash);
 | |
|     } else {
 | |
|       vote_dict~udict_set(256, phash, pstatus);
 | |
|     }
 | |
|   }
 | |
|   return vote_dict;
 | |
| }
 | |
| 
 | |
| int register_voting_proposal(slice cs, int msg_value) impure inline_ref {
 | |
|   var (expire_at, proposal, critical?) = (cs~load_uint(32), cs~load_ref(), cs~load_int(1));
 | |
|   if (expire_at >> 30) {
 | |
|     expire_at -= now();
 | |
|   }
 | |
|   var (param_id, param_val, hash) = parse_config_proposal(proposal);
 | |
|   if (hash >= 0) {
 | |
|     cell cur_val = config_param(param_id);
 | |
|     int cur_hash = null?(cur_val) ? 0 : cell_hash(cur_val);
 | |
|     if (cur_hash != hash) {
 | |
|       hash = -0xe2646356;  ;; bad current value
 | |
|     }
 | |
|   } else {
 | |
|     var m_params = config_param(9);
 | |
|     var (_, found?) = m_params.idict_get?(32, param_id);
 | |
|     if (found?) {
 | |
|       hash = -0xcd506e6c;  ;; cannot set mandatory parameter to null
 | |
|     }
 | |
|   }
 | |
|   if (param_val.cell_depth() >= 256) {
 | |
|     hash = -0xc2616456;  ;; bad value
 | |
|   }
 | |
|   if (hash < -1) {
 | |
|     return hash;  ;; return error if any
 | |
|   }
 | |
|   ifnot (critical?) {
 | |
|     var crit_params = config_param(10);
 | |
|     var (_, found?) = crit_params.idict_get?(32, param_id);
 | |
|     if (found?) {
 | |
|       hash = -0xc3726954;  ;; trying to set a critical parameter without critical flag
 | |
|     }
 | |
|   }
 | |
|   if (hash < -1) {
 | |
|     return hash;
 | |
|   }
 | |
|   ;; obtain vote proposal configuration
 | |
|   var vote_cfg = get_vote_config(critical?);
 | |
|   var (min_tot_rounds, max_tot_rounds, min_wins, max_losses, min_store_sec, max_store_sec, bit_price, cell_price) = vote_cfg;
 | |
|   if (expire_at < min_store_sec) {
 | |
|     return -0xc5787069;   ;; expired
 | |
|   }
 | |
|   expire_at = min(expire_at, max_store_sec);
 | |
|   ;; compute price
 | |
|   var (_, bits, refs) = compute_data_size(param_val, 1024);
 | |
|   var pps = bit_price * (bits + 1024) + cell_price * (refs + 2);
 | |
|   var price = pps * expire_at;
 | |
|   expire_at += now();
 | |
|   var (cfg_dict, stored_seqno, public_key, vote_dict) = load_data();
 | |
|   int phash = proposal.cell_hash();
 | |
|   var (pstatus, found?) = vote_dict.udict_get?(256, phash);
 | |
|   if (found?) {
 | |
|     ;; proposal already exists; we can only extend it
 | |
|     var (expires, r_proposal, r_critical?, voters, weight_remaining, vset_id, rest) = unpack_proposal_status(pstatus);
 | |
|     if (r_critical? != critical?) {
 | |
|       return -0xc3726955;  ;; cannot upgrade critical parameter to non-critical...
 | |
|     }
 | |
|     if (expires >= expire_at) {
 | |
|       return -0xc16c7245;  ;; proposal already exists
 | |
|     }
 | |
|     ;; recompute price
 | |
|     price = pps * (expire_at - expires + 16384);
 | |
|     if (msg_value - price < (1 << 30)) {
 | |
|       return -0xf0617924;   ;; need more money
 | |
|     }
 | |
|     ;; update expiration time
 | |
|     vote_dict~udict_set_builder(256, phash, begin_pack_proposal_status(expire_at, r_proposal, r_critical?, voters, weight_remaining, vset_id).store_slice(rest));
 | |
|     store_data(cfg_dict, stored_seqno, public_key, vote_dict);
 | |
|     return price;
 | |
|   }
 | |
|   if (msg_value - price < (1 << 30)) {
 | |
|     return -0xf0617924;   ;; need more money
 | |
|   }
 | |
|   ;; obtain current validator set data
 | |
|   var (vset, total_weight, _) = get_current_vset();
 | |
|   int weight_remaining = muldiv(total_weight, 3, 4);
 | |
|   ;; create new proposal
 | |
|   vote_dict~udict_set_builder(256, phash,
 | |
|     begin_pack_proposal_status(expire_at, proposal, critical?, null(), weight_remaining, vset.cell_hash())
 | |
|     .store_uint(max_tot_rounds, 8).store_uint(0, 16));
 | |
|   store_data(cfg_dict, stored_seqno, public_key, vote_dict);
 | |
|   return price;
 | |
| }
 | |
| 
 | |
| () recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure {
 | |
|   var cs = in_msg_cell.begin_parse();
 | |
|   var flags = cs~load_uint(4);  ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool
 | |
|   var s_addr = cs~load_msg_addr();
 | |
|   (int src_wc, int src_addr) = s_addr.parse_std_addr();
 | |
|   if ((src_wc + 1) | (flags & 1) | in_msg.slice_empty?()) {
 | |
|     ;; source not in masterchain, or a bounced message, or a simple transfer
 | |
|     return ();
 | |
|   }
 | |
|   int tag = in_msg~load_uint(32);
 | |
|   int query_id = in_msg~load_uint(64);
 | |
|   if (tag == 0x4e565354) {
 | |
|     ;; set next validator set
 | |
|     var vset = in_msg~load_ref();
 | |
|     in_msg.end_parse();
 | |
|     var elector_param = config_param(1);
 | |
|     var elector_addr = cell_null?(elector_param) ? -1 : elector_param.begin_parse().preload_uint(256);
 | |
|     var ok = false;
 | |
|     if (src_addr == elector_addr) {
 | |
|       ;; message from elector smart contract
 | |
|       ;; set next validator set
 | |
|       (var t_since, var t_until) = check_validator_set(vset);
 | |
|       var t = now();
 | |
|       ok = (t_since > t) & (t_until > t_since);
 | |
|     }
 | |
|     if (ok) {
 | |
|       set_conf_param(36, vset);
 | |
|       ;; send confirmation
 | |
|       return send_confirmation(s_addr, query_id, 0xee764f4b);
 | |
|     } else {
 | |
|       return send_error(s_addr, query_id, 0xee764f6f);
 | |
|     }
 | |
|   }
 | |
|   if (tag == 0x6e565052) {
 | |
|     ;; new voting proposal
 | |
|     var price = register_voting_proposal(in_msg, msg_value);
 | |
|     int mode = 64;
 | |
|     int ans_tag = - price;
 | |
|     if (price >= 0) {
 | |
|       ;; ok, debit price
 | |
|       raw_reserve(price, 4);
 | |
|       ans_tag = 0xee565052;
 | |
|       mode = 128;
 | |
|     }
 | |
|     return send_answer(s_addr, query_id, ans_tag, mode);
 | |
|   }
 | |
|   if (tag == 0x566f7465) {
 | |
|     ;; vote for a configuration proposal
 | |
|     var signature = in_msg~load_bits(512);
 | |
|     var msg_body = in_msg;
 | |
|     var (sign_tag, idx, phash) = (in_msg~load_uint(32), in_msg~load_uint(16), in_msg~load_uint(256));
 | |
|     in_msg.end_parse();
 | |
|     throw_unless(37, sign_tag == 0x566f7445);
 | |
|     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(phash, idx, weight);
 | |
|     return send_confirmation(s_addr, query_id, res + 0xd6745240);
 | |
|   }
 | |
|   ;; if tag is non-zero and its higher bit is zero, throw an exception (the message is an unsupported query)
 | |
|   ;; to bounce message back to sender
 | |
|   throw_unless(37, (tag == 0) | (tag & (1 << 31)));
 | |
|   ;; do nothing for other internal messages
 | |
| }
 | |
| 
 | |
| () recv_external(slice in_msg) impure {
 | |
|   var signature = in_msg~load_bits(512);
 | |
|   var cs = in_msg;
 | |
|   int action = cs~load_uint(32);
 | |
|   int msg_seqno = cs~load_uint(32);
 | |
|   var valid_until = cs~load_uint(32);
 | |
|   throw_if(35, valid_until < now());
 | |
|   throw_if(39, slice_depth(cs) > 128);
 | |
|   var (cfg_dict, stored_seqno, public_key, vote_dict) = load_data();
 | |
|   throw_unless(33, msg_seqno == stored_seqno);
 | |
|   if (action == 0x566f7465) {
 | |
|     ;; vote for a configuration proposal
 | |
|     var (idx, phash) = (cs~load_uint(16), cs~load_uint(256));
 | |
|     cs.end_parse();
 | |
|     var (vdescr, total_weight) = get_validator_descr(idx);
 | |
|     var (val_pubkey, weight) = unpack_validator_descr(vdescr);
 | |
|     throw_unless(34, check_data_signature(in_msg, signature, val_pubkey));
 | |
|     accept_message();
 | |
|     stored_seqno = (stored_seqno + 1) % (1 << 32);
 | |
|     store_data(cfg_dict, stored_seqno, public_key, vote_dict);
 | |
|     commit();
 | |
|     proceed_register_vote(phash, idx, weight);
 | |
|     return ();
 | |
|   }
 | |
|   throw_unless(34, check_signature(slice_hash(in_msg), signature, public_key));
 | |
|   accept_message();
 | |
|   stored_seqno = (stored_seqno + 1) % (1 << 32);
 | |
|   store_data(cfg_dict, stored_seqno, public_key, vote_dict);
 | |
|   commit();
 | |
|   (cfg_dict, public_key) = perform_action(cfg_dict, public_key, action, cs);
 | |
|   store_data(cfg_dict, stored_seqno, public_key, vote_dict);
 | |
| }
 | |
| 
 | |
| () run_ticktock(int is_tock) impure {
 | |
|   var (cfg_dict, stored_seqno, public_key, vote_dict) = load_data();
 | |
|   int kl = 32;
 | |
|   var next_vset = cfg_dict.idict_get_ref(kl, 36);
 | |
|   var updated? = false;
 | |
|   ifnot (next_vset.null?()) {
 | |
|     ;; check whether we have to set next_vset as the current validator set
 | |
|     var ds = next_vset.begin_parse();
 | |
|     if (ds.slice_bits() >= 40) {
 | |
|       var tag = ds~load_uint(8);
 | |
|       var since = ds.preload_uint(32);
 | |
|       if ((since <= now()) & (tag == 0x12)) {
 | |
|         ;; next validator set becomes active!
 | |
|         var cur_vset = cfg_dict~idict_set_get_ref(kl, 34, next_vset);  ;; next_vset -> cur_vset
 | |
|         cfg_dict~idict_set_get_ref(kl, 32, cur_vset);   ;; cur_vset -> prev_vset
 | |
|         cfg_dict~idict_delete?(kl, 36);             ;; (null) -> next_vset
 | |
|         updated? = true;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   ifnot (updated?) {
 | |
|     ;; if nothing has been done so far, scan a random voting proposal instead
 | |
|     vote_dict = scan_random_proposal(vote_dict);
 | |
|   }
 | |
|   ;; save data and return
 | |
|   return store_data(cfg_dict, stored_seqno, public_key, vote_dict);
 | |
| }
 | |
| 
 | |
| int seqno() method_id {
 | |
|   return get_data().begin_parse().preload_uint(32);
 | |
| }
 | |
| 
 | |
| _ unpack_proposal(slice pstatus) inline_ref {
 | |
|   (int expires, cell proposal, int critical?, cell voters, int weight_remaining, int vset_id, slice rest) = unpack_proposal_status(pstatus);
 | |
|   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);
 | |
|   var (rounds_remaining, losses, wins) = (rest~load_uint(8), rest~load_uint(8), rest~load_uint(8));
 | |
|   rest.end_parse();
 | |
|   var (param_id, param_val, param_hash) = parse_config_proposal(proposal);
 | |
|   return [expires, critical?, [param_id, param_val, param_hash], vset_id, voters_list, weight_remaining, rounds_remaining, losses, wins];
 | |
| }
 | |
| 
 | |
| _ get_proposal(int phash) method_id {
 | |
|   (_, _, _, var vote_dict) = load_data();
 | |
|   var (pstatus, found?) = vote_dict.udict_get?(256, phash);
 | |
|   ifnot (found?) {
 | |
|     return null();
 | |
|   }
 | |
|   return unpack_proposal(pstatus);
 | |
| }
 | |
| 
 | |
| _ list_proposals() method_id {
 | |
|   (_, _, _, var vote_dict) = load_data();
 | |
|   var phash = (1 << 255) + ((1 << 255) - 1);
 | |
|   var list = null();
 | |
|   do {
 | |
|     (phash, var pstatus, var f) = vote_dict.udict_get_prev?(256, phash);
 | |
|     if (f) {
 | |
|       list = cons([phash, unpack_proposal(pstatus)], list);
 | |
|     }
 | |
|   } until (~ f);
 | |
|   return list;
 | |
| }
 | |
| 
 | |
| _ proposal_storage_price(int critical?, int seconds, int bits, int refs) method_id {
 | |
|   var cfg_dict = get_data().begin_parse().preload_ref();
 | |
|   var cparam11 = cfg_dict.idict_get_ref(32, 11);
 | |
|   var (min_tot_rounds, max_tot_rounds, min_wins, max_losses, min_store_sec, max_store_sec, bit_price, cell_price) = get_vote_config_internal(critical?, cparam11);
 | |
|   if (seconds < min_store_sec) {
 | |
|     return -1;
 | |
|   }
 | |
|   seconds = min(seconds, max_store_sec);
 | |
|   return (bit_price * (bits + 1024) + cell_price * (refs + 2)) * seconds;
 | |
| }
 |