Ton

Форк
0
1189 строк · 42.5 Кб
1
;; Elector smartcontract
2

3
#include "stdlib.fc";
4

5
;; cur_elect credits past_elections grams active_id active_hash
6
(cell, cell, cell, int, int, int) load_data() inline_ref {
7
  var cs = get_data().begin_parse();
8
  var res = (cs~load_dict(), cs~load_dict(), cs~load_dict(), cs~load_grams(), cs~load_uint(32), cs~load_uint(256));
9
  cs.end_parse();
10
  return res;
11
}
12

13
;; cur_elect credits past_elections grams active_id active_hash
14
() store_data(elect, credits, past_elections, grams, active_id, active_hash) impure inline_ref {
15
  set_data(begin_cell()
16
    .store_dict(elect)
17
    .store_dict(credits)
18
    .store_dict(past_elections)
19
    .store_grams(grams)
20
    .store_uint(active_id, 32)
21
    .store_uint(active_hash, 256)
22
  .end_cell());
23
}
24

25
;; elect -> elect_at elect_close min_stake total_stake members failed finished
26
_ unpack_elect(elect) inline_ref {
27
  var es = elect.begin_parse();
28
  var res = (es~load_uint(32), es~load_uint(32), es~load_grams(), es~load_grams(), es~load_dict(), es~load_int(1), es~load_int(1));
29
  es.end_parse();
30
  return res;
31
}
32

33
cell pack_elect(elect_at, elect_close, min_stake, total_stake, members, failed, finished) inline_ref {
34
  return begin_cell()
35
    .store_uint(elect_at, 32)
36
    .store_uint(elect_close, 32)
37
    .store_grams(min_stake)
38
    .store_grams(total_stake)
39
    .store_dict(members)
40
    .store_int(failed, 1)
41
    .store_int(finished, 1)
42
  .end_cell();
43
}
44

45
;; slice -> unfreeze_at stake_held vset_hash frozen_dict total_stake bonuses complaints
46
_ unpack_past_election(slice fs) inline_ref {
47
  var res = (fs~load_uint(32), fs~load_uint(32), fs~load_uint(256), fs~load_dict(), fs~load_grams(), fs~load_grams(), fs~load_dict());
48
  fs.end_parse();
49
  return res;
50
}
51

52
builder pack_past_election(int unfreeze_at, int stake_held, int vset_hash, cell frozen_dict, int total_stake, int bonuses, cell complaints) inline_ref {
53
  return begin_cell()
54
      .store_uint(unfreeze_at, 32)
55
      .store_uint(stake_held, 32)
56
      .store_uint(vset_hash, 256)
57
      .store_dict(frozen_dict)
58
      .store_grams(total_stake)
59
      .store_grams(bonuses)
60
      .store_dict(complaints);
61
}
62

63
;; complaint_status#2d complaint:^ValidatorComplaint voters:(HashmapE 16 True)
64
;;   vset_id:uint256 weight_remaining:int64 = ValidatorComplaintStatus;
65
_ unpack_complaint_status(slice cs) inline_ref {
66
  throw_unless(9, cs~load_uint(8) == 0x2d);
67
  var res = (cs~load_ref(), cs~load_dict(), cs~load_uint(256), cs~load_int(64));
68
  cs.end_parse();
69
  return res;
70
}
71

72
builder pack_complaint_status(cell complaint, cell voters, int vset_id, int weight_remaining) inline_ref {
73
  return begin_cell()
74
    .store_uint(0x2d, 8)
75
    .store_ref(complaint)
76
    .store_dict(voters)
77
    .store_uint(vset_id, 256)
78
    .store_int(weight_remaining, 64);
79
}
80

81
;; validator_complaint#bc validator_pubkey:uint256 description:^ComplaintDescr
82
;;   created_at:uint32 severity:uint8 reward_addr:uint256 paid:Grams suggested_fine:Grams
83
;;   suggested_fine_part:uint32 = ValidatorComplaint;
84
_ unpack_complaint(slice cs) inline_ref {
85
  throw_unless(9, cs~load_int(8) == 0xbc - 0x100);
86
  var res = (cs~load_uint(256), cs~load_ref(), cs~load_uint(32), cs~load_uint(8), cs~load_uint(256), cs~load_grams(), cs~load_grams(), cs~load_uint(32));
87
  cs.end_parse();
88
  return res;
89
}
90

91
builder pack_complaint(int validator_pubkey, cell description, int created_at, int severity, int reward_addr, int paid, int suggested_fine, int suggested_fine_part) inline_ref {
92
  return begin_cell()
93
    .store_int(0xbc - 0x100, 8)
94
    .store_uint(validator_pubkey, 256)
95
    .store_ref(description)
96
    .store_uint(created_at, 32)
97
    .store_uint(severity, 8)
98
    .store_uint(reward_addr, 256)
99
    .store_grams(paid)
100
    .store_grams(suggested_fine)
101
    .store_uint(suggested_fine_part, 32);
102
}
103

104
;; complaint_prices#1a deposit:Grams bit_price:Grams cell_price:Grams = ComplaintPricing; 
105
(int, int, int) parse_complaint_prices(cell info) inline {
106
  var cs = info.begin_parse();
107
  throw_unless(9, cs~load_uint(8) == 0x1a);
108
  var res = (cs~load_grams(), cs~load_grams(), cs~load_grams());
109
  cs.end_parse();
110
  return res;
111
}
112

113
;; deposit bit_price cell_price
114
(int, int, int) get_complaint_prices() inline_ref {
115
  var info = config_param(13);
116
  return info.null?() ? (1 << 36, 1, 512) : info.parse_complaint_prices();
117
}
118

119
;; elected_for elections_begin_before elections_end_before stake_held_for
120
(int, int, int, int) get_validator_conf() {
121
  var cs = config_param(15).begin_parse();
122
  return (cs~load_int(32), cs~load_int(32), cs~load_int(32), cs.preload_int(32));
123
}
124

125
;; next three functions return information about current validator set (config param #34)
126
;; they are borrowed from config-code.fc
127
(cell, int, cell) get_current_vset() inline_ref {
128
  var vset = config_param(34);
129
  var cs = begin_parse(vset);
130
  ;; validators_ext#12 utime_since:uint32 utime_until:uint32 
131
  ;; total:(## 16) main:(## 16) { main <= total } { main >= 1 } 
132
  ;; total_weight:uint64
133
  throw_unless(40, cs~load_uint(8) == 0x12);
134
  cs~skip_bits(32 + 32 + 16 + 16);
135
  var (total_weight, dict) = (cs~load_uint(64), cs~load_dict());
136
  cs.end_parse();
137
  return (vset, total_weight, dict);
138
}
139

140
(slice, int) get_validator_descr(int idx) inline_ref {
141
  var (vset, total_weight, dict) = get_current_vset();
142
  var (value, _) = dict.udict_get?(16, idx);
143
  return (value, total_weight);
144
}
145

146
(int, int) unpack_validator_descr(slice cs) inline {
147
  ;; ed25519_pubkey#8e81278a pubkey:bits256 = SigPubKey;
148
  ;; validator#53 public_key:SigPubKey weight:uint64 = ValidatorDescr;
149
  ;; validator_addr#73 public_key:SigPubKey weight:uint64 adnl_addr:bits256 = ValidatorDescr;
150
  throw_unless(41, (cs~load_uint(8) & ~ 0x20) == 0x53);
151
  throw_unless(41, cs~load_uint(32) == 0x8e81278a);
152
  return (cs~load_uint(256), cs~load_uint(64));
153
}
154

155
() send_message_back(addr, ans_tag, query_id, body, grams, mode) impure inline_ref {
156
  ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000
157
  var msg = begin_cell()
158
    .store_uint(0x18, 6)
159
    .store_slice(addr)
160
    .store_grams(grams)
161
    .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
162
    .store_uint(ans_tag, 32)
163
    .store_uint(query_id, 64);
164
  if (body >= 0) {
165
    msg~store_uint(body, 32);
166
  }
167
  send_raw_message(msg.end_cell(), mode);
168
}
169

170
() return_stake(addr, query_id, reason) impure inline_ref {
171
  return send_message_back(addr, 0xee6f454c, query_id, reason, 0, 64);
172
}
173

174
() send_confirmation(addr, query_id, comment) impure inline_ref {
175
  return send_message_back(addr, 0xf374484c, query_id, comment, 1000000000, 2);
176
}
177

178
() send_validator_set_to_config(config_addr, vset, query_id) impure inline_ref {
179
  var msg = begin_cell()
180
    .store_uint(0xc4ff, 17)   ;; 0 11000100 0xff 
181
    .store_uint(config_addr, 256)
182
    .store_grams(1 << 30)     ;; ~1 gram of value to process and obtain answer
183
    .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
184
    .store_uint(0x4e565354, 32)
185
    .store_uint(query_id, 64)
186
    .store_ref(vset);
187
  send_raw_message(msg.end_cell(), 1);
188
}
189

190
;; credits 'amount' to 'addr' inside credit dictionary 'credits'
191
_ ~credit_to(credits, addr, amount) inline_ref {
192
  var (val, f) = credits.udict_get?(256, addr);
193
  if (f) {
194
    amount += val~load_grams();
195
  }
196
  credits~udict_set_builder(256, addr, begin_cell().store_grams(amount));
197
  return (credits, ());
198
}
199

200
() process_new_stake(s_addr, msg_value, cs, query_id) impure inline_ref {
201
  var (src_wc, src_addr) = parse_std_addr(s_addr);
202
  var ds = get_data().begin_parse();
203
  var elect = ds~load_dict();
204
  if (elect.null?() | (src_wc + 1)) {
205
    ;; no elections active, or source is not in masterchain
206
    ;; bounce message
207
    return return_stake(s_addr, query_id, 0);
208
  }
209
  ;; parse the remainder of new stake message
210
  var validator_pubkey = cs~load_uint(256);
211
  var stake_at = cs~load_uint(32);
212
  var max_factor = cs~load_uint(32);
213
  var adnl_addr = cs~load_uint(256);
214
  var signature = cs~load_ref().begin_parse().preload_bits(512);
215
  cs.end_parse();
216
  ifnot (check_data_signature(begin_cell()
217
      .store_uint(0x654c5074, 32)
218
      .store_uint(stake_at, 32)
219
      .store_uint(max_factor, 32)
220
      .store_uint(src_addr, 256)
221
      .store_uint(adnl_addr, 256)
222
    .end_cell().begin_parse(), signature, validator_pubkey)) {
223
    ;; incorrect signature, return stake
224
    return return_stake(s_addr, query_id, 1);
225
  }
226
  if (max_factor < 0x10000) {
227
    ;; factor must be >= 1. = 65536/65536
228
    return return_stake(s_addr, query_id, 6);
229
  }
230
  ;; parse current election data
231
  var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect();
232
  ;; elect_at~dump();
233
  msg_value -= 1000000000;   ;; deduct GR$1 for sending confirmation
234
  if ((msg_value << 12) < total_stake) {
235
    ;; stake smaller than 1/4096 of the total accumulated stakes, return
236
    return return_stake(s_addr, query_id, 2);
237
  }
238
  total_stake += msg_value;  ;; (provisionally) increase total stake
239
  if (stake_at != elect_at) {
240
    ;; stake for some other elections, return
241
    return return_stake(s_addr, query_id, 3);
242
  }
243
  if (finished) {
244
    ;; elections already finished, return stake
245
    return return_stake(s_addr, query_id, 0);
246
  }
247
  var (mem, found) = members.udict_get?(256, validator_pubkey);
248
  if (found) {
249
    ;; entry found, merge stakes
250
    msg_value += mem~load_grams();
251
    mem~load_uint(64);   ;; skip timestamp and max_factor
252
    found = (src_addr != mem~load_uint(256));
253
  }
254
  if (found) {
255
    ;; can make stakes for a public key from one address only
256
    return return_stake(s_addr, query_id, 4);
257
  }
258
  if (msg_value < min_stake) {
259
    ;; stake too small, return it
260
    return return_stake(s_addr, query_id, 5);
261
  }
262
  throw_unless(44, msg_value);
263
  accept_message();
264
  ;; store stake in the dictionary
265
  members~udict_set_builder(256, validator_pubkey, begin_cell()
266
    .store_grams(msg_value)
267
    .store_uint(now(), 32)
268
    .store_uint(max_factor, 32)
269
    .store_uint(src_addr, 256)
270
    .store_uint(adnl_addr, 256));
271
  ;; gather and save election data
272
  elect = pack_elect(elect_at, elect_close, min_stake, total_stake, members, false, false);
273
  set_data(begin_cell().store_dict(elect).store_slice(ds).end_cell());
274
  ;; return confirmation message
275
  if (query_id) {
276
    return send_confirmation(s_addr, query_id, 0);
277
  }
278
  return ();
279
}
280

281
(cell, int) unfreeze_without_bonuses(credits, freeze_dict, tot_stakes) inline_ref {
282
  var total = var recovered = 0;
283
  var pubkey = -1;
284
  do {
285
    (pubkey, var cs, var f) = freeze_dict.udict_get_next?(256, pubkey);
286
    if (f) {
287
      var (addr, weight, stake, banned) = (cs~load_uint(256), cs~load_uint(64), cs~load_grams(), cs~load_int(1));
288
      cs.end_parse();
289
      if (banned) {
290
        recovered += stake;
291
      } else {
292
        credits~credit_to(addr, stake);
293
      }
294
      total += stake;
295
    }
296
  } until (~ f);
297
  throw_unless(59, total == tot_stakes);
298
  return (credits, recovered);
299
}
300

301
(cell, int) unfreeze_with_bonuses(credits, freeze_dict, tot_stakes, tot_bonuses) inline_ref {
302
  var total = var recovered = var returned_bonuses = 0;
303
  var pubkey = -1;
304
  do {
305
    (pubkey, var cs, var f) = freeze_dict.udict_get_next?(256, pubkey);
306
    if (f) {
307
      var (addr, weight, stake, banned) = (cs~load_uint(256), cs~load_uint(64), cs~load_grams(), cs~load_int(1));
308
      cs.end_parse();
309
      if (banned) {
310
        recovered += stake;
311
      } else {
312
        var bonus = muldiv(tot_bonuses, stake, tot_stakes);
313
        returned_bonuses += bonus;
314
        credits~credit_to(addr, stake + bonus);
315
      }
316
      total += stake;
317
    }
318
  } until (~ f);
319
  throw_unless(59, (total == tot_stakes) & (returned_bonuses <= tot_bonuses));
320
  return (credits, recovered + tot_bonuses - returned_bonuses);
321
}
322

323
int stakes_sum(frozen_dict) inline_ref {
324
  var total = 0;
325
  var pubkey = -1;
326
  do {
327
    (pubkey, var cs, var f) = frozen_dict.udict_get_next?(256, pubkey);
328
    if (f) {
329
      cs~skip_bits(256 + 64);
330
      total += cs~load_grams();
331
    }
332
  } until (~ f);
333
  return total;
334
}
335

336
_ unfreeze_all(credits, past_elections, elect_id) inline_ref {
337
  var (fs, f) = past_elections~udict_delete_get?(32, elect_id);
338
  ifnot (f) {
339
    ;; no elections with this id
340
    return (credits, past_elections, 0);
341
  }
342
  var (unfreeze_at, stake_held, vset_hash, fdict, tot_stakes, bonuses, complaints) = fs.unpack_past_election();
343
  ;; tot_stakes = fdict.stakes_sum(); ;; TEMP BUGFIX
344
  var unused_prizes = (bonuses > 0) ?
345
    credits~unfreeze_with_bonuses(fdict, tot_stakes, bonuses) :
346
    credits~unfreeze_without_bonuses(fdict, tot_stakes);
347
  return (credits, past_elections, unused_prizes);
348
}
349

350
() config_set_confirmed(s_addr, cs, query_id, ok) impure inline_ref {
351
  var (src_wc, src_addr) = parse_std_addr(s_addr);
352
  var config_addr = config_param(0).begin_parse().preload_uint(256);
353
  var ds = get_data().begin_parse();
354
  var elect = ds~load_dict();
355
  if ((src_wc + 1) | (src_addr != config_addr) | elect.null?()) {
356
    ;; not from config smc, somebody's joke?
357
    ;; or no elections active (or just completed)
358
    return ();
359
  }
360
  var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect();
361
  if ((elect_at != query_id) | ~ finished) {
362
    ;; not these elections, or elections not finished yet
363
    return ();
364
  }
365
  accept_message();
366
  ifnot (ok) {
367
    ;; cancel elections, return stakes
368
    var (credits, past_elections, grams) = (ds~load_dict(), ds~load_dict(), ds~load_grams());
369
    (credits, past_elections, var unused_prizes) = unfreeze_all(credits, past_elections, elect_at);
370
    set_data(begin_cell()
371
      .store_int(false, 1)
372
      .store_dict(credits)
373
      .store_dict(past_elections)
374
      .store_grams(grams + unused_prizes)
375
      .store_slice(ds)
376
    .end_cell());
377
  }
378
  ;; ... do not remove elect until we see this set as the next elected validator set
379
}
380

381
() process_simple_transfer(s_addr, msg_value) impure inline_ref {
382
  var (elect, credits, past_elections, grams, active_id, active_hash) = load_data();
383
  (int src_wc, int src_addr) = parse_std_addr(s_addr);
384
  if (src_addr | (src_wc + 1) | (active_id == 0)) {
385
    ;; simple transfer to us (credit "nobody's" account)
386
    ;; (or no known active validator set)
387
    grams += msg_value;
388
    return store_data(elect, credits, past_elections, grams, active_id, active_hash);
389
  }
390
  ;; zero source address -1:00..00 (collecting validator fees)
391
  var (fs, f) = past_elections.udict_get?(32, active_id);
392
  ifnot (f) {
393
    ;; active validator set not found (?)
394
    grams += msg_value;
395
  } else {
396
    ;; credit active validator set bonuses
397
    var (unfreeze_at, stake_held, hash, dict, total_stake, bonuses, complaints) = fs.unpack_past_election();
398
    bonuses += msg_value;
399
    past_elections~udict_set_builder(32, active_id,
400
      pack_past_election(unfreeze_at, stake_held, hash, dict, total_stake, bonuses, complaints));
401
  }
402
  return store_data(elect, credits, past_elections, grams, active_id, active_hash);
403
}
404

405
() recover_stake(op, s_addr, cs, query_id) impure inline_ref {
406
  (int src_wc, int src_addr) = parse_std_addr(s_addr);
407
  if (src_wc + 1) {
408
    ;; not from masterchain, return error
409
    return send_message_back(s_addr, 0xfffffffe, query_id, op, 0, 64);
410
  }
411
  var ds = get_data().begin_parse();
412
  var (elect, credits) = (ds~load_dict(), ds~load_dict());
413
  var (cs, f) = credits~udict_delete_get?(256, src_addr);
414
  ifnot (f) {
415
    ;; no credit for sender, return error
416
    return send_message_back(s_addr, 0xfffffffe, query_id, op, 0, 64);
417
  }
418
  var amount = cs~load_grams();
419
  cs.end_parse();
420
  ;; save data
421
  set_data(begin_cell().store_dict(elect).store_dict(credits).store_slice(ds).end_cell());
422
  ;; send amount to sender in a new message
423
  send_raw_message(begin_cell()
424
    .store_uint(0x18, 6)
425
    .store_slice(s_addr)
426
    .store_grams(amount)
427
    .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
428
    .store_uint(0xf96f7324, 32)
429
    .store_uint(query_id, 64)
430
  .end_cell(), 64);
431
}
432

433
() after_code_upgrade(slice s_addr, slice cs, int query_id) impure method_id(1666) {
434
  var op = 0x4e436f64;
435
  return send_message_back(s_addr, 0xce436f64, query_id, op, 0, 64);
436
}
437

438
int upgrade_code(s_addr, cs, query_id) inline_ref {
439
  var c_addr = config_param(0);
440
  if (c_addr.null?()) {
441
    ;; no configuration smart contract known
442
    return false;
443
  }
444
  var config_addr = c_addr.begin_parse().preload_uint(256);
445
  var (src_wc, src_addr) = parse_std_addr(s_addr);
446
  if ((src_wc + 1) | (src_addr != config_addr)) {
447
    ;; not from configuration smart contract, return error
448
    return false;
449
  }
450
  accept_message();
451
  var code = cs~load_ref();
452
  set_code(code);
453
  ifnot(cs.slice_empty?()) {
454
    set_c3(code.begin_parse().bless());
455
    after_code_upgrade(s_addr, cs, query_id);
456
    throw(0);
457
  }
458
  return true;
459
}
460

461
int register_complaint(s_addr, complaint, msg_value) {
462
  var (src_wc, src_addr) = parse_std_addr(s_addr);
463
  if (src_wc + 1) { ;; not from masterchain, return error
464
    return -1;
465
  }
466
  if (complaint.slice_depth() >= 128) {
467
    return -3;  ;; invalid complaint
468
  }
469
  var (elect, credits, past_elections, grams, active_id, active_hash) = load_data();
470
  var election_id = complaint~load_uint(32);
471
  var (fs, f) = past_elections.udict_get?(32, election_id);
472
  ifnot (f) {  ;; election not found
473
    return -2;
474
  }
475
  var expire_in = fs.preload_uint(32) - now();
476
  if (expire_in <= 0) { ;; already expired
477
    return -4;
478
  }
479
  var (validator_pubkey, description, created_at, severity, reward_addr, paid, suggested_fine, suggested_fine_part) = unpack_complaint(complaint);
480
  reward_addr = src_addr;
481
  created_at = now();
482
  ;; compute complaint storage/creation price
483
  var (deposit, bit_price, cell_price) = get_complaint_prices();
484
  var (_, bits, refs) = slice_compute_data_size(complaint, 4096);
485
  var pps = (bits + 1024) * bit_price + (refs + 2) * cell_price;
486
  paid = pps * expire_in + deposit;
487
  if (msg_value < paid + (1 << 30)) { ;; not enough money
488
    return -5;
489
  }
490
  ;; re-pack modified complaint
491
  cell complaint = pack_complaint(validator_pubkey, description, created_at, severity, reward_addr, paid, suggested_fine, suggested_fine_part).end_cell();
492
  var (unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints) = unpack_past_election(fs);
493
  var (fs, f) = frozen_dict.udict_get?(256, validator_pubkey);
494
  ifnot (f) { ;; no such validator, cannot complain
495
    return -6;
496
  }
497
  fs~skip_bits(256 + 64);   ;; addr weight
498
  var validator_stake = fs~load_grams();
499
  int fine = suggested_fine + muldiv(validator_stake, suggested_fine_part, 1 << 32);
500
  if (fine > validator_stake) {  ;; validator's stake is less than suggested fine
501
    return -7;
502
  }
503
  if (fine <= paid) {  ;; fine is less than the money paid for creating complaint
504
    return -8;
505
  }
506
  ;; create complaint status
507
  var cstatus = pack_complaint_status(complaint, null(), 0, 0);
508
  ;; save complaint status into complaints
509
  var cpl_id = complaint.cell_hash();
510
  ifnot (complaints~udict_add_builder?(256, cpl_id, cstatus)) {
511
    return -9;   ;; complaint already exists
512
  }
513
  ;; pack past election info
514
  past_elections~udict_set_builder(32, election_id, pack_past_election(unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints));
515
  ;; pack persistent data
516
  ;; next line can be commented, but it saves a lot of stack manipulations
517
  var (elect, credits, _, grams, active_id, active_hash) = load_data();
518
  store_data(elect, credits, past_elections, grams, active_id, active_hash);
519
  return paid;
520
}
521

522
(cell, cell, int, int) punish(credits, frozen, complaint) inline_ref {
523
  var (validator_pubkey, description, created_at, severity, reward_addr, paid, suggested_fine, suggested_fine_part) = complaint.begin_parse().unpack_complaint();
524
  var (cs, f) = frozen.udict_get?(256, validator_pubkey);
525
  ifnot (f) {
526
    ;; no validator to punish
527
    return (credits, frozen, 0, 0);
528
  }
529
  var (addr, weight, stake, banned) = (cs~load_uint(256), cs~load_uint(64), cs~load_grams(), cs~load_int(1));
530
  cs.end_parse();
531
  int fine = min(stake, suggested_fine + muldiv(stake, suggested_fine_part, 1 << 32));
532
  stake -= fine;
533
  frozen~udict_set_builder(256, validator_pubkey, begin_cell()
534
    .store_uint(addr, 256)
535
    .store_uint(weight, 64)
536
    .store_grams(stake)
537
    .store_int(banned, 1));
538
  int reward = min(fine >> 3, paid * 8);
539
  credits~credit_to(reward_addr, reward);
540
  return (credits, frozen, fine - reward, fine);
541
}
542

543
(cell, cell, int) register_vote(complaints, chash, idx, weight) inline_ref {
544
  var (cstatus, found?) = complaints.udict_get?(256, chash);
545
  ifnot (found?) {
546
    ;; complaint not found
547
    return (complaints, null(), -1);
548
  }
549
  var (cur_vset, total_weight, _) = get_current_vset();
550
  int cur_vset_id = cur_vset.cell_hash();
551
  var (complaint, voters, vset_id, weight_remaining) = unpack_complaint_status(cstatus);
552
  int vset_old? = (vset_id != cur_vset_id);
553
  if ((weight_remaining < 0) & vset_old?) {
554
    ;; previous validator set already collected 2/3 votes, skip new votes
555
    return (complaints, null(), -3);
556
  }
557
  if (vset_old?) {
558
    ;; complaint votes belong to a previous validator set, reset voting
559
    vset_id = cur_vset_id;
560
    voters = null();
561
    weight_remaining = muldiv(total_weight, 2, 3);
562
  }
563
  var (_, found?) = voters.udict_get?(16, idx);
564
  if (found?) {
565
    ;; already voted for this proposal, ignore vote
566
    return (complaints, null(), 0);
567
  }
568
  ;; register vote
569
  voters~udict_set_builder(16, idx, begin_cell().store_uint(now(), 32));
570
  int old_wr = weight_remaining;
571
  weight_remaining -= weight;
572
  old_wr ^= weight_remaining;
573
  ;; save voters and weight_remaining
574
  complaints~udict_set_builder(256, chash, pack_complaint_status(complaint, voters, vset_id, weight_remaining));
575
  if (old_wr >= 0) {
576
    ;; not enough votes or already accepted
577
    return (complaints, null(), 1);
578
  }
579
  ;; complaint wins, prepare punishment
580
  return (complaints, complaint, 2);
581
}
582

583
int proceed_register_vote(election_id, chash, idx, weight) impure inline_ref {
584
  var (elect, credits, past_elections, grams, active_id, active_hash) = load_data();
585
  var (fs, f) = past_elections.udict_get?(32, election_id);
586
  ifnot (f) {  ;; election not found
587
    return -2;
588
  }
589
  var (unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints) = unpack_past_election(fs);
590
  (complaints, var accepted_complaint, var status) = register_vote(complaints, chash, idx, weight);
591
  if (status <= 0) {
592
    return status;
593
  }
594
  ifnot (accepted_complaint.null?()) {
595
    (credits, frozen_dict, int fine_unalloc, int fine_collected) = punish(credits, frozen_dict, accepted_complaint);
596
    grams += fine_unalloc;
597
    total_stake -= fine_collected;
598
  }
599
  past_elections~udict_set_builder(32, election_id, pack_past_election(unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints));
600
  store_data(elect, credits, past_elections, grams, active_id, active_hash);
601
  return status;
602
}
603

604
() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure {
605
  ;; do nothing for internal messages
606
  var cs = in_msg_cell.begin_parse();
607
  var flags = cs~load_uint(4);  ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool
608
  if (flags & 1) {
609
    ;; ignore all bounced messages
610
    return ();
611
  }
612
  var s_addr = cs~load_msg_addr();
613
  if (in_msg.slice_empty?()) {
614
    ;; inbound message has empty body
615
    return process_simple_transfer(s_addr, msg_value);
616
  }
617
  int op = in_msg~load_uint(32);
618
  if (op == 0) {
619
    ;; simple transfer with comment, return
620
    return process_simple_transfer(s_addr, msg_value);
621
  }
622
  int query_id = in_msg~load_uint(64);
623
  if (op == 0x4e73744b) {
624
    ;; new stake message
625
    return process_new_stake(s_addr, msg_value, in_msg, query_id);
626
  }
627
  if (op == 0x47657424) {
628
    ;; recover stake request
629
    return recover_stake(op, s_addr, in_msg, query_id);
630
  }
631
  if (op == 0x4e436f64) {
632
    ;; upgrade code (accepted only from configuration smart contract)
633
    var ok = upgrade_code(s_addr, in_msg, query_id);
634
    return send_message_back(s_addr, ok ? 0xce436f64 : 0xffffffff, query_id, op, 0, 64);
635
  }
636
  var cfg_ok = (op == 0xee764f4b);
637
  if (cfg_ok | (op == 0xee764f6f)) {
638
    ;; confirmation from configuration smart contract
639
    return config_set_confirmed(s_addr, in_msg, query_id, cfg_ok);
640
  }
641
  if (op == 0x52674370) {
642
    ;; new complaint 
643
    var price = register_complaint(s_addr, in_msg, msg_value);
644
    int mode = 64;
645
    int ans_tag = - price;
646
    if (price >= 0) {
647
      ;; ok, debit price
648
      raw_reserve(price, 4);
649
      ans_tag = 0;
650
      mode = 128;
651
    }
652
    return send_message_back(s_addr, ans_tag + 0xf2676350, query_id, op, 0, mode);
653
  }
654
  if (op == 0x56744370) {
655
    ;; vote for a complaint
656
    var signature = in_msg~load_bits(512);
657
    var msg_body = in_msg;
658
    var (sign_tag, idx, elect_id, chash) = (in_msg~load_uint(32), in_msg~load_uint(16), in_msg~load_uint(32), in_msg~load_uint(256));
659
    in_msg.end_parse();
660
    throw_unless(37, sign_tag == 0x56744350);
661
    var (vdescr, total_weight) = get_validator_descr(idx);
662
    var (val_pubkey, weight) = unpack_validator_descr(vdescr);
663
    throw_unless(34, check_data_signature(msg_body, signature, val_pubkey));
664
    int res = proceed_register_vote(elect_id, chash, idx, weight);
665
    return send_message_back(s_addr, res + 0xd6745240, query_id, op, 0, 64);
666
  }
667

668
  ifnot (op & (1 << 31)) {
669
    ;; unknown query, return error
670
    return send_message_back(s_addr, 0xffffffff, query_id, op, 0, 64);
671
  }
672
  ;; unknown answer, ignore
673
  return ();
674
}
675

676
int postpone_elections() impure {
677
  return false;
678
}
679

680
;; computes the total stake out of the first n entries of list l
681
_ compute_total_stake(l, n, m_stake) inline_ref {
682
  int tot_stake = 0;
683
  repeat (n) {
684
    (var h, l) = uncons(l);
685
    var stake = h.at(0);
686
    var max_f = h.at(1);
687
    stake = min(stake, (max_f * m_stake) >> 16);
688
    tot_stake += stake;
689
  }
690
  return tot_stake;
691
}
692

693
(cell, cell, int, cell, int, int) try_elect(credits, members, min_stake, max_stake, min_total_stake, max_stake_factor) {
694
  var cs = 16.config_param().begin_parse();
695
  var (max_validators, _, min_validators) = (cs~load_uint(16), cs~load_uint(16), cs~load_uint(16));
696
  cs.end_parse();
697
  min_validators = max(min_validators, 1);
698
  int n = 0;
699
  var sdict = new_dict();
700
  var pubkey = -1;
701
  do {
702
    (pubkey, var cs, var f) = members.udict_get_next?(256, pubkey);
703
    if (f) {
704
      var (stake, time, max_factor, addr, adnl_addr) = (cs~load_grams(), cs~load_uint(32), cs~load_uint(32), cs~load_uint(256), cs~load_uint(256));
705
      cs.end_parse();
706
      var key = begin_cell()
707
        .store_uint(stake, 128)
708
        .store_int(- time, 32)
709
        .store_uint(pubkey, 256)
710
      .end_cell().begin_parse();
711
      sdict~dict_set_builder(128 + 32 + 256, key, begin_cell()
712
          .store_uint(min(max_factor, max_stake_factor), 32)
713
          .store_uint(addr, 256)
714
          .store_uint(adnl_addr, 256));
715
      n += 1;
716
    }
717
  } until (~ f);
718
  n = min(n, max_validators);
719
  if (n < min_validators) {
720
    return (credits, new_dict(), 0, new_dict(), 0, 0);
721
  }
722
  var l = nil;
723
  do {
724
    var (key, cs, f) = sdict~dict::delete_get_min(128 + 32 + 256);
725
    if (f) {
726
      var (stake, _, pubkey) = (min(key~load_uint(128), max_stake), key~load_uint(32), key.preload_uint(256));
727
      var (max_f, _, adnl_addr) = (cs~load_uint(32), cs~load_uint(256), cs.preload_uint(256));
728
      l = cons([stake, max_f, pubkey, adnl_addr], l);
729
    }
730
  } until (~ f);
731
  ;; l is the list of all stakes in decreasing order
732
  int i = min_validators - 1;
733
  var l1 = l;
734
  repeat (i) {
735
    l1 = cdr(l1);
736
  }
737
  var (best_stake, m) = (0, 0);
738
  do {
739
    var stake = l1~list_next().at(0);
740
    i += 1;
741
    if (stake >= min_stake) {
742
      var tot_stake = compute_total_stake(l, i, stake);
743
      if (tot_stake > best_stake) {
744
        (best_stake, m) = (tot_stake, i);
745
      }
746
    }
747
  } until (i >= n);
748
  if ((m == 0) | (best_stake < min_total_stake)) {
749
    return (credits, new_dict(), 0, new_dict(), 0, 0);
750
  }
751
  ;; we have to select first m validators from list l
752
  l1 = touch(l);
753
  ;; l1~dump();  ;; DEBUG
754
  repeat (m - 1) {
755
    l1 = cdr(l1);
756
  }
757
  var m_stake = car(l1).at(0);  ;; minimal stake
758
  ;; create both the new validator set and the refund set
759
  int i = 0;
760
  var tot_stake = 0;
761
  var tot_weight = 0;
762
  var vset = new_dict();
763
  var frozen = new_dict();
764
  do {
765
    var [stake, max_f, pubkey, adnl_addr] = l~list_next();
766
    ;; lookup source address first
767
    var (val, f) = members.udict_get?(256, pubkey);
768
    throw_unless(61, f);
769
    (_, _, var src_addr) = (val~load_grams(), val~load_uint(64), val.preload_uint(256));
770
    if (i < m) {
771
      ;; one of the first m members, include into validator set
772
      var true_stake = min(stake, (max_f * m_stake) >> 16);
773
      stake -= true_stake;
774
      ;; ed25519_pubkey#8e81278a pubkey:bits256 = SigPubKey;  // 288 bits
775
      ;; validator_addr#73 public_key:SigPubKey weight:uint64 adnl_addr:bits256 = ValidatorDescr;
776
      var weight = (true_stake << 60) / best_stake;
777
      tot_stake += true_stake;
778
      tot_weight += weight;
779
      var vinfo = begin_cell()
780
        .store_uint(adnl_addr ? 0x73 : 0x53, 8)  ;; validator_addr#73 or validator#53
781
        .store_uint(0x8e81278a, 32)    ;; ed25519_pubkey#8e81278a
782
        .store_uint(pubkey, 256)       ;; pubkey:bits256
783
        .store_uint(weight, 64);       ;; weight:uint64
784
      if (adnl_addr) {
785
        vinfo~store_uint(adnl_addr, 256);  ;; adnl_addr:bits256
786
      }
787
      vset~udict_set_builder(16, i, vinfo);
788
      frozen~udict_set_builder(256, pubkey, begin_cell()
789
        .store_uint(src_addr, 256)
790
        .store_uint(weight, 64)
791
        .store_grams(true_stake)
792
        .store_int(false, 1));
793
    }
794
    if (stake) {
795
      ;; non-zero unused part of the stake, credit to the source address
796
      credits~credit_to(src_addr, stake);
797
    }
798
    i += 1;
799
  } until (l.null?());
800
  throw_unless(49, tot_stake == best_stake);
801
  return (credits, vset, tot_weight, frozen, tot_stake, m); 
802
}
803

804
int conduct_elections(ds, elect, credits) impure {
805
  var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect();
806
  if (now() < elect_close) {
807
    ;; elections not finished yet
808
    return false;
809
  }
810
  if (config_param(0).null?()) {
811
    ;; no configuration smart contract to send result to
812
    return postpone_elections();
813
  }
814
  var cs = config_param(17).begin_parse();
815
  min_stake = cs~load_grams();
816
  var max_stake = cs~load_grams();
817
  var min_total_stake = cs~load_grams();
818
  var max_stake_factor = cs~load_uint(32);
819
  cs.end_parse();
820
  if (total_stake < min_total_stake) {
821
    ;; insufficient total stake, postpone elections
822
    return postpone_elections();
823
  }
824
  if (failed) {
825
    ;; do not retry failed elections until new stakes arrive
826
    return postpone_elections();
827
  }
828
  if (finished) {
829
    ;; elections finished
830
    return false;
831
  }
832
  (credits, var vdict, var total_weight, var frozen, var total_stakes, var cnt) = try_elect(credits, members, min_stake, max_stake, min_total_stake, max_stake_factor);
833
  ;; pack elections; if cnt==0, set failed=true, finished=false.
834
  failed = (cnt == 0);
835
  finished = ~ failed;
836
  elect = pack_elect(elect_at, elect_close, min_stake, total_stake, members, failed, finished);
837
  ifnot (cnt) {
838
    ;; elections failed, set elect_failed to true
839
    set_data(begin_cell().store_dict(elect).store_dict(credits).store_slice(ds).end_cell());
840
    return postpone_elections();
841
  }
842
  ;; serialize a query to the configuration smart contract
843
  ;; to install the computed validator set as the next validator set
844
  var (elect_for, elect_begin_before, elect_end_before, stake_held) = get_validator_conf();
845
  var start = max(now() + elect_end_before - 60, elect_at);
846
  var main_validators = config_param(16).begin_parse().skip_bits(16).preload_uint(16);
847
  var vset = begin_cell()
848
    .store_uint(0x12, 8)      ;; validators_ext#12
849
    .store_uint(start, 32)    ;; utime_since:uint32
850
    .store_uint(start + elect_for, 32)  ;; utime_until:uint32
851
    .store_uint(cnt, 16)      ;; total:(## 16) 
852
    .store_uint(min(cnt, main_validators), 16)  ;; main:(## 16)
853
    .store_uint(total_weight, 64)      ;; total_weight:uint64
854
    .store_dict(vdict)        ;; list:(HashmapE 16 ValidatorDescr)
855
  .end_cell();
856
  var config_addr = config_param(0).begin_parse().preload_uint(256);
857
  send_validator_set_to_config(config_addr, vset, elect_at);
858
  ;; add frozen to the dictionary of past elections
859
  var past_elections = ds~load_dict();
860
  past_elections~udict_set_builder(32, elect_at, pack_past_election(
861
    start + elect_for + stake_held, stake_held, vset.cell_hash(),
862
    frozen, total_stakes, 0, null()));
863
  ;; store credits and frozen until end
864
  set_data(begin_cell()
865
    .store_dict(elect)
866
    .store_dict(credits)
867
    .store_dict(past_elections)
868
    .store_slice(ds)
869
  .end_cell());
870
  return true;
871
}
872

873
int update_active_vset_id() impure {
874
  var (elect, credits, past_elections, grams, active_id, active_hash) = load_data();
875
  var cur_hash = config_param(34).cell_hash();
876
  if (cur_hash == active_hash) {
877
    ;; validator set unchanged
878
    return false;
879
  }
880
  if (active_id) {
881
    ;; active_id becomes inactive
882
    var (fs, f) = past_elections.udict_get?(32, active_id);
883
    if (f) {
884
      ;; adjust unfreeze time of this validator set
885
      var unfreeze_time = fs~load_uint(32);
886
      var fs0 = fs;
887
      var (stake_held, hash) = (fs~load_uint(32), fs~load_uint(256));
888
      throw_unless(57, hash == active_hash);
889
      unfreeze_time = now() + stake_held;
890
      past_elections~udict_set_builder(32, active_id, begin_cell()
891
        .store_uint(unfreeze_time, 32)
892
        .store_slice(fs0));
893
    }
894
  }
895
  ;; look up new active_id by hash
896
  var id = -1;
897
  do {
898
    (id, var fs, var f) = past_elections.udict_get_next?(32, id);
899
    if (f) {
900
      var (tm, hash) = (fs~load_uint(64), fs~load_uint(256));
901
      if (hash == cur_hash) {
902
        ;; parse more of this record
903
        var (dict, total_stake, bonuses) = (fs~load_dict(), fs~load_grams(), fs~load_grams());
904
        ;; transfer 1/8 of accumulated everybody's grams to this validator set as bonuses
905
        var amount = (grams >> 3);
906
        grams -= amount;
907
        bonuses += amount;
908
        ;; serialize back
909
        past_elections~udict_set_builder(32, id, begin_cell()
910
          .store_uint(tm, 64)
911
          .store_uint(hash, 256)
912
          .store_dict(dict)
913
          .store_grams(total_stake)
914
          .store_grams(bonuses)
915
          .store_slice(fs));
916
        ;; found
917
        f = false;
918
      }
919
    }
920
  } until (~ f);
921
  active_id = (id.null?() ? 0 : id);
922
  active_hash = cur_hash;
923
  store_data(elect, credits, past_elections, grams, active_id, active_hash);
924
  return true;
925
}
926

927
int cell_hash_eq?(cell vset, int expected_vset_hash) inline_ref {
928
  return vset.null?() ? false : cell_hash(vset) == expected_vset_hash;
929
}
930

931
int validator_set_installed(ds, elect, credits) impure {
932
  var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect();
933
  ifnot (finished) {
934
    ;; elections not finished yet
935
    return false;
936
  }
937
  var past_elections = ds~load_dict();
938
  var (fs, f) = past_elections.udict_get?(32, elect_at);
939
  ifnot (f) {
940
    ;; no election data in dictionary
941
    return false;
942
  }
943
  ;; recover validator set hash
944
  var vset_hash = fs.skip_bits(64).preload_uint(256);
945
  if (config_param(34).cell_hash_eq?(vset_hash) | config_param(36).cell_hash_eq?(vset_hash)) {
946
    ;; this validator set has been installed, forget elections
947
    set_data(begin_cell()
948
      .store_int(false, 1)   ;; forget current elections
949
      .store_dict(credits)
950
      .store_dict(past_elections)
951
      .store_slice(ds)
952
    .end_cell());
953
    update_active_vset_id();
954
    return true;
955
  }
956
  return false;
957
}
958

959
int check_unfreeze() impure {
960
  var (elect, credits, past_elections, grams, active_id, active_hash) = load_data();
961
  int id = -1;
962
  do {
963
    (id, var fs, var f) = past_elections.udict_get_next?(32, id);
964
    if (f) {
965
      var unfreeze_at = fs~load_uint(32);
966
      if ((unfreeze_at <= now()) & (id != active_id)) {
967
        ;; unfreeze!
968
        (credits, past_elections, var unused_prizes) = unfreeze_all(credits, past_elections, id);
969
        grams += unused_prizes;
970
        ;; unfreeze only one at time, exit loop
971
        store_data(elect, credits, past_elections, grams, active_id, active_hash);
972
        ;; exit loop
973
        f = false;
974
      }
975
    }
976
  } until (~ f);
977
  return ~ id.null?();
978
}
979

980
int announce_new_elections(ds, elect, credits) {
981
  var next_vset = config_param(36);   ;; next validator set
982
  ifnot (next_vset.null?()) {
983
    ;; next validator set exists, no elections needed
984
    return false;
985
  }
986
  var elector_addr = config_param(1).begin_parse().preload_uint(256);
987
  var (my_wc, my_addr) = my_address().parse_std_addr();
988
  if ((my_wc + 1) | (my_addr != elector_addr)) {
989
    ;; this smart contract is not the elections smart contract anymore, no new elections
990
    return false;
991
  }
992
  var cur_vset = config_param(34);  ;; current validator set
993
  if (cur_vset.null?()) {
994
    return false;
995
  }
996
  var (elect_for, elect_begin_before, elect_end_before, stake_held) = get_validator_conf();
997
  var cur_valid_until = cur_vset.begin_parse().skip_bits(8 + 32).preload_uint(32);
998
  var t = now();
999
  var t0 = cur_valid_until - elect_begin_before;
1000
  if (t < t0) {
1001
    ;; too early for the next elections
1002
    return false;
1003
  }
1004
  ;; less than elect_before_begin seconds left, create new elections
1005
  if (t - t0 < 60) {
1006
    ;; pretend that the elections started at t0
1007
    t = t0;
1008
  }
1009
  ;; get stake parameters
1010
  (_, var min_stake) = config_param(17).begin_parse().load_grams();
1011
  ;; announce new elections
1012
  var elect_at = t + elect_begin_before;
1013
  ;; elect_at~dump();
1014
  var elect_close = elect_at - elect_end_before;
1015
  elect = pack_elect(elect_at, elect_close, min_stake, 0, new_dict(), false, false);
1016
  set_data(begin_cell().store_dict(elect).store_dict(credits).store_slice(ds).end_cell());
1017
  return true;
1018
}
1019

1020
() run_ticktock(int is_tock) impure {
1021
  ;; check whether an election is being conducted
1022
  var ds = get_data().begin_parse();
1023
  var (elect, credits) = (ds~load_dict(), ds~load_dict());
1024
  ifnot (elect.null?()) {
1025
    ;; have an active election
1026
    throw_if(0, conduct_elections(ds, elect, credits));  ;; elections conducted, exit
1027
    throw_if(0, validator_set_installed(ds, elect, credits));  ;; validator set installed, current elections removed
1028
  } else {
1029
    throw_if(0, announce_new_elections(ds, elect, credits));  ;; new elections announced, exit
1030
  }
1031
  throw_if(0, update_active_vset_id());  ;; active validator set id updated, exit
1032
  check_unfreeze();
1033
}
1034

1035
;; Get methods
1036

1037
;; returns active election id or 0
1038
int active_election_id() method_id {
1039
  var elect = get_data().begin_parse().preload_dict();
1040
  return elect.null?() ? 0 : elect.begin_parse().preload_uint(32);
1041
}
1042

1043
;; checks whether a public key participates in current elections
1044
int participates_in(int validator_pubkey) method_id {
1045
  var elect = get_data().begin_parse().preload_dict();
1046
  if (elect.null?()) {
1047
    return 0;
1048
  }
1049
  var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect();
1050
  var (mem, found) = members.udict_get?(256, validator_pubkey);
1051
  return found ? mem~load_grams() : 0;
1052
}
1053

1054
;; returns the list of all participants of current elections with their stakes
1055
_ participant_list() method_id {
1056
  var elect = get_data().begin_parse().preload_dict();
1057
  if (elect.null?()) {
1058
    return nil;
1059
  }
1060
  var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect();
1061
  var l = nil;
1062
  var id = (1 << 255) + ((1 << 255) - 1);
1063
  do {
1064
    (id, var fs, var f) = members.udict_get_prev?(256, id);
1065
    if (f) {
1066
      l = cons([id, fs~load_grams()], l);
1067
    }
1068
  } until (~ f);
1069
  return l;
1070
}
1071

1072
;; returns the list of all participants of current elections with their data
1073
_ participant_list_extended() method_id {
1074
  var elect = get_data().begin_parse().preload_dict();
1075
  if (elect.null?()) {
1076
    return (0, 0, 0, 0, nil, 0, 0);
1077
  }
1078
  var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect();
1079
  var l = nil;
1080
  var id = (1 << 255) + ((1 << 255) - 1);
1081
  do {
1082
    (id, var cs, var f) = members.udict_get_prev?(256, id);
1083
    if (f) {
1084
      var (stake, time, max_factor, addr, adnl_addr) = (cs~load_grams(), cs~load_uint(32), cs~load_uint(32), cs~load_uint(256), cs~load_uint(256));
1085
      cs.end_parse();
1086
      l = cons([id, [stake, max_factor, addr, adnl_addr]], l);
1087
    }
1088
  } until (~ f);
1089
  return (elect_at, elect_close, min_stake, total_stake, l, failed, finished);
1090
}
1091

1092
;; computes the return stake
1093
int compute_returned_stake(int wallet_addr) method_id {
1094
  var cs = get_data().begin_parse();
1095
  (_, var credits) = (cs~load_dict(), cs~load_dict());
1096
  var (val, f) = credits.udict_get?(256, wallet_addr);
1097
  return f ? val~load_grams() : 0;
1098
}
1099

1100
;; returns the list of past election ids
1101
tuple past_election_ids() method_id {
1102
  var (elect, credits, past_elections, grams, active_id, active_hash) = load_data();
1103
  var id = (1 << 32);
1104
  var list = null();
1105
  do {
1106
    (id, var fs, var f) = past_elections.udict_get_prev?(32, id);
1107
    if (f) {
1108
      list = cons(id, list);
1109
    }
1110
  } until (~ f);
1111
  return list;
1112
}
1113

1114
tuple past_elections() method_id {
1115
  var (elect, credits, past_elections, grams, active_id, active_hash) = load_data();
1116
  var id = (1 << 32);
1117
  var list = null();
1118
  do {
1119
    (id, var fs, var found) = past_elections.udict_get_prev?(32, id);
1120
    if (found) {
1121
      list = cons([id, unpack_past_election(fs)], list);
1122
    }
1123
  } until (~ found);
1124
  return list;
1125
}
1126

1127
tuple past_elections_list() method_id {
1128
  var (elect, credits, past_elections, grams, active_id, active_hash) = load_data();
1129
  var id = (1 << 32);
1130
  var list = null();
1131
  do {
1132
    (id, var fs, var found) = past_elections.udict_get_prev?(32, id);
1133
    if (found) {
1134
      var (unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints) = unpack_past_election(fs);
1135
      list = cons([id, unfreeze_at, vset_hash, stake_held], list);
1136
    }
1137
  } until (~ found);
1138
  return list;
1139
}
1140

1141
_ complete_unpack_complaint(slice cs) inline_ref {
1142
  var (complaint, voters, vset_id, weight_remaining) = cs.unpack_complaint_status();
1143
  var voters_list = null();
1144
  var voter_id = (1 << 32);
1145
  do {
1146
    (voter_id, _, var f) = voters.udict_get_prev?(16, voter_id);
1147
    if (f) {
1148
      voters_list = cons(voter_id, voters_list);
1149
    }
1150
  } until (~ f);
1151
  return [[complaint.begin_parse().unpack_complaint()], voters_list, vset_id, weight_remaining];
1152
}
1153

1154
cell get_past_complaints(int election_id) inline_ref method_id {
1155
  var (elect, credits, past_elections, grams, active_id, active_hash) = load_data();
1156
  var (fs, found?) = past_elections.udict_get?(32, election_id);
1157
  ifnot (found?) {
1158
    return null();
1159
  }
1160
  var (unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints) = unpack_past_election(fs);
1161
  return complaints;
1162
}
1163

1164
_ show_complaint(int election_id, int chash) method_id {
1165
  var complaints = get_past_complaints(election_id);
1166
  var (cs, found) = complaints.udict_get?(256, chash);
1167
  return found ? complete_unpack_complaint(cs) : null();
1168
}
1169

1170
tuple list_complaints(int election_id) method_id {
1171
  var complaints = get_past_complaints(election_id);
1172
  int id = (1 << 255) + ((1 << 255) - 1);
1173
  var list = null();
1174
  do {
1175
    (id, var cs, var found?) = complaints.udict_get_prev?(256, id);
1176
    if (found?) {
1177
      list = cons(pair(id, complete_unpack_complaint(cs)), list);
1178
    }
1179
  } until (~ found?);
1180
  return list;
1181
}
1182

1183
int complaint_storage_price(int bits, int refs, int expire_in) method_id {
1184
  ;; compute complaint storage/creation price
1185
  var (deposit, bit_price, cell_price) = get_complaint_prices();
1186
  var pps = (bits + 1024) * bit_price + (refs + 2) * cell_price;
1187
  var paid = pps * expire_in + deposit;
1188
  return paid + (1 << 30);
1189
}
1190

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

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

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

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