clone_solana_feature_gate_interface/
lib.rs

1//! Runtime features.
2//!
3//! Runtime features provide a mechanism for features to be simultaneously activated across the
4//! network. Since validators may choose when to upgrade, features must remain dormant until a
5//! sufficient majority of the network is running a version that would support a given feature.
6//!
7//! Feature activation is accomplished by:
8//! 1. Activation is requested by the feature authority, who issues a transaction to create the
9//!    feature account. The newly created feature account will have the value of
10//!    `Feature::default()`
11//! 2. When the next epoch is entered the runtime will check for new activation requests and
12//!    active them.  When this occurs, the activation slot is recorded in the feature account
13
14pub use clone_solana_sdk_ids::feature::{check_id, id, ID};
15#[cfg(feature = "bincode")]
16use {
17    clone_solana_account::{AccountSharedData, ReadableAccount, WritableAccount},
18    clone_solana_account_info::AccountInfo,
19    clone_solana_instruction::Instruction,
20    clone_solana_program_error::ProgramError,
21    clone_solana_pubkey::Pubkey,
22    clone_solana_rent::Rent,
23    clone_solana_system_interface::instruction as system_instruction,
24};
25
26#[cfg_attr(
27    feature = "serde",
28    derive(serde_derive::Deserialize, serde_derive::Serialize)
29)]
30#[derive(Default, Debug, PartialEq, Eq)]
31pub struct Feature {
32    pub activated_at: Option<u64>,
33}
34
35impl Feature {
36    pub const fn size_of() -> usize {
37        9 // see test_feature_size_of.
38    }
39
40    #[cfg(feature = "bincode")]
41    pub fn from_account_info(account_info: &AccountInfo) -> Result<Self, ProgramError> {
42        if *account_info.owner != id() {
43            return Err(ProgramError::InvalidAccountOwner);
44        }
45        bincode::deserialize(&account_info.data.borrow())
46            .map_err(|_| ProgramError::InvalidAccountData)
47    }
48}
49
50#[cfg(feature = "bincode")]
51/// Activate a feature
52pub fn activate(feature_id: &Pubkey, funding_address: &Pubkey, rent: &Rent) -> Vec<Instruction> {
53    activate_with_lamports(
54        feature_id,
55        funding_address,
56        rent.minimum_balance(Feature::size_of()),
57    )
58}
59
60#[cfg(feature = "bincode")]
61pub fn activate_with_lamports(
62    feature_id: &Pubkey,
63    funding_address: &Pubkey,
64    lamports: u64,
65) -> Vec<Instruction> {
66    vec![
67        system_instruction::transfer(funding_address, feature_id, lamports),
68        system_instruction::allocate(feature_id, Feature::size_of() as u64),
69        system_instruction::assign(feature_id, &id()),
70    ]
71}
72
73#[cfg(feature = "bincode")]
74pub fn from_account<T: ReadableAccount>(account: &T) -> Option<Feature> {
75    if account.owner() != &id() {
76        None
77    } else {
78        bincode::deserialize(account.data()).ok()
79    }
80}
81
82#[cfg(feature = "bincode")]
83pub fn to_account(feature: &Feature, account: &mut AccountSharedData) -> Option<()> {
84    bincode::serialize_into(account.data_as_mut_slice(), feature).ok()
85}
86
87#[cfg(feature = "bincode")]
88pub fn create_account(feature: &Feature, lamports: u64) -> AccountSharedData {
89    let data_len = Feature::size_of().max(bincode::serialized_size(feature).unwrap() as usize);
90    let mut account = AccountSharedData::new(lamports, data_len, &id());
91    to_account(feature, &mut account).unwrap();
92    account
93}
94
95#[cfg(test)]
96mod test {
97    use super::*;
98
99    #[test]
100    fn test_feature_size_of() {
101        assert_eq!(Feature::size_of() as u64, {
102            let feature = Feature {
103                activated_at: Some(0),
104            };
105            bincode::serialized_size(&feature).unwrap()
106        });
107        assert!(
108            Feature::size_of() >= bincode::serialized_size(&Feature::default()).unwrap() as usize
109        );
110        assert_eq!(Feature::default(), Feature { activated_at: None });
111
112        let features = [
113            Feature {
114                activated_at: Some(0),
115            },
116            Feature {
117                activated_at: Some(u64::MAX),
118            },
119        ];
120        for feature in &features {
121            assert_eq!(
122                Feature::size_of(),
123                bincode::serialized_size(feature).unwrap() as usize
124            );
125        }
126    }
127
128    #[test]
129    fn feature_deserialize_none() {
130        let just_initialized = AccountSharedData::new(42, Feature::size_of(), &id());
131        assert_eq!(
132            from_account(&just_initialized),
133            Some(Feature { activated_at: None })
134        );
135    }
136}