Ton
746 строк · 35.6 Кб
1;; The validator has his own wallet in the masterchain, on which he holds his own coins for operating.
2;; From this wallet he sends commands to this nominator pool (mostly `new_stake`, `update_validator_set` and `recover_stake`).
3;; Register/vote_for complaints and register/vote_for config proposals are sent from validator's wallet.
4;;
5;; Pool contract must be in masterchain.
6;; Nominators' wallets must be in the basechain.
7;; The validator in most cases have two pools (for even and odd validation rounds).
8
9#include "stdlib.fc";
10
11int op::new_stake() asm "0x4e73744b PUSHINT"; ;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L621
12int op::new_stake_error() asm "0xee6f454c PUSHINT"; ;; return_stake https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L169
13int op::new_stake_ok() asm "0xf374484c PUSHINT"; ;; send_confirmation https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L173
14
15int op::recover_stake() asm "0x47657424 PUSHINT"; ;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L625
16int op::recover_stake_error() asm "0xfffffffe PUSHINT"; ;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L407
17int op::recover_stake_ok() asm "0xf96f7324 PUSHINT"; ;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L426
18
19int ADDR_SIZE() asm "256 PUSHINT";
20int BOUNCEABLE() asm "0x18 PUSHINT";
21int NON_BOUNCEABLE() asm "0x10 PUSHINT";
22int SEND_MODE_PAY_FEE_SEPARATELY() asm "1 PUSHINT"; ;; means that the sender wants to pay transfer fees separately
23int SEND_MODE_IGNORE_ERRORS() asm "2 PUSHINT"; ;; means that any errors arising while processing this message during the action phase should be ignored
24int SEND_MODE_REMAINING_AMOUNT() asm "64 PUSHINT"; ;; is used for messages that carry all the remaining value of the inbound message in addition to the value initially indicated in the new message
25int ONE_TON() asm "1000000000 PUSHINT";
26int MIN_TONS_FOR_STORAGE() asm "10000000000 PUSHINT"; ;; 10 TON
27int DEPOSIT_PROCESSING_FEE() asm "1000000000 PUSHINT"; ;; 1 TON
28int MIN_STAKE_TO_SEND() asm "500000000000 PUSHINT"; ;; 500 TON
29int VOTES_LIFETIME() asm "2592000 PUSHINT"; ;; 30 days
30
31int binary_log_ceil(int x) asm "UBITSIZE";
32
33;; hex parse same with bridge https://github.com/ton-blockchain/bridge-func/blob/d03dbdbe9236e01efe7f5d344831bf770ac4c613/func/text_utils.fc
34(slice, int) ~load_hex_symbol(slice comment) {
35int n = comment~load_uint(8);
36n = n - 48;
37throw_unless(329, n >= 0);
38if (n < 10) {
39return (comment, (n));
40}
41n = n - 7;
42throw_unless(329, n >= 0);
43if (n < 16) {
44return (comment, (n));
45}
46n = n - 32;
47throw_unless(329, (n >= 0) & (n < 16));
48return (comment, n);
49}
50
51(slice, int) ~load_text_hex_number(slice comment, int byte_length) {
52int current_slice_length = comment.slice_bits() / 8;
53int result = 0;
54int counter = 0;
55repeat (2 * byte_length) {
56result = result * 16 + comment~load_hex_symbol();
57counter = counter + 1;
58if (counter == current_slice_length) {
59if (comment.slice_refs() == 1) {
60cell _cont = comment~load_ref();
61comment = _cont.begin_parse();
62current_slice_length = comment.slice_bits() / 8;
63counter = 0;
64}
65}
66}
67return (comment, result);
68}
69
70slice make_address(int wc, int addr) inline_ref {
71return begin_cell()
72.store_uint(4, 3).store_int(wc, 8).store_uint(addr, ADDR_SIZE()).end_cell().begin_parse();
73}
74
75;; https://github.com/ton-blockchain/ton/blob/ae5c0720143e231c32c3d2034cfe4e533a16d969/crypto/block/block.tlb#L584
76int is_elector_address(int wc, int addr) inline_ref {
77return (wc == -1) & (config_param(1).begin_parse().preload_uint(ADDR_SIZE()) == addr);
78}
79
80slice elector_address() inline_ref {
81int elector = config_param(1).begin_parse().preload_uint(ADDR_SIZE());
82return make_address(-1, elector);
83}
84
85;; https://github.com/ton-blockchain/ton/blob/ae5c0720143e231c32c3d2034cfe4e533a16d969/crypto/block/block.tlb#L721
86int max_recommended_punishment_for_validator_misbehaviour(int stake) inline_ref {
87cell cp = config_param(40);
88if (cell_null?(cp)) {
89return 101000000000; ;; 101 TON - https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/lite-client/lite-client.cpp#L3678
90}
91
92slice cs = cp.begin_parse();
93
94(int prefix,
95int default_flat_fine, int default_proportional_fine,
96int severity_flat_mult, int severity_proportional_mult,
97int unpunishable_interval,
98int long_interval, int long_flat_mult, int long_proportional_mult) =
99(cs~load_uint(8),
100cs~load_coins(), cs~load_uint(32),
101cs~load_uint(16), cs~load_uint(16),
102cs~load_uint(16),
103cs~load_uint(16), cs~load_uint(16), cs~load_uint(16)
104);
105
106;; https://github.com/ton-blockchain/ton/blob/master/lite-client/lite-client.cpp#L3721
107int fine = default_flat_fine;
108int fine_part = default_proportional_fine;
109
110fine *= severity_flat_mult; fine >>= 8;
111fine_part *= severity_proportional_mult; fine_part >>= 8;
112
113fine *= long_flat_mult; fine >>= 8;
114fine_part *= long_proportional_mult; fine_part >>= 8;
115
116return min(stake, fine + muldiv(stake, fine_part, 1 << 32)); ;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L529
117}
118
119;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/block/block.tlb#L632
120;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L118
121int get_validator_config() inline_ref {
122slice cs = config_param(15).begin_parse();
123(int validators_elected_for, int elections_start_before, int elections_end_before, int stake_held_for) = (cs~load_uint(32), cs~load_uint(32), cs~load_uint(32), cs.preload_uint(32));
124return stake_held_for;
125}
126
127;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/block/block.tlb#L712
128(int, int, cell) get_current_validator_set() inline_ref {
129cell vset = config_param(34); ;; current validator set
130slice cs = vset.begin_parse();
131;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/block/block.tlb#L579
132;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/config-code.fc#L49
133throw_unless(9, cs~load_uint(8) == 0x12); ;; validators_ext#12 only
134int utime_since = cs~load_uint(32); ;; actual start unixtime of current validation round
135int utime_until = cs~load_uint(32); ;; supposed end unixtime of current validation round (utime_until = utime_since + validators_elected_for); unfreeze_at = utime_until + stake_held_for
136return (utime_since, utime_until, vset);
137}
138
139;; check the validity of the new_stake message
140;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L208
141int check_new_stake_msg(slice cs) impure inline_ref {
142var validator_pubkey = cs~load_uint(256);
143var stake_at = cs~load_uint(32);
144var max_factor = cs~load_uint(32);
145var adnl_addr = cs~load_uint(256);
146var signature = cs~load_ref().begin_parse().preload_bits(512);
147cs.end_parse();
148return stake_at; ;; supposed start of next validation round (utime_since)
149}
150
151builder pack_nominator(int amount, int pending_deposit_amount) inline_ref {
152return begin_cell().store_coins(amount).store_coins(pending_deposit_amount);
153}
154
155(int, int) unpack_nominator(slice ds) inline_ref {
156return (
157ds~load_coins(), ;; amount
158ds~load_coins() ;; pending_deposit_amount
159);
160}
161
162cell pack_config(int validator_address, int validator_reward_share, int max_nominators_count, int min_validator_stake, int min_nominator_stake) inline_ref {
163return begin_cell()
164.store_uint(validator_address, ADDR_SIZE())
165.store_uint(validator_reward_share, 16)
166.store_uint(max_nominators_count, 16)
167.store_coins(min_validator_stake)
168.store_coins(min_nominator_stake)
169.end_cell();
170}
171
172(int, int, int, int, int) unpack_config(slice ds) inline_ref {
173return (
174ds~load_uint(ADDR_SIZE()), ;; validator_address
175ds~load_uint(16), ;; validator_reward_share
176ds~load_uint(16), ;; max_nominators_count
177ds~load_coins(), ;; min_validator_stake
178ds~load_coins() ;; min_nominator_stake
179);
180}
181
182() save_data(int state, int nominators_count, int stake_amount_sent, int validator_amount, cell config, cell nominators, cell withdraw_requests, int stake_at, int saved_validator_set_hash, int validator_set_changes_count, int validator_set_change_time, int stake_held_for, cell config_proposal_votings) impure inline_ref {
183set_data(begin_cell()
184.store_uint(state, 8)
185.store_uint(nominators_count, 16)
186.store_coins(stake_amount_sent)
187.store_coins(validator_amount)
188.store_ref(config)
189.store_dict(nominators)
190.store_dict(withdraw_requests)
191.store_uint(stake_at, 32)
192.store_uint(saved_validator_set_hash, 256)
193.store_uint(validator_set_changes_count, 8)
194.store_uint(validator_set_change_time, 32)
195.store_uint(stake_held_for, 32)
196.store_dict(config_proposal_votings)
197.end_cell());
198}
199
200(int, int, int, int, (int, int, int, int, int), cell, cell, int, int, int, int, int, cell) load_data() inline_ref {
201slice ds = get_data().begin_parse();
202return (
203ds~load_uint(8), ;; state
204ds~load_uint(16), ;; nominators_count
205ds~load_coins(), ;; stake_amount_sent
206ds~load_coins(), ;; validator_amount
207unpack_config(ds~load_ref().begin_parse()), ;; config
208ds~load_dict(), ;; nominators
209ds~load_dict(), ;; withdraw_requests
210ds~load_uint(32), ;; stake_at
211ds~load_uint(256), ;; saved_validator_set_hash
212ds~load_uint(8), ;; validator_set_changes_count
213ds~load_uint(32), ;; validator_set_change_time
214ds~load_uint(32), ;; stake_held_for
215ds~load_dict() ;; config_proposal_votings
216);
217}
218
219() send_msg(slice to_address, int amount, cell payload, int flags, int send_mode) impure inline_ref {
220int has_payload = ~ cell_null?(payload);
221
222builder msg = begin_cell()
223.store_uint(flags, 6)
224.store_slice(to_address)
225.store_coins(amount)
226.store_uint(has_payload ? 1 : 0, 1 + 4 + 4 + 64 + 32 + 1 + 1);
227
228if (has_payload) {
229msg = msg.store_ref(payload);
230}
231
232send_raw_message(msg.end_cell(), send_mode);
233}
234
235() send_excesses(slice sender_address) impure inline_ref {
236send_msg(sender_address, 0, null(), NON_BOUNCEABLE(), SEND_MODE_REMAINING_AMOUNT() + SEND_MODE_IGNORE_ERRORS()); ;; non-bouneable, remaining inbound message amount, fee deducted from amount, ignore errors
237}
238
239(cell, cell, int, int) withdraw_nominator(int address, cell nominators, cell withdraw_requests, int balance, int nominators_count) impure inline_ref {
240(slice nominator, int found) = nominators.udict_get?(ADDR_SIZE(), address);
241throw_unless(60, found);
242(int amount, int pending_deposit_amount) = unpack_nominator(nominator);
243int withdraw_amount = amount + pending_deposit_amount;
244
245if (withdraw_amount > balance - MIN_TONS_FOR_STORAGE()) {
246return (nominators, withdraw_requests, balance, nominators_count);
247}
248
249nominators~udict_delete?(ADDR_SIZE(), address);
250withdraw_requests~udict_delete?(ADDR_SIZE(), address);
251nominators_count -= 1;
252balance -= withdraw_amount;
253
254if (withdraw_amount >= ONE_TON()) {
255send_msg(make_address(0, address), withdraw_amount, null(), NON_BOUNCEABLE(), 0); ;; non-bouneable, fee deducted from amount, revert on errors
256}
257return (nominators, withdraw_requests, balance, nominators_count);
258}
259
260(cell, cell, int, int) process_withdraw_requests(cell nominators, cell withdraw_requests, int balance, int nominators_count, int limit) impure inline_ref {
261int count = 0;
262int address = -1;
263int need_break = 0;
264do {
265(address, slice cs, int f) = withdraw_requests.udict_get_next?(ADDR_SIZE(), address);
266if (f) {
267(nominators, withdraw_requests, int new_balance, nominators_count) = withdraw_nominator(address, nominators, withdraw_requests, balance, nominators_count);
268need_break = (new_balance == balance);
269balance = new_balance;
270count += 1;
271if (count >= limit) {
272need_break = -1;
273}
274}
275} until ((~ f) | (need_break));
276
277return (nominators, withdraw_requests, nominators_count, balance);
278}
279
280int calculate_total_nominators_amount(cell nominators) inline_ref {
281int total = 0;
282int address = -1;
283do {
284(address, slice cs, int f) = nominators.udict_get_next?(ADDR_SIZE(), address);
285if (f) {
286(int amount, int pending_deposit_amount) = unpack_nominator(cs);
287total += (amount + pending_deposit_amount);
288}
289} until (~ f);
290return total;
291}
292
293cell distribute_share(int reward, cell nominators) inline_ref {
294int total_amount = 0;
295int address = -1;
296do {
297(address, slice cs, int f) = nominators.udict_get_next?(ADDR_SIZE(), address);
298if (f) {
299(int amount, int pending_deposit_amount) = unpack_nominator(cs);
300total_amount += amount;
301}
302} until (~ f);
303
304cell new_nominators = new_dict();
305address = -1;
306do {
307(address, slice cs, int f) = nominators.udict_get_next?(ADDR_SIZE(), address);
308if (f) {
309(int amount, int pending_deposit_amount) = unpack_nominator(cs);
310if (total_amount > 0) {
311amount += muldiv(reward, amount, total_amount);
312if (amount < 0) {
313amount = 0;
314}
315}
316amount += pending_deposit_amount;
317new_nominators~udict_set_builder(ADDR_SIZE(), address, pack_nominator(amount, 0));
318}
319} until (~ f);
320
321return new_nominators;
322}
323
324() recv_internal(int msg_value, cell in_msg_full, slice in_msg_body) impure {
325int balance = pair_first(get_balance());
326
327slice cs = in_msg_full.begin_parse();
328int flags = cs~load_uint(4);
329
330slice sender_address = cs~load_msg_addr();
331(int sender_wc, int sender_addr) = parse_std_addr(sender_address);
332
333if (flags & 1) { ;; bounced messages
334if (in_msg_body.slice_bits() >= 64) {
335in_msg_body~skip_bits(32); ;; skip 0xFFFFFFFF bounced prefix
336int op = in_msg_body~load_uint(32);
337if ((op == op::new_stake()) & (is_elector_address(sender_wc, sender_addr))) {
338;; `new_stake` from nominator-pool should always be handled without throws by elector
339;; because nominator-pool do `check_new_stake_msg` and `msg_value` checks before sending `new_stake`.
340;; If the stake is not accepted elector will send `new_stake_error` response message.
341;; Nevertheless we do process theoretically possible bounced `new_stake`.
342
343(int state, int nominators_count, int stake_amount_sent, int validator_amount, (int validator_address, int validator_reward_share, int max_nominators_count, int min_validator_stake, int min_nominator_stake), cell nominators, cell withdraw_requests, int stake_at, int saved_validator_set_hash, int validator_set_changes_count, int validator_set_change_time, int stake_held_for, cell config_proposal_votings) = load_data();
344if (state == 1) {
345state = 0;
346}
347save_data(
348state,
349nominators_count,
350stake_amount_sent,
351validator_amount,
352pack_config(validator_address, validator_reward_share, max_nominators_count, min_validator_stake, min_nominator_stake),
353nominators,
354withdraw_requests,
355stake_at,
356saved_validator_set_hash,
357validator_set_changes_count,
358validator_set_change_time,
359stake_held_for,
360config_proposal_votings
361);
362}
363}
364return (); ;; ignore other bounces messages
365}
366
367int op = in_msg_body~load_uint(32);
368
369(int state, int nominators_count, int stake_amount_sent, int validator_amount, (int validator_address, int validator_reward_share, int max_nominators_count, int min_validator_stake, int min_nominator_stake), cell nominators, cell withdraw_requests, int stake_at, int saved_validator_set_hash, int validator_set_changes_count, int validator_set_change_time, int stake_held_for, cell config_proposal_votings) = load_data();
370
371if (op == 0) {
372;; We use simple text comments for nominator operations so nominators can do it from any wallet app.
373;; In other cases, they will need to put a stake on a browser extension, or use scripts, which can be inconvenient.
374
375;; Throw on any unexpected request so that the stake is bounced back to the nominator in case of a typo.
376
377int action = in_msg_body~load_uint(8);
378int is_vote = (action == 121) | (action == 110); ;; "y" or "n"
379throw_unless(64, (action == 100) | (action == 119) | is_vote); ;; "d" or "w" or "y" or "n"
380
381if (~ is_vote) {
382in_msg_body.end_parse();
383throw_unless(61, sender_wc == 0); ;; nominators only in basechain
384throw_unless(62, sender_addr != validator_address);
385}
386
387if (action == 100) { ;; "d" - deposit nominator (any time, will take effect in the next round)
388(slice nominator, int found) = nominators.udict_get?(ADDR_SIZE(), sender_addr);
389
390if (~ found) {
391nominators_count += 1;
392}
393throw_unless(65, nominators_count <= max_nominators_count);
394
395msg_value -= DEPOSIT_PROCESSING_FEE();
396throw_unless(66, msg_value > 0);
397
398(int amount, int pending_deposit_amount) = found ? unpack_nominator(nominator) : (0, 0);
399if (state == 0) {
400amount += msg_value;
401} else {
402pending_deposit_amount += msg_value;
403}
404throw_unless(67, amount + pending_deposit_amount >= min_nominator_stake);
405throw_unless(68, cell_depth(nominators) < max(5, binary_log_ceil(nominators_count) * 2) ); ;; prevent dict depth ddos
406nominators~udict_set_builder(ADDR_SIZE(), sender_addr, pack_nominator(amount, pending_deposit_amount));
407}
408
409if (action == 119) { ;; "w" - withdraw request (any time)
410if (state == 0) {
411(nominators, withdraw_requests, int new_balance, nominators_count) = withdraw_nominator(sender_addr, nominators, withdraw_requests, balance, nominators_count);
412if (new_balance - msg_value >= MIN_TONS_FOR_STORAGE()) {
413send_excesses(sender_address);
414}
415} else {
416(slice nominator, int found) = nominators.udict_get?(ADDR_SIZE(), sender_addr);
417throw_unless(69, found);
418withdraw_requests~udict_set_builder(ADDR_SIZE(), sender_addr, begin_cell());
419send_excesses(sender_address);
420}
421}
422
423if (is_vote) {
424int authorized = (sender_wc == -1) & (sender_addr == validator_address);
425
426if (~ authorized) {
427throw_unless(121, sender_wc == 0);
428(slice nominator, authorized) = nominators.udict_get?(ADDR_SIZE(), sender_addr);
429throw_unless(122, authorized);
430(int amount, int pending_deposit_amount) = unpack_nominator(nominator);
431throw_unless(123, amount > 0);
432}
433
434int proposal_hash = in_msg_body~load_text_hex_number(32);
435in_msg_body.end_parse();
436int support = action == 121;
437
438(slice votes_slice, int found) = config_proposal_votings.udict_get?(256, proposal_hash);
439
440if (~ found) {
441;; require higher fee to prevent dictionary spam
442int fee = ONE_TON();
443int power = cell_depth(config_proposal_votings);
444repeat (power) {
445fee = muldiv(fee, 15, 10);
446}
447throw_unless(123, msg_value >= fee);
448}
449
450(cell votes_dict, int votes_create_time) = found ? (votes_slice~load_dict(), votes_slice~load_uint(32)) : (new_dict(), now());
451
452(_, int vote_found) = votes_dict.udict_get?(256, sender_addr);
453throw_if(124, vote_found);
454votes_dict~udict_set_builder(256, sender_addr, begin_cell().store_int(support, 1).store_uint(now(), 32));
455
456builder new_votes = begin_cell().store_dict(votes_dict).store_uint(votes_create_time, 32);
457config_proposal_votings~udict_set_builder(256, proposal_hash, new_votes);
458
459if (found) {
460send_excesses(sender_address);
461}
462}
463
464} else {
465
466int query_id = in_msg_body~load_uint(64);
467
468if (is_elector_address(sender_wc, sender_addr)) { ;; response from elector
469
470accept_message();
471
472if (op == op::recover_stake_ok()) {
473state = 0;
474
475int reward = msg_value - stake_amount_sent;
476int nominators_reward = 0;
477
478if (reward <= 0) {
479validator_amount += reward;
480if (validator_amount < 0) {
481;; even this should never happen
482nominators_reward = validator_amount;
483validator_amount = 0;
484}
485} else {
486int validator_reward = (reward * validator_reward_share) / 10000;
487if (validator_reward > reward) { ;; Theoretical invalid case if validator_reward_share > 10000
488validator_reward = reward;
489}
490validator_amount += validator_reward;
491nominators_reward = reward - validator_reward;
492}
493
494nominators = distribute_share(nominators_reward, nominators); ;; call even if there was no reward to process deposit requests
495stake_amount_sent = 0;
496}
497
498if (state == 1) {
499if (op == op::new_stake_error()) { ;; error when new_stake; stake returned
500state = 0;
501}
502
503if (op == op::new_stake_ok()) {
504state = 2;
505}
506}
507
508;; else just accept coins from elector
509
510} else {
511
512;; throw on any unexpected request so that the coins is bounced back to the sender in case of a typo
513throw_unless(70, ((op >= 1) & (op <= 7)) | (op == op::recover_stake()) | (op == op::new_stake()));
514
515if (op == 1) {
516;; just accept coins
517}
518
519if (op == 2) { ;; process withdraw requests (at any time while the balance is enough)
520int limit = in_msg_body~load_uint(8);
521
522(nominators, withdraw_requests, nominators_count, int new_balance) = process_withdraw_requests(nominators, withdraw_requests, balance, nominators_count, limit);
523
524if (new_balance - msg_value >= MIN_TONS_FOR_STORAGE()) {
525send_excesses(sender_address);
526}
527}
528
529if (op == 3) { ;; emergency process withdraw request (at any time if the balance is enough)
530int request_address = in_msg_body~load_uint(ADDR_SIZE());
531(slice withdraw_request, int found) = withdraw_requests.udict_get?(ADDR_SIZE(), request_address);
532throw_unless(71, found);
533(nominators, withdraw_requests, int new_balance, nominators_count) = withdraw_nominator(request_address, nominators, withdraw_requests, balance, nominators_count);
534if (new_balance - msg_value >= MIN_TONS_FOR_STORAGE()) {
535send_excesses(sender_address);
536}
537}
538
539if (op == 6) { ;; update current valudator set hash (anyone can invoke)
540throw_unless(113, validator_set_changes_count < 3);
541(int utime_since, int utime_until, cell vset) = get_current_validator_set();
542int current_hash = cell_hash(vset);
543if (saved_validator_set_hash != current_hash) {
544saved_validator_set_hash = current_hash;
545validator_set_changes_count += 1;
546validator_set_change_time = now();
547}
548send_excesses(sender_address);
549}
550
551if (op == 7) { ;; clean up outdating votings
552int t = now();
553int proposal_hash = -1;
554do {
555(proposal_hash, slice votes_slice, int found) = config_proposal_votings.udict_get_next?(256, proposal_hash);
556if (found) {
557(cell votes_dict, int votes_create_time) = (votes_slice~load_dict(), votes_slice~load_uint(32));
558if (t - votes_create_time > VOTES_LIFETIME()) {
559config_proposal_votings~udict_delete?(256, proposal_hash);
560}
561}
562} until (~ found);
563send_excesses(sender_address);
564}
565
566if (op == op::recover_stake()) { ;; send recover_stake to elector (anyone can send)
567
568;; We need to take all credits from the elector at once,
569;; because if we do not take all at once, then it will be processed as a fine by pool.
570;; In the elector, credits (`credit_to`) are accrued in three places:
571;; 1) return of surplus stake in elections (`try_elect`)
572;; 2) reward for complaint when punish (`punish`) - before unfreezing
573;; 3) unfreeze round (`unfreeze_without_bonuses`/`unfreeze_with_bonuses`)
574;; We need to be guaranteed to wait for unfreezing round and only then send `recover_stake`.
575;; So we are waiting for the change of 3 validator sets.
576
577;; ADDITIONAL NOTE:
578;; In a special case (if the network was down), the config theoretically can refuse the elector to save a new round after election - https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/config-code.fc#L494
579;; and the elector will start a new election - https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L364
580;; in this case, our pool will have to skip the round, but it will be able to recover stake later
581
582throw_unless(111, validator_set_changes_count >= 2);
583throw_unless(112, (validator_set_changes_count > 2) | (now() - validator_set_change_time > stake_held_for + 60));
584;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L887
585
586cell payload = begin_cell().store_uint(op::recover_stake(), 32).store_uint(query_id, 64).end_cell();
587send_msg(elector_address(), 0, payload, BOUNCEABLE(), SEND_MODE_REMAINING_AMOUNT()); ;; bounceable, carry all the remaining value of the inbound message, fee deducted from amount, revert on errors
588}
589
590;; message from validator
591
592if (op == 4) { ;; deposit validator (any time)
593throw_unless(73, (sender_wc == -1) & (sender_addr == validator_address));
594msg_value -= DEPOSIT_PROCESSING_FEE();
595throw_unless(74, msg_value > 0);
596validator_amount += msg_value;
597}
598
599if (op == 5) { ;; withdraw validator (after recover_stake and before new_stake)
600throw_unless(74, state == 0); ;; no withdraw request because validator software can wait right time
601throw_unless(75, (sender_wc == -1) & (sender_addr == validator_address));
602int request_amount = in_msg_body~load_coins();
603throw_unless(78, request_amount > 0);
604
605int total_nominators_amount = calculate_total_nominators_amount(nominators);
606;; the validator can withdraw everything that does not belong to the nominators
607throw_unless(76, request_amount <= balance - MIN_TONS_FOR_STORAGE() - total_nominators_amount);
608validator_amount -= request_amount;
609if (validator_amount < 0) {
610validator_amount = 0;
611}
612send_msg(make_address(-1, validator_address), request_amount, null(), NON_BOUNCEABLE(), 0); ;; non-bouneable, fee deducted from amount, revert on errors
613int new_balance = balance - request_amount;
614if (new_balance - msg_value >= MIN_TONS_FOR_STORAGE()) {
615send_excesses(sender_address);
616}
617}
618
619if (op == op::new_stake()) {
620throw_unless(78, (sender_wc == -1) & (sender_addr == validator_address));
621
622throw_unless(79, state == 0);
623
624throw_unless(80, query_id); ;; query_id must be greater then 0 to receive confirmation message from elector
625
626throw_unless(86, msg_value >= ONE_TON()); ;; must be greater then new_stake sending to elector fee
627
628int value = in_msg_body~load_coins();
629
630slice msg = in_msg_body;
631
632stake_at = check_new_stake_msg(in_msg_body);
633
634stake_amount_sent = value - ONE_TON();
635
636throw_unless(81, value >= MIN_STAKE_TO_SEND());
637
638throw_unless(82, value <= balance - MIN_TONS_FOR_STORAGE());
639
640throw_unless(83, validator_amount >= min_validator_stake);
641
642throw_unless(84, validator_amount >= max_recommended_punishment_for_validator_misbehaviour(stake_amount_sent));
643
644throw_unless(85, cell_null?(withdraw_requests)); ;; no withdraw requests
645
646state = 1;
647(int utime_since, int utime_until, cell vset) = get_current_validator_set();
648saved_validator_set_hash = cell_hash(vset); ;; current validator set, we will be in next validator set
649validator_set_changes_count = 0;
650validator_set_change_time = utime_since;
651stake_held_for = get_validator_config(); ;; save `stake_held_for` in case the config changes in the process
652
653send_msg(elector_address(), value, begin_cell().store_uint(op, 32).store_uint(query_id, 64).store_slice(msg).end_cell(), BOUNCEABLE(), SEND_MODE_PAY_FEE_SEPARATELY()); ;; pay fee separately, rever on errors
654}
655}
656}
657
658save_data(
659state,
660nominators_count,
661stake_amount_sent,
662validator_amount,
663pack_config(validator_address, validator_reward_share, max_nominators_count, min_validator_stake, min_nominator_stake),
664nominators,
665withdraw_requests,
666stake_at,
667saved_validator_set_hash,
668validator_set_changes_count,
669validator_set_change_time,
670stake_held_for,
671config_proposal_votings
672);
673}
674
675;; Get methods
676
677_ get_pool_data() method_id {
678return load_data();
679}
680
681int has_withdraw_requests() method_id {
682(int state, int nominators_count, int stake_amount_sent, int validator_amount, (int validator_address, int validator_reward_share, int max_nominators_count, int min_validator_stake, int min_nominator_stake), cell nominators, cell withdraw_requests, int stake_at, int saved_validator_set_hash, int validator_set_changes_count, int validator_set_change_time, int stake_held_for, cell config_proposal_votings) = load_data();
683return ~ cell_null?(withdraw_requests);
684}
685
686(int, int, int) get_nominator_data(int nominator_address) method_id {
687(int state, int nominators_count, int stake_amount_sent, int validator_amount, (int validator_address, int validator_reward_share, int max_nominators_count, int min_validator_stake, int min_nominator_stake), cell nominators, cell withdraw_requests, int stake_at, int saved_validator_set_hash, int validator_set_changes_count, int validator_set_change_time, int stake_held_for, cell config_proposal_votings) = load_data();
688
689(slice nominator, int found) = nominators.udict_get?(ADDR_SIZE(), nominator_address);
690throw_unless(86, found);
691(int amount, int pending_deposit_amount) = unpack_nominator(nominator);
692(slice withdraw_request, int withdraw_found) = withdraw_requests.udict_get?(ADDR_SIZE(), nominator_address);
693
694return (amount, pending_deposit_amount, withdraw_found);
695}
696
697int get_max_punishment(int stake) method_id {
698return max_recommended_punishment_for_validator_misbehaviour(stake);
699}
700
701tuple list_nominators() method_id {
702(int state, int nominators_count, int stake_amount_sent, int validator_amount, (int validator_address, int validator_reward_share, int max_nominators_count, int min_validator_stake, int min_nominator_stake), cell nominators, cell withdraw_requests, int stake_at, int saved_validator_set_hash, int validator_set_changes_count, int validator_set_change_time, int stake_held_for, cell config_proposal_votings) = load_data();
703var list = null();
704int address = -1;
705do {
706(address, slice nominator, int found) = nominators.udict_get_next?(ADDR_SIZE(), address);
707if (found) {
708(int amount, int pending_deposit_amount) = unpack_nominator(nominator);
709(_, int withdraw_requested) = withdraw_requests.udict_get?(ADDR_SIZE(), address);
710list = cons(tuple4(address, amount, pending_deposit_amount, withdraw_requested), list);
711}
712} until (~ found);
713return list;
714}
715
716tuple list_votes() method_id {
717(int state, int nominators_count, int stake_amount_sent, int validator_amount, (int validator_address, int validator_reward_share, int max_nominators_count, int min_validator_stake, int min_nominator_stake), cell nominators, cell withdraw_requests, int stake_at, int saved_validator_set_hash, int validator_set_changes_count, int validator_set_change_time, int stake_held_for, cell config_proposal_votings) = load_data();
718var list = null();
719int proposal_hash = -1;
720do {
721(proposal_hash, slice votes_slice, int found) = config_proposal_votings.udict_get_next?(256, proposal_hash);
722if (found) {
723(cell votes_dict, int votes_create_time) = (votes_slice~load_dict(), votes_slice~load_uint(32));
724list = cons(pair(proposal_hash, votes_create_time), list);
725}
726} until (~ found);
727return list;
728}
729
730tuple list_voters(int proposal_hash) method_id {
731(int state, int nominators_count, int stake_amount_sent, int validator_amount, (int validator_address, int validator_reward_share, int max_nominators_count, int min_validator_stake, int min_nominator_stake), cell nominators, cell withdraw_requests, int stake_at, int saved_validator_set_hash, int validator_set_changes_count, int validator_set_change_time, int stake_held_for, cell config_proposal_votings) = load_data();
732var list = null();
733(slice votes_slice, int found) = config_proposal_votings.udict_get?(256, proposal_hash);
734throw_unless(133, found);
735cell votes_dict = votes_slice~load_dict();
736
737int address = -1;
738do {
739(address, slice cs, int found) = votes_dict.udict_get_next?(ADDR_SIZE(), address);
740if (found) {
741(int support, int vote_time) = (cs~load_int(1), cs~load_uint(32));
742list = cons(triple(address, support, vote_time), list);
743}
744} until (~ found);
745return list;
746}
747