diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 0b2598f8dc..673530f6fe 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -2137,66 +2137,6 @@ mod tests { assert_eq!(bank.get_slots_in_epoch(5000), genesis_block.slots_per_epoch); } - #[test] - fn test_epoch_schedule() { - // one week of slots at 8 ticks/slot, 10 ticks/sec is - // (1 * 7 * 24 * 4500u64).next_power_of_two(); - - // test values between MINIMUM_SLOT_LEN and MINIMUM_SLOT_LEN * 16, should cover a good mix - for slots_per_epoch in MINIMUM_SLOT_LENGTH as u64..=MINIMUM_SLOT_LENGTH as u64 * 16 { - let epoch_schedule = EpochSchedule::new(slots_per_epoch, slots_per_epoch / 2, true); - - assert_eq!(epoch_schedule.get_first_slot_in_epoch(0), 0); - assert_eq!( - epoch_schedule.get_last_slot_in_epoch(0), - MINIMUM_SLOT_LENGTH as u64 - 1 - ); - - let mut last_stakers = 0; - let mut last_epoch = 0; - let mut last_slots_in_epoch = MINIMUM_SLOT_LENGTH as u64; - for slot in 0..(2 * slots_per_epoch) { - // verify that stakers_epoch is continuous over the warmup - // and into the first normal epoch - - let stakers = epoch_schedule.get_stakers_epoch(slot); - if stakers != last_stakers { - assert_eq!(stakers, last_stakers + 1); - last_stakers = stakers; - } - - let (epoch, offset) = epoch_schedule.get_epoch_and_slot_index(slot); - - // verify that epoch increases continuously - if epoch != last_epoch { - assert_eq!(epoch, last_epoch + 1); - last_epoch = epoch; - assert_eq!(epoch_schedule.get_first_slot_in_epoch(epoch), slot); - assert_eq!(epoch_schedule.get_last_slot_in_epoch(epoch - 1), slot - 1); - - // verify that slots in an epoch double continuously - // until they reach slots_per_epoch - - let slots_in_epoch = epoch_schedule.get_slots_in_epoch(epoch); - if slots_in_epoch != last_slots_in_epoch { - if slots_in_epoch != slots_per_epoch { - assert_eq!(slots_in_epoch, last_slots_in_epoch * 2); - } - } - last_slots_in_epoch = slots_in_epoch; - } - // verify that the slot offset is less than slots_in_epoch - assert!(offset < last_slots_in_epoch); - } - - // assert that these changed ;) - assert!(last_stakers != 0); // t - assert!(last_epoch != 0); - // assert that we got to "normal" mode - assert!(last_slots_in_epoch == slots_per_epoch); - } - } - #[test] fn test_is_delta_true() { let (genesis_block, mint_keypair) = create_genesis_block(500); diff --git a/runtime/src/epoch_schedule.rs b/runtime/src/epoch_schedule.rs index cc6fd725e7..35bfb89f95 100644 --- a/runtime/src/epoch_schedule.rs +++ b/runtime/src/epoch_schedule.rs @@ -2,7 +2,7 @@ use solana_vote_api::vote_state::MAX_LOCKOUT_HISTORY; pub const MINIMUM_SLOT_LENGTH: usize = MAX_LOCKOUT_HISTORY + 1; -#[derive(Default, Debug, PartialEq, Eq, Clone, Copy, Deserialize, Serialize)] +#[derive(Default, Debug, Clone, Copy, PartialEq, Deserialize, Serialize)] pub struct EpochSchedule { /// The maximum number of slots in each epoch. pub slots_per_epoch: u64, @@ -102,3 +102,68 @@ impl EpochSchedule { self.get_first_slot_in_epoch(epoch) + self.get_slots_in_epoch(epoch) - 1 } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_epoch_schedule() { + // one week of slots at 8 ticks/slot, 10 ticks/sec is + // (1 * 7 * 24 * 4500u64).next_power_of_two(); + + // test values between MINIMUM_SLOT_LEN and MINIMUM_SLOT_LEN * 16, should cover a good mix + for slots_per_epoch in MINIMUM_SLOT_LENGTH as u64..=MINIMUM_SLOT_LENGTH as u64 * 16 { + let epoch_schedule = EpochSchedule::new(slots_per_epoch, slots_per_epoch / 2, true); + + assert_eq!(epoch_schedule.get_first_slot_in_epoch(0), 0); + assert_eq!( + epoch_schedule.get_last_slot_in_epoch(0), + MINIMUM_SLOT_LENGTH as u64 - 1 + ); + + let mut last_stakers = 0; + let mut last_epoch = 0; + let mut last_slots_in_epoch = MINIMUM_SLOT_LENGTH as u64; + for slot in 0..(2 * slots_per_epoch) { + // verify that stakers_epoch is continuous over the warmup + // and into the first normal epoch + + let stakers = epoch_schedule.get_stakers_epoch(slot); + if stakers != last_stakers { + assert_eq!(stakers, last_stakers + 1); + last_stakers = stakers; + } + + let (epoch, offset) = epoch_schedule.get_epoch_and_slot_index(slot); + + // verify that epoch increases continuously + if epoch != last_epoch { + assert_eq!(epoch, last_epoch + 1); + last_epoch = epoch; + assert_eq!(epoch_schedule.get_first_slot_in_epoch(epoch), slot); + assert_eq!(epoch_schedule.get_last_slot_in_epoch(epoch - 1), slot - 1); + + // verify that slots in an epoch double continuously + // until they reach slots_per_epoch + + let slots_in_epoch = epoch_schedule.get_slots_in_epoch(epoch); + if slots_in_epoch != last_slots_in_epoch { + if slots_in_epoch != slots_per_epoch { + assert_eq!(slots_in_epoch, last_slots_in_epoch * 2); + } + } + last_slots_in_epoch = slots_in_epoch; + } + // verify that the slot offset is less than slots_in_epoch + assert!(offset < last_slots_in_epoch); + } + + // assert that these changed ;) + assert!(last_stakers != 0); // t + assert!(last_epoch != 0); + // assert that we got to "normal" mode + assert!(last_slots_in_epoch == slots_per_epoch); + } + } +} diff --git a/sdk/src/genesis_block.rs b/sdk/src/genesis_block.rs index ecaf0fd2ab..9c2cfc6cff 100644 --- a/sdk/src/genesis_block.rs +++ b/sdk/src/genesis_block.rs @@ -3,6 +3,7 @@ use crate::account::Account; use crate::fee_calculator::FeeCalculator; use crate::hash::{hash, Hash}; +use crate::inflation::Inflation; use crate::poh_config::PohConfig; use crate::pubkey::Pubkey; use crate::signature::{Keypair, KeypairUtil}; @@ -22,6 +23,7 @@ pub struct GenesisBlock { pub stakers_slot_offset: u64, pub epoch_warmup: bool, pub poh_config: PohConfig, + pub inflation: Inflation, } // useful for basic tests @@ -50,6 +52,7 @@ impl Default for GenesisBlock { stakers_slot_offset: DEFAULT_SLOTS_PER_EPOCH, ticks_per_slot: DEFAULT_TICKS_PER_SLOT, poh_config: PohConfig::default(), + inflation: Inflation::default(), } } } diff --git a/sdk/src/inflation.rs b/sdk/src/inflation.rs new file mode 100644 index 0000000000..1e3886c016 --- /dev/null +++ b/sdk/src/inflation.rs @@ -0,0 +1,118 @@ +//! configuration for network inflation + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +pub struct Inflation { + /// Initial inflation percentage, from time=0 + pub initial: f64, + + /// Terminal inflation percentage, to time=INF + pub terminal: f64, + + /// Rate per year, at which inflation is lowered until reaching terminal + /// i.e. inflation(year) == MAX(terminal, initial*((1-taper)^year)) + pub taper: f64, + + /// Percentage of total inflation allocated to the foundation + pub foundation: f64, + /// Duration of foundationt pool inflation, in years + pub foundation_term: f64, + + /// Percentage of total inflation allocated to grant pools + pub grant: f64, + /// Duration of grant pool inflation, in years + pub grant_term: f64, + + /// Percentage of total inflation allocated to replicator rewards + pub replicator: f64, +} + +const DEFAULT_INITIAL: f64 = 0.15; +const DEFAULT_TERMINAL: f64 = 0.015; +const DEFAULT_TAPER: f64 = 0.15; +const DEFAULT_FOUNDATION: f64 = 0.05; +const DEFAULT_GRANT: f64 = 0.05; +const DEFAULT_FOUNDATION_GRANT_TERM: f64 = 7.0; +const DEFAULT_REPLICATOR: f64 = 0.10; + +impl Default for Inflation { + fn default() -> Self { + Self { + initial: DEFAULT_INITIAL, + terminal: DEFAULT_TERMINAL, + taper: DEFAULT_TAPER, + foundation: DEFAULT_FOUNDATION, + foundation_term: DEFAULT_FOUNDATION_GRANT_TERM, + grant: DEFAULT_GRANT, + grant_term: DEFAULT_FOUNDATION_GRANT_TERM, + replicator: DEFAULT_REPLICATOR, + } + } +} + +impl Inflation { + /// inflation rate at year + pub fn total(&self, year: f64) -> f64 { + let tapered = self.initial * ((1.0 - self.taper).powf(year)); + + if tapered > self.terminal { + tapered + } else { + self.terminal + } + } + + /// portion of total that goes to validators + pub fn validator(&self, year: f64) -> f64 { + self.total(year) - self.replicator(year) - self.grant(year) - self.foundation(year) + } + + /// portion of total that goes to replicators + pub fn replicator(&self, year: f64) -> f64 { + self.total(year) * self.replicator + } + + /// portion of total that goes to grant pools + pub fn grant(&self, year: f64) -> f64 { + if year < self.grant_term { + self.total(year) * self.grant + } else { + 0.0 + } + } + + /// portion of total that goes to foundation + pub fn foundation(&self, year: f64) -> f64 { + if year < self.foundation_term { + self.total(year) * self.foundation + } else { + 0.0 + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_inflation_basic() { + let inflation = Inflation::default(); + + let mut last = inflation.total(0.0); + + for year in &[0.1, 0.5, 1.0, DEFAULT_FOUNDATION_GRANT_TERM, 100.0] { + let total = inflation.total(*year); + assert_eq!( + total, + inflation.validator(*year) + + inflation.replicator(*year) + + inflation.grant(*year) + + inflation.foundation(*year) + ); + assert!(total < last); + assert!(total >= inflation.terminal); + last = total; + } + assert_eq!(last, inflation.terminal); + } +} diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index aa4c18549c..addcdd99c9 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -5,6 +5,7 @@ pub mod client; pub mod fee_calculator; pub mod genesis_block; pub mod hash; +pub mod inflation; pub mod instruction; pub mod instruction_processor_utils; pub mod loader_instruction;