Move Feature struct to solana-program
This commit is contained in:
parent
8415c22b59
commit
4b65e32f22
|
@ -15,7 +15,6 @@ use solana_sdk::{
|
||||||
feature_set::FEATURE_NAMES,
|
feature_set::FEATURE_NAMES,
|
||||||
message::Message,
|
message::Message,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
system_instruction,
|
|
||||||
transaction::Transaction,
|
transaction::Transaction,
|
||||||
};
|
};
|
||||||
use std::{collections::HashMap, fmt, sync::Arc};
|
use std::{collections::HashMap, fmt, sync::Arc};
|
||||||
|
@ -281,7 +280,7 @@ fn process_status(
|
||||||
let feature_id = &feature_ids[i];
|
let feature_id = &feature_ids[i];
|
||||||
let feature_name = FEATURE_NAMES.get(feature_id).unwrap();
|
let feature_name = FEATURE_NAMES.get(feature_id).unwrap();
|
||||||
if let Some(account) = account {
|
if let Some(account) = account {
|
||||||
if let Some(feature) = Feature::from_account(&account) {
|
if let Some(feature) = feature::from_account(&account) {
|
||||||
let feature_status = match feature.activated_at {
|
let feature_status = match feature.activated_at {
|
||||||
None => CliFeatureStatus::Pending,
|
None => CliFeatureStatus::Pending,
|
||||||
Some(activation_slot) => CliFeatureStatus::Active(activation_slot),
|
Some(activation_slot) => CliFeatureStatus::Active(activation_slot),
|
||||||
|
@ -322,7 +321,7 @@ fn process_activate(
|
||||||
.next()
|
.next()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
if let Some(account) = account {
|
if let Some(account) = account {
|
||||||
if Feature::from_account(&account).is_some() {
|
if feature::from_account(&account).is_some() {
|
||||||
return Err(format!("{} has already been activated", feature_id).into());
|
return Err(format!("{} has already been activated", feature_id).into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -342,15 +341,11 @@ fn process_activate(
|
||||||
&config.signers[0].pubkey(),
|
&config.signers[0].pubkey(),
|
||||||
|lamports| {
|
|lamports| {
|
||||||
Message::new(
|
Message::new(
|
||||||
&[
|
&feature::activate_with_lamports(
|
||||||
system_instruction::transfer(
|
&feature_id,
|
||||||
&config.signers[0].pubkey(),
|
&config.signers[0].pubkey(),
|
||||||
&feature_id,
|
lamports,
|
||||||
lamports,
|
),
|
||||||
),
|
|
||||||
system_instruction::allocate(&feature_id, Feature::size_of() as u64),
|
|
||||||
system_instruction::assign(&feature_id, &feature::id()),
|
|
||||||
],
|
|
||||||
Some(&config.signers[0].pubkey()),
|
Some(&config.signers[0].pubkey()),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
@ -6,7 +6,6 @@ use crate::{
|
||||||
append_vec::StoredAccount,
|
append_vec::StoredAccount,
|
||||||
bank::{HashAgeKind, TransactionProcessResult},
|
bank::{HashAgeKind, TransactionProcessResult},
|
||||||
blockhash_queue::BlockhashQueue,
|
blockhash_queue::BlockhashQueue,
|
||||||
feature_set::{self, FeatureSet},
|
|
||||||
rent_collector::RentCollector,
|
rent_collector::RentCollector,
|
||||||
system_instruction_processor::{get_system_account_kind, SystemAccountKind},
|
system_instruction_processor::{get_system_account_kind, SystemAccountKind},
|
||||||
transaction_utils::OrderedIterator,
|
transaction_utils::OrderedIterator,
|
||||||
|
@ -18,6 +17,7 @@ use solana_sdk::{
|
||||||
account::Account,
|
account::Account,
|
||||||
account_utils::StateMut,
|
account_utils::StateMut,
|
||||||
clock::{Epoch, Slot},
|
clock::{Epoch, Slot},
|
||||||
|
feature_set::{self, FeatureSet},
|
||||||
fee_calculator::{FeeCalculator, FeeConfig},
|
fee_calculator::{FeeCalculator, FeeConfig},
|
||||||
genesis_config::ClusterType,
|
genesis_config::ClusterType,
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
|
|
|
@ -12,8 +12,6 @@ use crate::{
|
||||||
blockhash_queue::BlockhashQueue,
|
blockhash_queue::BlockhashQueue,
|
||||||
builtins,
|
builtins,
|
||||||
epoch_stakes::{EpochStakes, NodeVoteAccounts},
|
epoch_stakes::{EpochStakes, NodeVoteAccounts},
|
||||||
feature::Feature,
|
|
||||||
feature_set::{self, FeatureSet},
|
|
||||||
instruction_recorder::InstructionRecorder,
|
instruction_recorder::InstructionRecorder,
|
||||||
log_collector::LogCollector,
|
log_collector::LogCollector,
|
||||||
message_processor::{Executors, MessageProcessor},
|
message_processor::{Executors, MessageProcessor},
|
||||||
|
@ -40,6 +38,8 @@ use solana_sdk::{
|
||||||
},
|
},
|
||||||
epoch_info::EpochInfo,
|
epoch_info::EpochInfo,
|
||||||
epoch_schedule::EpochSchedule,
|
epoch_schedule::EpochSchedule,
|
||||||
|
feature,
|
||||||
|
feature_set::{self, FeatureSet},
|
||||||
fee_calculator::{FeeCalculator, FeeConfig, FeeRateGovernor},
|
fee_calculator::{FeeCalculator, FeeConfig, FeeRateGovernor},
|
||||||
genesis_config::{ClusterType, GenesisConfig},
|
genesis_config::{ClusterType, GenesisConfig},
|
||||||
hard_forks::HardForks,
|
hard_forks::HardForks,
|
||||||
|
@ -3919,13 +3919,13 @@ impl Bank {
|
||||||
for feature_id in &self.feature_set.inactive {
|
for feature_id in &self.feature_set.inactive {
|
||||||
let mut activated = None;
|
let mut activated = None;
|
||||||
if let Some(mut account) = self.get_account(feature_id) {
|
if let Some(mut account) = self.get_account(feature_id) {
|
||||||
if let Some(mut feature) = Feature::from_account(&account) {
|
if let Some(mut feature) = feature::from_account(&account) {
|
||||||
match feature.activated_at {
|
match feature.activated_at {
|
||||||
None => {
|
None => {
|
||||||
if allow_new_activations {
|
if allow_new_activations {
|
||||||
// Feature has been requested, activate it now
|
// Feature has been requested, activate it now
|
||||||
feature.activated_at = Some(slot);
|
feature.activated_at = Some(slot);
|
||||||
if feature.to_account(&mut account).is_some() {
|
if feature::to_account(&feature, &mut account).is_some() {
|
||||||
self.store_account(feature_id, &account);
|
self.store_account(feature_id, &account);
|
||||||
}
|
}
|
||||||
newly_activated.insert(*feature_id);
|
newly_activated.insert(*feature_id);
|
||||||
|
@ -4103,6 +4103,7 @@ mod tests {
|
||||||
account_utils::StateMut,
|
account_utils::StateMut,
|
||||||
clock::{DEFAULT_SLOTS_PER_EPOCH, DEFAULT_TICKS_PER_SLOT},
|
clock::{DEFAULT_SLOTS_PER_EPOCH, DEFAULT_TICKS_PER_SLOT},
|
||||||
epoch_schedule::MINIMUM_SLOTS_PER_EPOCH,
|
epoch_schedule::MINIMUM_SLOTS_PER_EPOCH,
|
||||||
|
feature::Feature,
|
||||||
genesis_config::create_genesis_config,
|
genesis_config::create_genesis_config,
|
||||||
instruction::{AccountMeta, CompiledInstruction, Instruction, InstructionError},
|
instruction::{AccountMeta, CompiledInstruction, Instruction, InstructionError},
|
||||||
keyed_account::KeyedAccount,
|
keyed_account::KeyedAccount,
|
||||||
|
@ -9571,13 +9572,13 @@ mod tests {
|
||||||
// Request `test_feature` activation
|
// Request `test_feature` activation
|
||||||
let feature = Feature::default();
|
let feature = Feature::default();
|
||||||
assert_eq!(feature.activated_at, None);
|
assert_eq!(feature.activated_at, None);
|
||||||
bank.store_account(&test_feature, &feature.create_account(42));
|
bank.store_account(&test_feature, &feature::create_account(&feature, 42));
|
||||||
|
|
||||||
// Run `compute_active_feature_set` disallowing new activations
|
// Run `compute_active_feature_set` disallowing new activations
|
||||||
let new_activations = bank.compute_active_feature_set(false);
|
let new_activations = bank.compute_active_feature_set(false);
|
||||||
assert!(new_activations.is_empty());
|
assert!(new_activations.is_empty());
|
||||||
assert!(!bank.feature_set.is_active(&test_feature));
|
assert!(!bank.feature_set.is_active(&test_feature));
|
||||||
let feature = Feature::from_account(&bank.get_account(&test_feature).expect("get_account"))
|
let feature = feature::from_account(&bank.get_account(&test_feature).expect("get_account"))
|
||||||
.expect("from_account");
|
.expect("from_account");
|
||||||
assert_eq!(feature.activated_at, None);
|
assert_eq!(feature.activated_at, None);
|
||||||
|
|
||||||
|
@ -9585,7 +9586,7 @@ mod tests {
|
||||||
let new_activations = bank.compute_active_feature_set(true);
|
let new_activations = bank.compute_active_feature_set(true);
|
||||||
assert_eq!(new_activations.len(), 1);
|
assert_eq!(new_activations.len(), 1);
|
||||||
assert!(bank.feature_set.is_active(&test_feature));
|
assert!(bank.feature_set.is_active(&test_feature));
|
||||||
let feature = Feature::from_account(&bank.get_account(&test_feature).expect("get_account"))
|
let feature = feature::from_account(&bank.get_account(&test_feature).expect("get_account"))
|
||||||
.expect("from_account");
|
.expect("from_account");
|
||||||
assert_eq!(feature.activated_at, Some(1));
|
assert_eq!(feature.activated_at, Some(1));
|
||||||
|
|
||||||
|
@ -9720,12 +9721,14 @@ mod tests {
|
||||||
assert_eq!(clock.unix_timestamp, bank.unix_timestamp_from_genesis());
|
assert_eq!(clock.unix_timestamp, bank.unix_timestamp_from_genesis());
|
||||||
|
|
||||||
// Request `timestamp_correction` activation
|
// Request `timestamp_correction` activation
|
||||||
let feature = Feature {
|
|
||||||
activated_at: Some(bank.slot),
|
|
||||||
};
|
|
||||||
bank.store_account(
|
bank.store_account(
|
||||||
&feature_set::timestamp_correction::id(),
|
&feature_set::timestamp_correction::id(),
|
||||||
&feature.create_account(42),
|
&feature::create_account(
|
||||||
|
&Feature {
|
||||||
|
activated_at: Some(bank.slot),
|
||||||
|
},
|
||||||
|
42,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
bank.compute_active_feature_set(true);
|
bank.compute_active_feature_set(true);
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
bank::{Builtin, Builtins},
|
bank::{Builtin, Builtins},
|
||||||
feature_set, system_instruction_processor,
|
system_instruction_processor,
|
||||||
};
|
};
|
||||||
use solana_sdk::{pubkey::Pubkey, system_program};
|
use solana_sdk::{feature_set, pubkey::Pubkey, system_program};
|
||||||
|
|
||||||
/// Builtin programs that are always available
|
/// Builtin programs that are always available
|
||||||
fn genesis_builtins() -> Vec<Builtin> {
|
fn genesis_builtins() -> Vec<Builtin> {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::{feature::Feature, feature_set::FeatureSet};
|
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
account::Account,
|
account::Account,
|
||||||
|
feature::{self, Feature},
|
||||||
|
feature_set::FeatureSet,
|
||||||
fee_calculator::FeeRateGovernor,
|
fee_calculator::FeeRateGovernor,
|
||||||
genesis_config::{ClusterType, GenesisConfig},
|
genesis_config::{ClusterType, GenesisConfig},
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
|
@ -127,15 +128,14 @@ pub fn create_genesis_config_with_leader(
|
||||||
pub fn activate_all_features(genesis_config: &mut GenesisConfig) {
|
pub fn activate_all_features(genesis_config: &mut GenesisConfig) {
|
||||||
// Activate all features at genesis in development mode
|
// Activate all features at genesis in development mode
|
||||||
for feature_id in FeatureSet::default().inactive {
|
for feature_id in FeatureSet::default().inactive {
|
||||||
let feature = Feature {
|
|
||||||
activated_at: Some(0),
|
|
||||||
};
|
|
||||||
genesis_config.accounts.insert(
|
genesis_config.accounts.insert(
|
||||||
feature_id,
|
feature_id,
|
||||||
feature.create_account(std::cmp::max(
|
feature::create_account(
|
||||||
genesis_config.rent.minimum_balance(Feature::size_of()),
|
&Feature {
|
||||||
1,
|
activated_at: Some(0),
|
||||||
)),
|
},
|
||||||
|
std::cmp::max(genesis_config.rent.minimum_balance(Feature::size_of()), 1),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,10 +31,6 @@ pub mod transaction_batch;
|
||||||
pub mod transaction_utils;
|
pub mod transaction_utils;
|
||||||
pub mod vote_sender_types;
|
pub mod vote_sender_types;
|
||||||
|
|
||||||
// TODO: Refactor all feature users to reference the solana_sdk definitions directly and remove the
|
|
||||||
// next line
|
|
||||||
use solana_sdk::{feature, feature_set};
|
|
||||||
|
|
||||||
extern crate solana_config_program;
|
extern crate solana_config_program;
|
||||||
extern crate solana_stake_program;
|
extern crate solana_stake_program;
|
||||||
extern crate solana_vote_program;
|
extern crate solana_vote_program;
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
feature_set::{instructions_sysvar_enabled, FeatureSet},
|
instruction_recorder::InstructionRecorder, log_collector::LogCollector,
|
||||||
instruction_recorder::InstructionRecorder,
|
native_loader::NativeLoader, rent_collector::RentCollector,
|
||||||
log_collector::LogCollector,
|
|
||||||
native_loader::NativeLoader,
|
|
||||||
rent_collector::RentCollector,
|
|
||||||
};
|
};
|
||||||
use log::*;
|
use log::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
account::Account,
|
account::Account,
|
||||||
clock::Epoch,
|
clock::Epoch,
|
||||||
|
feature_set::{instructions_sysvar_enabled, FeatureSet},
|
||||||
instruction::{CompiledInstruction, Instruction, InstructionError},
|
instruction::{CompiledInstruction, Instruction, InstructionError},
|
||||||
keyed_account::{create_keyed_readonly_accounts, KeyedAccount},
|
keyed_account::{create_keyed_readonly_accounts, KeyedAccount},
|
||||||
message::Message,
|
message::Message,
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
/// Runtime features.
|
||||||
|
///
|
||||||
|
/// Feature activation is accomplished by:
|
||||||
|
/// 1. Activation is requested by the feature authority, who issues a transaction to create the
|
||||||
|
/// feature account. The newly created feature account will have the value of
|
||||||
|
/// `Feature::default()`
|
||||||
|
/// 2. When the next epoch is entered the runtime will check for new activation requests and
|
||||||
|
/// active them. When this occurs, the activation slot is recorded in the feature account
|
||||||
|
///
|
||||||
|
use crate::{
|
||||||
|
account_info::AccountInfo, clock::Slot, instruction::Instruction, program_error::ProgramError,
|
||||||
|
pubkey::Pubkey, rent::Rent, system_instruction,
|
||||||
|
};
|
||||||
|
|
||||||
|
crate::declare_id!("Feature111111111111111111111111111111111111");
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct Feature {
|
||||||
|
pub activated_at: Option<Slot>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Feature {
|
||||||
|
pub fn size_of() -> usize {
|
||||||
|
bincode::serialized_size(&Feature {
|
||||||
|
activated_at: Some(0),
|
||||||
|
})
|
||||||
|
.unwrap() as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_account_info(account_info: &AccountInfo) -> Result<Self, ProgramError> {
|
||||||
|
if *account_info.owner != id() {
|
||||||
|
return Err(ProgramError::InvalidArgument);
|
||||||
|
}
|
||||||
|
bincode::deserialize(&account_info.data.borrow()).map_err(|_| ProgramError::InvalidArgument)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Activate a feature
|
||||||
|
pub fn activate(feature_id: &Pubkey, funding_address: &Pubkey, rent: &Rent) -> Vec<Instruction> {
|
||||||
|
activate_with_lamports(
|
||||||
|
feature_id,
|
||||||
|
funding_address,
|
||||||
|
rent.minimum_balance(Feature::size_of()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn activate_with_lamports(
|
||||||
|
feature_id: &Pubkey,
|
||||||
|
funding_address: &Pubkey,
|
||||||
|
lamports: u64,
|
||||||
|
) -> Vec<Instruction> {
|
||||||
|
vec![
|
||||||
|
system_instruction::transfer(funding_address, feature_id, lamports),
|
||||||
|
system_instruction::allocate(feature_id, Feature::size_of() as u64),
|
||||||
|
system_instruction::assign(feature_id, &id()),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use solana_program::clock::Slot;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn feature_sizeof() {
|
||||||
|
assert!(
|
||||||
|
Feature::size_of() >= bincode::serialized_size(&Feature::default()).unwrap() as usize
|
||||||
|
);
|
||||||
|
assert_eq!(Feature::default(), Feature { activated_at: None });
|
||||||
|
|
||||||
|
let features = [
|
||||||
|
Feature {
|
||||||
|
activated_at: Some(0),
|
||||||
|
},
|
||||||
|
Feature {
|
||||||
|
activated_at: Some(Slot::MAX),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
for feature in &features {
|
||||||
|
assert_eq!(
|
||||||
|
Feature::size_of(),
|
||||||
|
bincode::serialized_size(feature).unwrap() as usize
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ pub mod decode_error;
|
||||||
pub mod entrypoint;
|
pub mod entrypoint;
|
||||||
pub mod entrypoint_deprecated;
|
pub mod entrypoint_deprecated;
|
||||||
pub mod epoch_schedule;
|
pub mod epoch_schedule;
|
||||||
|
pub mod feature;
|
||||||
pub mod fee_calculator;
|
pub mod fee_calculator;
|
||||||
pub mod hash;
|
pub mod hash;
|
||||||
pub mod incinerator;
|
pub mod incinerator;
|
||||||
|
|
|
@ -1,44 +1,23 @@
|
||||||
use solana_sdk::{account::Account, clock::Slot};
|
use crate::account::Account;
|
||||||
|
pub use solana_program::feature::*;
|
||||||
|
|
||||||
solana_sdk::declare_id!("Feature111111111111111111111111111111111111");
|
pub fn from_account(account: &Account) -> Option<Feature> {
|
||||||
|
if account.owner != id() {
|
||||||
/// The `Feature` struct is the on-chain representation of a runtime feature.
|
None
|
||||||
///
|
} else {
|
||||||
/// Feature activation is accomplished by:
|
bincode::deserialize(&account.data).ok()
|
||||||
/// 1. Activation is requested by the feature authority, who issues a transaction to create the
|
}
|
||||||
/// feature account. The newly created feature account will have the value of
|
|
||||||
/// `Feature::default()`
|
|
||||||
/// 2. When the next epoch is entered the runtime will check for new activation requests and
|
|
||||||
/// active them. When this occurs, the activation slot is recorded in the feature account
|
|
||||||
///
|
|
||||||
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
|
|
||||||
pub struct Feature {
|
|
||||||
pub activated_at: Option<Slot>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Feature {
|
pub fn to_account(feature: &Feature, account: &mut Account) -> Option<()> {
|
||||||
pub fn size_of() -> usize {
|
bincode::serialize_into(&mut account.data[..], feature).ok()
|
||||||
bincode::serialized_size(&Self {
|
}
|
||||||
activated_at: Some(0),
|
|
||||||
})
|
pub fn create_account(feature: &Feature, lamports: u64) -> Account {
|
||||||
.unwrap() as usize
|
let data_len = Feature::size_of().max(bincode::serialized_size(feature).unwrap() as usize);
|
||||||
}
|
let mut account = Account::new(lamports, data_len, &id());
|
||||||
pub fn from_account(account: &Account) -> Option<Self> {
|
to_account(feature, &mut account).unwrap();
|
||||||
if account.owner != id() {
|
account
|
||||||
None
|
|
||||||
} else {
|
|
||||||
bincode::deserialize(&account.data).ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn to_account(&self, account: &mut Account) -> Option<()> {
|
|
||||||
bincode::serialize_into(&mut account.data[..], self).ok()
|
|
||||||
}
|
|
||||||
pub fn create_account(&self, lamports: u64) -> Account {
|
|
||||||
let data_len = Self::size_of().max(bincode::serialized_size(self).unwrap() as usize);
|
|
||||||
let mut account = Account::new(lamports, data_len, &id());
|
|
||||||
self.to_account(&mut account).unwrap();
|
|
||||||
account
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -49,31 +28,8 @@ mod test {
|
||||||
fn feature_deserialize_none() {
|
fn feature_deserialize_none() {
|
||||||
let just_initialized = Account::new(42, Feature::size_of(), &id());
|
let just_initialized = Account::new(42, Feature::size_of(), &id());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Feature::from_account(&just_initialized),
|
from_account(&just_initialized),
|
||||||
Some(Feature { activated_at: None })
|
Some(Feature { activated_at: None })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn feature_sizeof() {
|
|
||||||
assert!(
|
|
||||||
Feature::size_of() >= bincode::serialized_size(&Feature::default()).unwrap() as usize
|
|
||||||
);
|
|
||||||
assert_eq!(Feature::default(), Feature { activated_at: None });
|
|
||||||
|
|
||||||
let features = [
|
|
||||||
Feature {
|
|
||||||
activated_at: Some(0),
|
|
||||||
},
|
|
||||||
Feature {
|
|
||||||
activated_at: Some(Slot::MAX),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
for feature in &features {
|
|
||||||
assert_eq!(
|
|
||||||
Feature::size_of(),
|
|
||||||
bincode::serialized_size(feature).unwrap() as usize
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue