-
Notifications
You must be signed in to change notification settings - Fork 116
/
Diem.move
1873 lines (1725 loc) · 87.4 KB
/
Diem.move
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
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
address 0x1 {
/// The `Diem` module describes the concept of a coin in the Diem framework. It introduces the
/// resource `Diem::Diem<CoinType>`, representing a coin of given coin type.
/// The module defines functions operating on coins as well as functionality like
/// minting and burning of coins.
module Diem {
use 0x1::CoreAddresses;
use 0x1::Errors;
use 0x1::Event::{Self, EventHandle};
use 0x1::FixedPoint32::{Self, FixedPoint32};
use 0x1::RegisteredCurrencies;
use 0x1::Signer;
use 0x1::Roles;
use 0x1::DiemTimestamp;
use 0x1::Vector;
/// The `Diem` resource defines the Diem coin for each currency in
/// Diem. Each "coin" is coupled with a type `CoinType` specifying the
/// currency of the coin, and a `value` field specifying the value
/// of the coin (in the base units of the currency `CoinType`
/// and specified in the `CurrencyInfo` resource for that `CoinType`
/// published under the `CoreAddresses::CURRENCY_INFO_ADDRESS()` account address).
struct Diem<CoinType> has store {
/// The value of this coin in the base units for `CoinType`
value: u64
}
/// The `MintCapability` resource defines a capability to allow minting
/// of coins of `CoinType` currency by the holder of this capability.
/// This capability is held only either by the `CoreAddresses::TREASURY_COMPLIANCE_ADDRESS()`
/// account or the `0x1::GAS` module (and `CoreAddresses::DIEM_ROOT_ADDRESS()` in testnet).
struct MintCapability<CoinType> has key, store { }
/// The `BurnCapability` resource defines a capability to allow coins
/// of `CoinType` currency to be burned by the holder of it.
struct BurnCapability<CoinType> has key, store { }
/// A `MintEvent` is emitted every time a Diem coin is minted. This
/// contains the `amount` minted (in base units of the currency being
/// minted) along with the `currency_code` for the coin(s) being
/// minted, and that is defined in the `currency_code` field of the
/// `CurrencyInfo` resource for the currency.
struct MintEvent has drop, store {
/// Funds added to the system
amount: u64,
/// ASCII encoded symbol for the coin type (e.g., "GAS")
currency_code: vector<u8>,
}
/// A `BurnEvent` is emitted every time a non-synthetic Diem coin
/// (i.e., a Diem coin with false `is_synthetic` field) is
/// burned. It contains the `amount` burned in base units for the
/// currency, along with the `currency_code` for the coins being burned
/// (and as defined in the `CurrencyInfo` resource for that currency).
/// It also contains the `preburn_address` from which the coin is
/// extracted for burning.
struct BurnEvent has drop, store {
/// Funds removed from the system
amount: u64,
/// ASCII encoded symbol for the coin type (e.g., "GAS")
currency_code: vector<u8>,
/// Address with the `PreburnQueue` resource that stored the now-burned funds
preburn_address: address,
}
/// A `PreburnEvent` is emitted every time an `amount` of funds with
/// a coin type `currency_code` is enqueued in the `PreburnQueue` resource under
/// the account at the address `preburn_address`.
struct PreburnEvent has drop, store {
/// The amount of funds waiting to be removed (burned) from the system
amount: u64,
/// ASCII encoded symbol for the coin type (e.g., "GAS")
currency_code: vector<u8>,
/// Address with the `PreburnQueue` resource that now holds the funds
preburn_address: address,
}
/// A `CancelBurnEvent` is emitted every time funds of `amount` in a `Preburn`
/// resource held in a `PreburnQueue` at `preburn_address` is canceled (removed from the
/// preburn queue, but not burned). The currency of the funds is given by the
/// `currency_code` as defined in the `CurrencyInfo` for that currency.
struct CancelBurnEvent has drop, store {
/// The amount of funds returned
amount: u64,
/// ASCII encoded symbol for the coin type (e.g., "GAS")
currency_code: vector<u8>,
/// Address of the `PreburnQueue` resource that held the now-returned funds.
preburn_address: address,
}
/// An `ToXDXExchangeRateUpdateEvent` is emitted every time the to-XDX exchange
/// rate for the currency given by `currency_code` is updated.
struct ToXDXExchangeRateUpdateEvent has drop, store {
/// The currency code of the currency whose exchange rate was updated.
currency_code: vector<u8>,
/// The new on-chain to-GAS exchange rate between the
/// `currency_code` currency and GAS. Represented in conversion
/// between the (on-chain) base-units for the currency and microdiem.
new_to_xdx_exchange_rate: u64,
}
/// The `CurrencyInfo<CoinType>` resource stores the various
/// pieces of information needed for a currency (`CoinType`) that is
/// registered on-chain. This resource _must_ be published under the
/// address given by `CoreAddresses::CURRENCY_INFO_ADDRESS()` in order for the registration of
/// `CoinType` as a recognized currency on-chain to be successful. At
/// the time of registration, the `MintCapability<CoinType>` and
/// `BurnCapability<CoinType>` capabilities are returned to the caller.
/// Unless they are specified otherwise the fields in this resource are immutable.
struct CurrencyInfo<CoinType> has key {
/// The total value for the currency represented by `CoinType`. Mutable.
total_value: u128,
/// Value of funds that are in the process of being burned. Mutable.
preburn_value: u64,
/// The (rough) exchange rate from `CoinType` to `GAS`. Mutable.
to_xdx_exchange_rate: FixedPoint32,
/// Holds whether or not this currency is synthetic (contributes to the
/// off-chain reserve) or not. An example of such a synthetic
///currency would be the GAS.
is_synthetic: bool,
/// The scaling factor for the coin (i.e. the amount to divide by
/// to get to the human-readable representation for this currency).
/// e.g. 10^6 for `XUS`
scaling_factor: u64,
/// The smallest fractional part (number of decimal places) to be
/// used in the human-readable representation for the currency (e.g.
/// 10^2 for `XUS` cents)
fractional_part: u64,
/// The code symbol for this `CoinType`. ASCII encoded.
/// e.g. for "GAS" this is x"584458". No character limit.
currency_code: vector<u8>,
/// Minting of new currency of CoinType is allowed only if this field is true.
/// We may want to disable the ability to mint further coins of a
/// currency while that currency is still around. This allows us to
/// keep the currency in circulation while disallowing further
/// creation of coins in the `CoinType` currency. Mutable.
can_mint: bool,
/// Event stream for minting and where `MintEvent`s will be emitted.
mint_events: EventHandle<MintEvent>,
/// Event stream for burning, and where `BurnEvent`s will be emitted.
burn_events: EventHandle<BurnEvent>,
/// Event stream for preburn requests, and where all
/// `PreburnEvent`s for this `CoinType` will be emitted.
preburn_events: EventHandle<PreburnEvent>,
/// Event stream for all cancelled preburn requests for this
/// `CoinType`.
cancel_burn_events: EventHandle<CancelBurnEvent>,
/// Event stream for emiting exchange rate change events
exchange_rate_update_events: EventHandle<ToXDXExchangeRateUpdateEvent>,
}
/// The maximum value for `CurrencyInfo.scaling_factor`
const MAX_SCALING_FACTOR: u64 = 10000000000;
/// Data structure invariant for CurrencyInfo. Asserts that `CurrencyInfo.scaling_factor`
/// is always greater than 0 and not greater than `MAX_SCALING_FACTOR`
spec CurrencyInfo {
invariant 0 < scaling_factor && scaling_factor <= MAX_SCALING_FACTOR;
}
/// A holding area where funds that will subsequently be burned wait while their underlying
/// assets are moved off-chain.
/// This resource can only be created by the holder of a `BurnCapability`
/// or during an upgrade process to the `PreburnQueue` by a designated
/// dealer. An account that contains this address has the authority to
/// initiate a burn request. A burn request can be resolved by the holder
/// of a `BurnCapability` by either (1) burning the funds, or (2) returning
/// the funds to the account that initiated the burn request.
struct Preburn<CoinType> has key, store {
/// A single pending burn amount. This is an element in the
/// `PreburnQueue` resource published under each Designated Dealer account.
to_burn: Diem<CoinType>,
}
/// A preburn request, along with (an opaque to Move) metadata that is
/// associated with the preburn request.
struct PreburnWithMetadata<CoinType> has store {
preburn: Preburn<CoinType>,
metadata: vector<u8>,
}
/// A queue of preburn requests. This is a FIFO queue whose elements
/// are indexed by the value held within each preburn resource in the
/// `preburns` field. When burning or cancelling a burn of a given
/// `amount`, the `Preburn` resource with with the smallest index in this
/// queue matching `amount` in its `to_burn` coin's `value` field will be
/// removed and its contents either (1) burned, or (2) returned
/// back to the holding DD's account balance. Every `Preburn` resource in
/// the `PreburnQueue` must have a nonzero coin value within it.
/// This resource can be created by either the TreasuryCompliance
/// account, or during the upgrade process, by a designated dealer with an
/// existing `Preburn` resource in `CoinType`
struct PreburnQueue<CoinType> has key {
/// The queue of preburn requests
preburns: vector<PreburnWithMetadata<CoinType>>,
}
spec PreburnQueue {
/// The number of outstanding preburn requests is bounded.
invariant len(preburns) <= MAX_OUTSTANDING_PREBURNS;
/// No preburn request can have a zero value.
/// The `value` field of any coin in a `Preburn` resource
/// within this field must be nonzero.
invariant forall i in 0..len(preburns): preburns[i].preburn.to_burn.value > 0;
}
/// Maximum u64 value.
const MAX_U64: u64 = 18446744073709551615;
/// Maximum u128 value.
const MAX_U128: u128 = 340282366920938463463374607431768211455;
/// A `BurnCapability` resource is in an unexpected state.
const EBURN_CAPABILITY: u64 = 0;
/// A property expected of a `CurrencyInfo` resource didn't hold
const ECURRENCY_INFO: u64 = 1;
/// A property expected of a `Preburn` resource didn't hold
const EPREBURN: u64 = 2;
/// The preburn slot is already occupied with coins to be burned.
const EPREBURN_OCCUPIED: u64 = 3;
/// A burn was attempted on `Preburn` resource that cointained no coins
const EPREBURN_EMPTY: u64 = 4;
/// Minting is not allowed for the specified currency
const EMINTING_NOT_ALLOWED: u64 = 5;
/// The currency specified is a synthetic (non-fiat) currency
const EIS_SYNTHETIC_CURRENCY: u64 = 6;
/// A property expected of the coin provided didn't hold
const ECOIN: u64 = 7;
/// The destruction of a non-zero coin was attempted. Non-zero coins must be burned.
const EDESTRUCTION_OF_NONZERO_COIN: u64 = 8;
/// A property expected of `MintCapability` didn't hold
const EMINT_CAPABILITY: u64 = 9;
/// A withdrawal greater than the value of the coin was attempted.
const EAMOUNT_EXCEEDS_COIN_VALUE: u64 = 10;
/// A property expected of the `PreburnQueue` resource didn't hold.
const EPREBURN_QUEUE: u64 = 11;
/// A preburn with a matching amount in the preburn queue was not found.
const EPREBURN_NOT_FOUND: u64 = 12;
/// The maximum number of preburn requests that can be outstanding for a
/// given designated dealer/currency.
const MAX_OUTSTANDING_PREBURNS: u64 = 256;
/// Initialization of the `Diem` module. Initializes the set of
/// registered currencies in the `0x1::RegisteredCurrencies` on-chain
/// config, and publishes the `CurrencyRegistrationCapability` under the
/// `CoreAddresses::DIEM_ROOT_ADDRESS()`. This can only be called from genesis.
public fun initialize(
dr_account: &signer,
) {
DiemTimestamp::assert_genesis();
// Operational constraint
CoreAddresses::assert_diem_root(dr_account);
RegisteredCurrencies::initialize(dr_account);
}
spec initialize {
include DiemTimestamp::AbortsIfNotGenesis;
include CoreAddresses::AbortsIfNotDiemRoot{account: dr_account};
include RegisteredCurrencies::InitializeAbortsIf;
include RegisteredCurrencies::InitializeEnsures;
}
/// Publishes the `BurnCapability` `cap` for the `CoinType` currency under `account`. `CoinType`
/// must be a registered currency type. The caller must pass a treasury compliance account.
public fun publish_burn_capability<CoinType: store>(
dr_account: &signer,
cap: BurnCapability<CoinType>,
) {
Roles::assert_diem_root(dr_account); /////// 0L /////////
assert_is_currency<CoinType>();
assert(
!exists<BurnCapability<CoinType>>(Signer::address_of(dr_account)),
Errors::already_published(EBURN_CAPABILITY)
);
move_to(dr_account, cap)
}
spec publish_burn_capability {
aborts_if !spec_is_currency<CoinType>();
include PublishBurnCapAbortsIfs<CoinType>;
}
spec schema PublishBurnCapAbortsIfs<CoinType> {
dr_account: &signer;
/// Must abort if dr_account does not have the TreasuryCompliance role.
/// Only a TreasuryCompliance account can have the BurnCapability [[H3]][PERMISSION].
include Roles::AbortsIfNotTreasuryCompliance{account: dr_account};
aborts_if exists<BurnCapability<CoinType>>(Signer::spec_address_of(dr_account)) with Errors::ALREADY_PUBLISHED;
}
spec schema PublishBurnCapEnsures<CoinType> {
dr_account: &signer;
ensures exists<BurnCapability<CoinType>>(Signer::spec_address_of(dr_account));
}
/// Mints `amount` of currency. The `account` must hold a
/// `MintCapability<CoinType>` at the top-level in order for this call
/// to be successful.
public fun mint<CoinType: store>(account: &signer, value: u64): Diem<CoinType>
acquires CurrencyInfo, MintCapability {
let addr = Signer::address_of(account);
assert(exists<MintCapability<CoinType>>(addr), Errors::requires_capability(EMINT_CAPABILITY));
mint_with_capability(
value,
borrow_global<MintCapability<CoinType>>(addr)
)
}
spec mint {
modifies global<CurrencyInfo<CoinType>>(CoreAddresses::CURRENCY_INFO_ADDRESS());
ensures exists<CurrencyInfo<CoinType>>(CoreAddresses::CURRENCY_INFO_ADDRESS());
/// Must abort if the account does not have the MintCapability [[H1]][PERMISSION].
aborts_if !exists<MintCapability<CoinType>>(Signer::spec_address_of(account)) with Errors::REQUIRES_CAPABILITY;
include MintAbortsIf<CoinType>;
include MintEnsures<CoinType>;
}
/// Burns the coins held in the first `Preburn` request in the `PreburnQueue`
/// resource held under `preburn_address` that is equal to `amount`.
/// Calls to this functions will fail if the `account` does not have a
/// published `BurnCapability` for the `CoinType` published under it, or if
/// there is not a `Preburn` request in the `PreburnQueue` that does not
/// equal `amount`.
public fun burn<CoinType: store>(
account: &signer,
preburn_address: address,
amount: u64,
) acquires BurnCapability, CurrencyInfo, PreburnQueue {
let addr = Signer::address_of(account);
assert(exists<BurnCapability<CoinType>>(addr), Errors::requires_capability(EBURN_CAPABILITY));
burn_with_capability(
preburn_address,
borrow_global<BurnCapability<CoinType>>(addr),
amount
)
}
spec burn {
include BurnAbortsIf<CoinType>;
include BurnEnsures<CoinType>;
include BurnWithResourceCapEmits<CoinType>{preburn: spec_make_preburn(amount)};
}
spec schema BurnAbortsIf<CoinType> {
account: signer;
preburn_address: address;
/// Must abort if the account does not have the BurnCapability [[H3]][PERMISSION].
aborts_if !exists<BurnCapability<CoinType>>(Signer::spec_address_of(account)) with Errors::REQUIRES_CAPABILITY;
include BurnWithCapabilityAbortsIf<CoinType>;
}
spec schema BurnEnsures<CoinType> {
account: signer;
preburn_address: address;
include BurnWithCapabilityEnsures<CoinType>;
}
spec schema AbortsIfNoPreburnQueue<CoinType> {
preburn_address: address;
aborts_if !exists<PreburnQueue<CoinType>>(preburn_address) with Errors::NOT_PUBLISHED;
}
/// Cancels the `Preburn` request in the `PreburnQueue` resource held
/// under the `preburn_address` with a value equal to `amount`, and returns the coins.
/// Calls to this will fail if the sender does not have a published
/// `BurnCapability<CoinType>`, or if there is no preburn request
/// outstanding in the `PreburnQueue` resource under `preburn_address` with
/// a value equal to `amount`.
public fun cancel_burn<CoinType: store>(
account: &signer,
preburn_address: address,
amount: u64,
): Diem<CoinType> acquires BurnCapability, CurrencyInfo, PreburnQueue {
let addr = Signer::address_of(account);
assert(exists<BurnCapability<CoinType>>(addr), Errors::requires_capability(EBURN_CAPABILITY));
cancel_burn_with_capability(
preburn_address,
borrow_global<BurnCapability<CoinType>>(addr),
amount,
)
}
spec cancel_burn {
let currency_info = global<CurrencyInfo<CoinType>>(CoreAddresses::CURRENCY_INFO_ADDRESS());
let post post_currency_info = global<CurrencyInfo<CoinType>>(CoreAddresses::CURRENCY_INFO_ADDRESS());
modifies global<PreburnQueue<CoinType>>(preburn_address);
modifies global<CurrencyInfo<CoinType>>(CoreAddresses::CURRENCY_INFO_ADDRESS());
include CancelBurnAbortsIf<CoinType>;
include CancelBurnWithCapEnsures<CoinType>;
include CancelBurnWithCapEmits<CoinType>;
ensures exists<CurrencyInfo<CoinType>>(CoreAddresses::CURRENCY_INFO_ADDRESS());
ensures exists<PreburnQueue<CoinType>>(preburn_address);
ensures post_currency_info == update_field(
currency_info,
preburn_value,
post_currency_info.preburn_value
);
ensures result.value == amount;
ensures result.value > 0;
}
spec schema CancelBurnAbortsIf<CoinType> {
account: signer;
preburn_address: address;
amount: u64;
/// Must abort if the account does not have the BurnCapability [[H3]][PERMISSION].
aborts_if !exists<BurnCapability<CoinType>>(Signer::spec_address_of(account)) with Errors::REQUIRES_CAPABILITY;
include CancelBurnWithCapAbortsIf<CoinType>;
}
/// Mint a new `Diem` coin of `CoinType` currency worth `value`. The
/// caller must have a reference to a `MintCapability<CoinType>`. Only
/// the treasury compliance account or the `0x1::GAS` module can acquire such a
/// reference.
public fun mint_with_capability<CoinType: store>(
value: u64,
_capability: &MintCapability<CoinType>
): Diem<CoinType> acquires CurrencyInfo {
assert_is_currency<CoinType>();
let currency_code = currency_code<CoinType>();
// update market cap resource to reflect minting
let info = borrow_global_mut<CurrencyInfo<CoinType>>(CoreAddresses::CURRENCY_INFO_ADDRESS());
assert(info.can_mint, Errors::invalid_state(EMINTING_NOT_ALLOWED));
assert(MAX_U128 - info.total_value >= (value as u128), Errors::limit_exceeded(ECURRENCY_INFO));
info.total_value = info.total_value + (value as u128);
// don't emit mint events for synthetic currenices as this does not
// change the total value of fiat currencies held on-chain.
if (!info.is_synthetic) {
Event::emit_event(
&mut info.mint_events,
MintEvent{
amount: value,
currency_code,
}
);
};
Diem<CoinType> { value }
}
spec mint_with_capability {
pragma opaque;
modifies global<CurrencyInfo<CoinType>>(CoreAddresses::CURRENCY_INFO_ADDRESS());
ensures exists<CurrencyInfo<CoinType>>(CoreAddresses::CURRENCY_INFO_ADDRESS());
include MintAbortsIf<CoinType>;
include MintEnsures<CoinType>;
include MintEmits<CoinType>;
}
spec schema MintAbortsIf<CoinType> {
value: u64;
include AbortsIfNoCurrency<CoinType>;
aborts_if !spec_currency_info<CoinType>().can_mint with Errors::INVALID_STATE;
aborts_if spec_currency_info<CoinType>().total_value + value > max_u128() with Errors::LIMIT_EXCEEDED;
}
spec schema MintEnsures<CoinType> {
value: u64;
result: Diem<CoinType>;
let currency_info = global<CurrencyInfo<CoinType>>(CoreAddresses::CURRENCY_INFO_ADDRESS());
let post post_currency_info = global<CurrencyInfo<CoinType>>(CoreAddresses::CURRENCY_INFO_ADDRESS());
ensures exists<CurrencyInfo<CoinType>>(CoreAddresses::CURRENCY_INFO_ADDRESS());
ensures post_currency_info == update_field(currency_info, total_value, currency_info.total_value + value);
ensures result.value == value;
}
spec schema MintEmits<CoinType> {
value: u64;
let currency_info = global<CurrencyInfo<CoinType>>(CoreAddresses::CURRENCY_INFO_ADDRESS());
let handle = currency_info.mint_events;
let msg = MintEvent{
amount: value,
currency_code: currency_info.currency_code,
};
emits msg to handle if !currency_info.is_synthetic;
}
/// Add the `coin` to the `preburn.to_burn` field in the `Preburn` resource
/// held in the preburn queue at the address `preburn_address` if it is
/// empty, otherwise raise a `EPREBURN_OCCUPIED` Error. Emits a
/// `PreburnEvent` to the `preburn_events` event stream in the
/// `CurrencyInfo` for the `CoinType` passed in. However, if the currency
/// being preburned is a synthetic currency (`is_synthetic = true`) then no
/// `PreburnEvent` will be emitted.
fun preburn_with_resource<CoinType: store>(
coin: Diem<CoinType>,
preburn: &mut Preburn<CoinType>,
preburn_address: address,
) acquires CurrencyInfo {
let coin_value = value(&coin);
// Throw if already occupied
assert(value(&preburn.to_burn) == 0, Errors::invalid_state(EPREBURN_OCCUPIED));
deposit(&mut preburn.to_burn, coin);
let currency_code = currency_code<CoinType>();
let info = borrow_global_mut<CurrencyInfo<CoinType>>(CoreAddresses::CURRENCY_INFO_ADDRESS());
assert(MAX_U64 - info.preburn_value >= coin_value, Errors::limit_exceeded(ECOIN));
info.preburn_value = info.preburn_value + coin_value;
// don't emit preburn events for synthetic currenices as this does not
// change the total value of fiat currencies held on-chain, and
// therefore no off-chain movement of the backing coins needs to be
// performed.
if (!info.is_synthetic) {
Event::emit_event(
&mut info.preburn_events,
PreburnEvent{
amount: coin_value,
currency_code,
preburn_address,
}
);
};
}
spec preburn_with_resource {
modifies global<CurrencyInfo<CoinType>>(CoreAddresses::CURRENCY_INFO_ADDRESS());
ensures exists<CurrencyInfo<CoinType>>(CoreAddresses::CURRENCY_INFO_ADDRESS());
include PreburnWithResourceAbortsIf<CoinType>{amount: coin.value};
include PreburnEnsures<CoinType>{amount: coin.value};
include PreburnWithResourceEmits<CoinType>{amount: coin.value};
}
spec schema PreburnWithResourceAbortsIf<CoinType> {
amount: u64;
preburn: Preburn<CoinType>;
aborts_if preburn.to_burn.value > 0 with Errors::INVALID_STATE;
include PreburnAbortsIf<CoinType>;
}
spec schema PreburnAbortsIf<CoinType> {
amount: u64;
include AbortsIfNoCurrency<CoinType>;
aborts_if spec_currency_info<CoinType>().preburn_value + amount > MAX_U64 with Errors::LIMIT_EXCEEDED;
}
spec schema PreburnEnsures<CoinType> {
amount: u64;
preburn: Preburn<CoinType>;
let info = spec_currency_info<CoinType>();
let post post_info = spec_currency_info<CoinType>();
ensures post_info == update_field(info, preburn_value, info.preburn_value + amount);
}
spec schema PreburnWithResourceEmits<CoinType> {
amount: u64;
preburn_address: address;
let info = spec_currency_info<CoinType>();
let currency_code = spec_currency_code<CoinType>();
let handle = info.preburn_events;
let msg = PreburnEvent {
amount,
currency_code,
preburn_address,
};
emits msg to handle if !info.is_synthetic;
}
///////////////////////////////////////////////////////////////////////////
// Treasury Compliance specific methods for DDs
///////////////////////////////////////////////////////////////////////////
/// Create a `Preburn<CoinType>` resource.
/// This is useful for places where a module needs to be able to burn coins
/// outside of a Designated Dealer, e.g., for transaction fees, or for the GAS reserve.
public fun create_preburn<CoinType: store>(
dr_account: &signer
): Preburn<CoinType> {
Roles::assert_diem_root(dr_account); /////// 0L /////////
assert_is_currency<CoinType>();
Preburn<CoinType> { to_burn: zero<CoinType>() }
}
spec create_preburn {
include CreatePreburnAbortsIf<CoinType>;
}
spec schema CreatePreburnAbortsIf<CoinType> {
dr_account: signer;
include Roles::AbortsIfNotTreasuryCompliance{account: dr_account};
include AbortsIfNoCurrency<CoinType>;
}
/// Publish an empty `PreburnQueue` resource under the Designated Dealer
/// dealer account `account`.
fun publish_preburn_queue<CoinType: store>(
account: &signer
) {
let account_addr = Signer::address_of(account);
Roles::assert_designated_dealer(account);
assert_is_currency<CoinType>();
assert(
!exists<Preburn<CoinType>>(account_addr),
Errors::invalid_state(EPREBURN)
);
assert(
!exists<PreburnQueue<CoinType>>(account_addr),
Errors::already_published(EPREBURN_QUEUE)
);
move_to(account, PreburnQueue<CoinType> {
preburns: Vector::empty()
})
}
spec publish_preburn_queue {
pragma opaque;
let account_addr = Signer::spec_address_of(account);
modifies global<PreburnQueue<CoinType>>(account_addr);
// the preburn queue cannot already exist
aborts_if exists<PreburnQueue<CoinType>>(account_addr) with Errors::ALREADY_PUBLISHED;
// There cannot be a preburn resource published at the same time as a
// `PreburnQueue` resource of the same currency.
aborts_if exists<Preburn<CoinType>>(account_addr) with Errors::INVALID_STATE;
include PublishPreburnQueueAbortsIf<CoinType>;
include PublishPreburnQueueEnsures<CoinType>;
}
spec schema PublishPreburnQueueAbortsIf<CoinType> {
account: signer;
include Roles::AbortsIfNotDesignatedDealer;
include AbortsIfNoCurrency<CoinType>;
}
spec schema PublishPreburnQueueEnsures<CoinType> {
account: signer;
let account_addr = Signer::spec_address_of(account);
// The preburn queue is published at the end of this function,
ensures exists<PreburnQueue<CoinType>>(account_addr);
// there cannot be a preburn resource for the same currency as the account,
ensures !exists<Preburn<CoinType>>(account_addr);
// and the preburn queue is empty
ensures Vector::length(global<PreburnQueue<CoinType>>(account_addr).preburns) == 0;
}
/// Publish a `Preburn` resource under `account`. This function is
/// used for bootstrapping the designated dealer at account-creation
/// time, and the association TC account `dr_account` (at `CoreAddresses::TREASURY_COMPLIANCE_ADDRESS()`) is creating
/// this resource for the designated dealer `account`.
public fun publish_preburn_queue_to_account<CoinType: store>(
account: &signer,
dr_account: &signer
) acquires CurrencyInfo {
Roles::assert_designated_dealer(account);
Roles::assert_diem_root(dr_account); /////// 0L /////////
assert(!is_synthetic_currency<CoinType>(), Errors::invalid_argument(EIS_SYNTHETIC_CURRENCY));
publish_preburn_queue<CoinType>(account)
}
spec publish_preburn_queue_to_account {
pragma opaque;
let account_addr = Signer::spec_address_of(account);
modifies global<PreburnQueue<CoinType>>(account_addr);
/// The premission "PreburnCurrency" is granted to DesignatedDealer [[H4]][PERMISSION].
/// Must abort if the account does not have the DesignatedDealer role.
include Roles::AbortsIfNotDesignatedDealer;
/// PreburnQueue is published under the DesignatedDealer account.
include PublishPreburnQueueAbortsIf<CoinType>;
include PublishPreburnQueueEnsures<CoinType>;
ensures exists<PreburnQueue<CoinType>>(account_addr);
include Roles::AbortsIfNotTreasuryCompliance{account: dr_account};
include AbortsIfNoCurrency<CoinType>;
aborts_if is_synthetic_currency<CoinType>() with Errors::INVALID_ARGUMENT;
aborts_if exists<PreburnQueue<CoinType>>(account_addr) with Errors::ALREADY_PUBLISHED;
aborts_if exists<Preburn<CoinType>>(account_addr) with Errors::INVALID_STATE;
}
///////////////////////////////////////////////////////////////////////////
/// Upgrade a designated dealer account from using a single `Preburn`
/// resource to using a `PreburnQueue` resource so that multiple preburn
/// requests can be outstanding in the same currency for a designated dealer.
fun upgrade_preburn<CoinType: store>(account: &signer)
acquires Preburn, PreburnQueue {
Roles::assert_designated_dealer(account);
let sender = Signer::address_of(account);
let preburn_exists = exists<Preburn<CoinType>>(sender);
let preburn_queue_exists = exists<PreburnQueue<CoinType>>(sender);
// The DD must already have an existing `Preburn` resource, and not a
// `PreburnQueue` resource already, in order to be upgraded.
if (preburn_exists && !preburn_queue_exists) {
let Preburn { to_burn } = move_from<Preburn<CoinType>>(sender);
publish_preburn_queue<CoinType>(account);
// If the DD has an old preburn balance, this is converted over
// into the new preburn queue when it's upgraded.
if (to_burn.value > 0) {
add_preburn_to_queue(account, PreburnWithMetadata {
preburn: Preburn { to_burn },
metadata: x"",
})
} else {
destroy_zero(to_burn)
};
}
}
spec upgrade_preburn {
let account_addr = Signer::spec_address_of(account);
modifies global<Preburn<CoinType>>(account_addr);
modifies global<PreburnQueue<CoinType>>(account_addr);
include UpgradePreburnAbortsIf<CoinType>;
include UpgradePreburnEnsures<CoinType>;
}
spec schema UpgradePreburnAbortsIf<CoinType> {
account: signer;
let account_addr = Signer::spec_address_of(account);
let upgrade = exists<Preburn<CoinType>>(account_addr) && !exists<PreburnQueue<CoinType>>(account_addr);
/// Must abort if the account doesn't have the `PreburnQueue` or
/// `Preburn` resource to satisfy [[H4]][PERMISSION] of `preburn_to`.
include upgrade ==> PublishPreburnQueueAbortsIf<CoinType>;
include Roles::AbortsIfNotDesignatedDealer;
}
spec schema UpgradePreburnEnsures<CoinType> {
account: signer;
let account_addr = Signer::spec_address_of(account);
let upgrade = exists<Preburn<CoinType>>(account_addr) && !exists<PreburnQueue<CoinType>>(account_addr);
let preburn = global<Preburn<CoinType>>(account_addr);
ensures upgrade ==>
!exists<Preburn<CoinType>>(account_addr) && exists<PreburnQueue<CoinType>>(account_addr);
ensures upgrade && preburn.to_burn.value > 0 ==>
global<PreburnQueue<CoinType>>(account_addr).preburns ==
vec(PreburnWithMetadata { preburn, metadata: x"" });
ensures upgrade && preburn.to_burn.value == 0 ==>
global<PreburnQueue<CoinType>>(account_addr).preburns == vec();
}
/// Add the `preburn` request to the preburn queue of `account`, and check that the
/// number of preburn requests does not exceed `MAX_OUTSTANDING_PREBURNS`.
fun add_preburn_to_queue<CoinType: store>(account: &signer, preburn: PreburnWithMetadata<CoinType>)
acquires PreburnQueue {
let account_addr = Signer::address_of(account);
assert(exists<PreburnQueue<CoinType>>(account_addr), Errors::invalid_state(EPREBURN_QUEUE));
assert(value(&preburn.preburn.to_burn) > 0, Errors::invalid_argument(EPREBURN));
let preburns = &mut borrow_global_mut<PreburnQueue<CoinType>>(account_addr).preburns;
assert(
Vector::length(preburns) < MAX_OUTSTANDING_PREBURNS,
Errors::limit_exceeded(EPREBURN_QUEUE)
);
Vector::push_back(preburns, preburn);
}
spec add_preburn_to_queue {
pragma opaque;
let account_addr = Signer::spec_address_of(account);
let preburns = global<PreburnQueue<CoinType>>(account_addr).preburns;
let post post_preburns = global<PreburnQueue<CoinType>>(account_addr).preburns;
modifies global<PreburnQueue<CoinType>>(account_addr);
aborts_if !exists<PreburnQueue<CoinType>>(account_addr) with Errors::INVALID_STATE;
include AddPreburnToQueueAbortsIf<CoinType>;
ensures exists<PreburnQueue<CoinType>>(account_addr);
ensures Vector::eq_push_back(post_preburns, preburns, preburn);
}
spec schema AddPreburnToQueueAbortsIf<CoinType> {
account: signer;
preburn: PreburnWithMetadata<CoinType>;
let account_addr = Signer::spec_address_of(account);
aborts_if preburn.preburn.to_burn.value == 0 with Errors::INVALID_ARGUMENT;
aborts_if exists<PreburnQueue<CoinType>>(account_addr) &&
Vector::length(global<PreburnQueue<CoinType>>(account_addr).preburns) >= MAX_OUTSTANDING_PREBURNS
with Errors::LIMIT_EXCEEDED;
}
/// Sends `coin` to the preburn queue for `account`, where it will wait to either be burned
/// or returned to the balance of `account`.
/// Calls to this function will fail if:
/// * `account` does not have a `PreburnQueue<CoinType>` resource published under it; or
/// * the preburn queue is already at capacity (i.e., at `MAX_OUTSTANDING_PREBURNS`); or
/// * `coin` has a `value` field of zero.
public fun preburn_to<CoinType: store>(
account: &signer,
coin: Diem<CoinType>
) acquires CurrencyInfo, Preburn, PreburnQueue {
Roles::assert_designated_dealer(account);
// any coin that is preburned needs to have a nonzero value
assert(value(&coin) > 0, Errors::invalid_argument(ECOIN));
let sender = Signer::address_of(account);
// After an upgrade a `Preburn` resource no longer exists in this
// currency, and it is replaced with a `PreburnQueue` resource
// for the same currency.
upgrade_preburn<CoinType>(account);
let preburn = PreburnWithMetadata {
preburn: Preburn { to_burn: zero<CoinType>() },
metadata: x"",
};
preburn_with_resource(coin, &mut preburn.preburn, sender);
add_preburn_to_queue(account, preburn);
}
spec preburn_to {
pragma opaque;
include PreburnToAbortsIf<CoinType>{amount: coin.value};
include PreburnToEnsures<CoinType>{amount: coin.value};
let account_addr = Signer::spec_address_of(account);
include PreburnWithResourceEmits<CoinType>{amount: coin.value, preburn_address: account_addr};
}
spec schema PreburnToAbortsIf<CoinType> {
account: signer;
amount: u64;
let account_addr = Signer::spec_address_of(account);
/// Must abort if the account doesn't have the PreburnQueue or Preburn resource, or has not
/// the correct role [[H4]][PERMISSION].
aborts_if !(exists<Preburn<CoinType>>(account_addr) || exists<PreburnQueue<CoinType>>(account_addr));
include Roles::AbortsIfNotDesignatedDealer;
include PreburnAbortsIf<CoinType>;
include UpgradePreburnAbortsIf<CoinType>;
include AddPreburnToQueueAbortsIf<CoinType>{preburn: PreburnWithMetadata{ preburn: spec_make_preburn(amount), metadata: x"" } };
}
spec schema PreburnToEnsures<CoinType> {
account: signer;
amount: u64;
let account_addr = Signer::spec_address_of(account);
/// Removes the preburn resource if it exists
modifies global<Preburn<CoinType>>(account_addr);
/// Publishes if it doesn't exists. Updates its state either way.
modifies global<PreburnQueue<CoinType>>(account_addr);
ensures exists<PreburnQueue<CoinType>>(account_addr);
// The preburn amount in the currency info can be updated.
modifies global<CurrencyInfo<CoinType>>(CoreAddresses::CURRENCY_INFO_ADDRESS());
include PreburnEnsures<CoinType>{preburn: spec_make_preburn(amount)};
}
/// Remove the oldest preburn request in the `PreburnQueue<CoinType>`
/// resource published under `preburn_address` whose value is equal to `amount`.
/// Calls to this function will fail if:
/// * `preburn_address` doesn't have a `PreburnQueue<CoinType>` resource published under it; or
/// * a preburn request with the correct value for `amount` cannot be found in the preburn queue for `preburn_address`;
fun remove_preburn_from_queue<CoinType: store>(preburn_address: address, amount: u64): PreburnWithMetadata<CoinType>
acquires PreburnQueue {
assert(exists<PreburnQueue<CoinType>>(preburn_address), Errors::not_published(EPREBURN_QUEUE));
// We search from the head of the queue
let index = 0;
let preburn_queue = &mut borrow_global_mut<PreburnQueue<CoinType>>(preburn_address).preburns;
let queue_length = Vector::length(preburn_queue);
while ({
spec {
assert index <= queue_length;
assert forall j in 0..index: preburn_queue[j].preburn.to_burn.value != amount;
};
(index < queue_length)
}) {
let elem = Vector::borrow(preburn_queue, index);
if (value(&elem.preburn.to_burn) == amount) {
let preburn = Vector::remove(preburn_queue, index);
// Make sure that the value is correct
return preburn
};
index = index + 1;
};
spec {
assert index == queue_length;
assert forall j in 0..queue_length: preburn_queue[j].preburn.to_burn.value != amount;
};
// If we didn't return already, we couldn't find a preburn with a matching value.
abort Errors::invalid_state(EPREBURN_NOT_FOUND)
}
spec remove_preburn_from_queue {
pragma opaque;
modifies global<PreburnQueue<CoinType>>(preburn_address);
include RemovePreburnFromQueueAbortsIf<CoinType>;
include RemovePreburnFromQueueEnsures<CoinType>;
ensures result.preburn.to_burn.value == amount;
}
spec schema RemovePreburnFromQueueAbortsIf<CoinType> {
preburn_address: address;
amount: u64;
let preburn_queue = global<PreburnQueue<CoinType>>(preburn_address).preburns;
aborts_if !exists<PreburnQueue<CoinType>>(preburn_address) with Errors::NOT_PUBLISHED;
aborts_if forall i in 0..len(preburn_queue): preburn_queue[i].preburn.to_burn.value != amount with Errors::INVALID_STATE;
}
/// > TODO: See this cannot currently be expressed in the MSL.
/// > See https://github.com/diem/diem/issues/7615 for more information.
spec schema RemovePreburnFromQueueEnsures<CoinType> {
preburn_address: address;
amount: u64;
ensures old(exists<PreburnQueue<CoinType>>(preburn_address)) ==> exists<PreburnQueue<CoinType>>(preburn_address);
// let preburn_queue = global<PreburnQueue<CoinType>>(preburn_address).preburns;
// let preburn = Preburn { to_burn: Diem { value: amount }};
// let (found, index) = Vector::index_of(preburn_queue, preburn);
// ensures found ==> Vector::eq_remove_elem_at_index(index, preburn_queue, old(preburn_queue));
}
/// Permanently removes the coins in the oldest preburn request in the
/// `PreburnQueue` resource under `preburn_address` that has a `to_burn`
/// value of `amount` and updates the market cap accordingly.
/// This function can only be called by the holder of a `BurnCapability<CoinType: store>`.
/// Calls to this function will fail if the there is no `PreburnQueue<CoinType: store>`
/// resource under `preburn_address`, or, if there is no preburn request in
/// the preburn queue with a `to_burn` amount equal to `amount`.
public fun burn_with_capability<CoinType: store>(
preburn_address: address,
capability: &BurnCapability<CoinType>,
amount: u64,
) acquires CurrencyInfo, PreburnQueue {
// Remove the preburn request
let PreburnWithMetadata{ preburn, metadata: _ } = remove_preburn_from_queue<CoinType>(preburn_address, amount);
// Burn the contained coins
burn_with_resource_cap(&mut preburn, preburn_address, capability);
let Preburn { to_burn } = preburn;
destroy_zero(to_burn);
}
spec burn_with_capability {
include BurnWithResourceCapEmits<CoinType>{preburn: spec_make_preburn(amount)};
include BurnWithCapabilityAbortsIf<CoinType>;
include BurnWithCapabilityEnsures<CoinType>;
}
spec schema BurnWithCapabilityAbortsIf<CoinType> {
preburn_address: address;
amount: u64;
let preburn = spec_make_preburn<CoinType>(amount);
include AbortsIfNoPreburnQueue<CoinType>;
include RemovePreburnFromQueueAbortsIf<CoinType>;
include BurnWithResourceCapAbortsIf<CoinType>{preburn: preburn};
}
spec schema BurnWithCapabilityEnsures<CoinType> {
preburn_address: address;
amount: u64;
let preburn = spec_make_preburn<CoinType>(amount);
include BurnWithResourceCapEnsures<CoinType>{preburn: preburn};
include RemovePreburnFromQueueEnsures<CoinType>;
}
/// Permanently removes the coins held in the `Preburn` resource (in `to_burn` field)
/// that was stored in a `PreburnQueue` at `preburn_address` and updates the market cap accordingly.
/// This function can only be called by the holder of a `BurnCapability<CoinType: store>`.
/// Calls to this function will fail if the preburn `to_burn` area for `CoinType` is empty.
fun burn_with_resource_cap<CoinType: store>(
preburn: &mut Preburn<CoinType>,
preburn_address: address,
_capability: &BurnCapability<CoinType>
) acquires CurrencyInfo {
let currency_code = currency_code<CoinType>();
// Abort if no coin present in preburn area
assert(preburn.to_burn.value > 0, Errors::invalid_state(EPREBURN_EMPTY));
// destroy the coin in Preburn area
let Diem { value } = withdraw_all<CoinType>(&mut preburn.to_burn);
// update the market cap
assert_is_currency<CoinType>();
let info = borrow_global_mut<CurrencyInfo<CoinType>>(CoreAddresses::CURRENCY_INFO_ADDRESS());
assert(info.total_value >= (value as u128), Errors::limit_exceeded(ECURRENCY_INFO));
info.total_value = info.total_value - (value as u128);
assert(info.preburn_value >= value, Errors::limit_exceeded(EPREBURN));
info.preburn_value = info.preburn_value - value;
// don't emit burn events for synthetic currenices as this does not
// change the total value of fiat currencies held on-chain.
if (!info.is_synthetic) {
Event::emit_event(
&mut info.burn_events,
BurnEvent {
amount: value,
currency_code,
preburn_address,
}
);
};
}
spec burn_with_resource_cap {
let pre_preburn = preburn;
include BurnWithResourceCapAbortsIf<CoinType>{preburn: pre_preburn};
include BurnWithResourceCapEnsures<CoinType>{preburn: pre_preburn};
include BurnWithResourceCapEmits<CoinType>{preburn: pre_preburn};
}
spec schema BurnWithResourceCapAbortsIf<CoinType> {
preburn: Preburn<CoinType>;
include AbortsIfNoCurrency<CoinType>;
let to_burn = preburn.to_burn.value;
let info = spec_currency_info<CoinType>();
aborts_if to_burn == 0 with Errors::INVALID_STATE;
aborts_if info.total_value < to_burn with Errors::LIMIT_EXCEEDED;
aborts_if info.preburn_value < to_burn with Errors::LIMIT_EXCEEDED;
}
spec schema BurnWithResourceCapEnsures<CoinType> {
preburn: Preburn<CoinType>;
ensures spec_currency_info<CoinType>().total_value
== old(spec_currency_info<CoinType>().total_value) - preburn.to_burn.value;
ensures spec_currency_info<CoinType>().preburn_value
== old(spec_currency_info<CoinType>().preburn_value) - preburn.to_burn.value;
}
spec schema BurnWithResourceCapEmits<CoinType> {
preburn: Preburn<CoinType>;
preburn_address: address;
let info = spec_currency_info<CoinType>();
let currency_code = spec_currency_code<CoinType>();
let handle = info.burn_events;
emits BurnEvent {
amount: preburn.to_burn.value,
currency_code,
preburn_address,
}
to handle if !info.is_synthetic;
}
//////// 0L ////////
// Only the VM should at times be able to burn a coin in its posession.
// should burn immediately, and bypass the Diem preburn stuff.
public fun vm_burn_this_coin<CoinType: store>(
vm: &signer,
coin: Diem<CoinType>,
) acquires CurrencyInfo {
CoreAddresses::assert_vm(vm);
let currency_code = currency_code<CoinType>();
let value = coin.value;
// update the market cap
assert_is_currency<CoinType>();
let info = borrow_global_mut<CurrencyInfo<CoinType>>(CoreAddresses::CURRENCY_INFO_ADDRESS());
assert(info.total_value >= (value as u128), Errors::limit_exceeded(ECURRENCY_INFO));
info.total_value = info.total_value - (value as u128);
// zero and destroy
coin.value = 0;
destroy_zero(coin);
Event::emit_event(
&mut info.burn_events,
BurnEvent {
amount: value,