16
16
17
17
use std:: collections:: { HashMap , HashSet } ;
18
18
use std:: ops:: { Deref , DerefMut , Range } ;
19
+ use std:: sync:: LazyLock ;
19
20
20
21
use clarity:: util:: secp256k1:: Secp256k1PublicKey ;
21
22
use clarity:: vm:: ast:: ASTRules ;
22
23
use clarity:: vm:: costs:: ExecutionCost ;
23
- use clarity:: vm:: events:: StacksTransactionEvent ;
24
+ use clarity:: vm:: events:: { STXEventType , STXMintEventData , StacksTransactionEvent } ;
24
25
use clarity:: vm:: types:: PrincipalData ;
25
26
use clarity:: vm:: { ClarityVersion , Value } ;
26
27
use lazy_static:: lazy_static;
@@ -32,7 +33,9 @@ use stacks_common::codec::{
32
33
read_next, write_next, Error as CodecError , StacksMessageCodec , MAX_MESSAGE_LEN ,
33
34
MAX_PAYLOAD_LEN ,
34
35
} ;
35
- use stacks_common:: consts:: { FIRST_BURNCHAIN_CONSENSUS_HASH , FIRST_STACKS_BLOCK_HASH } ;
36
+ use stacks_common:: consts:: {
37
+ FIRST_BURNCHAIN_CONSENSUS_HASH , FIRST_STACKS_BLOCK_HASH , MICROSTACKS_PER_STACKS ,
38
+ } ;
36
39
use stacks_common:: types:: chainstate:: {
37
40
BlockHeaderHash , BurnchainHeaderHash , ConsensusHash , SortitionId , StacksAddress , StacksBlockId ,
38
41
StacksPrivateKey , StacksPublicKey , TrieHash , VRFSeed ,
@@ -74,6 +77,7 @@ use crate::chainstate::nakamoto::tenure::{
74
77
NakamotoTenureEventId , NAKAMOTO_TENURES_SCHEMA_1 , NAKAMOTO_TENURES_SCHEMA_2 ,
75
78
NAKAMOTO_TENURES_SCHEMA_3 ,
76
79
} ;
80
+ use crate :: chainstate:: stacks:: boot:: SIP_031_NAME ;
77
81
use crate :: chainstate:: stacks:: db:: blocks:: DummyEventDispatcher ;
78
82
use crate :: chainstate:: stacks:: db:: {
79
83
DBConfig as ChainstateConfig , StacksChainState , StacksDBConn , StacksDBTx ,
@@ -573,6 +577,176 @@ impl MaturedMinerRewards {
573
577
}
574
578
}
575
579
580
+ /// Struct describing the intervals in which SIP-031 emission are applied.
581
+ #[ derive( Debug , Clone , PartialEq , Serialize , Deserialize ) ]
582
+ pub struct SIP031EmissionInterval {
583
+ /// amount of uSTX to emit
584
+ pub amount : u128 ,
585
+ /// height of the burn chain in which the interval starts
586
+ pub start_height : u64 ,
587
+ }
588
+
589
+ // From SIP-031:
590
+ //
591
+ // | Bitcoin Height | STX Emission |
592
+ // |----------------|------------ |
593
+ // | 907,740 | 475 |
594
+ // | 960,300 | 1,140 |
595
+ // | 1,012,860 | 1,705 |
596
+ // | 1,065,420 | 1,305 |
597
+ // | 1,117,980 | 1,155 |
598
+ // | 1,170,540 | 0 |
599
+
600
+ /// Mainnet sip-031 emission intervals
601
+ pub static SIP031_EMISSION_INTERVALS_MAINNET : LazyLock < [ SIP031EmissionInterval ; 6 ] > =
602
+ LazyLock :: new ( || {
603
+ let emissions_schedule = [
604
+ SIP031EmissionInterval {
605
+ amount : 0 ,
606
+ start_height : 1_170_540 ,
607
+ } ,
608
+ SIP031EmissionInterval {
609
+ amount : 1_155 * u128:: from ( MICROSTACKS_PER_STACKS ) ,
610
+ start_height : 1_117_980 ,
611
+ } ,
612
+ SIP031EmissionInterval {
613
+ amount : 1_305 * u128:: from ( MICROSTACKS_PER_STACKS ) ,
614
+ start_height : 1_065_420 ,
615
+ } ,
616
+ SIP031EmissionInterval {
617
+ amount : 1_705 * u128:: from ( MICROSTACKS_PER_STACKS ) ,
618
+ start_height : 1_012_860 ,
619
+ } ,
620
+ SIP031EmissionInterval {
621
+ amount : 1_140 * u128:: from ( MICROSTACKS_PER_STACKS ) ,
622
+ start_height : 960_300 ,
623
+ } ,
624
+ SIP031EmissionInterval {
625
+ amount : 475 * u128:: from ( MICROSTACKS_PER_STACKS ) ,
626
+ start_height : 907_740 ,
627
+ } ,
628
+ ] ;
629
+ assert ! ( SIP031EmissionInterval :: check_inversed_order(
630
+ & emissions_schedule
631
+ ) ) ;
632
+ emissions_schedule
633
+ } ) ;
634
+
635
+ /// Testnet sip-031 emission intervals (starting from 2100, basically dummy values)
636
+ pub static SIP031_EMISSION_INTERVALS_TESTNET : LazyLock < [ SIP031EmissionInterval ; 6 ] > =
637
+ LazyLock :: new ( || {
638
+ let emissions_schedule = [
639
+ SIP031EmissionInterval {
640
+ amount : 0 ,
641
+ start_height : 2_600 ,
642
+ } ,
643
+ SIP031EmissionInterval {
644
+ amount : 0 ,
645
+ start_height : 2_500 ,
646
+ } ,
647
+ SIP031EmissionInterval {
648
+ amount : 0 ,
649
+ start_height : 2_400 ,
650
+ } ,
651
+ SIP031EmissionInterval {
652
+ amount : 0 ,
653
+ start_height : 2_300 ,
654
+ } ,
655
+ SIP031EmissionInterval {
656
+ amount : 0 ,
657
+ start_height : 2_200 ,
658
+ } ,
659
+ SIP031EmissionInterval {
660
+ amount : 0 ,
661
+ start_height : 2_100 ,
662
+ } ,
663
+ ] ;
664
+ assert ! ( SIP031EmissionInterval :: check_inversed_order(
665
+ & emissions_schedule
666
+ ) ) ;
667
+ emissions_schedule
668
+ } ) ;
669
+
670
+ /// Used for testing to substitute a sip-031 emission schedule
671
+ #[ cfg( test) ]
672
+ pub static SIP031_EMISSION_INTERVALS_TEST : std:: sync:: Mutex < Option < Vec < SIP031EmissionInterval > > > =
673
+ std:: sync:: Mutex :: new ( None ) ;
674
+
675
+ #[ cfg( test) ]
676
+ pub fn set_test_sip_031_emission_schedule ( emission_schedule : Option < Vec < SIP031EmissionInterval > > ) {
677
+ match SIP031_EMISSION_INTERVALS_TEST . lock ( ) {
678
+ Ok ( mut schedule_guard) => {
679
+ * schedule_guard = emission_schedule;
680
+ }
681
+ Err ( _e) => {
682
+ panic ! ( "SIP031_EMISSION_INTERVALS_TEST mutex poisoned" ) ;
683
+ }
684
+ }
685
+ }
686
+
687
+ #[ cfg( test) ]
688
+ fn get_sip_031_emission_schedule ( _mainnet : bool ) -> Vec < SIP031EmissionInterval > {
689
+ match SIP031_EMISSION_INTERVALS_TEST . lock ( ) {
690
+ Ok ( schedule_opt) => {
691
+ if let Some ( schedule) = ( * schedule_opt) . as_ref ( ) {
692
+ info ! ( "Use overridden SIP-031 emission schedule {:?}" , & schedule) ;
693
+ return schedule. clone ( ) ;
694
+ } else {
695
+ return vec ! [ ] ;
696
+ }
697
+ }
698
+ Err ( _e) => {
699
+ panic ! ( "COINBASE_INTERVALS_TEST mutex poisoned" ) ;
700
+ }
701
+ }
702
+ }
703
+
704
+ #[ cfg( not( test) ) ]
705
+ fn get_sip_031_emission_schedule ( mainnet : bool ) -> Vec < SIP031EmissionInterval > {
706
+ if mainnet {
707
+ SIP031_EMISSION_INTERVALS_MAINNET . to_vec ( )
708
+ } else {
709
+ SIP031_EMISSION_INTERVALS_TESTNET . to_vec ( )
710
+ }
711
+ }
712
+
713
+ impl SIP031EmissionInterval {
714
+ /// Look up the amount of STX to emit at the start of the tenure at the specified height.
715
+ /// Precondition: `intervals` must be sorted in descending order by `start_height`
716
+ pub fn get_sip_031_emission_at_height ( height : u64 , mainnet : bool ) -> u128 {
717
+ let intervals = get_sip_031_emission_schedule ( mainnet) ;
718
+
719
+ if intervals. is_empty ( ) {
720
+ return 0 ;
721
+ }
722
+
723
+ for interval in intervals {
724
+ if height >= interval. start_height {
725
+ return interval. amount ;
726
+ }
727
+ }
728
+
729
+ // default emission (out of SIP-031 ranges)
730
+ return 0 ;
731
+ }
732
+
733
+ /// Verify that a list of intervals is sorted in descending order by `start_height`
734
+ pub fn check_inversed_order ( intervals : & [ SIP031EmissionInterval ] ) -> bool {
735
+ if intervals. len ( ) < 2 {
736
+ return true ;
737
+ }
738
+
739
+ let mut ht = intervals[ 0 ] . start_height ;
740
+ for interval in intervals. iter ( ) . skip ( 1 ) {
741
+ if interval. start_height > ht {
742
+ return false ;
743
+ }
744
+ ht = interval. start_height ;
745
+ }
746
+ true
747
+ }
748
+ }
749
+
576
750
/// Result of preparing to produce or validate a block
577
751
pub struct SetupBlockResult < ' a , ' b > {
578
752
/// Handle to the ClarityVM
@@ -4042,6 +4216,8 @@ impl NakamotoChainState {
4042
4216
"parent_header_hash" => %parent_header_hash,
4043
4217
) ;
4044
4218
4219
+ let evaluated_epoch = clarity_tx. get_epoch ( ) ;
4220
+
4045
4221
if new_tenure {
4046
4222
clarity_tx
4047
4223
. connection ( )
@@ -4061,9 +4237,63 @@ impl NakamotoChainState {
4061
4237
) ;
4062
4238
e
4063
4239
} ) ?;
4064
- }
4065
4240
4066
- let evaluated_epoch = clarity_tx. get_epoch ( ) ;
4241
+ let mainnet = clarity_tx. config . mainnet ;
4242
+
4243
+ if evaluated_epoch. includes_sip_031 ( ) {
4244
+ println ! (
4245
+ "\n \n NEW TENURE {} {} (parent: {}) {:?} {}\n \n " ,
4246
+ coinbase_height,
4247
+ burn_header_height,
4248
+ parent_burn_height,
4249
+ evaluated_epoch,
4250
+ tx_receipts. len( )
4251
+ ) ;
4252
+
4253
+ let sip_031_mint_and_transfer_amount =
4254
+ SIP031EmissionInterval :: get_sip_031_emission_at_height (
4255
+ u64:: from ( burn_header_height) ,
4256
+ mainnet,
4257
+ ) ;
4258
+
4259
+ if sip_031_mint_and_transfer_amount > 0 {
4260
+ let boot_code_address = boot_code_addr ( mainnet) ;
4261
+ let boot_code_auth = boot_code_tx_auth ( boot_code_address. clone ( ) ) ;
4262
+
4263
+ let recipient = PrincipalData :: Contract ( boot_code_id ( SIP_031_NAME , mainnet) ) ;
4264
+
4265
+ let mint_and_account_receipt =
4266
+ clarity_tx. connection ( ) . as_transaction ( |tx_conn| {
4267
+ tx_conn
4268
+ . with_clarity_db ( |db| {
4269
+ db. increment_ustx_liquid_supply (
4270
+ sip_031_mint_and_transfer_amount,
4271
+ )
4272
+ . map_err ( |e| e. into ( ) )
4273
+ } )
4274
+ . expect ( "FATAL: `SIP-031 mint` overflowed" ) ;
4275
+ StacksChainState :: account_credit (
4276
+ tx_conn,
4277
+ & recipient,
4278
+ u64:: try_from ( sip_031_mint_and_transfer_amount)
4279
+ . expect ( "FATAL: transferred more STX than exist" ) ,
4280
+ ) ;
4281
+ } ) ;
4282
+
4283
+ let event = STXEventType :: STXMintEvent ( STXMintEventData {
4284
+ recipient,
4285
+ amount : sip_031_mint_and_transfer_amount,
4286
+ } ) ;
4287
+
4288
+ /*
4289
+ .events
4290
+ .push(StacksTransactionEvent::STXEvent(event));
4291
+
4292
+ tx_receipts.push(sip_031_initialization_receipt);
4293
+ */
4294
+ }
4295
+ }
4296
+ }
4067
4297
4068
4298
let auto_unlock_events = if evaluated_epoch >= StacksEpochId :: Epoch21 {
4069
4299
let unlock_events = StacksChainState :: check_and_handle_reward_start (
0 commit comments