From 25460f76e709a9768b2d68b2f17428209c410c24 Mon Sep 17 00:00:00 2001 From: Joe C Date: Wed, 4 Oct 2023 19:54:59 +0200 Subject: [PATCH] Bank: Add function to replace empty account with upgradeable program on feature activation (#32783) * replace program account * modify for all cases * remove non-data swap * address tests & conditional feedback * get the rent involved * mix in owner & executable * feature-related cases * stripped back to feature-specific case only * added feature * address initial feedback * added more lamport checks * condense tests * using test_case * add fail cases to tests * more cleanup * add verifiably built program * update program account state * cleaned up serializing logic * use full word capitalization * rename old & new to dst & src * swap src and dst in parameters * add warnings and errors * rename feature to programify * test suite description clarity * remove strings from datapoints * spell out source and destination * more verbose comments in account replace functions * move lamport calculation * swap lamport check for state check * move replace functions to helper module * make replace_account methods fallible * refactor error handling * add test for source program state --- runtime/src/bank.rs | 56 +-- runtime/src/bank/replace_account.rs | 191 ++++++++++ runtime/src/bank/tests.rs | 421 +++++++++++++++++++-- runtime/src/inline_feature_gate_program.rs | 5 + runtime/src/lib.rs | 1 + sdk/src/feature_set.rs | 5 + 6 files changed, 615 insertions(+), 64 deletions(-) create mode 100644 runtime/src/bank/replace_account.rs create mode 100644 runtime/src/inline_feature_gate_program.rs diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index adc9da3a4b..68e5492186 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -42,6 +42,7 @@ use { builtins::{BuiltinPrototype, BUILTINS}, epoch_rewards_hasher::hash_rewards_into_partitions, epoch_stakes::{EpochStakes, NodeVoteAccounts}, + inline_feature_gate_program, runtime_config::RuntimeConfig, serde_snapshot::BankIncrementalSnapshotPersistence, snapshot_hash::SnapshotHash, @@ -215,6 +216,7 @@ pub mod bank_hash_details; mod builtin_programs; pub mod epoch_accounts_hash_utils; mod metrics; +mod replace_account; mod serde_snapshot; mod sysvar_cache; #[cfg(test)] @@ -8054,6 +8056,24 @@ impl Bank { if new_feature_activations.contains(&feature_set::update_hashes_per_tick::id()) { self.apply_updated_hashes_per_tick(DEFAULT_HASHES_PER_TICK); } + + if new_feature_activations.contains(&feature_set::programify_feature_gate_program::id()) { + let datapoint_name = "bank-progamify_feature_gate_program"; + if let Err(e) = replace_account::replace_empty_account_with_upgradeable_program( + self, + &feature::id(), + &inline_feature_gate_program::noop_program::id(), + datapoint_name, + ) { + warn!( + "{}: Failed to replace empty account {} with upgradeable program: {}", + datapoint_name, + feature::id(), + e + ); + datapoint_warn!(datapoint_name, ("slot", self.slot(), i64),); + } + } } fn apply_updated_hashes_per_tick(&mut self, hashes_per_tick: u64) { @@ -8196,42 +8216,6 @@ impl Bank { } } - /// Use to replace programs by feature activation - #[allow(dead_code)] - fn replace_program_account( - &mut self, - old_address: &Pubkey, - new_address: &Pubkey, - datapoint_name: &'static str, - ) { - if let Some(old_account) = self.get_account_with_fixed_root(old_address) { - if let Some(new_account) = self.get_account_with_fixed_root(new_address) { - datapoint_info!(datapoint_name, ("slot", self.slot, i64)); - - // Burn lamports in the old account - self.capitalization - .fetch_sub(old_account.lamports(), Relaxed); - - // Transfer new account to old account - self.store_account(old_address, &new_account); - - // Clear new account - self.store_account(new_address, &AccountSharedData::default()); - - // Unload a program from the bank's cache - self.loaded_programs_cache - .write() - .unwrap() - .remove_programs([*old_address].into_iter()); - - self.calculate_and_update_accounts_data_size_delta_off_chain( - old_account.data().len(), - new_account.data().len(), - ); - } - } - } - /// Get all the accounts for this bank and calculate stats pub fn get_total_accounts_stats(&self) -> ScanResult { let accounts = self.get_all_accounts()?; diff --git a/runtime/src/bank/replace_account.rs b/runtime/src/bank/replace_account.rs new file mode 100644 index 0000000000..8d650aeebe --- /dev/null +++ b/runtime/src/bank/replace_account.rs @@ -0,0 +1,191 @@ +use { + super::Bank, + log::*, + solana_accounts_db::accounts_index::ZeroLamport, + solana_sdk::{ + account::{Account, AccountSharedData, ReadableAccount}, + bpf_loader_upgradeable::{self, UpgradeableLoaderState}, + pubkey::Pubkey, + }, + std::sync::atomic::Ordering::Relaxed, + thiserror::Error, +}; + +/// Errors returned by `replace_account` methods +#[derive(Debug, Error)] +pub enum ReplaceAccountError { + /// Account not found + #[error("Account not found: {0:?}")] + AccountNotFound(Pubkey), + /// Account exists + #[error("Account exists: {0:?}")] + AccountExists(Pubkey), + #[error("Bincode Error: {0}")] + BincodeError(#[from] bincode::Error), + /// Not an upgradeable program + #[error("Not an upgradeable program")] + NotAnUpgradeableProgram, +} + +/// Moves one account in place of another +/// `source`: the account to replace with +/// `destination`: the account to be replaced +fn move_account( + bank: &Bank, + source_address: &Pubkey, + source_account: &V, + destination_address: &Pubkey, + destination_account: Option<&U>, +) where + U: ReadableAccount + Sync + ZeroLamport, + V: ReadableAccount + Sync + ZeroLamport, +{ + let (destination_lamports, destination_len) = match destination_account { + Some(destination_account) => ( + destination_account.lamports(), + destination_account.data().len(), + ), + None => (0, 0), + }; + + // Burn lamports in the destination account + bank.capitalization.fetch_sub(destination_lamports, Relaxed); + + // Transfer source account to destination account + bank.store_account(destination_address, source_account); + + // Clear source account + bank.store_account(source_address, &AccountSharedData::default()); + + bank.calculate_and_update_accounts_data_size_delta_off_chain( + destination_len, + source_account.data().len(), + ); +} + +/// Use to replace non-upgradeable programs by feature activation +/// `source`: the non-upgradeable program account to replace with +/// `destination`: the non-upgradeable program account to be replaced +#[allow(dead_code)] +pub(crate) fn replace_non_upgradeable_program_account( + bank: &Bank, + source_address: &Pubkey, + destination_address: &Pubkey, + datapoint_name: &'static str, +) -> Result<(), ReplaceAccountError> { + let destination_account = bank + .get_account_with_fixed_root(destination_address) + .ok_or(ReplaceAccountError::AccountNotFound(*destination_address))?; + let source_account = bank + .get_account_with_fixed_root(source_address) + .ok_or(ReplaceAccountError::AccountNotFound(*source_address))?; + + datapoint_info!(datapoint_name, ("slot", bank.slot, i64)); + + move_account( + bank, + source_address, + &source_account, + destination_address, + Some(&destination_account), + ); + + // Unload a program from the bank's cache + bank.loaded_programs_cache + .write() + .unwrap() + .remove_programs([*destination_address].into_iter()); + + Ok(()) +} + +/// Use to replace an empty account with a program by feature activation +/// Note: The upgradeable program should have both: +/// - Program account +/// - Program data account +/// `source`: the upgradeable program account to replace with +/// `destination`: the empty account to be replaced +pub(crate) fn replace_empty_account_with_upgradeable_program( + bank: &Bank, + source_address: &Pubkey, + destination_address: &Pubkey, + datapoint_name: &'static str, +) -> Result<(), ReplaceAccountError> { + // Must be attempting to replace an empty account with a program + // account _and_ data account + let source_account = bank + .get_account_with_fixed_root(source_address) + .ok_or(ReplaceAccountError::AccountNotFound(*source_address))?; + + let (destination_data_address, _) = Pubkey::find_program_address( + &[destination_address.as_ref()], + &bpf_loader_upgradeable::id(), + ); + let (source_data_address, _) = + Pubkey::find_program_address(&[source_address.as_ref()], &bpf_loader_upgradeable::id()); + + // Make sure the data within the source account is the PDA of its + // data account. This also means it has at least the necessary + // lamports for rent. + let source_state = bincode::deserialize::(source_account.data())?; + if !matches!(source_state, UpgradeableLoaderState::Program { .. }) { + return Err(ReplaceAccountError::NotAnUpgradeableProgram); + } + + let source_data_account = bank + .get_account_with_fixed_root(&source_data_address) + .ok_or(ReplaceAccountError::AccountNotFound(source_data_address))?; + + // Make sure the destination account is empty + // We aren't going to check that there isn't a data account at + // the known program-derived address (ie. `destination_data_address`), + // because if it exists, it will be overwritten + if bank + .get_account_with_fixed_root(destination_address) + .is_some() + { + return Err(ReplaceAccountError::AccountExists(*destination_address)); + } + let state = UpgradeableLoaderState::Program { + programdata_address: destination_data_address, + }; + let data = bincode::serialize(&state)?; + let lamports = bank.get_minimum_balance_for_rent_exemption(data.len()); + let created_program_account = Account { + lamports, + data, + owner: bpf_loader_upgradeable::id(), + executable: true, + rent_epoch: source_account.rent_epoch(), + }; + + datapoint_info!(datapoint_name, ("slot", bank.slot, i64)); + let change_in_capitalization = source_account.lamports().saturating_sub(lamports); + + // Replace the destination data account with the source one + // If the destination data account does not exist, it will be created + // If it does exist, it will be overwritten + move_account( + bank, + &source_data_address, + &source_data_account, + &destination_data_address, + bank.get_account_with_fixed_root(&destination_data_address) + .as_ref(), + ); + + // Write the source data account's PDA into the destination program account + move_account( + bank, + source_address, + &created_program_account, + destination_address, + None::<&AccountSharedData>, + ); + + // Any remaining lamports in the source program account are burnt + bank.capitalization + .fetch_sub(change_in_capitalization, Relaxed); + + Ok(()) +} diff --git a/runtime/src/bank/tests.rs b/runtime/src/bank/tests.rs index 3263eb9c41..58ce790d43 100644 --- a/runtime/src/bank/tests.rs +++ b/runtime/src/bank/tests.rs @@ -8,6 +8,10 @@ use { }, crate::{ accounts_background_service::{PrunedBanksRequestHandler, SendDroppedBankCallback}, + bank::replace_account::{ + replace_empty_account_with_upgradeable_program, + replace_non_upgradeable_program_account, ReplaceAccountError, + }, bank_client::BankClient, epoch_rewards_hasher::hash_rewards_into_partitions, genesis_utils::{ @@ -8003,42 +8007,403 @@ fn test_compute_active_feature_set() { assert!(feature_set.is_active(&test_feature)); } -#[test] -fn test_program_replacement() { - let mut bank = create_simple_test_bank(0); - - // Setup original program account - let old_address = Pubkey::new_unique(); - let new_address = Pubkey::new_unique(); - bank.store_account_and_update_capitalization( - &old_address, - &AccountSharedData::from(Account { - lamports: 100, - ..Account::default() - }), - ); - assert_eq!(bank.get_balance(&old_address), 100); - - // Setup new program account - let new_program_account = AccountSharedData::from(Account { - lamports: 123, +fn test_program_replace_set_up_account( + bank: &Bank, + pubkey: &Pubkey, + lamports: u64, + state: &T, + owner: &Pubkey, + executable: bool, +) -> AccountSharedData { + let data_len = bincode::serialized_size(state).unwrap() as usize; + let mut account = AccountSharedData::from(Account { + lamports, + owner: *owner, + executable, + data: vec![0u8; data_len], ..Account::default() }); - bank.store_account_and_update_capitalization(&new_address, &new_program_account); - assert_eq!(bank.get_balance(&new_address), 123); + account.serialize_data(state).unwrap(); + bank.store_account_and_update_capitalization(pubkey, &account); + assert_eq!(bank.get_balance(pubkey), lamports); + account +} + +#[test] +fn test_replace_non_upgradeable_program_account() { + // Non-upgradeable program + // - Destination: [Destination program data] + // - Source: [*Source program data] + // + // Should replace the destination program account with the source program account: + // - Destination: [*Source program data] + let bpf_id = bpf_loader::id(); + let bank = create_simple_test_bank(0); + + let destination = Pubkey::new_unique(); + let destination_state = vec![0u8; 4]; + let destination_lamports = bank.get_minimum_balance_for_rent_exemption(destination_state.len()); + test_program_replace_set_up_account( + &bank, + &destination, + destination_lamports, + &destination_state, + &bpf_id, + true, + ); + + let source = Pubkey::new_unique(); + let source_state = vec![6; 30]; + let source_lamports = bank.get_minimum_balance_for_rent_exemption(source_state.len()); + let check_source_account = test_program_replace_set_up_account( + &bank, + &source, + source_lamports, + &source_state, + &bpf_id, + true, + ); + let check_data_account_data = check_source_account.data().to_vec(); let original_capitalization = bank.capitalization(); - bank.replace_program_account(&old_address, &new_address, "bank-apply_program_replacement"); + replace_non_upgradeable_program_account( + &bank, + &source, + &destination, + "bank-apply_program_replacement", + ) + .unwrap(); - // New program account is now empty - assert_eq!(bank.get_balance(&new_address), 0); + // Destination program account balance is now the source program account's balance + assert_eq!(bank.get_balance(&destination), source_lamports); - // Old program account holds the new program account - assert_eq!(bank.get_account(&old_address), Some(new_program_account)); + // Source program account is now empty + assert_eq!(bank.get_balance(&source), 0); - // Lamports in the old token account were burnt - assert_eq!(bank.capitalization(), original_capitalization - 100); + // Destination program account now holds the source program data, ie: + // - Destination: [*Source program data] + let destination_account = bank.get_account(&destination).unwrap(); + assert_eq!(destination_account.data(), &check_data_account_data); + + // Ownership & executable match the source program account + assert_eq!(destination_account.owner(), &bpf_id); + assert!(destination_account.executable()); + + // The destination account's original lamports balance was burnt + assert_eq!( + bank.capitalization(), + original_capitalization - destination_lamports + ); +} + +#[test_case( + Pubkey::new_unique(), + None; + "Empty destination account _without_ corresponding data account" +)] +#[test_case( + Pubkey::new_unique(), + Some(vec![4; 40]); + "Empty destination account _with_ corresponding data account" +)] +#[test_case( + feature::id(), // `Feature11111111` + None; + "Native destination account _without_ corresponding data account" +)] +#[test_case( + feature::id(), // `Feature11111111` + Some(vec![4; 40]); + "Native destination account _with_ corresponding data account" +)] +fn test_replace_empty_account_with_upgradeable_program_success( + destination: Pubkey, + maybe_destination_data_state: Option>, // Inner data of the destination program _data_ account +) { + // Ensures a program account and data account are created when replacing an + // empty account, ie: + // - Destination: PDA(DestinationData) + // - DestinationData: [Destination program data] + // + // If the destination data account exists, it will be overwritten + let bpf_upgradeable_id = bpf_loader_upgradeable::id(); + let bank = create_simple_test_bank(0); + + // Create the test source accounts, one for program and one for data + let source = Pubkey::new_unique(); + let (source_data, _) = Pubkey::find_program_address(&[source.as_ref()], &bpf_upgradeable_id); + let source_state = UpgradeableLoaderState::Program { + programdata_address: source_data, + }; + let source_lamports = + bank.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_program()); + let source_data_state = vec![6; 30]; + let source_data_lamports = bank.get_minimum_balance_for_rent_exemption(source_data_state.len()); + test_program_replace_set_up_account( + &bank, + &source, + source_lamports, + &source_state, + &bpf_upgradeable_id, + true, + ); + let check_source_data_account = test_program_replace_set_up_account( + &bank, + &source_data, + source_data_lamports, + &source_data_state, + &bpf_upgradeable_id, + false, + ); + let check_data_account_data = check_source_data_account.data().to_vec(); + + // Derive the well-known PDA address for the destination data account + let (destination_data, _) = + Pubkey::find_program_address(&[destination.as_ref()], &bpf_upgradeable_id); + + // Determine the lamports that will be burnt after the replacement + let burnt_after_rent = if let Some(destination_data_state) = maybe_destination_data_state { + // Create the data account if necessary + let destination_data_lamports = + bank.get_minimum_balance_for_rent_exemption(destination_data_state.len()); + test_program_replace_set_up_account( + &bank, + &destination_data, + destination_data_lamports, + &destination_data_state, + &bpf_upgradeable_id, + false, + ); + destination_data_lamports + source_lamports + - bank.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_program()) + } else { + source_lamports + - bank.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_program()) + }; + + let original_capitalization = bank.capitalization(); + + // Do the replacement + replace_empty_account_with_upgradeable_program( + &bank, + &source, + &destination, + "bank-apply_empty_account_replacement_for_program", + ) + .unwrap(); + + // Destination program account was created and funded to pay for minimum rent + // for the PDA + assert_eq!( + bank.get_balance(&destination), + bank.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_program()), + ); + + // Destination data account was created, now holds the source data account's balance + assert_eq!(bank.get_balance(&destination_data), source_data_lamports); + + // Source program accounts are now empty + assert_eq!(bank.get_balance(&source), 0); + assert_eq!(bank.get_balance(&source_data), 0); + + // Destination program account holds the PDA, ie: + // - Destination: PDA(DestinationData) + let destination_account = bank.get_account(&destination).unwrap(); + assert_eq!( + destination_account.data(), + &bincode::serialize(&UpgradeableLoaderState::Program { + programdata_address: destination_data + }) + .unwrap(), + ); + + // Destination data account holds the source data, ie: + // - DestinationData: [*Source program data] + let destination_data_account = bank.get_account(&destination_data).unwrap(); + assert_eq!(destination_data_account.data(), &check_data_account_data); + + // Ownership & executable match the source program accounts + assert_eq!(destination_account.owner(), &bpf_upgradeable_id); + assert!(destination_account.executable()); + assert_eq!(destination_data_account.owner(), &bpf_upgradeable_id); + assert!(!destination_data_account.executable()); + + // The remaining lamports from both program accounts minus the rent-exempt + // minimum were burnt + assert_eq!( + bank.capitalization(), + original_capitalization - burnt_after_rent + ); +} + +#[test_case( + None; + "Existing destination account _without_ corresponding data account" +)] +#[test_case( + Some(vec![4; 40]); + "Existing destination account _with_ corresponding data account" +)] +fn test_replace_empty_account_with_upgradeable_program_fail_when_account_exists( + maybe_destination_data_state: Option>, // Inner data of the destination program _data_ account +) { + // Should not be allowed to execute replacement + let bpf_upgradeable_id = bpf_loader_upgradeable::id(); + let bank = create_simple_test_bank(0); + + // Create the test destination account with some arbitrary data and lamports balance + let destination = Pubkey::new_unique(); + let destination_state = vec![0, 0, 0, 0]; // Arbitrary bytes, doesn't matter + let destination_lamports = bank.get_minimum_balance_for_rent_exemption(destination_state.len()); + let destination_account = test_program_replace_set_up_account( + &bank, + &destination, + destination_lamports, + &destination_state, + &bpf_upgradeable_id, + true, + ); + + // Create the test source accounts, one for program and one for data + let source = Pubkey::new_unique(); + let (source_data, _) = Pubkey::find_program_address(&[source.as_ref()], &bpf_upgradeable_id); + let source_state = UpgradeableLoaderState::Program { + programdata_address: source_data, + }; + let source_lamports = + bank.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_program()); + let source_data_state = vec![6; 30]; + let source_data_lamports = bank.get_minimum_balance_for_rent_exemption(source_data_state.len()); + let source_account = test_program_replace_set_up_account( + &bank, + &source, + source_lamports, + &source_state, + &bpf_upgradeable_id, + true, + ); + let source_data_account = test_program_replace_set_up_account( + &bank, + &source_data, + source_data_lamports, + &source_data_state, + &bpf_upgradeable_id, + false, + ); + + // Derive the well-known PDA address for the destination data account + let (destination_data, _) = + Pubkey::find_program_address(&[destination.as_ref()], &bpf_upgradeable_id); + + // Create the data account if necessary + let destination_data_account = + if let Some(destination_data_state) = maybe_destination_data_state { + let destination_data_lamports = + bank.get_minimum_balance_for_rent_exemption(destination_data_state.len()); + let destination_data_account = test_program_replace_set_up_account( + &bank, + &destination_data, + destination_data_lamports, + &destination_data_state, + &bpf_upgradeable_id, + false, + ); + Some(destination_data_account) + } else { + None + }; + + let original_capitalization = bank.capitalization(); + + // Attempt the replacement + assert_matches!( + replace_empty_account_with_upgradeable_program( + &bank, + &source, + &destination, + "bank-apply_empty_account_replacement_for_program", + ) + .unwrap_err(), + ReplaceAccountError::AccountExists(..) + ); + + // Everything should be unchanged + assert_eq!(bank.get_account(&destination).unwrap(), destination_account); + if let Some(destination_data_account) = destination_data_account { + assert_eq!( + bank.get_account(&destination_data).unwrap(), + destination_data_account + ); + } + assert_eq!(bank.get_account(&source).unwrap(), source_account); + assert_eq!(bank.get_account(&source_data).unwrap(), source_data_account); + assert_eq!(bank.capitalization(), original_capitalization); +} + +#[test] +fn test_replace_empty_account_with_upgradeable_program_fail_when_not_upgradeable_program() { + // Should not be allowed to execute replacement + let bpf_upgradeable_id = bpf_loader_upgradeable::id(); + let bank = create_simple_test_bank(0); + + // Create the test destination account with some arbitrary data and lamports balance + let destination = Pubkey::new_unique(); + let destination_state = vec![0, 0, 0, 0]; // Arbitrary bytes, doesn't matter + let destination_lamports = bank.get_minimum_balance_for_rent_exemption(destination_state.len()); + let destination_account = test_program_replace_set_up_account( + &bank, + &destination, + destination_lamports, + &destination_state, + &bpf_upgradeable_id, + true, + ); + + // Create the test source accounts, one for program and one for data + let source = Pubkey::new_unique(); + let (source_data, _) = Pubkey::find_program_address(&[source.as_ref()], &bpf_upgradeable_id); + let source_state = [0, 0, 0, 0]; // Arbitrary bytes, NOT an upgradeable program + let source_lamports = + bank.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_program()); + let source_data_state = vec![6; 30]; + let source_data_lamports = bank.get_minimum_balance_for_rent_exemption(source_data_state.len()); + let source_account = test_program_replace_set_up_account( + &bank, + &source, + source_lamports, + &source_state, + &bpf_upgradeable_id, + true, + ); + let source_data_account = test_program_replace_set_up_account( + &bank, + &source_data, + source_data_lamports, + &source_data_state, + &bpf_upgradeable_id, + false, + ); + + let original_capitalization = bank.capitalization(); + + // Attempt the replacement + assert_matches!( + replace_empty_account_with_upgradeable_program( + &bank, + &source, + &destination, + "bank-apply_empty_account_replacement_for_program", + ) + .unwrap_err(), + ReplaceAccountError::NotAnUpgradeableProgram + ); + + // Everything should be unchanged + assert_eq!(bank.get_account(&destination).unwrap(), destination_account); + assert_eq!(bank.get_account(&source).unwrap(), source_account); + assert_eq!(bank.get_account(&source_data).unwrap(), source_data_account); + assert_eq!(bank.capitalization(), original_capitalization); } fn min_rent_exempt_balance_for_sysvars(bank: &Bank, sysvar_ids: &[Pubkey]) -> u64 { diff --git a/runtime/src/inline_feature_gate_program.rs b/runtime/src/inline_feature_gate_program.rs new file mode 100644 index 0000000000..125dc74df2 --- /dev/null +++ b/runtime/src/inline_feature_gate_program.rs @@ -0,0 +1,5 @@ +//! Contains replacement program IDs for the feature gate program + +pub(crate) mod noop_program { + solana_sdk::declare_id!("2rqZsQBbacRbuAuTSuJ7n49UQT9fzes8RLggFcmB9YuN"); +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index ff94a68c69..503d24410e 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -14,6 +14,7 @@ pub mod commitment; mod epoch_rewards_hasher; pub mod epoch_stakes; pub mod genesis_utils; +pub mod inline_feature_gate_program; pub mod inline_spl_associated_token_account; pub mod loader_utils; pub mod non_circulating_supply; diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index b414a5f6ab..9ec56b03e0 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -700,6 +700,10 @@ pub mod better_error_codes_for_tx_lamport_check { solana_sdk::declare_id!("Ffswd3egL3tccB6Rv3XY6oqfdzn913vUcjCSnpvCKpfx"); } +pub mod programify_feature_gate_program { + solana_sdk::declare_id!("8GdovDzVwWU5edz2G697bbB7GZjrUc6aQZLWyNNAtHdg"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -870,6 +874,7 @@ lazy_static! { (require_rent_exempt_split_destination::id(), "Require stake split destination account to be rent exempt"), (better_error_codes_for_tx_lamport_check::id(), "better error codes for tx lamport check #33353"), (enable_alt_bn128_compression_syscall::id(), "add alt_bn128 compression syscalls"), + (programify_feature_gate_program::id(), "move feature gate activation logic to an on-chain program #32783"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter()