-
Notifications
You must be signed in to change notification settings - Fork 4
/
config-code.fc
813 lines (778 loc) · 32.9 KB
/
config-code.fc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
;; Simple configuration smart contract
#include "constants.fc";
() set_conf_param(int index, cell value) impure {
slice cs = get_data().begin_parse();
cell cfg_dict = cs~load_ref();
cfg_dict~idict_set_ref(32, index, value);
set_data(begin_cell().store_ref(cfg_dict).store_slice(cs).end_cell());
}
(cell, int, int, cell) load_data() inline_ref {
slice cs = get_data().begin_parse();
(cell, int, int, cell) res = (cs~load_ref(), cs~load_uint(32), cs~load_uint(256), cs~load_dict());
cs.end_parse();
return res;
}
() store_data(cell cfg_dict,
int stored_seqno,
int public_key,
cell vote_dict) impure inline_ref {
set_data(begin_cell()
.store_ref(cfg_dict)
.store_uint(stored_seqno, 32)
.store_uint(public_key, 256)
.store_dict(vote_dict)
.end_cell());
}
;; (min_tot_rounds, max_tot_rounds, min_wins, max_losses, min_store_sec, max_store_sec, bit_price, cell_price)
(int, int, int, int, int, int, int, int) parse_vote_config(cell c) inline {
slice cs = c.begin_parse();
;; cfg_vote_cfg#36 min_tot_rounds:uint8 max_tot_rounds:uint8
;; min_wins:uint8 max_losses:uint8
;; min_store_sec:uint32 max_store_sec:uint32
;; bit_price:uint32 cell_price:uint32 = ConfigProposalSetup;
throw_unless(44, cs~load_uint(8) == 0x36);
(int, int, int, int, int, int, int, int) res = (cs~load_uint(8),
cs~load_uint(8),
cs~load_uint(8),
cs~load_uint(8),
cs~load_uint(32),
cs~load_uint(32),
cs~load_uint(32),
cs~load_uint(32));
cs.end_parse();
return res;
}
;; (min_tot_rounds, max_tot_rounds, min_wins, max_losses, min_store_sec, max_store_sec, bit_price, cell_price)
(int, int, int, int, int, int, int, int) get_vote_config_internal(int critical?, cell cparam11) inline_ref {
slice cs = cparam11.begin_parse();
;; cfg_vote_setup#91 normal_params:^ConfigProposalSetup critical_params:^ConfigProposalSetup = ConfigVotingSetup;
throw_unless(44, cs~load_uint(8) == 0x91);
if (critical?) {
cs~load_ref();
}
return parse_vote_config(cs.preload_ref());
}
;; (min_tot_rounds, max_tot_rounds, min_wins, max_losses, min_store_sec, max_store_sec, bit_price, cell_price)
(int, int, int, int, int, int, int, int) get_vote_config(int critical?) inline_ref {
return get_vote_config_internal(critical?, config_param(config_params::voting_setup));
}
(int, int) check_validator_set(cell vset) inline {
slice cs = vset.begin_parse();
throw_unless(9, cs~load_uint(8) == 0x12); ;; validators_ext#12 only
int utime_since = cs~load_uint(32);
int utime_until = cs~load_uint(32);
int total = cs~load_uint(16);
int main = cs~load_uint(16);
throw_unless(9, main > 0);
throw_unless(9, total >= main);
return (utime_since, utime_until);
}
() send_answer(addr, query_id, ans_tag, mode) impure inline_ref {
;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000
send_raw_message(begin_cell() ;; see "Message X" description in crypto/block/block.tlb
;; or https://ton.org/docs/#/smart-contracts/messages?id=sending-messages
.store_uint(0x18, 6) ;; 0x18 = 0b011000 = {0, 1, 1 , 0, 00}
;; First 0 means int_msg_info$0 tag
;; 1 1 0 are flags (ihr_disabled, bounce, bounced)
;; 00 is a source address addr_none$00 tag,
;; correct value added automatically
.store_slice(addr) ;; destination address
.store_uint(0, 4 + 1 + 4 + 4 + 64 + 32 + 1 + 1)
;; first 4 zero bits mean zero nanotons
;; note that mode 64 will add to this sum amount in incoming message minus gas fees
;; and mode 128 will add all non-reserved amount on account balance
;; 1 zero bit means there is no other:ExtraCurrencyCollection
;; 4 + 4 zero bits for empty ihr_fee:Grams and fwd_fee:Grams,
;; correct values added automatically
;; 64 + 32 zero bits for created_lt:uint64 and created_at:uint32,
;; correct values added automatically, see "CommonMsgInfo" description
;; 1 zero bit means there is no StateInit structure
;; 1 zero bit means the message body is represented
;; in this cell, not in reference
;; The following bits are the message body
.store_uint(ans_tag, 32)
.store_uint(query_id, 64)
.end_cell(), mode);
}
;; forward a message to elector smart contract to make it upgrade its code
() change_elector_code(slice cs) impure inline_ref {
int dest_addr = config_param(config_params::elector_address).begin_parse().preload_uint(256);
int query_id = now();
send_raw_message(begin_cell() ;; see "Message X" description in crypto/block/block.tlb
;; or https://ton.org/docs/#/smart-contracts/messages?id=sending-messages
.store_uint(0xc4ff, 17) ;; 0xc4ff = 0b01100010011111111 = {0, 1, 1, 0, 00, 10, 0, 11111111}
;; First 0 means int_msg_info$0 tag
;; 110 are flags (ihr_disabled, bounce, bounced)
;; 00 is a source address addr_none$00 tag,
;; correct value added automatically
;; 10 means addr_std$10 tag, 0 means no anycast
;; 0xFF means workchain_id (masterchain) dest_addr
.store_uint(dest_addr, 256) ;; destination address hash_part, see "MsgAddressInt" description
.store_grams(ONECOIN) ;; 1 TONCOIN of value to process and obtain answer
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) ;; 1 zero bit means there is no other:ExtraCurrencyCollection
;; 4 + 4 zero bits for empty ihr_fee:Grams and fwd_fee:Grams,
;; correct values added automatically
;; 64 + 32 zero bits for created_lt:uint64 and created_at:uint32,
;; correct values added automatically, see "CommonMsgInfo" description
;; 1 zero bit means there is no StateInit structure
;; 1 zero bit means the message body is represented
;; in this cell, not in reference
;; The following bits are the message body
.store_uint(op::set_new_code, 32)
.store_uint(query_id, 64)
.store_slice(cs)
.end_cell(), 0);
}
() after_code_upgrade(slice param, cont old_code) impure method_id(1666) {
}
(cell, int) perform_action(cell cfg_dict, int public_key, int action, slice cs) inline_ref {
if (action == op::update_config_parameter) {
;; change one configuration parameter
int param_index = cs~load_int(32);
;; If there is ref - set it as parameter
;; if there is no ref - remove parameter
;; It would be better to save ref as (Maybe ^Cell), for now keep for
;; backward compatibility
if(cs.slice_refs() == 1) {
var param_value = cs~load_ref();
cs.end_parse();
cfg_dict~idict_set_ref(32, param_index, param_value);
} else {
(cfg_dict, int success) = cfg_dict.idict_delete?(32, param_index);
}
return (cfg_dict, public_key);
} elseif (action == op::set_new_code) {
;; change configuration smart contract code
cell new_code = cs~load_ref();
set_code(new_code);
cont old_code = get_c3();
set_c3(new_code.begin_parse().bless());
after_code_upgrade(cs, old_code);
throw(0);
return (cfg_dict, public_key);
} elseif (action == op::update_config_key) {
;; change configuration master public key
public_key = cs~load_uint(256);
cs.end_parse();
return (cfg_dict, public_key);
} elseif (action == op::update_elector_code) {
;; change election smart contract code
change_elector_code(cs);
return (cfg_dict, public_key);
} else {
throw_if(32, action);
return (cfg_dict, public_key);
}
}
(cell, int, cell) get_current_vset() inline_ref {
cell vset = config_param(config_params::current_validators_set);
slice cs = begin_parse(vset);
;; validators_ext#12 utime_since:uint32 utime_until:uint32
;; total:(## 16) main:(## 16) { main <= total } { main >= 1 }
;; total_weight:uint64
throw_unless(40, cs~load_uint(8) == 0x12);
cs~skip_bits(32 + 32 + 16 + 16);
(int total_weight, cell dict) = (cs~load_uint(64), cs~load_dict());
cs.end_parse();
return (vset, total_weight, dict);
}
(slice, int) get_validator_descr(int idx) inline_ref {
(cell vset, int total_weight, cell dict) = get_current_vset();
(slice value, _) = dict.udict_get?(16, idx);
return (value, total_weight);
}
(int, int) unpack_validator_descr(slice cs) inline_ref {
;; ed25519_pubkey#8e81278a pubkey:bits256 = SigPubKey;
;; validator#53 public_key:SigPubKey weight:uint64 = ValidatorDescr;
;; validator_addr#73 public_key:SigPubKey weight:uint64 adnl_addr:bits256 = ValidatorDescr;
throw_unless(41, (cs~load_uint(8) & ~ 0x20) == 0x53);
throw_unless(41, cs~load_uint(32) == tag::ed25519_pubkey);
return (cs~load_uint(256), cs~load_uint(64));
}
;; cfg_proposal#f3 param_id:int32 param_value:(Maybe ^Cell) if_hash_equal:(Maybe uint256)
;; c -> (param-id param-cell maybe-hash)
(int, cell, int) parse_config_proposal(cell c) inline_ref {
slice cs = c.begin_parse();
throw_unless(44, cs~load_uint(8) == 0xf3);
(int id, cell val, int hash) = (cs~load_int(32), cs~load_maybe_ref(), cs~load_int(1));
if (hash) {
hash = cs~load_uint(256);
} else {
hash = -1;
}
cs.end_parse();
return (id, val, hash);
}
(cell, int, cell) accept_proposal(cell cfg_dict, cell proposal, int critical?) inline {
(int param_id, cell param_val, int req_hash) = parse_config_proposal(proposal);
cell cur_val = cfg_dict.idict_get_ref(32, param_id);
int cur_hash = null?(cur_val) ? 0 : cell_hash(cur_val);
if ((cur_hash != req_hash) & (req_hash >= 0)) {
;; current value has incorrect hash, do not apply changes
return (cfg_dict, 0, null());
}
cell mparams = cfg_dict.idict_get_ref(32, 9); ;; mandatory parameters
(_, int found?) = mparams.idict_get?(32, param_id);
if (found? & param_val.null?()) {
;; cannot set a mandatory parameter to (null)
return (cfg_dict, 0, null());
}
cell cparams = cfg_dict.idict_get_ref(32, 10); ;; critical parameters
(_, found?) = cparams.idict_get?(32, param_id);
if (found? & (~ critical?)) {
;; trying to set a critical parameter after a non-critical voting
return (cfg_dict, 0, null());
}
;; we only check that key is valid, but checkins some signature
if(param_id == special_params::set_config_key) {
slice ps = param_val.begin_parse();
int key = ps~load_uint(256);
slice signature = ps;
ifnot(check_signature(tag::signature_challenge, signature, key)) {
return (cfg_dict, 0, null());
}
}
if(param_id == special_params::config_multikey) {
int key = -1;
do {
(key, slice signature, int found?) = param_val.udict_get_next?(256, key);
if(found?) {
ifnot(check_signature(tag::signature_challenge, signature, key)) {
return (cfg_dict, 0, null());
}
}
} until ( ~ found?);
}
;; CHANGE ONE CONFIGURATION PARAMETER (!)
cfg_dict~idict_set_ref(32, param_id, param_val);
return (cfg_dict, param_id, param_val);
}
(cell, int) perform_proposed_action(cell cfg_dict, int public_key, int param_id, cell param_val) inline {
if (param_id == special_params::set_config_key) {
;; appoint or depose dictator
return (cfg_dict, param_val.null?() ? 0 : param_val.begin_parse().preload_uint(256));
}
if (param_val.null?()) {
return (cfg_dict, public_key);
}
if (param_id == special_params::update_config_code) {
slice cs = param_val.begin_parse();
cell new_code = cs~load_ref();
set_code(new_code);
cont old_code = get_c3();
set_c3(new_code.begin_parse().bless());
after_code_upgrade(cs, old_code);
throw(0);
return (cfg_dict, public_key);
}
if (param_id == special_params::update_elector_code) {
slice cs = param_val.begin_parse();
change_elector_code(cs);
}
return (cfg_dict, public_key);
}
;; cfg_proposal_status#ce expires:uint32 proposal:^ConfigProposal is_critical:Bool
;; voters:(HashmapE 16 True) remaining_weight:int64 validator_set_id:uint256
;; rounds_remaining:uint8 wins:uint8 losses:uint8 = ConfigProposalStatus;
(int, cell, int, cell, int, int, slice) unpack_proposal_status(slice cs) inline_ref {
throw_unless(44, cs~load_uint(8) == 0xce);
return (cs~load_uint(32), cs~load_ref(), cs~load_int(1), cs~load_dict(), cs~load_int(64), cs~load_uint(256), cs);
}
slice update_proposal_status(slice rest, int weight_remaining, int critical?) inline_ref {
(int min_tot_rounds, int max_tot_rounds, int min_wins, int max_losses, _, _, _, _)
= get_vote_config(critical?);
(int rounds_remaining, int wins, int losses) = (rest~load_uint(8), rest~load_uint(8), rest~load_uint(8));
if (weight_remaining >= 0) {
losses += 1;
}
if (losses > max_losses) {
;; lost too many times
return null();
}
rounds_remaining -= 1;
if (rounds_remaining < 0) {
;; existed for too many rounds
return null();
}
return begin_cell()
.store_uint(rounds_remaining, 8)
.store_uint(wins, 8)
.store_uint(losses, 8)
.end_cell().begin_parse();
}
builder begin_pack_proposal_status(int expires,
cell proposal,
int critical?,
cell voters,
int weight_remaining,
int vset_id) inline {
return begin_cell()
.store_int(0xce - 0x100, 8)
.store_uint(expires, 32)
.store_ref(proposal)
.store_int(critical?, 1)
.store_dict(voters)
.store_int(weight_remaining, 64)
.store_uint(vset_id, 256);
}
(cell, cell, int) register_vote(cell vote_dict,
int phash,
int idx,
int weight) inline_ref {
(slice pstatus, int found?) = vote_dict.udict_get?(256, phash);
ifnot (found?) {
;; config proposal not found
return (vote_dict, null(), -1);
}
(cell cur_vset, int total_weight, _) = get_current_vset();
int cur_vset_id = cur_vset.cell_hash();
(int expires,
cell proposal,
int critical?,
cell voters,
int weight_remaining,
int vset_id,
slice rest) =
unpack_proposal_status(pstatus);
if (expires <= now()) {
;; config proposal expired, delete and report not found
vote_dict~udict_delete?(256, phash);
return (vote_dict, null(), -1);
}
if (vset_id != cur_vset_id) {
;; config proposal belongs to a previous validator set
vset_id = cur_vset_id;
rest = update_proposal_status(rest, weight_remaining, critical?);
voters = null();
weight_remaining = muldiv(total_weight, 3, 4);
}
if (rest.null?()) {
;; discard proposal (existed for too many rounds, or too many losses)
vote_dict~udict_delete?(256, phash);
return (vote_dict, null(), -1);
}
(_, int found?) = voters.udict_get?(16, idx);
if (found?) {
;; already voted for this proposal, ignore vote
return (vote_dict, null(), -2);
}
;; register vote
voters~udict_set_builder(16, idx, begin_cell().store_uint(now(), 32));
int old_wr = weight_remaining;
weight_remaining -= weight;
if ( (old_wr < 0) | ;;proposal already accepted in this round
(weight_remaining >= 0)) { ;; not enough votes
;; simply update weight_remaining
vote_dict~udict_set_builder(256, phash, begin_pack_proposal_status(expires, proposal, critical?, voters, weight_remaining, vset_id).store_slice(rest));
return (vote_dict, null(), 2);
}
;; proposal wins in this round
(int min_tot_rounds, int max_tot_rounds, int min_wins, int max_losses, _, _, _, _) = get_vote_config(critical?);
(int rounds_remaining, int wins, int losses) = (rest~load_uint(8), rest~load_uint(8), rest~load_uint(8));
wins += 1;
if (wins >= min_wins) {
;; proposal is accepted, remove and process
vote_dict~udict_delete?(256, phash);
return (vote_dict, proposal, 6 - critical?);
}
;; update proposal info
vote_dict~udict_set_builder(256, phash,
begin_pack_proposal_status(expires, proposal, critical?, voters, weight_remaining, vset_id)
.store_uint(rounds_remaining, 8)
.store_uint(wins, 8)
.store_uint(losses, 8));
return (vote_dict, null(), 2);
}
int proceed_register_vote(int phash, int idx, int weight) impure inline_ref {
(cell cfg_dict, int stored_seqno, int public_key, cell vote_dict) = load_data();
(vote_dict, cell accepted_proposal, int status) = register_vote(vote_dict, phash, idx, weight);
store_data(cfg_dict, stored_seqno, public_key, vote_dict);
ifnot (accepted_proposal.null?()) {
int critical? = 6 - status;
(cfg_dict, int param_id, cell param_val) = accept_proposal(cfg_dict, accepted_proposal, critical?);
store_data(cfg_dict, stored_seqno, public_key, vote_dict);
if (param_id) {
commit();
(cfg_dict, public_key) = perform_proposed_action(cfg_dict, public_key, param_id, param_val);
store_data(cfg_dict, stored_seqno, public_key, vote_dict);
}
}
return status;
}
(slice, int) scan_proposal(slice pstatus) inline {
(cell cur_vset, int total_weight, _) = get_current_vset();
int cur_vset_id = cur_vset.cell_hash();
(int expires,
cell proposal,
int critical?,
cell voters,
int weight_remaining,
int vset_id,
slice rest) =
unpack_proposal_status(pstatus);
if (expires <= now()) {
;; config proposal expired, delete
return (null(), true);
}
if (vset_id == cur_vset_id) {
;; config proposal already processed or voted for in this round, change nothing
return (pstatus, false);
}
;; config proposal belongs to a previous validator set
vset_id = cur_vset_id;
rest = update_proposal_status(rest, weight_remaining, critical?);
voters = null();
weight_remaining = muldiv(total_weight, 3, 4);
if (rest.null?()) {
;; discard proposal (existed for too many rounds, or too many losses)
return (null(), true);
}
;; return updated proposal
return (begin_pack_proposal_status(expires, proposal, critical?, voters, weight_remaining, vset_id).store_slice(rest).end_cell().begin_parse(), true);
}
cell scan_random_proposal(cell vote_dict) inline {
int rand = random();
(int phash, slice pstatus, int found?) = vote_dict.udict_get_nexteq?(256, rand);
ifnot (found?) {
(phash, pstatus, found?) = vote_dict.udict_get_nexteq?(256, -1);
}
ifnot (found?) {
return vote_dict;
}
(pstatus, int changed?) = scan_proposal(pstatus);
if (changed?) {
if (pstatus.null?()) {
vote_dict~udict_delete?(256, phash);
} else {
vote_dict~udict_set(256, phash, pstatus);
}
}
return vote_dict;
}
;; price, error_code
(int, int) register_voting_proposal(slice cs,
int msg_value) impure inline {
(int expire_in, cell proposal, int critical?) = (cs~load_uint(32), cs~load_ref(), cs~load_int(1));
cs.end_parse();
if(expire_in >> 30) {
expire_in -= now();
}
(int param_id, cell param_val, int hash) = parse_config_proposal(proposal);
if (hash >= 0) {
cell cur_val = config_param(param_id);
int cur_hash = null?(cur_val) ? 0 : cell_hash(cur_val);
if (cur_hash != hash) {
return (0, error::proposal::old_mismatch); ;; bad current value
}
} else {
;; _ mandatory_params:(Hashmap 32 True) = ConfigParam 9;
cell m_params = config_param(config_params::mandatory_params);
(_, int found?) = m_params.idict_get?(32, param_id);
if (found?) {
return (0, error::proposal::mandatory_nullified); ;; cannot set mandatory parameter to null
}
}
if (param_val.cell_depth() >= 128) {
return (0, error::proposal::too_deep);
}
ifnot (critical?) {
;; _ critical_params:(Hashmap 32 True) = ConfigParam 10;
cell crit_params = config_param(config_params::critical_params);
(_, int found?) = crit_params.idict_get?(32, param_id);
if (found?) {
return (0, error::proposal::critical_flag_missing); ;; trying to set a critical parameter without critical flag
}
}
;; obtain vote proposal configuration
(int min_tot_rounds,
int max_tot_rounds,
int min_wins,
int max_losses,
int min_store_sec,
int max_store_sec,
int bit_price,
int cell_price) = get_vote_config(critical?);
if (expire_in < min_store_sec) {
return (0, error::proposal::expired);
}
expire_in = min(expire_in, max_store_sec);
;; compute price
(_, int bits, int refs) = compute_data_size(param_val, 1024);
int pps = bit_price * (bits + 1024) + cell_price * (refs + 2);
int price = pps * expire_in;
int expire_at = expire_in + now();
(cell cfg_dict, int stored_seqno, int public_key, cell vote_dict) = load_data();
int phash = proposal.cell_hash();
(slice pstatus, int found?) = vote_dict.udict_get?(256, phash);
if (found?) {
;; proposal already exists; we can only extend it
(int expires,
cell r_proposal,
int r_critical?,
cell voters,
int weight_remaining,
int vset_id,
slice rest) =
unpack_proposal_status(pstatus);
if (r_critical? != critical?) {
return (0, error::proposal::critical_flag_mismatch); ;; cannot upgrade critical parameter to non-critical...
}
if (expires >= expire_at) {
return (0, error::proposal::already_exists);
}
;; recompute price
price = pps * (expire_at - expires + 16384);
if (msg_value - price < ONECOIN) {
return (0, error::proposal::insufficient_fee);
}
;; update expiration time
vote_dict~udict_set_builder(256, phash, begin_pack_proposal_status(expire_at, r_proposal, r_critical?, voters, weight_remaining, vset_id).store_slice(rest));
store_data(cfg_dict, stored_seqno, public_key, vote_dict);
return (price, 0);
}
if (msg_value - price < ONECOIN) {
return (0, error::proposal::insufficient_fee);
}
;; obtain current validator set data
(cell vset, int total_weight, _) = get_current_vset();
int weight_remaining = muldiv(total_weight, 3, 4);
;; create new proposal
vote_dict~udict_set_builder(256, phash,
begin_pack_proposal_status(expire_at, proposal, critical?, null(), weight_remaining, vset.cell_hash())
.store_uint(max_tot_rounds, 8).store_uint(0, 16));
store_data(cfg_dict, stored_seqno, public_key, vote_dict);
return (price, 0);
}
() recv_internal(int msg_value,
cell in_msg_cell,
slice in_msg) impure {
slice cs = in_msg_cell.begin_parse();
int flags = cs~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool
slice s_addr = cs~load_msg_addr();
(int src_wc, int src_addr) = s_addr.parse_std_addr();
int is_bounced = flags & 1;
if ((src_wc != - 1) | is_bounced | in_msg.slice_empty?()) {
;; source not in masterchain, or a bounced message, or a simple transfer
return ();
}
int tag = in_msg~load_uint(32);
int query_id = in_msg~load_uint(64);
if (tag == op::set_next_validator_set) {
;; set next validator set
cell vset = in_msg~load_ref();
in_msg.end_parse();
cell elector_param = config_param(config_params::elector_address);
int elector_addr = cell_null?(elector_param) ? -1 : elector_param.begin_parse().preload_uint(256);
int ok = false;
if (src_addr == elector_addr) {
;; message from elector smart contract
;; set next validator set
(int t_since, int t_until) = check_validator_set(vset);
int t = now();
ok = (t_since > t) & (t_until > t_since);
}
if (ok) {
set_conf_param(config_params::next_validators_set, vset);
;; send confirmation
return send_answer(s_addr, query_id, op::response::update_vset_confirm, 64);
} else {
return send_answer(s_addr, query_id, op::response::update_vset_reject, 64);
}
}
if (tag == op::new_voting_proposal) {
;; new voting proposal
(int price, int error) = register_voting_proposal(in_msg, msg_value);
int mode = 64;
int ans_tag = error;
ifnot (error) {
;; ok, debit price
raw_reserve(price, 4);
ans_tag = op::response::proposal_accepted;
mode = 128;
}
return send_answer(s_addr, query_id, ans_tag, mode);
}
if (tag == op::vote_for_config_proposal) {
;; vote for a configuration proposal via signature
ifnot(cell_null?(config_param(special_params::signed_votes_disabled))) {
slice signature = in_msg~load_bits(512);
slice msg_body = in_msg;
(int sign_tag, int idx, int phash) = (in_msg~load_uint(32), in_msg~load_uint(16), in_msg~load_uint(256));
in_msg.end_parse();
throw_unless(37, sign_tag == tag::signature_challenge);
(slice vdescr, int total_weight) = get_validator_descr(idx);
(int val_pubkey, int weight) = unpack_validator_descr(vdescr);
throw_unless(34, check_data_signature(msg_body, signature, val_pubkey));
int res = proceed_register_vote(phash, idx, weight);
return send_answer(s_addr, query_id, res + op::response::vote_result, 64);
}
}
if (tag == op::vote_for_config_proposal_from_elector) {
slice cs = in_msg~load_ref().begin_parse();
(int idx, int phash, int vset_id) = (cs~load_uint(16), cs~load_uint(256), cs~load_uint(256));
cell elector_param = config_param(config_params::elector_address);
int elector_addr = cell_null?(elector_param) ? -1 : elector_param.begin_parse().preload_uint(256);
(cell cur_vset, int total_weight, cell validators) = get_current_vset();
int cur_vset_id = cur_vset.cell_hash();
if( ~ (src_addr == elector_addr)) {
return send_answer(s_addr, query_id, error::unauthorized, 64);
}
if( ~ (vset_id == cur_vset_id)) {
return send_answer(s_addr, query_id, error::expired_vset, 64);
}
(slice vdescr, _) = validators.udict_get?(16, idx);
(int val_pubkey, int weight) = unpack_validator_descr(vdescr);
int res = proceed_register_vote(phash, idx, weight);
return send_answer(s_addr, query_id, res + op::response::vote_result, 64);
}
;; if tag is non-zero and its higher bit is zero, throw an exception (the message is an unsupported query)
;; to bounce message back to sender
throw_unless(37, (tag == 0) | (tag & (1 << 31)));
;; do nothing for other internal messages
}
() recv_external(slice in_msg) impure {
slice signature = in_msg~load_bits(512);
slice cs = in_msg;
int action = cs~load_uint(32);
int msg_seqno = cs~load_uint(32);
int valid_until = cs~load_uint(32);
throw_if(35, valid_until < now());
throw_if(39, slice_depth(cs) > 128);
(cell cfg_dict, int stored_seqno, int public_key, cell vote_dict) = load_data();
throw_unless(33, msg_seqno == stored_seqno);
if (action == op::vote_for_config_proposal &
cell_null?(config_param(special_params::signed_votes_disabled))) {
;; vote for a configuration proposal
(int idx, int phash) = (cs~load_uint(16), cs~load_uint(256));
cs.end_parse();
(slice vdescr, int total_weight) = get_validator_descr(idx);
(int val_pubkey, int weight) = unpack_validator_descr(vdescr);
throw_unless(34, check_data_signature(in_msg, signature, val_pubkey));
accept_message();
stored_seqno = (stored_seqno + 1) % (1 << 32);
store_data(cfg_dict, stored_seqno, public_key, vote_dict);
commit();
proceed_register_vote(phash, idx, weight);
return ();
}
throw_unless(34, check_signature(slice_hash(in_msg), signature, public_key));
accept_message();
stored_seqno = (stored_seqno + 1) % (1 << 32);
store_data(cfg_dict, stored_seqno, public_key, vote_dict);
commit();
cell key_dict = config_param(special_params::config_multikey);
if(~ key_dict.cell_null?()) {
int sign_num = 1;
int total_signee = 1;
if(in_msg.slice_refs() < 1) {
return ();
}
cell signatures = in_msg~load_ref();
int key = -1;
do {
(key, _, int found?) = key_dict.udict_get_next?(256, key);
total_signee += 1;
if(found?) {
(slice sig, int have_sig?) = signatures.udict_get?(256, key);
if(have_sig?) {
;; Note master key signs message with signatures from other keys
;; but other keys sign message without (obviously) their signatures
ifnot (check_signature(slice_hash(in_msg), sig, key)) { ;; wrong signature causes fail
return ();
}
sign_num += 1;
}
}
} until ( ~ found?);
if(sign_num < muldiv(total_signee, 2, 3)) { ;; at least 2/3 of signers should sign
return ();
}
}
(cfg_dict, public_key) = perform_action(cfg_dict, public_key, action, cs);
store_data(cfg_dict, stored_seqno, public_key, vote_dict);
}
() run_ticktock(int is_tock) impure {
(cell cfg_dict, int stored_seqno, int public_key, cell vote_dict) = load_data();
int kl = 32;
cell next_vset = cfg_dict.idict_get_ref(kl, 36);
int updated? = false;
ifnot (next_vset.null?()) {
;; check whether we have to set next_vset as the current validator set
slice ds = next_vset.begin_parse();
if (ds.slice_bits() >= 40) {
int tag = ds~load_uint(8);
int since = ds.preload_uint(32);
if ((since <= now()) & (tag == 0x12)) {
;; next validator set becomes active!
cell cur_vset = cfg_dict~idict_set_get_ref(kl, 34, next_vset); ;; next_vset -> cur_vset
cfg_dict~idict_set_get_ref(kl, 32, cur_vset); ;; cur_vset -> prev_vset
cfg_dict~idict_delete?(kl, 36); ;; (null) -> next_vset
updated? = true;
}
}
}
ifnot (updated?) {
;; if nothing has been done so far, scan a random voting proposal instead
vote_dict = scan_random_proposal(vote_dict);
}
;; save data and return
return store_data(cfg_dict, stored_seqno, public_key, vote_dict);
}
int seqno() method_id {
return get_data().begin_parse().preload_uint(32);
}
;; [expires, critical?, [param_id, param_val, param_hash], vset_id, voters_list, weight_remaining, rounds_remaining, wins, losses]
[int, int, [int, cell, int], int, tuple, int, int, int, int] unpack_proposal(slice pstatus) inline_ref {
(int expires, cell proposal, int critical?, cell voters, int weight_remaining, int vset_id, slice rest) = unpack_proposal_status(pstatus);
tuple voters_list = null();
int voter_id = (1 << 32);
do {
(voter_id, _, int f) = voters.udict_get_prev?(16, voter_id);
if (f) {
voters_list = cons(voter_id, voters_list);
}
} until (~ f);
;; Note there is a bug in config contract currently deployed in testnet2:
;; wins and losses are messed up
(int rounds_remaining, int wins, int losses) = (rest~load_uint(8), rest~load_uint(8), rest~load_uint(8));
rest.end_parse();
(int param_id, cell param_val, int param_hash) = parse_config_proposal(proposal);
return [expires, critical?, [param_id, param_val, param_hash], vset_id, voters_list, weight_remaining, rounds_remaining, wins, losses];
}
[int, int, [int, cell, int], int, tuple, int, int, int, int] get_proposal(int phash) method_id {
(_, _, _, cell vote_dict) = load_data();
(slice pstatus, int found?) = vote_dict.udict_get?(256, phash);
ifnot (found?) {
return null();
}
return unpack_proposal(pstatus);
}
tuple list_proposals() method_id {
(_, _, _, cell vote_dict) = load_data();
int phash = (1 << 255) + ((1 << 255) - 1);
tuple list = null();
do {
(phash, slice pstatus, int f) = vote_dict.udict_get_prev?(256, phash);
if (f) {
list = cons([phash, unpack_proposal(pstatus)], list);
}
} until (~ f);
return list;
}
int proposal_storage_price(int critical?, int seconds, int bits, int refs) method_id {
cell cfg_dict = get_data().begin_parse().preload_ref();
cell cparam11 = cfg_dict.idict_get_ref(32, 11);
(int min_tot_rounds,
int max_tot_rounds,
int min_wins,
int max_losses,
int min_store_sec,
int max_store_sec,
int bit_price,
int cell_price) = get_vote_config_internal(critical?, cparam11);
if (seconds < min_store_sec) {
return -1;
}
seconds = min(seconds, max_store_sec);
return (bit_price * (bits + 1024) + cell_price * (refs + 2)) * seconds;
}