use lazy_static::lazy_static; use solana_sdk::{ clock::Slot, hash::{Hash, Hasher}, pubkey::Pubkey, }; use std::collections::{HashMap, HashSet}; pub mod instructions_sysvar_enabled { solana_sdk::declare_id!("EnvhHCLvg55P7PDtbvR1NwuTuAeodqpusV3MR5QEK8gs"); } pub mod secp256k1_program_enabled { solana_sdk::declare_id!("E3PHP7w8kB7np3CTQ1qQ2tW3KCtjRSXBQgW9vM2mWv2Y"); } pub mod consistent_recent_blockhashes_sysvar { solana_sdk::declare_id!("3h1BQWPDS5veRsq6mDBWruEpgPxRJkfwGexg5iiQ9mYg"); } pub mod deprecate_rewards_sysvar { solana_sdk::declare_id!("GaBtBJvmS4Arjj5W1NmFcyvPjsHN38UGYDq2MDwbs9Qu"); } pub mod pico_inflation { solana_sdk::declare_id!("4RWNif6C2WCNiKVW7otP4G7dkmkHGyKQWRpuZ1pxKU5m"); } pub mod full_inflation { pub mod devnet_and_testnet { solana_sdk::declare_id!("DT4n6ABDqs6w4bnfwrXT9rsprcPf6cdDga1egctaPkLC"); } pub mod mainnet { pub mod certusone { pub mod vote { solana_sdk::declare_id!("BzBBveUDymEYoYzcMWNQCx3cd4jQs7puaVFHLtsbB6fm"); } pub mod enable { solana_sdk::declare_id!("7XRJcS5Ud5vxGB54JbK9N2vBZVwnwdBNeJW1ibRgD9gx"); } } } } pub mod spl_token_v2_multisig_fix { solana_sdk::declare_id!("E5JiFDQCwyC6QfT9REFyMpfK2mHcmv1GUDySU1Ue7TYv"); } pub mod no_overflow_rent_distribution { solana_sdk::declare_id!("4kpdyrcj5jS47CZb2oJGfVxjYbsMm2Kx97gFyZrxxwXz"); } pub mod stake_program_v2 { solana_sdk::declare_id!("Gvd9gGJZDHGMNf1b3jkxrfBQSR5etrfTQSBNKCvLSFJN"); } pub mod rewrite_stake { solana_sdk::declare_id!("6ap2eGy7wx5JmsWUmQ5sHwEWrFSDUxSti2k5Hbfv5BZG"); } pub mod filter_stake_delegation_accounts { solana_sdk::declare_id!("GE7fRxmW46K6EmCD9AMZSbnaJ2e3LfqCZzdHi9hmYAgi"); } pub mod bpf_loader_upgradeable_program { solana_sdk::declare_id!("FbhK8HN9qvNHvJcoFVHAEUCNkagHvu7DTWzdnLuVQ5u4"); } pub mod stake_program_v3 { solana_sdk::declare_id!("Ego6nTu7WsBcZBvVqJQKp6Yku2N3mrfG8oYCfaLZkAeK"); } pub mod require_custodian_for_locked_stake_authorize { solana_sdk::declare_id!("D4jsDcXaqdW8tDAWn8H4R25Cdns2YwLneujSL1zvjW6R"); } pub mod spl_token_v2_self_transfer_fix { solana_sdk::declare_id!("BL99GYhdjjcv6ys22C9wPgn2aTVERDbPHHo4NbS3hgp7"); } pub mod warp_timestamp_again { solana_sdk::declare_id!("GvDsGDkH5gyzwpDhxNixx8vtx1kwYHH13RiNAPw27zXb"); } pub mod check_init_vote_data { solana_sdk::declare_id!("3ccR6QpxGYsAbWyfevEtBNGfWV4xBffxRj2tD6A9i39F"); } pub mod check_program_owner { solana_sdk::declare_id!("5XnbR5Es9YXEARRuP6mdvoxiW3hx5atNNeBmwVd8P3QD"); } pub mod require_stake_for_gossip { solana_sdk::declare_id!("6oNzd5Z3M2L1xo4Q5hoox7CR2DuW7m1ETLWH5jHJthwa"); } pub mod cpi_data_cost { solana_sdk::declare_id!("Hrg5bXePPGiAVWZfDHbvjqytSeyBDPAGAQ7v6N5i4gCX"); } pub mod upgradeable_close_instruction { solana_sdk::declare_id!("FsPaByos3gA9bUEhp3EimQpQPCoSvCEigHod496NmABQ"); } pub mod demote_sysvar_write_locks { solana_sdk::declare_id!("86LJYRuq2zgtHuL3FccR6hqFJQMQkFoun4knAxcPiF1P"); } pub mod sysvar_via_syscall { solana_sdk::declare_id!("7411E6gFQLDhQkdRjmpXwM1hzHMMoYQUjHicmvGPC1Nf"); } pub mod check_duplicates_by_hash { solana_sdk::declare_id!("8ZqTSYHgzyaYCcXJPMViRy6afCFSgNvYooPDeVdyj5GC"); } pub mod enforce_aligned_host_addrs { solana_sdk::declare_id!("6Qob9Z4RwGdf599FDVCqsjuKjR8ZFR3oVs2ByRLWBsua"); } pub mod set_upgrade_authority_via_cpi_enabled { solana_sdk::declare_id!("GQdjCCptpGECG7QfE35hKTAopB1umGoSrdKfax2VmZWy"); } pub mod update_data_on_realloc { solana_sdk::declare_id!("BkPcYCrwHXBoTsv9vMhiRF9gteZmDj3Uwisz9CDjoMKp"); } pub mod keccak256_syscall_enabled { solana_sdk::declare_id!("7Ua8mFtahVfA3WCY9LoXDAJJdvJRJHckvSSr1dD8FTWc"); } pub mod stake_program_v4 { solana_sdk::declare_id!("Dc7djyhP9aLfdq2zktpvskeAjpG56msCU1yexpxXiWZb"); } pub mod memory_ops_syscalls { solana_sdk::declare_id!("ENQi37wsVhTvFz2gUiZAAbqFEWGN2jwFsqdEDTE8A4MU"); } pub mod add_missing_program_error_mappings { solana_sdk::declare_id!("3QEUpjhgPEt92nz3Mqf6pABkHPGCQwSvKtyGMq4SuQyL"); } pub mod system_transfer_zero_check { solana_sdk::declare_id!("BrTR9hzw4WBGFP65AJMbpAo64DcA3U6jdPSga9fMV5cS"); } lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ (instructions_sysvar_enabled::id(), "instructions sysvar"), (secp256k1_program_enabled::id(), "secp256k1 program"), (consistent_recent_blockhashes_sysvar::id(), "consistent recentblockhashes sysvar"), (deprecate_rewards_sysvar::id(), "deprecate unused rewards sysvar"), (pico_inflation::id(), "pico inflation"), (full_inflation::devnet_and_testnet::id(), "full inflation on devnet and testnet"), (spl_token_v2_multisig_fix::id(), "spl-token multisig fix"), (no_overflow_rent_distribution::id(), "no overflow rent distribution"), (stake_program_v2::id(), "solana_stake_program v2"), (rewrite_stake::id(), "rewrite stake"), (filter_stake_delegation_accounts::id(), "filter stake_delegation_accounts #14062"), (bpf_loader_upgradeable_program::id(), "upgradeable bpf loader"), (stake_program_v3::id(), "solana_stake_program v3"), (require_custodian_for_locked_stake_authorize::id(), "require custodian to authorize withdrawer change for locked stake"), (spl_token_v2_self_transfer_fix::id(), "spl-token self-transfer fix"), (full_inflation::mainnet::certusone::enable::id(), "full inflation enabled by Certus One"), (full_inflation::mainnet::certusone::vote::id(), "community vote allowing Certus One to enable full inflation"), (warp_timestamp_again::id(), "warp timestamp again, adjust bounding to 25% fast 80% slow #15204"), (check_init_vote_data::id(), "check initialized Vote data"), (check_program_owner::id(), "limit programs to operating on accounts owned by itself"), (require_stake_for_gossip::id(), "require stakes for propagating crds values through gossip #15561"), (cpi_data_cost::id(), "charge the compute budget for data passed via CPI"), (upgradeable_close_instruction::id(), "close upgradeable buffer accounts"), (demote_sysvar_write_locks::id(), "demote builtins and sysvar write locks to readonly #15497"), (sysvar_via_syscall::id(), "provide sysvars via syscalls"), (check_duplicates_by_hash::id(), "use transaction message hash for duplicate check"), (enforce_aligned_host_addrs::id(), "enforce aligned host addresses"), (set_upgrade_authority_via_cpi_enabled::id(), "set upgrade authority instruction via cpi calls for upgradable programs"), (update_data_on_realloc::id(), "Retain updated data values modified after realloc via CPI"), (keccak256_syscall_enabled::id(), "keccak256 syscall"), (stake_program_v4::id(), "solana_stake_program v4"), (memory_ops_syscalls::id(), "add syscalls for memory operations"), (add_missing_program_error_mappings::id(), "add missing program error mappings"), (system_transfer_zero_check::id(), "perform all checks for transfers of 0 lamports"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter() .cloned() .collect(); /// Unique identifier of the current software's feature set pub static ref ID: Hash = { let mut hasher = Hasher::default(); let mut feature_ids = FEATURE_NAMES.keys().collect::>(); feature_ids.sort(); for feature in feature_ids { hasher.hash(feature.as_ref()); } hasher.result() }; } #[derive(Clone, PartialEq, Eq, Hash)] pub struct FullInflationFeaturePair { pub vote_id: Pubkey, // Feature that grants the candidate the ability to enable full inflation pub enable_id: Pubkey, // Feature to enable full inflation by the candidate } lazy_static! { /// Set of feature pairs that once enabled will trigger full inflation pub static ref FULL_INFLATION_FEATURE_PAIRS: HashSet = [ FullInflationFeaturePair { vote_id: full_inflation::mainnet::certusone::vote::id(), enable_id: full_inflation::mainnet::certusone::enable::id(), }, ] .iter() .cloned() .collect(); } /// `FeatureSet` holds the set of currently active/inactive runtime features #[derive(AbiExample, Debug, Clone)] pub struct FeatureSet { pub active: HashMap, pub inactive: HashSet, } impl Default for FeatureSet { fn default() -> Self { // All features disabled Self { active: HashMap::new(), inactive: FEATURE_NAMES.keys().cloned().collect(), } } } impl FeatureSet { pub fn is_active(&self, feature_id: &Pubkey) -> bool { self.active.contains_key(feature_id) } pub fn activated_slot(&self, feature_id: &Pubkey) -> Option { self.active.get(feature_id).copied() } /// List of enabled features that trigger full inflation pub fn full_inflation_features_enabled(&self) -> HashSet { let mut hash_set = FULL_INFLATION_FEATURE_PAIRS .iter() .filter_map(|pair| { if self.is_active(&pair.vote_id) && self.is_active(&pair.enable_id) { Some(pair.enable_id) } else { None } }) .collect::>(); if self.is_active(&full_inflation::devnet_and_testnet::id()) { hash_set.insert(full_inflation::devnet_and_testnet::id()); } hash_set } /// All features enabled, useful for testing pub fn all_enabled() -> Self { Self { active: FEATURE_NAMES.keys().cloned().map(|key| (key, 0)).collect(), inactive: HashSet::new(), } } } #[cfg(test)] mod test { use super::*; #[test] fn test_full_inflation_features_enabled_devnet_and_testnet() { let mut feature_set = FeatureSet::default(); assert!(feature_set.full_inflation_features_enabled().is_empty()); feature_set .active .insert(full_inflation::devnet_and_testnet::id(), 42); assert_eq!( feature_set.full_inflation_features_enabled(), [full_inflation::devnet_and_testnet::id()] .iter() .cloned() .collect() ); } #[test] fn test_full_inflation_features_enabled() { // Normal sequence: vote_id then enable_id let mut feature_set = FeatureSet::default(); assert!(feature_set.full_inflation_features_enabled().is_empty()); feature_set .active .insert(full_inflation::mainnet::certusone::vote::id(), 42); assert!(feature_set.full_inflation_features_enabled().is_empty()); feature_set .active .insert(full_inflation::mainnet::certusone::enable::id(), 42); assert_eq!( feature_set.full_inflation_features_enabled(), [full_inflation::mainnet::certusone::enable::id()] .iter() .cloned() .collect() ); // Backwards sequence: enable_id and then vote_id let mut feature_set = FeatureSet::default(); assert!(feature_set.full_inflation_features_enabled().is_empty()); feature_set .active .insert(full_inflation::mainnet::certusone::enable::id(), 42); assert!(feature_set.full_inflation_features_enabled().is_empty()); feature_set .active .insert(full_inflation::mainnet::certusone::vote::id(), 42); assert_eq!( feature_set.full_inflation_features_enabled(), [full_inflation::mainnet::certusone::enable::id()] .iter() .cloned() .collect() ); } }