Skip to content

Commit e560a0a

Browse files
committed
sip-031 mint-and-transfer per tenure infrastructure
1 parent 006a6c9 commit e560a0a

File tree

2 files changed

+251
-4
lines changed
  • stacks-common/src/types
  • stackslib/src/chainstate/nakamoto

2 files changed

+251
-4
lines changed

stacks-common/src/types/mod.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,23 @@ impl StacksEpochId {
549549
),
550550
}
551551
}
552+
553+
/// Whether or not this epoch is part of the SIP-031 schedule
554+
pub fn includes_sip_031(&self) -> bool {
555+
match self {
556+
StacksEpochId::Epoch10
557+
| StacksEpochId::Epoch20
558+
| StacksEpochId::Epoch2_05
559+
| StacksEpochId::Epoch21
560+
| StacksEpochId::Epoch22
561+
| StacksEpochId::Epoch23
562+
| StacksEpochId::Epoch24
563+
| StacksEpochId::Epoch25
564+
| StacksEpochId::Epoch30
565+
| StacksEpochId::Epoch31 => false,
566+
StacksEpochId::Epoch32 => true,
567+
}
568+
}
552569
}
553570

554571
impl std::fmt::Display for StacksEpochId {

stackslib/src/chainstate/nakamoto/mod.rs

Lines changed: 234 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@
1616

1717
use std::collections::{HashMap, HashSet};
1818
use std::ops::{Deref, DerefMut, Range};
19+
use std::sync::LazyLock;
1920

2021
use clarity::util::secp256k1::Secp256k1PublicKey;
2122
use clarity::vm::ast::ASTRules;
2223
use clarity::vm::costs::ExecutionCost;
23-
use clarity::vm::events::StacksTransactionEvent;
24+
use clarity::vm::events::{STXEventType, STXMintEventData, StacksTransactionEvent};
2425
use clarity::vm::types::PrincipalData;
2526
use clarity::vm::{ClarityVersion, Value};
2627
use lazy_static::lazy_static;
@@ -32,7 +33,9 @@ use stacks_common::codec::{
3233
read_next, write_next, Error as CodecError, StacksMessageCodec, MAX_MESSAGE_LEN,
3334
MAX_PAYLOAD_LEN,
3435
};
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+
};
3639
use stacks_common::types::chainstate::{
3740
BlockHeaderHash, BurnchainHeaderHash, ConsensusHash, SortitionId, StacksAddress, StacksBlockId,
3841
StacksPrivateKey, StacksPublicKey, TrieHash, VRFSeed,
@@ -74,6 +77,7 @@ use crate::chainstate::nakamoto::tenure::{
7477
NakamotoTenureEventId, NAKAMOTO_TENURES_SCHEMA_1, NAKAMOTO_TENURES_SCHEMA_2,
7578
NAKAMOTO_TENURES_SCHEMA_3,
7679
};
80+
use crate::chainstate::stacks::boot::SIP_031_NAME;
7781
use crate::chainstate::stacks::db::blocks::DummyEventDispatcher;
7882
use crate::chainstate::stacks::db::{
7983
DBConfig as ChainstateConfig, StacksChainState, StacksDBConn, StacksDBTx,
@@ -573,6 +577,176 @@ impl MaturedMinerRewards {
573577
}
574578
}
575579

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+
576750
/// Result of preparing to produce or validate a block
577751
pub struct SetupBlockResult<'a, 'b> {
578752
/// Handle to the ClarityVM
@@ -4042,6 +4216,8 @@ impl NakamotoChainState {
40424216
"parent_header_hash" => %parent_header_hash,
40434217
);
40444218

4219+
let evaluated_epoch = clarity_tx.get_epoch();
4220+
40454221
if new_tenure {
40464222
clarity_tx
40474223
.connection()
@@ -4061,9 +4237,63 @@ impl NakamotoChainState {
40614237
);
40624238
e
40634239
})?;
4064-
}
40654240

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\nNEW 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+
}
40674297

40684298
let auto_unlock_events = if evaluated_epoch >= StacksEpochId::Epoch21 {
40694299
let unlock_events = StacksChainState::check_and_handle_reward_start(

0 commit comments

Comments
 (0)