Ton

Форк
0
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

11
int op::new_stake() asm "0x4e73744b PUSHINT"; ;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L621
12
int op::new_stake_error() asm "0xee6f454c PUSHINT"; ;; return_stake https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L169
13
int op::new_stake_ok() asm "0xf374484c PUSHINT"; ;; send_confirmation https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L173
14

15
int op::recover_stake() asm "0x47657424 PUSHINT"; ;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L625
16
int op::recover_stake_error() asm "0xfffffffe PUSHINT"; ;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L407
17
int op::recover_stake_ok() asm "0xf96f7324 PUSHINT"; ;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L426
18

19
int ADDR_SIZE() asm "256 PUSHINT";
20
int BOUNCEABLE() asm "0x18 PUSHINT";
21
int NON_BOUNCEABLE() asm "0x10 PUSHINT";
22
int SEND_MODE_PAY_FEE_SEPARATELY() asm "1 PUSHINT"; ;; means that the sender wants to pay transfer fees separately
23
int SEND_MODE_IGNORE_ERRORS() asm "2 PUSHINT"; ;; means that any errors arising while processing this message during the action phase should be ignored
24
int 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
25
int ONE_TON() asm "1000000000 PUSHINT";
26
int MIN_TONS_FOR_STORAGE() asm "10000000000 PUSHINT"; ;; 10 TON
27
int DEPOSIT_PROCESSING_FEE() asm "1000000000 PUSHINT"; ;; 1 TON
28
int MIN_STAKE_TO_SEND() asm "500000000000 PUSHINT"; ;; 500 TON
29
int VOTES_LIFETIME() asm "2592000 PUSHINT"; ;; 30 days
30

31
int 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) {
35
    int n = comment~load_uint(8);
36
    n = n - 48;
37
    throw_unless(329, n >= 0);
38
    if (n < 10) {
39
        return (comment, (n));
40
    }
41
    n = n - 7;
42
    throw_unless(329, n >= 0);
43
    if (n < 16) {
44
        return (comment, (n));
45
    }
46
    n = n - 32;
47
    throw_unless(329, (n >= 0) & (n < 16));
48
    return (comment, n);
49
}
50

51
(slice, int) ~load_text_hex_number(slice comment, int byte_length) {
52
    int current_slice_length = comment.slice_bits() / 8;
53
    int result = 0;
54
    int counter = 0;
55
    repeat (2 * byte_length) {
56
        result = result * 16 + comment~load_hex_symbol();
57
        counter = counter + 1;
58
        if (counter == current_slice_length) {
59
            if (comment.slice_refs() == 1) {
60
                cell _cont = comment~load_ref();
61
                comment = _cont.begin_parse();
62
                current_slice_length = comment.slice_bits() / 8;
63
                counter = 0;
64
            }
65
        }
66
    }
67
    return (comment, result);
68
}
69

70
slice make_address(int wc, int addr) inline_ref {
71
    return 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
76
int is_elector_address(int wc, int addr) inline_ref {
77
  return (wc == -1) & (config_param(1).begin_parse().preload_uint(ADDR_SIZE()) == addr);
78
}
79

80
slice elector_address() inline_ref {
81
    int elector = config_param(1).begin_parse().preload_uint(ADDR_SIZE());
82
    return make_address(-1, elector);
83
}
84

85
;; https://github.com/ton-blockchain/ton/blob/ae5c0720143e231c32c3d2034cfe4e533a16d969/crypto/block/block.tlb#L721
86
int max_recommended_punishment_for_validator_misbehaviour(int stake) inline_ref {
87
    cell cp = config_param(40);
88
    if (cell_null?(cp)) {
89
        return 101000000000; ;; 101 TON - https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/lite-client/lite-client.cpp#L3678
90
    }
91

92
    slice cs = cp.begin_parse();
93

94
    (int prefix,
95
     int default_flat_fine, int default_proportional_fine,
96
     int severity_flat_mult, int severity_proportional_mult,
97
     int unpunishable_interval,
98
     int long_interval, int long_flat_mult, int long_proportional_mult) =
99
        (cs~load_uint(8),
100
         cs~load_coins(), cs~load_uint(32),
101
         cs~load_uint(16), cs~load_uint(16),
102
         cs~load_uint(16),
103
         cs~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
107
     int fine = default_flat_fine;
108
     int fine_part = default_proportional_fine;
109

110
     fine *= severity_flat_mult; fine >>= 8;
111
     fine_part *= severity_proportional_mult; fine_part >>= 8;
112

113
     fine *= long_flat_mult; fine >>= 8;
114
     fine_part *= long_proportional_mult; fine_part >>= 8;
115

116
     return 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
121
int get_validator_config() inline_ref {
122
    slice 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));
124
    return 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 {
129
    cell vset = config_param(34); ;; current validator set
130
    slice 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
133
    throw_unless(9, cs~load_uint(8) == 0x12);  ;; validators_ext#12 only
134
    int utime_since = cs~load_uint(32); ;; actual start unixtime of current validation round
135
    int 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
136
    return (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
141
int check_new_stake_msg(slice cs) impure inline_ref {
142
    var validator_pubkey = cs~load_uint(256);
143
    var stake_at = cs~load_uint(32);
144
    var max_factor = cs~load_uint(32);
145
    var adnl_addr = cs~load_uint(256);
146
    var signature = cs~load_ref().begin_parse().preload_bits(512);
147
    cs.end_parse();
148
    return stake_at; ;; supposed start of next validation round (utime_since)
149
}
150

151
builder pack_nominator(int amount, int pending_deposit_amount) inline_ref {
152
    return begin_cell().store_coins(amount).store_coins(pending_deposit_amount);
153
}
154

155
(int, int) unpack_nominator(slice ds) inline_ref {
156
    return (
157
        ds~load_coins(), ;; amount
158
        ds~load_coins()  ;; pending_deposit_amount
159
    );
160
}
161

162
cell pack_config(int validator_address, int validator_reward_share, int max_nominators_count, int min_validator_stake, int min_nominator_stake) inline_ref {
163
    return 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 {
173
    return (
174
        ds~load_uint(ADDR_SIZE()), ;; validator_address
175
        ds~load_uint(16), ;; validator_reward_share
176
        ds~load_uint(16), ;; max_nominators_count
177
        ds~load_coins(), ;; min_validator_stake
178
        ds~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 {
183
    set_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 {
201
    slice ds = get_data().begin_parse();
202
    return (
203
        ds~load_uint(8), ;; state
204
        ds~load_uint(16), ;; nominators_count
205
        ds~load_coins(), ;; stake_amount_sent
206
        ds~load_coins(), ;; validator_amount
207
        unpack_config(ds~load_ref().begin_parse()), ;; config
208
        ds~load_dict(), ;; nominators
209
        ds~load_dict(), ;; withdraw_requests
210
        ds~load_uint(32), ;; stake_at
211
        ds~load_uint(256), ;; saved_validator_set_hash
212
        ds~load_uint(8), ;; validator_set_changes_count
213
        ds~load_uint(32), ;; validator_set_change_time
214
        ds~load_uint(32), ;; stake_held_for
215
        ds~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 {
220
    int has_payload = ~ cell_null?(payload);
221

222
    builder 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

228
    if (has_payload) {
229
        msg = msg.store_ref(payload);
230
    }
231

232
    send_raw_message(msg.end_cell(), send_mode);
233
}
234

235
() send_excesses(slice sender_address) impure inline_ref {
236
    send_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);
241
    throw_unless(60, found);
242
    (int amount, int pending_deposit_amount) = unpack_nominator(nominator);
243
    int withdraw_amount = amount + pending_deposit_amount;
244

245
    if (withdraw_amount > balance - MIN_TONS_FOR_STORAGE()) {
246
        return (nominators, withdraw_requests, balance, nominators_count);
247
    }
248

249
    nominators~udict_delete?(ADDR_SIZE(), address);
250
    withdraw_requests~udict_delete?(ADDR_SIZE(), address);
251
    nominators_count -= 1;
252
    balance -= withdraw_amount;
253

254
    if (withdraw_amount >= ONE_TON()) {
255
        send_msg(make_address(0, address), withdraw_amount, null(), NON_BOUNCEABLE(), 0); ;; non-bouneable, fee deducted from amount, revert on errors
256
    }
257
    return (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 {
261
    int count = 0;
262
    int address = -1;
263
    int need_break = 0;
264
    do {
265
        (address, slice cs, int f) = withdraw_requests.udict_get_next?(ADDR_SIZE(), address);
266
        if (f) {
267
            (nominators, withdraw_requests, int new_balance, nominators_count) = withdraw_nominator(address, nominators, withdraw_requests, balance, nominators_count);
268
            need_break = (new_balance == balance);
269
            balance = new_balance;
270
            count += 1;
271
            if (count >= limit) {
272
                need_break = -1;
273
            }
274
        }
275
    } until ((~ f) | (need_break));
276

277
    return (nominators, withdraw_requests, nominators_count, balance);
278
}
279

280
int calculate_total_nominators_amount(cell nominators) inline_ref {
281
    int total = 0;
282
    int address = -1;
283
    do {
284
        (address, slice cs, int f) = nominators.udict_get_next?(ADDR_SIZE(), address);
285
        if (f) {
286
            (int amount, int pending_deposit_amount) = unpack_nominator(cs);
287
            total += (amount + pending_deposit_amount);
288
        }
289
    } until (~ f);
290
    return total;
291
}
292

293
cell distribute_share(int reward, cell nominators) inline_ref {
294
    int total_amount = 0;
295
    int address = -1;
296
    do {
297
        (address, slice cs, int f) = nominators.udict_get_next?(ADDR_SIZE(), address);
298
        if (f) {
299
            (int amount, int pending_deposit_amount) = unpack_nominator(cs);
300
            total_amount += amount;
301
        }
302
    } until (~ f);
303

304
    cell new_nominators = new_dict();
305
    address = -1;
306
    do {
307
        (address, slice cs, int f) = nominators.udict_get_next?(ADDR_SIZE(), address);
308
        if (f) {
309
            (int amount, int pending_deposit_amount) = unpack_nominator(cs);
310
            if (total_amount > 0) {
311
                amount += muldiv(reward, amount, total_amount);
312
                if (amount < 0) {
313
                    amount = 0;
314
                }
315
            }
316
            amount += pending_deposit_amount;
317
            new_nominators~udict_set_builder(ADDR_SIZE(), address, pack_nominator(amount, 0));
318
        }
319
    } until (~ f);
320

321
    return new_nominators;
322
}
323

324
() recv_internal(int msg_value, cell in_msg_full, slice in_msg_body) impure {
325
    int balance = pair_first(get_balance());
326

327
    slice cs = in_msg_full.begin_parse();
328
    int flags = cs~load_uint(4);
329

330
    slice sender_address = cs~load_msg_addr();
331
    (int sender_wc, int sender_addr) = parse_std_addr(sender_address);
332

333
    if (flags & 1) { ;; bounced messages
334
        if (in_msg_body.slice_bits() >= 64) {
335
            in_msg_body~skip_bits(32); ;; skip 0xFFFFFFFF bounced prefix
336
            int op = in_msg_body~load_uint(32);
337
            if ((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();
344
                if (state == 1) {
345
                    state = 0;
346
                }
347
                save_data(
348
                    state,
349
                    nominators_count,
350
                    stake_amount_sent,
351
                    validator_amount,
352
                    pack_config(validator_address, validator_reward_share, max_nominators_count, min_validator_stake, min_nominator_stake),
353
                    nominators,
354
                    withdraw_requests,
355
                    stake_at,
356
                    saved_validator_set_hash,
357
                    validator_set_changes_count,
358
                    validator_set_change_time,
359
                    stake_held_for,
360
                    config_proposal_votings
361
                );
362
            }
363
        }
364
        return (); ;; ignore other bounces messages
365
    }
366

367
    int 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

371
    if (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

377
        int action = in_msg_body~load_uint(8);
378
        int is_vote = (action == 121) | (action == 110); ;; "y" or "n"
379
        throw_unless(64, (action == 100) | (action == 119) | is_vote); ;; "d" or "w" or "y" or "n"
380

381
        if (~ is_vote) {
382
            in_msg_body.end_parse();
383
            throw_unless(61, sender_wc == 0); ;; nominators only in basechain
384
            throw_unless(62, sender_addr != validator_address);
385
        }
386

387
        if (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

390
            if (~ found) {
391
                nominators_count += 1;
392
            }
393
            throw_unless(65, nominators_count <= max_nominators_count);
394

395
            msg_value -= DEPOSIT_PROCESSING_FEE();
396
            throw_unless(66, msg_value > 0);
397

398
            (int amount, int pending_deposit_amount) = found ? unpack_nominator(nominator) : (0, 0);
399
            if (state == 0) {
400
                amount += msg_value;
401
            } else {
402
                pending_deposit_amount += msg_value;
403
            }
404
            throw_unless(67, amount + pending_deposit_amount >= min_nominator_stake);
405
            throw_unless(68, cell_depth(nominators) < max(5, binary_log_ceil(nominators_count) * 2) ); ;; prevent dict depth ddos
406
            nominators~udict_set_builder(ADDR_SIZE(), sender_addr, pack_nominator(amount, pending_deposit_amount));
407
        }
408

409
        if (action == 119) { ;; "w" - withdraw request (any time)
410
            if (state == 0) {
411
                (nominators, withdraw_requests, int new_balance, nominators_count) = withdraw_nominator(sender_addr, nominators, withdraw_requests, balance, nominators_count);
412
                if (new_balance - msg_value >= MIN_TONS_FOR_STORAGE()) {
413
                    send_excesses(sender_address);
414
                }
415
            } else {
416
                (slice nominator, int found) = nominators.udict_get?(ADDR_SIZE(), sender_addr);
417
                throw_unless(69, found);
418
                withdraw_requests~udict_set_builder(ADDR_SIZE(), sender_addr, begin_cell());
419
                send_excesses(sender_address);
420
            }
421
        }
422

423
        if (is_vote) {
424
            int authorized = (sender_wc == -1) & (sender_addr == validator_address);
425

426
            if (~ authorized) {
427
                throw_unless(121, sender_wc == 0);
428
                (slice nominator, authorized) = nominators.udict_get?(ADDR_SIZE(), sender_addr);
429
                throw_unless(122, authorized);
430
                (int amount, int pending_deposit_amount) = unpack_nominator(nominator);
431
                throw_unless(123, amount > 0);
432
            }
433

434
            int proposal_hash = in_msg_body~load_text_hex_number(32);
435
            in_msg_body.end_parse();
436
            int support = action == 121;
437

438
            (slice votes_slice, int found) = config_proposal_votings.udict_get?(256, proposal_hash);
439

440
            if (~ found) {
441
                ;; require higher fee to prevent dictionary spam
442
                int fee = ONE_TON();
443
                int power = cell_depth(config_proposal_votings);
444
                repeat (power) {
445
                    fee = muldiv(fee, 15, 10);
446
               }
447
               throw_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);
453
            throw_if(124, vote_found);
454
            votes_dict~udict_set_builder(256, sender_addr, begin_cell().store_int(support, 1).store_uint(now(), 32));
455

456
            builder new_votes = begin_cell().store_dict(votes_dict).store_uint(votes_create_time, 32);
457
            config_proposal_votings~udict_set_builder(256, proposal_hash, new_votes);
458

459
            if (found) {
460
                send_excesses(sender_address);
461
            }
462
        }
463

464
    } else {
465

466
        int query_id = in_msg_body~load_uint(64);
467

468
        if (is_elector_address(sender_wc, sender_addr)) { ;; response from elector
469

470
            accept_message();
471

472
            if (op == op::recover_stake_ok()) {
473
                state = 0;
474

475
                int reward = msg_value - stake_amount_sent;
476
                int nominators_reward = 0;
477

478
                if (reward <= 0) {
479
                    validator_amount += reward;
480
                    if (validator_amount < 0) {
481
                        ;; even this should never happen
482
                        nominators_reward = validator_amount;
483
                        validator_amount = 0;
484
                    }
485
                } else {
486
                    int validator_reward = (reward * validator_reward_share) / 10000;
487
                    if (validator_reward > reward) { ;; Theoretical invalid case if validator_reward_share > 10000
488
                        validator_reward = reward;
489
                    }
490
                    validator_amount += validator_reward;
491
                    nominators_reward = reward - validator_reward;
492
                }
493

494
                nominators = distribute_share(nominators_reward, nominators); ;; call even if there was no reward to process deposit requests
495
                stake_amount_sent = 0;
496
            }
497

498
            if (state == 1) {
499
                if (op == op::new_stake_error()) { ;; error when new_stake; stake returned
500
                    state = 0;
501
                }
502

503
                if (op == op::new_stake_ok()) {
504
                    state = 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
513
            throw_unless(70, ((op >= 1) & (op <= 7)) | (op == op::recover_stake()) | (op == op::new_stake()));
514

515
            if (op == 1) {
516
                ;; just accept coins
517
            }
518

519
            if (op == 2) { ;; process withdraw requests (at any time while the balance is enough)
520
                int 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

524
                if (new_balance - msg_value >= MIN_TONS_FOR_STORAGE()) {
525
                    send_excesses(sender_address);
526
                }
527
            }
528

529
            if (op == 3) { ;; emergency process withdraw request  (at any time if the balance is enough)
530
                int request_address = in_msg_body~load_uint(ADDR_SIZE());
531
                (slice withdraw_request, int found) = withdraw_requests.udict_get?(ADDR_SIZE(), request_address);
532
                throw_unless(71, found);
533
                (nominators, withdraw_requests, int new_balance, nominators_count) = withdraw_nominator(request_address, nominators, withdraw_requests, balance, nominators_count);
534
                if (new_balance - msg_value >= MIN_TONS_FOR_STORAGE()) {
535
                    send_excesses(sender_address);
536
                }
537
            }
538

539
            if (op == 6) { ;; update current valudator set hash (anyone can invoke)
540
                throw_unless(113, validator_set_changes_count < 3);
541
                (int utime_since, int utime_until, cell vset) = get_current_validator_set();
542
                int current_hash = cell_hash(vset);
543
                if (saved_validator_set_hash != current_hash) {
544
                    saved_validator_set_hash = current_hash;
545
                    validator_set_changes_count += 1;
546
                    validator_set_change_time = now();
547
                }
548
                send_excesses(sender_address);
549
            }
550

551
            if (op == 7) { ;; clean up outdating votings
552
                int t = now();
553
                int proposal_hash = -1;
554
                do {
555
                    (proposal_hash, slice votes_slice, int found) = config_proposal_votings.udict_get_next?(256, proposal_hash);
556
                    if (found) {
557
                        (cell votes_dict, int votes_create_time) = (votes_slice~load_dict(), votes_slice~load_uint(32));
558
                        if (t - votes_create_time > VOTES_LIFETIME()) {
559
                            config_proposal_votings~udict_delete?(256, proposal_hash);
560
                        }
561
                    }
562
                } until (~ found);
563
                send_excesses(sender_address);
564
            }
565

566
            if (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

582
                throw_unless(111, validator_set_changes_count >= 2);
583
                throw_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

586
                cell payload = begin_cell().store_uint(op::recover_stake(), 32).store_uint(query_id, 64).end_cell();
587
                send_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

592
            if (op == 4) { ;; deposit validator (any time)
593
                throw_unless(73, (sender_wc == -1) & (sender_addr == validator_address));
594
                msg_value -= DEPOSIT_PROCESSING_FEE();
595
                throw_unless(74, msg_value > 0);
596
                validator_amount += msg_value;
597
            }
598

599
            if (op == 5) { ;; withdraw validator (after recover_stake and before new_stake)
600
                throw_unless(74, state == 0); ;; no withdraw request because validator software can wait right time
601
                throw_unless(75, (sender_wc == -1) & (sender_addr == validator_address));
602
                int request_amount = in_msg_body~load_coins();
603
                throw_unless(78, request_amount > 0);
604

605
                int total_nominators_amount = calculate_total_nominators_amount(nominators);
606
                ;; the validator can withdraw everything that does not belong to the nominators
607
                throw_unless(76, request_amount <= balance - MIN_TONS_FOR_STORAGE() - total_nominators_amount);
608
                validator_amount -= request_amount;
609
                if (validator_amount < 0) {
610
                    validator_amount = 0;
611
                }
612
                send_msg(make_address(-1, validator_address), request_amount, null(), NON_BOUNCEABLE(), 0); ;; non-bouneable, fee deducted from amount, revert on errors
613
                int new_balance = balance - request_amount;
614
                if (new_balance - msg_value >= MIN_TONS_FOR_STORAGE()) {
615
                    send_excesses(sender_address);
616
                }
617
            }
618

619
            if (op == op::new_stake()) {
620
                throw_unless(78, (sender_wc == -1) & (sender_addr == validator_address));
621

622
                throw_unless(79, state == 0);
623

624
                throw_unless(80, query_id); ;; query_id must be greater then 0 to receive confirmation message from elector
625

626
                throw_unless(86, msg_value >= ONE_TON()); ;; must be greater then new_stake sending to elector fee
627

628
                int value = in_msg_body~load_coins();
629

630
                slice msg = in_msg_body;
631

632
                stake_at = check_new_stake_msg(in_msg_body);
633

634
                stake_amount_sent = value - ONE_TON();
635

636
                throw_unless(81, value >= MIN_STAKE_TO_SEND());
637

638
                throw_unless(82, value <= balance - MIN_TONS_FOR_STORAGE());
639

640
                throw_unless(83, validator_amount >= min_validator_stake);
641

642
                throw_unless(84, validator_amount >= max_recommended_punishment_for_validator_misbehaviour(stake_amount_sent));
643

644
                throw_unless(85, cell_null?(withdraw_requests)); ;; no withdraw requests
645

646
                state = 1;
647
                (int utime_since, int utime_until, cell vset) = get_current_validator_set();
648
                saved_validator_set_hash = cell_hash(vset); ;; current validator set, we will be in next validator set
649
                validator_set_changes_count = 0;
650
                validator_set_change_time = utime_since;
651
                stake_held_for = get_validator_config(); ;; save `stake_held_for` in case the config changes in the process
652

653
                send_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

658
    save_data(
659
        state,
660
        nominators_count,
661
        stake_amount_sent,
662
        validator_amount,
663
        pack_config(validator_address, validator_reward_share, max_nominators_count, min_validator_stake, min_nominator_stake),
664
        nominators,
665
        withdraw_requests,
666
        stake_at,
667
        saved_validator_set_hash,
668
        validator_set_changes_count,
669
        validator_set_change_time,
670
        stake_held_for,
671
        config_proposal_votings
672
    );
673
}
674

675
;; Get methods
676

677
_ get_pool_data() method_id {
678
    return load_data();
679
}
680

681
int 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();
683
    return ~ 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);
690
    throw_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

694
    return (amount, pending_deposit_amount, withdraw_found);
695
}
696

697
int get_max_punishment(int stake) method_id {
698
    return max_recommended_punishment_for_validator_misbehaviour(stake);
699
}
700

701
tuple 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();
703
    var list = null();
704
    int address = -1;
705
    do {
706
        (address, slice nominator, int found) = nominators.udict_get_next?(ADDR_SIZE(), address);
707
        if (found) {
708
            (int amount, int pending_deposit_amount) = unpack_nominator(nominator);
709
            (_, int withdraw_requested) = withdraw_requests.udict_get?(ADDR_SIZE(), address);
710
            list = cons(tuple4(address, amount, pending_deposit_amount, withdraw_requested), list);
711
        }
712
    } until (~ found);
713
    return list;
714
}
715

716
tuple 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();
718
    var list = null();
719
    int proposal_hash = -1;
720
    do {
721
        (proposal_hash, slice votes_slice, int found) = config_proposal_votings.udict_get_next?(256, proposal_hash);
722
        if (found) {
723
            (cell votes_dict, int votes_create_time) = (votes_slice~load_dict(), votes_slice~load_uint(32));
724
            list = cons(pair(proposal_hash, votes_create_time), list);
725
        }
726
    } until (~ found);
727
    return list;
728
}
729

730
tuple 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();
732
    var list = null();
733
    (slice votes_slice, int found) = config_proposal_votings.udict_get?(256, proposal_hash);
734
    throw_unless(133, found);
735
    cell votes_dict = votes_slice~load_dict();
736

737
    int address = -1;
738
    do {
739
        (address, slice cs, int found) = votes_dict.udict_get_next?(ADDR_SIZE(), address);
740
        if (found) {
741
            (int support, int vote_time) = (cs~load_int(1), cs~load_uint(32));
742
            list = cons(triple(address, support, vote_time), list);
743
        }
744
    } until (~ found);
745
    return list;
746
}
747

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.