;; Simple configuration smart contract () 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() >= 128) { 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); ;; Note there is a bug in config contract currently deployed in testnet2: ;; wins and losses are messed up var (rounds_remaining, wins, losses) = (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; }