diff --git a/programs/mpl-core/src/plugins/external_plugin_adapters.rs b/programs/mpl-core/src/plugins/external_plugin_adapters.rs index 7068807d..47fb976a 100644 --- a/programs/mpl-core/src/plugins/external_plugin_adapters.rs +++ b/programs/mpl-core/src/plugins/external_plugin_adapters.rs @@ -3,12 +3,12 @@ use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, pubkey::Pubkey, }; -use strum::EnumCount; +use strum::{EnumCount, EnumIter}; use crate::{ error::MplCoreError, plugins::{approve, reject}, - state::{AssetV1, SolanaAccount}, + state::{AssetV1, DataBlob, SolanaAccount}, }; use super::{ @@ -22,7 +22,17 @@ use super::{ /// List of third party plugin types. #[repr(C)] #[derive( - Clone, Copy, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq, EnumCount, PartialOrd, Ord, + Clone, + Copy, + Debug, + BorshSerialize, + BorshDeserialize, + Eq, + PartialEq, + EnumCount, + PartialOrd, + Ord, + EnumIter, )] pub enum ExternalPluginAdapterType { /// Lifecycle Hook. @@ -39,6 +49,17 @@ pub enum ExternalPluginAdapterType { DataSection, } +impl ExternalPluginAdapterType { + /// A u8 enum. + const BASE_LEN: usize = 1; +} + +impl DataBlob for ExternalPluginAdapterType { + fn len(&self) -> usize { + Self::BASE_LEN + } +} + impl From<&ExternalPluginAdapterKey> for ExternalPluginAdapterType { fn from(key: &ExternalPluginAdapterKey) -> Self { match key { @@ -383,7 +404,9 @@ impl From<&ExternalPluginAdapterInitInfo> for ExternalPluginAdapter { } #[repr(C)] -#[derive(Eq, PartialEq, Clone, BorshSerialize, BorshDeserialize, Debug, PartialOrd, Ord, Hash)] +#[derive( + Eq, PartialEq, Clone, BorshSerialize, BorshDeserialize, Debug, PartialOrd, Ord, Hash, EnumIter, +)] /// An enum listing all the lifecyle events available for external plugin adapter hooks. Note that some /// lifecycle events such as adding and removing plugins will be checked by default as they are /// inherently part of the external plugin adapter system. @@ -398,6 +421,17 @@ pub enum HookableLifecycleEvent { Update, } +impl HookableLifecycleEvent { + /// A u8 enum. + const BASE_LEN: usize = 1; +} + +impl DataBlob for HookableLifecycleEvent { + fn len(&self) -> usize { + Self::BASE_LEN + } +} + /// Prefix used with some of the `ExtraAccounts` that are PDAs. pub const MPL_CORE_PREFIX: &str = "mpl-core"; @@ -747,3 +781,37 @@ impl From<&ExternalPluginAdapterInitInfo> for ExternalPluginAdapterKey { } } } + +/// Test DataBlob sizing +#[cfg(test)] +mod test { + use strum::IntoEnumIterator; + + use super::*; + + #[test] + fn test_external_plugin_adapter_type_size() { + for fixture in ExternalPluginAdapterType::iter() { + let serialized = fixture.try_to_vec().unwrap(); + assert_eq!( + serialized.len(), + fixture.len(), + "Serialized {:?} should match size returned by len()", + fixture + ); + } + } + + #[test] + fn test_hookable_lifecycle_event_size() { + for fixture in HookableLifecycleEvent::iter() { + let serialized = fixture.try_to_vec().unwrap(); + assert_eq!( + serialized.len(), + fixture.len(), + "Serialized {:?} should match size returned by len()", + fixture + ); + } + } +} diff --git a/programs/mpl-core/src/plugins/internal/authority_managed/add_blocker.rs b/programs/mpl-core/src/plugins/internal/authority_managed/add_blocker.rs index 9a2a4671..e254602a 100644 --- a/programs/mpl-core/src/plugins/internal/authority_managed/add_blocker.rs +++ b/programs/mpl-core/src/plugins/internal/authority_managed/add_blocker.rs @@ -16,11 +16,8 @@ use crate::{ pub struct AddBlocker {} impl DataBlob for AddBlocker { - fn get_initial_size() -> usize { - 0 - } - - fn get_size(&self) -> usize { + fn len(&self) -> usize { + // Stateless data blob 0 } } @@ -41,3 +38,15 @@ impl PluginValidation for AddBlocker { reject!() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_add_blocker_len() { + let add_blocker = AddBlocker {}; + let serialized = add_blocker.try_to_vec().unwrap(); + assert_eq!(serialized.len(), add_blocker.len()); + } +} diff --git a/programs/mpl-core/src/plugins/internal/authority_managed/attributes.rs b/programs/mpl-core/src/plugins/internal/authority_managed/attributes.rs index 49444977..c9811caa 100644 --- a/programs/mpl-core/src/plugins/internal/authority_managed/attributes.rs +++ b/programs/mpl-core/src/plugins/internal/authority_managed/attributes.rs @@ -6,9 +6,20 @@ use borsh::{BorshDeserialize, BorshSerialize}; #[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq, Default)] pub struct Attribute { /// The Key of the attribute. - pub key: String, // 4 + pub key: String, // 4 + len /// The Value of the attribute. - pub value: String, // 4 + pub value: String, // 4 + len +} + +impl Attribute { + const BASE_LEN: usize = 4 // The length of the Key string + + 4; // The length of the Value string +} + +impl DataBlob for Attribute { + fn len(&self) -> usize { + Self::BASE_LEN + self.key.len() + self.value.len() + } } /// The Attributes plugin allows the authority to add arbitrary Key-Value pairs to the asset. @@ -16,10 +27,12 @@ pub struct Attribute { #[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq, Default)] pub struct Attributes { /// A vector of Key-Value pairs. - pub attribute_list: Vec, // 4 + pub attribute_list: Vec, // 4 + len * Attribute } impl Attributes { + const BASE_LEN: usize = 4; // The length of the attribute list + /// Initialize the Attributes plugin, unfrozen by default. pub fn new() -> Self { Self::default() @@ -27,13 +40,54 @@ impl Attributes { } impl DataBlob for Attributes { - fn get_initial_size() -> usize { - 4 - } - - fn get_size(&self) -> usize { - 4 // TODO: Implement this. + fn len(&self) -> usize { + Self::BASE_LEN + + self + .attribute_list + .iter() + .map(|attr| attr.len()) + .sum::() } } impl PluginValidation for Attributes {} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_attribute_len() { + let attribute = Attribute { + key: "test".to_string(), + value: "test".to_string(), + }; + let serialized = attribute.try_to_vec().unwrap(); + assert_eq!(serialized.len(), attribute.len()); + } + + #[test] + fn test_attributes_default_len() { + let attributes = Attributes::new(); + let serialized = attributes.try_to_vec().unwrap(); + assert_eq!(serialized.len(), attributes.len()); + } + + #[test] + fn test_attributes_len() { + let attributes = Attributes { + attribute_list: vec![ + Attribute { + key: "test".to_string(), + value: "test".to_string(), + }, + Attribute { + key: "test2".to_string(), + value: "test2".to_string(), + }, + ], + }; + let serialized = attributes.try_to_vec().unwrap(); + assert_eq!(serialized.len(), attributes.len()); + } +} diff --git a/programs/mpl-core/src/plugins/internal/authority_managed/immutable_metadata.rs b/programs/mpl-core/src/plugins/internal/authority_managed/immutable_metadata.rs index 04ecb80f..8229fa8b 100644 --- a/programs/mpl-core/src/plugins/internal/authority_managed/immutable_metadata.rs +++ b/programs/mpl-core/src/plugins/internal/authority_managed/immutable_metadata.rs @@ -14,11 +14,8 @@ use crate::{ pub struct ImmutableMetadata {} impl DataBlob for ImmutableMetadata { - fn get_initial_size() -> usize { - 0 - } - - fn get_size(&self) -> usize { + fn len(&self) -> usize { + // Stateless data blob 0 } } @@ -32,3 +29,15 @@ impl PluginValidation for ImmutableMetadata { reject!() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_immutable_metadata_len() { + let immutable_metadata = ImmutableMetadata {}; + let serialized = immutable_metadata.try_to_vec().unwrap(); + assert_eq!(serialized.len(), immutable_metadata.len()); + } +} diff --git a/programs/mpl-core/src/plugins/internal/authority_managed/master_edition.rs b/programs/mpl-core/src/plugins/internal/authority_managed/master_edition.rs index ed008ed4..fb75c68e 100644 --- a/programs/mpl-core/src/plugins/internal/authority_managed/master_edition.rs +++ b/programs/mpl-core/src/plugins/internal/authority_managed/master_edition.rs @@ -1,6 +1,6 @@ use borsh::{BorshDeserialize, BorshSerialize}; -use crate::plugins::PluginValidation; +use crate::{plugins::PluginValidation, state::DataBlob}; /// The master edition plugin allows the creator to specify details on the master edition including max supply, name, and uri. /// The default authority for this plugin is the creator. @@ -8,11 +8,49 @@ use crate::plugins::PluginValidation; #[derive(Clone, BorshSerialize, BorshDeserialize, Default, Debug, PartialEq, Eq)] pub struct MasterEdition { /// The max supply of editions - pub max_supply: Option, + pub max_supply: Option, // 1 + optional 4 /// optional master edition name - pub name: Option, + pub name: Option, // 1 + optional 4 /// optional master edition uri - pub uri: Option, + pub uri: Option, // 1 + optional 4 +} + +impl MasterEdition { + const BASE_LEN: usize = 1 // The max_supply option + + 1 // The name option + + 1; // The uri option } impl PluginValidation for MasterEdition {} + +impl DataBlob for MasterEdition { + fn len(&self) -> usize { + Self::BASE_LEN + + self.max_supply.map_or(0, |_| 4) + + self.name.as_ref().map_or(0, |name| 4 + name.len()) + + self.uri.as_ref().map_or(0, |uri| 4 + uri.len()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_master_edition_default_len() { + let master_edition = MasterEdition::default(); + let serialized = master_edition.try_to_vec().unwrap(); + assert_eq!(serialized.len(), master_edition.len()); + } + + #[test] + fn test_master_edition_len() { + let master_edition = MasterEdition { + max_supply: Some(100), + name: Some("test".to_string()), + uri: Some("test".to_string()), + }; + let serialized = master_edition.try_to_vec().unwrap(); + assert_eq!(serialized.len(), master_edition.len()); + } +} diff --git a/programs/mpl-core/src/plugins/internal/authority_managed/royalties.rs b/programs/mpl-core/src/plugins/internal/authority_managed/royalties.rs index 97b863fa..71987e23 100644 --- a/programs/mpl-core/src/plugins/internal/authority_managed/royalties.rs +++ b/programs/mpl-core/src/plugins/internal/authority_managed/royalties.rs @@ -8,34 +8,72 @@ use crate::error::MplCoreError; use crate::plugins::{ abstain, reject, Plugin, PluginValidation, PluginValidationContext, ValidationResult, }; +use crate::state::DataBlob; /// The creator on an asset and whether or not they are verified. #[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)] pub struct Creator { - address: Pubkey, - percentage: u8, + /// The address of the creator. + pub address: Pubkey, // 32 + /// The percentage of royalties to be paid to the creator. + pub percentage: u8, // 1 +} + +impl Creator { + const BASE_LEN: usize = 32 // The address + + 1; // The percentage +} + +impl DataBlob for Creator { + fn len(&self) -> usize { + Self::BASE_LEN + } } /// The rule set for an asset indicating where it is allowed to be transferred. #[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)] pub enum RuleSet { /// No rules are enforced. - None, + None, // 1 /// Allow list of programs that are allowed to transfer, receive, or send the asset. - ProgramAllowList(Vec), + ProgramAllowList(Vec), // 4 /// Deny list of programs that are not allowed to transfer, receive, or send the asset. - ProgramDenyList(Vec), + ProgramDenyList(Vec), // 4 +} + +impl RuleSet { + const BASE_LEN: usize = 1; // The rule set discriminator +} + +impl DataBlob for RuleSet { + fn len(&self) -> usize { + Self::BASE_LEN + + match self { + RuleSet::ProgramAllowList(allow_list) => 4 + allow_list.len() * 32, + RuleSet::ProgramDenyList(deny_list) => 4 + deny_list.len() * 32, + RuleSet::None => 0, + } + } } /// Traditional royalties structure for an asset. #[derive(Clone, BorshSerialize, BorshDeserialize, Debug, Eq, PartialEq)] pub struct Royalties { /// The percentage of royalties to be paid to the creators. - basis_points: u16, + pub basis_points: u16, // 2 /// A list of creators to receive royalties. - creators: Vec, + pub creators: Vec, // 4 /// The rule set for the asset to enforce royalties. - rule_set: RuleSet, + pub rule_set: RuleSet, // 1 +} + +impl DataBlob for Royalties { + fn len(&self) -> usize { + 2 // basis_points + + 4 // creators length + + self.creators.iter().map(|creator| creator.len()).sum::() + + self.rule_set.len() // rule_set + } } fn validate_royalties(royalties: &Royalties) -> Result { @@ -133,3 +171,92 @@ impl PluginValidation for Royalties { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_creator_len() { + let creator = Creator { + address: Pubkey::default(), + percentage: 100, + }; + let serialized = creator.try_to_vec().unwrap(); + assert_eq!(serialized.len(), creator.len()); + } + + #[test] + fn test_rule_set_default_len() { + let rule_set = RuleSet::None; + let serialized = rule_set.try_to_vec().unwrap(); + assert_eq!(serialized.len(), rule_set.len()); + } + + #[test] + fn test_rule_set_len() { + let rule_sets = vec![ + RuleSet::ProgramAllowList(vec![Pubkey::default()]), + RuleSet::ProgramDenyList(vec![Pubkey::default(), Pubkey::default()]), + ]; + for rule_set in rule_sets { + let serialized = rule_set.try_to_vec().unwrap(); + assert_eq!(serialized.len(), rule_set.len()); + } + } + + #[test] + fn test_royalties_len() { + let royalties = vec![ + Royalties { + basis_points: 0, + creators: vec![], + rule_set: RuleSet::None, + }, + Royalties { + basis_points: 1, + creators: vec![Creator { + address: Pubkey::default(), + percentage: 1, + }], + rule_set: RuleSet::ProgramAllowList(vec![]), + }, + Royalties { + basis_points: 2, + creators: vec![ + Creator { + address: Pubkey::default(), + percentage: 2, + }, + Creator { + address: Pubkey::default(), + percentage: 3, + }, + ], + rule_set: RuleSet::ProgramDenyList(vec![Pubkey::default()]), + }, + Royalties { + basis_points: 3, + creators: vec![ + Creator { + address: Pubkey::default(), + percentage: 3, + }, + Creator { + address: Pubkey::default(), + percentage: 4, + }, + Creator { + address: Pubkey::default(), + percentage: 5, + }, + ], + rule_set: RuleSet::ProgramDenyList(vec![Pubkey::default(), Pubkey::default()]), + }, + ]; + for royalty in royalties { + let serialized = royalty.try_to_vec().unwrap(); + assert_eq!(serialized.len(), royalty.len()); + } + } +} diff --git a/programs/mpl-core/src/plugins/internal/authority_managed/update_delegate.rs b/programs/mpl-core/src/plugins/internal/authority_managed/update_delegate.rs index d8a952d2..b3c2457d 100644 --- a/programs/mpl-core/src/plugins/internal/authority_managed/update_delegate.rs +++ b/programs/mpl-core/src/plugins/internal/authority_managed/update_delegate.rs @@ -19,10 +19,12 @@ use crate::plugins::{ #[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)] pub struct UpdateDelegate { /// Additional update delegates. Not currently available to be used. - pub additional_delegates: Vec, // 4 + pub additional_delegates: Vec, // 4 + len * 32 } impl UpdateDelegate { + const BASE_LEN: usize = 4; // The additional delegates length + /// Initialize the UpdateDelegate plugin. pub fn new() -> Self { Self { @@ -38,12 +40,8 @@ impl Default for UpdateDelegate { } impl DataBlob for UpdateDelegate { - fn get_initial_size() -> usize { - 0 - } - - fn get_size(&self) -> usize { - 0 + fn len(&self) -> usize { + Self::BASE_LEN + self.additional_delegates.len() * 32 } } @@ -211,3 +209,31 @@ impl PluginValidation for UpdateDelegate { abstain!() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_update_delegate_default_len() { + let update_delegate = UpdateDelegate::default(); + let serialized = update_delegate.try_to_vec().unwrap(); + assert_eq!(serialized.len(), update_delegate.len()); + } + + #[test] + fn test_update_delegate_len() { + let update_delegates = vec![ + UpdateDelegate { + additional_delegates: vec![Pubkey::default()], + }, + UpdateDelegate { + additional_delegates: vec![Pubkey::default(), Pubkey::default()], + }, + ]; + for update_delegate in update_delegates { + let serialized = update_delegate.try_to_vec().unwrap(); + assert_eq!(serialized.len(), update_delegate.len()); + } + } +} diff --git a/programs/mpl-core/src/plugins/internal/authority_managed/verified_creators.rs b/programs/mpl-core/src/plugins/internal/authority_managed/verified_creators.rs index a942c0e4..81b14c13 100644 --- a/programs/mpl-core/src/plugins/internal/authority_managed/verified_creators.rs +++ b/programs/mpl-core/src/plugins/internal/authority_managed/verified_creators.rs @@ -8,19 +8,43 @@ use crate::error::MplCoreError; use crate::plugins::{ abstain, Plugin, PluginValidation, PluginValidationContext, ValidationResult, }; +use crate::state::DataBlob; /// The creator on an asset and whether or not they are verified. #[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq, Hash)] pub struct VerifiedCreatorsSignature { - address: Pubkey, - verified: bool, + /// The address of the creator. + pub address: Pubkey, // 32 + /// Whether or not the creator is verified. + pub verified: bool, // 1 +} + +impl VerifiedCreatorsSignature { + const BASE_LEN: usize = 32 // The address + + 1; // The verified boolean +} + +impl DataBlob for VerifiedCreatorsSignature { + fn len(&self) -> usize { + Self::BASE_LEN + } } /// Structure for storing verified creators, often used in conjunction with the Royalties plugin #[derive(Clone, BorshSerialize, BorshDeserialize, Debug, Eq, PartialEq)] pub struct VerifiedCreators { /// A list of signatures - signatures: Vec, + pub signatures: Vec, // 4 + len * VerifiedCreatorsSignature +} + +impl VerifiedCreators { + const BASE_LEN: usize = 4; // The signatures length +} + +impl DataBlob for VerifiedCreators { + fn len(&self) -> usize { + Self::BASE_LEN + self.signatures.iter().map(|sig| sig.len()).sum::() + } } struct SignatureChangeIndices { @@ -199,3 +223,43 @@ impl PluginValidation for VerifiedCreators { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_verified_creators_signature_len() { + let verified_creators_signature = VerifiedCreatorsSignature { + address: Pubkey::default(), + verified: false, + }; + let serialized = verified_creators_signature.try_to_vec().unwrap(); + assert_eq!(serialized.len(), verified_creators_signature.len()); + } + + #[test] + fn test_verified_creators_default_len() { + let verified_creators = VerifiedCreators { signatures: vec![] }; + let serialized = verified_creators.try_to_vec().unwrap(); + assert_eq!(serialized.len(), verified_creators.len()); + } + + #[test] + fn test_verified_creators_len() { + let verified_creators = VerifiedCreators { + signatures: vec![ + VerifiedCreatorsSignature { + address: Pubkey::default(), + verified: false, + }, + VerifiedCreatorsSignature { + address: Pubkey::default(), + verified: true, + }, + ], + }; + let serialized = verified_creators.try_to_vec().unwrap(); + assert_eq!(serialized.len(), verified_creators.len()); + } +} diff --git a/programs/mpl-core/src/plugins/internal/owner_managed/autograph.rs b/programs/mpl-core/src/plugins/internal/owner_managed/autograph.rs index abf621df..7370b3d8 100644 --- a/programs/mpl-core/src/plugins/internal/owner_managed/autograph.rs +++ b/programs/mpl-core/src/plugins/internal/owner_managed/autograph.rs @@ -8,20 +8,38 @@ use crate::{ plugins::{ abstain, approve, Plugin, PluginValidation, PluginValidationContext, ValidationResult, }, + state::DataBlob, }; /// The creator on an asset and whether or not they are verified. #[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq, Hash)] pub struct AutographSignature { - address: Pubkey, - message: String, + /// The address of the creator. + pub address: Pubkey, // 32 + /// The message of the creator. + pub message: String, // 4 + len +} + +impl AutographSignature { + const BASE_LEN: usize = 32 // The address + + 4; // The message length +} + +impl DataBlob for AutographSignature { + fn len(&self) -> usize { + Self::BASE_LEN + self.message.len() + } } /// Structure for an autograph book, often used in conjunction with the Royalties plugin for verified creators #[derive(Clone, BorshSerialize, BorshDeserialize, Debug, Eq, PartialEq)] pub struct Autograph { /// A list of signatures with option message - signatures: Vec, + pub signatures: Vec, // 4 + len * Autograph len +} + +impl Autograph { + const BASE_LEN: usize = 4; // The signatures length } fn validate_autograph( @@ -122,3 +140,49 @@ impl PluginValidation for Autograph { } } } + +impl DataBlob for Autograph { + fn len(&self) -> usize { + Self::BASE_LEN + self.signatures.iter().map(|sig| sig.len()).sum::() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_autograph_signature_len() { + let autograph_signature = AutographSignature { + address: Pubkey::default(), + message: "test".to_string(), + }; + let serialized = autograph_signature.try_to_vec().unwrap(); + assert_eq!(serialized.len(), autograph_signature.len()); + } + + #[test] + fn test_autograph_default_len() { + let autograph = Autograph { signatures: vec![] }; + let serialized = autograph.try_to_vec().unwrap(); + assert_eq!(serialized.len(), autograph.len()); + } + + #[test] + fn test_autograph_len() { + let autograph = Autograph { + signatures: vec![ + AutographSignature { + address: Pubkey::default(), + message: "test".to_string(), + }, + AutographSignature { + address: Pubkey::default(), + message: "test2".to_string(), + }, + ], + }; + let serialized = autograph.try_to_vec().unwrap(); + assert_eq!(serialized.len(), autograph.len()); + } +} diff --git a/programs/mpl-core/src/plugins/internal/owner_managed/burn_delegate.rs b/programs/mpl-core/src/plugins/internal/owner_managed/burn_delegate.rs index 15075fb3..e0c4c646 100644 --- a/programs/mpl-core/src/plugins/internal/owner_managed/burn_delegate.rs +++ b/programs/mpl-core/src/plugins/internal/owner_managed/burn_delegate.rs @@ -26,11 +26,8 @@ impl Default for BurnDelegate { } impl DataBlob for BurnDelegate { - fn get_initial_size() -> usize { - 0 - } - - fn get_size(&self) -> usize { + fn len(&self) -> usize { + // Stateless data blob 0 } } @@ -52,3 +49,15 @@ impl PluginValidation for BurnDelegate { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_burn_delegate_len() { + let burn_delegate = BurnDelegate::default(); + let serialized = burn_delegate.try_to_vec().unwrap(); + assert_eq!(serialized.len(), burn_delegate.len()); + } +} diff --git a/programs/mpl-core/src/plugins/internal/owner_managed/freeze_delegate.rs b/programs/mpl-core/src/plugins/internal/owner_managed/freeze_delegate.rs index e9e2574f..ffcd36d3 100644 --- a/programs/mpl-core/src/plugins/internal/owner_managed/freeze_delegate.rs +++ b/programs/mpl-core/src/plugins/internal/owner_managed/freeze_delegate.rs @@ -19,6 +19,8 @@ pub struct FreezeDelegate { } impl FreezeDelegate { + const BASE_LEN: usize = 1; // The frozen boolean + /// Initialize the Freeze plugin, unfrozen by default. pub fn new() -> Self { Self { frozen: false } @@ -32,12 +34,8 @@ impl Default for FreezeDelegate { } impl DataBlob for FreezeDelegate { - fn get_initial_size() -> usize { - 1 - } - - fn get_size(&self) -> usize { - 1 + fn len(&self) -> usize { + Self::BASE_LEN } } @@ -109,3 +107,15 @@ impl PluginValidation for FreezeDelegate { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_freeze_delegate_len() { + let freeze_delegate = FreezeDelegate::default(); + let serialized = freeze_delegate.try_to_vec().unwrap(); + assert_eq!(serialized.len(), freeze_delegate.len()); + } +} diff --git a/programs/mpl-core/src/plugins/internal/owner_managed/transfer_delegate.rs b/programs/mpl-core/src/plugins/internal/owner_managed/transfer_delegate.rs index 0ef04138..67da2f19 100644 --- a/programs/mpl-core/src/plugins/internal/owner_managed/transfer_delegate.rs +++ b/programs/mpl-core/src/plugins/internal/owner_managed/transfer_delegate.rs @@ -27,11 +27,8 @@ impl Default for TransferDelegate { } impl DataBlob for TransferDelegate { - fn get_initial_size() -> usize { - 0 - } - - fn get_size(&self) -> usize { + fn len(&self) -> usize { + // Stateless data blob 0 } } @@ -53,3 +50,15 @@ impl PluginValidation for TransferDelegate { abstain!() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_transfer_delegate_len() { + let transfer_delegate = TransferDelegate::default(); + let serialized = transfer_delegate.try_to_vec().unwrap(); + assert_eq!(serialized.len(), transfer_delegate.len()); + } +} diff --git a/programs/mpl-core/src/plugins/internal/permanent/edition.rs b/programs/mpl-core/src/plugins/internal/permanent/edition.rs index 576c18e3..1c94305e 100644 --- a/programs/mpl-core/src/plugins/internal/permanent/edition.rs +++ b/programs/mpl-core/src/plugins/internal/permanent/edition.rs @@ -1,8 +1,11 @@ use borsh::{BorshDeserialize, BorshSerialize}; use solana_program::program_error::ProgramError; -use crate::plugins::{ - abstain, reject, Plugin, PluginValidation, PluginValidationContext, ValidationResult, +use crate::{ + plugins::{ + abstain, reject, Plugin, PluginValidation, PluginValidationContext, ValidationResult, + }, + state::DataBlob, }; /// The edition plugin allows the creator to set an edition number on the asset @@ -14,6 +17,10 @@ pub struct Edition { pub number: u32, } +impl Edition { + const BASE_LEN: usize = 4; // The edition number +} + impl PluginValidation for Edition { fn validate_add_plugin( &self, @@ -43,3 +50,21 @@ impl PluginValidation for Edition { } } } + +impl DataBlob for Edition { + fn len(&self) -> usize { + Self::BASE_LEN + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_edition_len() { + let edition = Edition { number: 1 }; + let serialized = edition.try_to_vec().unwrap(); + assert_eq!(serialized.len(), edition.len()); + } +} diff --git a/programs/mpl-core/src/plugins/internal/permanent/permanent_burn_delegate.rs b/programs/mpl-core/src/plugins/internal/permanent/permanent_burn_delegate.rs index 9717ffdd..b5e14361 100644 --- a/programs/mpl-core/src/plugins/internal/permanent/permanent_burn_delegate.rs +++ b/programs/mpl-core/src/plugins/internal/permanent/permanent_burn_delegate.rs @@ -16,11 +16,8 @@ use crate::{ pub struct PermanentBurnDelegate {} impl DataBlob for PermanentBurnDelegate { - fn get_initial_size() -> usize { - 0 - } - - fn get_size(&self) -> usize { + fn len(&self) -> usize { + // Stateless data blob 0 } } @@ -54,3 +51,15 @@ impl PluginValidation for PermanentBurnDelegate { abstain!() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_permanent_burn_delegate_len() { + let permanent_burn_delegate = PermanentBurnDelegate::default(); + let serialized = permanent_burn_delegate.try_to_vec().unwrap(); + assert_eq!(serialized.len(), permanent_burn_delegate.len()); + } +} diff --git a/programs/mpl-core/src/plugins/internal/permanent/permanent_freeze_delegate.rs b/programs/mpl-core/src/plugins/internal/permanent/permanent_freeze_delegate.rs index 04b3daf7..75a92d22 100644 --- a/programs/mpl-core/src/plugins/internal/permanent/permanent_freeze_delegate.rs +++ b/programs/mpl-core/src/plugins/internal/permanent/permanent_freeze_delegate.rs @@ -18,6 +18,8 @@ pub struct PermanentFreezeDelegate { } impl PermanentFreezeDelegate { + const BASE_LEN: usize = 1; // The frozen boolean + /// Initialize the PermanentFreezeDelegate plugin, unfrozen by default. pub fn new() -> Self { Self { frozen: false } @@ -31,12 +33,8 @@ impl Default for PermanentFreezeDelegate { } impl DataBlob for PermanentFreezeDelegate { - fn get_initial_size() -> usize { - 1 - } - - fn get_size(&self) -> usize { - 1 + fn len(&self) -> usize { + Self::BASE_LEN } } @@ -90,3 +88,15 @@ impl PluginValidation for PermanentFreezeDelegate { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_permanent_freeze_delegate_len() { + let permanent_freeze_delegate = PermanentFreezeDelegate::default(); + let serialized = permanent_freeze_delegate.try_to_vec().unwrap(); + assert_eq!(serialized.len(), permanent_freeze_delegate.len()); + } +} diff --git a/programs/mpl-core/src/plugins/internal/permanent/permanent_transfer_delegate.rs b/programs/mpl-core/src/plugins/internal/permanent/permanent_transfer_delegate.rs index e7435595..3cd91827 100644 --- a/programs/mpl-core/src/plugins/internal/permanent/permanent_transfer_delegate.rs +++ b/programs/mpl-core/src/plugins/internal/permanent/permanent_transfer_delegate.rs @@ -15,11 +15,8 @@ use crate::plugins::{ pub struct PermanentTransferDelegate {} impl DataBlob for PermanentTransferDelegate { - fn get_initial_size() -> usize { - 0 - } - - fn get_size(&self) -> usize { + fn len(&self) -> usize { + // Stateless data blob 0 } } @@ -53,3 +50,15 @@ impl PluginValidation for PermanentTransferDelegate { abstain!() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_permanent_transfer_delegate_len() { + let permanent_transfer_delegate = PermanentTransferDelegate::default(); + let serialized = permanent_transfer_delegate.try_to_vec().unwrap(); + assert_eq!(serialized.len(), permanent_transfer_delegate.len()); + } +} diff --git a/programs/mpl-core/src/plugins/lifecycle.rs b/programs/mpl-core/src/plugins/lifecycle.rs index d3810c16..5c893276 100644 --- a/programs/mpl-core/src/plugins/lifecycle.rs +++ b/programs/mpl-core/src/plugins/lifecycle.rs @@ -5,12 +5,11 @@ use std::collections::BTreeMap; use crate::{ error::MplCoreError, - state::{Authority, Key, UpdateAuthority}, -}; - -use crate::plugins::{ - ExternalPluginAdapter, ExternalPluginAdapterKey, ExternalRegistryRecord, Plugin, PluginType, - RegistryRecord, + plugins::{ + ExternalPluginAdapter, ExternalPluginAdapterKey, ExternalRegistryRecord, Plugin, + PluginType, RegistryRecord, + }, + state::{Authority, DataBlob, Key, UpdateAuthority}, }; /// Lifecycle permissions @@ -37,7 +36,15 @@ pub struct ExternalCheckResult { pub flags: u32, } +impl DataBlob for ExternalCheckResult { + fn len(&self) -> usize { + Self::BASE_LEN + } +} + impl ExternalCheckResult { + const BASE_LEN: usize = 4; // u32 flags + pub(crate) fn none() -> Self { Self { flags: 0 } } @@ -1105,3 +1112,20 @@ pub(crate) fn validate_external_plugin_adapter_checks<'a>( abstain!() } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_external_check_result_size() { + let fixture = ExternalCheckResult { flags: 0 }; + let serialized = fixture.try_to_vec().unwrap(); + assert_eq!( + serialized.len(), + fixture.len(), + "Serialized {:?} should match size returned by len()", + fixture + ); + } +} diff --git a/programs/mpl-core/src/plugins/mod.rs b/programs/mpl-core/src/plugins/mod.rs index f9c9e637..ed4cfe5b 100644 --- a/programs/mpl-core/src/plugins/mod.rs +++ b/programs/mpl-core/src/plugins/mod.rs @@ -19,7 +19,7 @@ use num_derive::ToPrimitive; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, msg, program_error::ProgramError, }; -use strum::EnumCount; +use strum::{EnumCount, EnumIter}; use crate::{ error::MplCoreError, @@ -88,6 +88,35 @@ impl Plugin { impl Compressible for Plugin {} +impl DataBlob for Plugin { + fn len(&self) -> usize { + 1 // The discriminator + + match self { + Plugin::Royalties(royalties) => royalties.len(), + Plugin::FreezeDelegate(freeze_delegate) => freeze_delegate.len(), + Plugin::BurnDelegate(burn_delegate) => burn_delegate.len(), + Plugin::TransferDelegate(transfer_delegate) => transfer_delegate.len(), + Plugin::UpdateDelegate(update_delegate) => update_delegate.len(), + Plugin::PermanentFreezeDelegate(permanent_freeze_delegate) => { + permanent_freeze_delegate.len() + } + Plugin::Attributes(attributes) => attributes.len(), + Plugin::PermanentTransferDelegate(permanent_transfer_delegate) => { + permanent_transfer_delegate.len() + } + Plugin::PermanentBurnDelegate(permanent_burn_delegate) => { + permanent_burn_delegate.len() + } + Plugin::Edition(edition) => edition.len(), + Plugin::MasterEdition(master_edition) => master_edition.len(), + Plugin::AddBlocker(add_blocker) => add_blocker.len(), + Plugin::ImmutableMetadata(immutable_metadata) => immutable_metadata.len(), + Plugin::VerifiedCreators(verified_creators) => verified_creators.len(), + Plugin::Autograph(autograph) => autograph.len(), + } + } +} + /// List of first party plugin types. #[repr(C)] #[derive( @@ -103,6 +132,7 @@ impl Compressible for Plugin {} EnumCount, PartialOrd, Ord, + EnumIter, )] pub enum PluginType { /// Royalties plugin. @@ -137,6 +167,11 @@ pub enum PluginType { Autograph, } +impl PluginType { + /// A u8 enum discriminator. + const BASE_LEN: usize = 1; +} + /// The list of permanent delegate types. pub const PERMANENT_DELEGATES: [PluginType; 3] = [ PluginType::PermanentFreezeDelegate, @@ -145,12 +180,8 @@ pub const PERMANENT_DELEGATES: [PluginType; 3] = [ ]; impl DataBlob for PluginType { - fn get_initial_size() -> usize { - 2 - } - - fn get_size(&self) -> usize { - 2 + fn len(&self) -> usize { + Self::BASE_LEN } } @@ -206,3 +237,191 @@ pub(crate) struct PluginAuthorityPair { pub(crate) plugin: Plugin, pub(crate) authority: Option, } + +#[cfg(test)] +mod test { + use solana_program::pubkey::Pubkey; + use strum::IntoEnumIterator; + + use super::*; + + #[test] + fn test_plugin_empty_size() { + //TODO: Implement Default for all plugins in a separate PR. + let plugins = vec![ + Plugin::Royalties(Royalties { + basis_points: 0, + creators: vec![], + rule_set: RuleSet::None, + }), + Plugin::FreezeDelegate(FreezeDelegate { frozen: false }), + Plugin::BurnDelegate(BurnDelegate {}), + Plugin::TransferDelegate(TransferDelegate {}), + Plugin::UpdateDelegate(UpdateDelegate { + additional_delegates: vec![], + }), + Plugin::PermanentFreezeDelegate(PermanentFreezeDelegate { frozen: false }), + Plugin::Attributes(Attributes { + attribute_list: vec![], + }), + Plugin::PermanentTransferDelegate(PermanentTransferDelegate {}), + Plugin::PermanentBurnDelegate(PermanentBurnDelegate {}), + Plugin::Edition(Edition { number: 0 }), + Plugin::MasterEdition(MasterEdition { + max_supply: None, + name: None, + uri: None, + }), + Plugin::AddBlocker(AddBlocker {}), + Plugin::ImmutableMetadata(ImmutableMetadata {}), + Plugin::VerifiedCreators(VerifiedCreators { signatures: vec![] }), + Plugin::Autograph(Autograph { signatures: vec![] }), + ]; + + assert_eq!( + plugins.len(), + PluginType::COUNT, + "All plugins should be tested" + ); + + for fixture in plugins { + let serialized = fixture.try_to_vec().unwrap(); + assert_eq!( + serialized.len(), + fixture.len(), + "Serialized {:?} should match size returned by len()", + fixture + ); + } + } + + #[test] + fn test_plugin_different_size() { + //TODO: Implement Default for all plugins in a separate PR. + let plugins: Vec> = vec![ + vec![ + Plugin::Royalties(Royalties { + basis_points: 0, + creators: vec![], + rule_set: RuleSet::None, + }), + Plugin::Royalties(Royalties { + basis_points: 1, + creators: vec![Creator { + address: Pubkey::default(), + percentage: 1, + }], + rule_set: RuleSet::ProgramAllowList(vec![]), + }), + Plugin::Royalties(Royalties { + basis_points: 2, + creators: vec![ + Creator { + address: Pubkey::default(), + percentage: 2, + }, + Creator { + address: Pubkey::default(), + percentage: 3, + }, + ], + rule_set: RuleSet::ProgramDenyList(vec![Pubkey::default()]), + }), + Plugin::Royalties(Royalties { + basis_points: 3, + creators: vec![ + Creator { + address: Pubkey::default(), + percentage: 3, + }, + Creator { + address: Pubkey::default(), + percentage: 4, + }, + Creator { + address: Pubkey::default(), + percentage: 5, + }, + ], + rule_set: RuleSet::ProgramDenyList(vec![Pubkey::default(), Pubkey::default()]), + }), + ], + vec![Plugin::FreezeDelegate(FreezeDelegate { frozen: true })], + vec![Plugin::BurnDelegate(BurnDelegate {})], + vec![Plugin::TransferDelegate(TransferDelegate {})], + vec![Plugin::UpdateDelegate(UpdateDelegate { + additional_delegates: vec![Pubkey::default(), Pubkey::default()], + })], + vec![Plugin::PermanentFreezeDelegate(PermanentFreezeDelegate { + frozen: true, + })], + vec![Plugin::Attributes(Attributes { + attribute_list: vec![ + Attribute { + key: "test".to_string(), + value: "test".to_string(), + }, + Attribute { + key: "test2".to_string(), + value: "test2".to_string(), + }, + ], + })], + vec![Plugin::PermanentTransferDelegate( + PermanentTransferDelegate {}, + )], + vec![Plugin::PermanentBurnDelegate(PermanentBurnDelegate {})], + vec![Plugin::Edition(Edition { number: 1 })], + vec![Plugin::MasterEdition(MasterEdition { + max_supply: Some(1), + name: Some("test".to_string()), + uri: Some("test".to_string()), + })], + vec![Plugin::AddBlocker(AddBlocker {})], + vec![Plugin::ImmutableMetadata(ImmutableMetadata {})], + vec![Plugin::VerifiedCreators(VerifiedCreators { + signatures: vec![VerifiedCreatorsSignature { + address: Pubkey::default(), + verified: true, + }], + })], + vec![Plugin::Autograph(Autograph { + signatures: vec![AutographSignature { + address: Pubkey::default(), + message: "test".to_string(), + }], + })], + ]; + + assert_eq!( + plugins.len(), + PluginType::COUNT, + "All plugins should be tested" + ); + + for fixtures in plugins { + for fixture in fixtures { + let serialized = fixture.try_to_vec().unwrap(); + assert_eq!( + serialized.len(), + fixture.len(), + "Serialized {:?} should match size returned by len()", + fixture + ); + } + } + } + + #[test] + fn test_plugin_type_size() { + for fixture in PluginType::iter() { + let serialized = fixture.try_to_vec().unwrap(); + assert_eq!( + serialized.len(), + fixture.len(), + "Serialized {:?} should match size returned by len()", + fixture + ); + } + } +} diff --git a/programs/mpl-core/src/plugins/plugin_header.rs b/programs/mpl-core/src/plugins/plugin_header.rs index 366feda3..bf4b2520 100644 --- a/programs/mpl-core/src/plugins/plugin_header.rs +++ b/programs/mpl-core/src/plugins/plugin_header.rs @@ -14,13 +14,14 @@ pub struct PluginHeaderV1 { pub plugin_registry_offset: usize, // 8 } -impl DataBlob for PluginHeaderV1 { - fn get_initial_size() -> usize { - 1 + 8 - } +impl PluginHeaderV1 { + const BASE_LEN: usize = 1 // Key + + 8; // Offset +} - fn get_size(&self) -> usize { - 1 + 8 +impl DataBlob for PluginHeaderV1 { + fn len(&self) -> usize { + Self::BASE_LEN } } @@ -29,3 +30,18 @@ impl SolanaAccount for PluginHeaderV1 { Key::PluginHeaderV1 } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_plugin_header_v1_len() { + let header = PluginHeaderV1 { + key: Key::PluginHeaderV1, + plugin_registry_offset: 0, + }; + let serialized = header.try_to_vec().unwrap(); + assert_eq!(serialized.len(), header.len()); + } +} diff --git a/programs/mpl-core/src/plugins/plugin_registry.rs b/programs/mpl-core/src/plugins/plugin_registry.rs index 046f1813..c559db9d 100644 --- a/programs/mpl-core/src/plugins/plugin_registry.rs +++ b/programs/mpl-core/src/plugins/plugin_registry.rs @@ -27,6 +27,10 @@ pub struct PluginRegistryV1 { } impl PluginRegistryV1 { + const BASE_LEN: usize = 1 // Key + + 4 // Registry Length + + 4; // External Registry Length + /// Evaluate checks for all plugins in the registry. pub(crate) fn check_registry( &self, @@ -112,12 +116,18 @@ impl PluginRegistryV1 { } impl DataBlob for PluginRegistryV1 { - fn get_initial_size() -> usize { - 9 - } - - fn get_size(&self) -> usize { - 9 //TODO: Fix this + fn len(&self) -> usize { + Self::BASE_LEN + + self + .registry + .iter() + .map(|record| record.len()) + .sum::() + + self + .external_registry + .iter() + .map(|record| record.len()) + .sum::() } } @@ -132,7 +142,7 @@ impl SolanaAccount for PluginRegistryV1 { #[derive(Clone, BorshSerialize, BorshDeserialize, Debug)] pub struct RegistryRecord { /// The type of plugin. - pub plugin_type: PluginType, // 2 + pub plugin_type: PluginType, // 1 /// The authority who has permission to utilize a plugin. pub authority: Authority, // Variable /// The offset to the plugin in the account. @@ -146,6 +156,12 @@ impl RegistryRecord { } } +impl DataBlob for RegistryRecord { + fn len(&self) -> usize { + self.plugin_type.len() + self.authority.len() + 8 + } +} + /// A type to store the mapping of third party plugin type to third party plugin header and data. #[repr(C)] #[derive(Clone, BorshSerialize, BorshDeserialize, Debug, Eq, PartialEq)] @@ -188,3 +204,175 @@ impl ExternalRegistryRecord { Ok(()) } } + +impl DataBlob for ExternalRegistryRecord { + fn len(&self) -> usize { + let mut len = self.plugin_type.len() + self.authority.len() + + 1 // Lifecycle checks option + + 8 // Offset + + 1 // Data offset option + + 1; // Data len option + + if let Some(checks) = &self.lifecycle_checks { + len += 4 // 4 bytes for the length of the checks vector + + checks.len() + * (1 // HookableLifecycleEvent is a u8 enum + + 4); // ExternalCheckResult is a u32 flags + } + + if self.data_offset.is_some() { + len += 8; + } + + if self.data_len.is_some() { + len += 8; + } + + len + } +} + +#[cfg(test)] +mod tests { + use solana_program::pubkey::Pubkey; + + use super::*; + + #[test] + fn test_plugin_registry_v1_default_len() { + let registry = PluginRegistryV1 { + key: Key::PluginRegistryV1, + registry: vec![], + external_registry: vec![], + }; + let serialized = registry.try_to_vec().unwrap(); + assert_eq!(serialized.len(), registry.len()); + } + + #[test] + fn test_plugin_registry_v1_different_len() { + let registry = PluginRegistryV1 { + key: Key::PluginRegistryV1, + registry: vec![ + RegistryRecord { + plugin_type: PluginType::TransferDelegate, + authority: Authority::UpdateAuthority, + offset: 0, + }, + RegistryRecord { + plugin_type: PluginType::FreezeDelegate, + authority: Authority::Owner, + offset: 1, + }, + RegistryRecord { + plugin_type: PluginType::PermanentBurnDelegate, + authority: Authority::Address { + address: Pubkey::default(), + }, + offset: 2, + }, + ], + external_registry: vec![ + ExternalRegistryRecord { + plugin_type: ExternalPluginAdapterType::LifecycleHook, + authority: Authority::UpdateAuthority, + lifecycle_checks: None, + offset: 3, + data_offset: None, + data_len: None, + }, + ExternalRegistryRecord { + plugin_type: ExternalPluginAdapterType::Oracle, + authority: Authority::Owner, + lifecycle_checks: Some(vec![]), + offset: 3, + data_offset: Some(4), + data_len: None, + }, + ExternalRegistryRecord { + plugin_type: ExternalPluginAdapterType::AppData, + authority: Authority::Address { + address: Pubkey::default(), + }, + lifecycle_checks: Some(vec![( + HookableLifecycleEvent::Create, + ExternalCheckResult { flags: 5 }, + )]), + offset: 6, + data_offset: Some(7), + data_len: Some(8), + }, + ], + }; + let serialized = registry.try_to_vec().unwrap(); + assert_eq!(serialized.len(), registry.len()); + } + + #[test] + fn test_registry_record_len() { + let records = vec![ + RegistryRecord { + plugin_type: PluginType::TransferDelegate, + authority: Authority::UpdateAuthority, + offset: 0, + }, + RegistryRecord { + plugin_type: PluginType::FreezeDelegate, + authority: Authority::Owner, + offset: 1, + }, + RegistryRecord { + plugin_type: PluginType::PermanentBurnDelegate, + authority: Authority::Address { + address: Pubkey::default(), + }, + offset: 2, + }, + ]; + + for record in records { + let serialized = record.try_to_vec().unwrap(); + assert_eq!(serialized.len(), record.len()); + } + } + + #[test] + fn test_external_registry_record_len() { + let records = vec![ + ExternalRegistryRecord { + plugin_type: ExternalPluginAdapterType::LifecycleHook, + authority: Authority::UpdateAuthority, + lifecycle_checks: None, + offset: 3, + data_offset: None, + data_len: None, + }, + ExternalRegistryRecord { + plugin_type: ExternalPluginAdapterType::Oracle, + authority: Authority::Owner, + lifecycle_checks: Some(vec![]), + offset: 3, + data_offset: Some(4), + data_len: None, + }, + ExternalRegistryRecord { + plugin_type: ExternalPluginAdapterType::AppData, + authority: Authority::Address { + address: Pubkey::default(), + }, + lifecycle_checks: Some(vec![( + HookableLifecycleEvent::Create, + ExternalCheckResult { flags: 5 }, + )]), + offset: 6, + data_offset: Some(7), + data_len: Some(8), + }, + ]; + + for record in records { + let serialized = record.try_to_vec().unwrap(); + assert_eq!(serialized.len(), record.len()); + } + } +} diff --git a/programs/mpl-core/src/plugins/utils.rs b/programs/mpl-core/src/plugins/utils.rs index f2f25d1b..ed61b3b1 100644 --- a/programs/mpl-core/src/plugins/utils.rs +++ b/programs/mpl-core/src/plugins/utils.rs @@ -27,16 +27,18 @@ pub fn create_meta_idempotent<'a, T: SolanaAccount + DataBlob>( account: &AccountInfo<'a>, payer: &AccountInfo<'a>, system_program: &AccountInfo<'a>, -) -> Result<(T, PluginHeaderV1, PluginRegistryV1), ProgramError> { +) -> Result<(T, usize, PluginHeaderV1, PluginRegistryV1), ProgramError> { let core = T::load(account, 0)?; - let header_offset = core.get_size(); + let header_offset = core.len(); // Check if the plugin header and registry exist. if header_offset == account.data_len() { // They don't exist, so create them. let header = PluginHeaderV1 { key: Key::PluginHeaderV1, - plugin_registry_offset: header_offset + PluginHeaderV1::get_initial_size(), + plugin_registry_offset: header_offset + + 1 // Plugin Header Key + + 8, // Plugin Registry Offset }; let registry = PluginRegistryV1 { key: Key::PluginRegistryV1, @@ -48,19 +50,19 @@ pub fn create_meta_idempotent<'a, T: SolanaAccount + DataBlob>( account, payer, system_program, - header.plugin_registry_offset + PluginRegistryV1::get_initial_size(), + header.plugin_registry_offset + registry.len(), )?; header.save(account, header_offset)?; registry.save(account, header.plugin_registry_offset)?; - Ok((core, header, registry)) + Ok((core, header_offset, header, registry)) } else { // They exist, so load them. let header = PluginHeaderV1::load(account, header_offset)?; let registry = PluginRegistryV1::load(account, header.plugin_registry_offset)?; - Ok((core, header, registry)) + Ok((core, header_offset, header, registry)) } } @@ -70,13 +72,15 @@ pub fn create_plugin_meta<'a, T: SolanaAccount + DataBlob>( account: &AccountInfo<'a>, payer: &AccountInfo<'a>, system_program: &AccountInfo<'a>, -) -> Result<(PluginHeaderV1, PluginRegistryV1), ProgramError> { - let header_offset = asset.get_size(); +) -> Result<(usize, PluginHeaderV1, PluginRegistryV1), ProgramError> { + let header_offset = asset.len(); // They don't exist, so create them. let header = PluginHeaderV1 { key: Key::PluginHeaderV1, - plugin_registry_offset: header_offset + PluginHeaderV1::get_initial_size(), + plugin_registry_offset: header_offset + + 1 // Plugin Header Key + + 8, // Plugin Registry Offset }; let registry = PluginRegistryV1 { key: Key::PluginRegistryV1, @@ -88,13 +92,13 @@ pub fn create_plugin_meta<'a, T: SolanaAccount + DataBlob>( account, payer, system_program, - header.plugin_registry_offset + PluginRegistryV1::get_initial_size(), + header.plugin_registry_offset + registry.len(), )?; header.save(account, header_offset)?; registry.save(account, header.plugin_registry_offset)?; - Ok((header, registry)) + Ok((header_offset, header, registry)) } /// Assert that the Plugin metadata has been initialized. @@ -102,7 +106,7 @@ pub fn assert_plugins_initialized(account: &AccountInfo) -> ProgramResult { let mut bytes: &[u8] = &(*account.data).borrow(); let asset = AssetV1::deserialize(&mut bytes)?; - if asset.get_size() == account.data_len() { + if asset.len() == account.data_len() { return Err(MplCoreError::PluginsNotInitialized.into()); } @@ -116,11 +120,11 @@ pub fn fetch_plugin( ) -> Result<(Authority, U, usize), ProgramError> { let asset = T::load(account, 0)?; - if asset.get_size() == account.data_len() { + if asset.len() == account.data_len() { return Err(MplCoreError::PluginNotFound.into()); } - let header = PluginHeaderV1::load(account, asset.get_size())?; + let header = PluginHeaderV1::load(account, asset.len())?; let PluginRegistryV1 { registry, .. } = PluginRegistryV1::load(account, header.plugin_registry_offset)?; @@ -155,15 +159,15 @@ pub fn fetch_wrapped_plugin( plugin_type: PluginType, ) -> Result<(Authority, Plugin), ProgramError> { let size = match core { - Some(core) => core.get_size(), + Some(core) => core.len(), None => { let asset = T::load(account, 0)?; - if asset.get_size() == account.data_len() { + if asset.len() == account.data_len() { return Err(MplCoreError::PluginNotFound.into()); } - asset.get_size() + asset.len() } }; @@ -191,15 +195,15 @@ pub fn fetch_wrapped_external_plugin_adapter( plugin_key: &ExternalPluginAdapterKey, ) -> Result<(ExternalRegistryRecord, ExternalPluginAdapter), ProgramError> { let size = match core { - Some(core) => core.get_size(), + Some(core) => core.len(), None => { let asset = T::load(account, 0)?; - if asset.get_size() == account.data_len() { + if asset.len() == account.data_len() { return Err(MplCoreError::ExternalPluginAdapterNotFound.into()); } - asset.get_size() + asset.len() } }; @@ -225,11 +229,11 @@ pub fn fetch_wrapped_external_plugin_adapter( pub fn fetch_plugins(account: &AccountInfo) -> Result, ProgramError> { let asset = AssetV1::load(account, 0)?; - if asset.get_size() == account.data_len() { + if asset.len() == account.data_len() { return Err(MplCoreError::PluginNotFound.into()); } - let header = PluginHeaderV1::load(account, asset.get_size())?; + let header = PluginHeaderV1::load(account, asset.len())?; let PluginRegistryV1 { registry, .. } = PluginRegistryV1::load(account, header.plugin_registry_offset)?; @@ -242,11 +246,11 @@ pub fn list_plugins( ) -> Result, ProgramError> { let asset = T::load(account, 0)?; - if asset.get_size() == account.data_len() { + if asset.len() == account.data_len() { return Err(MplCoreError::PluginNotFound.into()); } - let header = PluginHeaderV1::load(account, asset.get_size())?; + let header = PluginHeaderV1::load(account, asset.len())?; let PluginRegistryV1 { registry, .. } = PluginRegistryV1::load(account, header.plugin_registry_offset)?; @@ -257,20 +261,19 @@ pub fn list_plugins( } /// Add a plugin to the registry and initialize it. +#[allow(clippy::too_many_arguments)] pub fn initialize_plugin<'a, T: DataBlob + SolanaAccount>( plugin: &Plugin, authority: &Authority, + header_offset: usize, plugin_header: &mut PluginHeaderV1, plugin_registry: &mut PluginRegistryV1, account: &AccountInfo<'a>, payer: &AccountInfo<'a>, system_program: &AccountInfo<'a>, ) -> ProgramResult { - let core = T::load(account, 0)?; - let header_offset = core.get_size(); let plugin_type = plugin.into(); - let plugin_data = plugin.try_to_vec()?; - let plugin_size = plugin_data.len(); + let plugin_size = plugin.len(); // You cannot add a duplicate plugin. if plugin_registry @@ -290,7 +293,7 @@ pub fn initialize_plugin<'a, T: DataBlob + SolanaAccount>( }; let size_increase = plugin_size - .checked_add(new_registry_record.try_to_vec()?.len()) + .checked_add(new_registry_record.len()) .ok_or(MplCoreError::NumericalOverflow)?; let new_registry_offset = plugin_header @@ -319,7 +322,7 @@ pub fn initialize_plugin<'a, T: DataBlob + SolanaAccount>( #[allow(clippy::too_many_arguments)] pub fn initialize_external_plugin_adapter<'a, T: DataBlob + SolanaAccount>( init_info: &ExternalPluginAdapterInitInfo, - core: Option<&T>, + header_offset: usize, plugin_header: &mut PluginHeaderV1, plugin_registry: &mut PluginRegistryV1, account: &AccountInfo<'a>, @@ -327,18 +330,6 @@ pub fn initialize_external_plugin_adapter<'a, T: DataBlob + SolanaAccount>( system_program: &AccountInfo<'a>, appended_data: Option<&[u8]>, ) -> ProgramResult { - let header_offset = match core { - Some(core) => core.get_size(), - None => { - let asset = T::load(account, 0)?; - - if asset.get_size() == account.data_len() { - return Err(MplCoreError::ExternalPluginAdapterNotFound.into()); - } - - asset.get_size() - } - }; let plugin_type = init_info.into(); // Note currently we are blocking adding LifecycleHook and LinkedLifecycleHook external plugin adapters as they @@ -536,7 +527,7 @@ pub fn update_external_plugin_adapter_data<'a, T: DataBlob + SolanaAccount>( plugin_registry.external_registry[record_index].data_len = Some(new_data_len); plugin_registry.save(account, new_registry_offset as usize)?; - plugin_header.save(account, core.map_or(0, |core| core.get_size()))?; + plugin_header.save(account, core.map_or(0, |core| core.len()))?; Ok(()) } @@ -579,12 +570,12 @@ pub fn delete_plugin<'a, T: DataBlob>( payer: &AccountInfo<'a>, system_program: &AccountInfo<'a>, ) -> ProgramResult { - if asset.get_size() == account.data_len() { + if asset.len() == account.data_len() { return Err(MplCoreError::PluginNotFound.into()); } //TODO: Bytemuck this. - let mut header = PluginHeaderV1::load(account, asset.get_size())?; + let mut header = PluginHeaderV1::load(account, asset.len())?; let mut plugin_registry = PluginRegistryV1::load(account, header.plugin_registry_offset)?; if let Some(index) = plugin_registry @@ -632,7 +623,7 @@ pub fn delete_plugin<'a, T: DataBlob>( ); header.plugin_registry_offset = new_registry_offset; - header.save(account, asset.get_size())?; + header.save(account, asset.len())?; // Move offsets for existing registry records. plugin_registry.bump_offsets(plugin_offset, -(serialized_plugin.len() as isize))?; @@ -655,12 +646,12 @@ pub fn delete_external_plugin_adapter<'a, T: DataBlob>( payer: &AccountInfo<'a>, system_program: &AccountInfo<'a>, ) -> ProgramResult { - if asset.get_size() == account.data_len() { + if asset.len() == account.data_len() { return Err(MplCoreError::ExternalPluginAdapterNotFound.into()); } //TODO: Bytemuck this. - let mut header = PluginHeaderV1::load(account, asset.get_size())?; + let mut header = PluginHeaderV1::load(account, asset.len())?; let mut plugin_registry = PluginRegistryV1::load(account, header.plugin_registry_offset)?; let result = find_external_plugin_adapter(&plugin_registry, plugin_key, account)?; @@ -710,7 +701,7 @@ pub fn delete_external_plugin_adapter<'a, T: DataBlob>( ); header.plugin_registry_offset = new_registry_offset; - header.save(account, asset.get_size())?; + header.save(account, asset.len())?; // Move offsets for existing registry records. plugin_registry.bump_offsets(plugin_offset, -(serialized_plugin_len as isize))?; diff --git a/programs/mpl-core/src/processor/add_external_plugin_adapter.rs b/programs/mpl-core/src/processor/add_external_plugin_adapter.rs index 9f95acf6..8d9e0aa0 100644 --- a/programs/mpl-core/src/processor/add_external_plugin_adapter.rs +++ b/programs/mpl-core/src/processor/add_external_plugin_adapter.rs @@ -113,7 +113,6 @@ pub(crate) fn add_external_plugin_adapter<'a>( ctx.accounts.payer, ctx.accounts.system_program, &args.init_info, - &asset, ) } @@ -172,7 +171,7 @@ pub(crate) fn add_collection_external_plugin_adapter<'a>( let external_plugin_adapter = ExternalPluginAdapter::from(&args.init_info); // Validate collection permissions. - let (core, _, _) = validate_collection_permissions( + let _ = validate_collection_permissions( accounts, authority, ctx.accounts.collection, @@ -192,7 +191,6 @@ pub(crate) fn add_collection_external_plugin_adapter<'a>( ctx.accounts.payer, ctx.accounts.system_program, &args.init_info, - &core, ) } @@ -201,13 +199,12 @@ fn process_add_external_plugin_adapter<'a, T: DataBlob + SolanaAccount>( payer: &AccountInfo<'a>, system_program: &AccountInfo<'a>, init_info: &ExternalPluginAdapterInitInfo, - core: &T, ) -> ProgramResult { - let (_, mut plugin_header, mut plugin_registry) = + let (_, header_offset, mut plugin_header, mut plugin_registry) = create_meta_idempotent::(account, payer, system_program)?; initialize_external_plugin_adapter::( init_info, - Some(core), + header_offset, &mut plugin_header, &mut plugin_registry, account, diff --git a/programs/mpl-core/src/processor/add_plugin.rs b/programs/mpl-core/src/processor/add_plugin.rs index 77c1626b..cff3a3b3 100644 --- a/programs/mpl-core/src/processor/add_plugin.rs +++ b/programs/mpl-core/src/processor/add_plugin.rs @@ -183,11 +183,12 @@ fn process_add_plugin<'a, T: DataBlob + SolanaAccount>( plugin: &Plugin, authority: &Authority, ) -> ProgramResult { - let (_, mut plugin_header, mut plugin_registry) = + let (_, header_offset, mut plugin_header, mut plugin_registry) = create_meta_idempotent::(account, payer, system_program)?; initialize_plugin::( plugin, authority, + header_offset, &mut plugin_header, &mut plugin_registry, account, diff --git a/programs/mpl-core/src/processor/create.rs b/programs/mpl-core/src/processor/create.rs index b86ccb08..bc14f00f 100644 --- a/programs/mpl-core/src/processor/create.rs +++ b/programs/mpl-core/src/processor/create.rs @@ -173,12 +173,13 @@ pub(crate) fn process_create<'a>( if let Some(plugins) = args.plugins { if !plugins.is_empty() { - let (mut plugin_header, mut plugin_registry) = create_plugin_meta::( - &new_asset, - ctx.accounts.asset, - ctx.accounts.payer, - ctx.accounts.system_program, - )?; + let (header_offset, mut plugin_header, mut plugin_registry) = + create_plugin_meta::( + &new_asset, + ctx.accounts.asset, + ctx.accounts.payer, + ctx.accounts.system_program, + )?; for plugin in &plugins { // TODO move into plugin validation when asset/collection is part of validation context let plugin_type = PluginType::from(&plugin.plugin); @@ -209,6 +210,7 @@ pub(crate) fn process_create<'a>( initialize_plugin::( &plugin.plugin, &plugin.authority.unwrap_or(plugin.plugin.manager()), + header_offset, &mut plugin_header, &mut plugin_registry, ctx.accounts.asset, @@ -221,11 +223,12 @@ pub(crate) fn process_create<'a>( if let Some(plugins) = args.external_plugin_adapters { if !plugins.is_empty() { - let (_, mut plugin_header, mut plugin_registry) = create_meta_idempotent::( - ctx.accounts.asset, - ctx.accounts.payer, - ctx.accounts.system_program, - )?; + let (_, header_offset, mut plugin_header, mut plugin_registry) = + create_meta_idempotent::( + ctx.accounts.asset, + ctx.accounts.payer, + ctx.accounts.system_program, + )?; for plugin_init_info in &plugins { let external_check_result_bits = ExternalCheckResultBits::from( ExternalPluginAdapter::check_create(plugin_init_info), @@ -267,7 +270,7 @@ pub(crate) fn process_create<'a>( } initialize_external_plugin_adapter::( plugin_init_info, - Some(&new_asset), + header_offset, &mut plugin_header, &mut plugin_registry, ctx.accounts.asset, diff --git a/programs/mpl-core/src/processor/create_collection.rs b/programs/mpl-core/src/processor/create_collection.rs index 33d9a732..2a14cfb4 100644 --- a/programs/mpl-core/src/processor/create_collection.rs +++ b/programs/mpl-core/src/processor/create_collection.rs @@ -113,12 +113,13 @@ pub(crate) fn process_create_collection<'a>( let mut force_approved = false; if let Some(plugins) = args.plugins { if !plugins.is_empty() { - let (mut plugin_header, mut plugin_registry) = create_plugin_meta::( - &new_collection, - ctx.accounts.collection, - ctx.accounts.payer, - ctx.accounts.system_program, - )?; + let (header_offset, mut plugin_header, mut plugin_registry) = + create_plugin_meta::( + &new_collection, + ctx.accounts.collection, + ctx.accounts.payer, + ctx.accounts.system_program, + )?; for plugin in &plugins { // Cannot have owner-managed plugins on collection. if plugin.plugin.manager() == Authority::Owner { @@ -153,6 +154,7 @@ pub(crate) fn process_create_collection<'a>( initialize_plugin::( &plugin.plugin, &plugin.authority.unwrap_or(plugin.plugin.manager()), + header_offset, &mut plugin_header, &mut plugin_registry, ctx.accounts.collection, @@ -165,11 +167,12 @@ pub(crate) fn process_create_collection<'a>( if let Some(plugins) = args.external_plugin_adapters { if !plugins.is_empty() { - let (_, mut plugin_header, mut plugin_registry) = create_meta_idempotent::( - ctx.accounts.collection, - ctx.accounts.payer, - ctx.accounts.system_program, - )?; + let (_, header_offset, mut plugin_header, mut plugin_registry) = + create_meta_idempotent::( + ctx.accounts.collection, + ctx.accounts.payer, + ctx.accounts.system_program, + )?; for plugin_init_info in &plugins { if let ExternalPluginAdapterInitInfo::DataSection(_) = plugin_init_info { return Err(MplCoreError::CannotAddDataSection.into()); @@ -177,7 +180,7 @@ pub(crate) fn process_create_collection<'a>( initialize_external_plugin_adapter::( plugin_init_info, - Some(&new_collection), + header_offset, &mut plugin_header, &mut plugin_registry, ctx.accounts.collection, diff --git a/programs/mpl-core/src/processor/update.rs b/programs/mpl-core/src/processor/update.rs index 397a2aaa..6095da39 100644 --- a/programs/mpl-core/src/processor/update.rs +++ b/programs/mpl-core/src/processor/update.rs @@ -129,7 +129,7 @@ fn update<'a>( // Increment sequence number and save only if it is `Some(_)`. asset.increment_seq_and_save(ctx.accounts.asset)?; - let asset_size = asset.get_size() as isize; + let asset_size = asset.len() as isize; let mut dirty = false; if let Some(new_update_authority) = args.new_update_authority { @@ -187,7 +187,7 @@ fn update<'a>( // Get a set of all the plugins on the collection (if any). let plugin_set: HashSet<_> = - if new_collection_account.data_len() > new_collection.get_size() { + if new_collection_account.data_len() > new_collection.len() { let plugin_list = list_plugins::(new_collection_account)?; plugin_list.into_iter().collect() } else { @@ -298,7 +298,7 @@ pub(crate) fn update_collection<'a>( Some(HookableLifecycleEvent::Update), )?; - let collection_size = collection.get_size() as isize; + let collection_size = collection.len() as isize; let mut dirty = false; if let Some(new_update_authority) = ctx.accounts.new_update_authority { @@ -341,7 +341,7 @@ fn process_update<'a, T: DataBlob + SolanaAccount>( (plugin_header.clone(), plugin_registry.clone()) { // The new size of the asset and new offset of the plugin header. - let new_core_size = core.get_size() as isize; + let new_core_size = core.len() as isize; // The difference in size between the new and old asset which is used to calculate the new size of the account. let size_diff = new_core_size @@ -362,7 +362,7 @@ fn process_update<'a, T: DataBlob + SolanaAccount>( // The offset of the first plugin is the core size plus the size of the plugin header. let plugin_offset = core_size - .checked_add(plugin_header.get_size() as isize) + .checked_add(plugin_header.len() as isize) .ok_or(MplCoreError::NumericalOverflow)?; let new_plugin_offset = plugin_offset @@ -387,7 +387,7 @@ fn process_update<'a, T: DataBlob + SolanaAccount>( plugin_registry.save(account, new_registry_offset as usize)?; } else { - resize_or_reallocate_account(account, payer, system_program, core.get_size())?; + resize_or_reallocate_account(account, payer, system_program, core.len())?; } core.save(account, 0)?; diff --git a/programs/mpl-core/src/processor/update_external_plugin_adapter.rs b/programs/mpl-core/src/processor/update_external_plugin_adapter.rs index ed6d8622..a0398d9e 100644 --- a/programs/mpl-core/src/processor/update_external_plugin_adapter.rs +++ b/programs/mpl-core/src/processor/update_external_plugin_adapter.rs @@ -250,7 +250,7 @@ fn process_update_external_plugin_adapter<'a, T: DataBlob + SolanaAccount>( src.len(), ); - plugin_header.save(account, core.get_size())?; + plugin_header.save(account, core.len())?; // Move offsets for existing registry records. plugin_registry.bump_offsets(registry_record.offset, plugin_size_diff)?; diff --git a/programs/mpl-core/src/processor/update_plugin.rs b/programs/mpl-core/src/processor/update_plugin.rs index a4810f36..64fc9adf 100644 --- a/programs/mpl-core/src/processor/update_plugin.rs +++ b/programs/mpl-core/src/processor/update_plugin.rs @@ -196,7 +196,7 @@ fn process_update_plugin<'a, T: DataBlob + SolanaAccount>( src.len(), ); - plugin_header.save(account, core.get_size())?; + plugin_header.save(account, core.len())?; plugin_registry.bump_offsets(registry_record.offset, size_diff)?; diff --git a/programs/mpl-core/src/processor/write_external_plugin_adapter_data.rs b/programs/mpl-core/src/processor/write_external_plugin_adapter_data.rs index ac4060d7..46026553 100644 --- a/programs/mpl-core/src/processor/write_external_plugin_adapter_data.rs +++ b/programs/mpl-core/src/processor/write_external_plugin_adapter_data.rs @@ -215,7 +215,7 @@ fn process_write_external_plugin_data<'a, T: DataBlob + SolanaAccount>( } } ExternalPluginAdapter::LinkedAppData(app_data) => { - let (_, mut header, mut registry) = + let (_, header_offset, mut header, mut registry) = create_meta_idempotent::(account, payer, system_program)?; match fetch_wrapped_external_plugin_adapter( @@ -261,7 +261,7 @@ fn process_write_external_plugin_data<'a, T: DataBlob + SolanaAccount>( parent_key: LinkedDataKey::LinkedAppData(app_data.data_authority), schema: app_data.schema, }), - Some(core), + header_offset, &mut header, &mut registry, account, @@ -274,7 +274,7 @@ fn process_write_external_plugin_data<'a, T: DataBlob + SolanaAccount>( parent_key: LinkedDataKey::LinkedAppData(app_data.data_authority), schema: app_data.schema, }), - Some(core), + header_offset, &mut header, &mut registry, account, diff --git a/programs/mpl-core/src/state/asset.rs b/programs/mpl-core/src/state/asset.rs index 30ec3c56..ae14b30d 100644 --- a/programs/mpl-core/src/state/asset.rs +++ b/programs/mpl-core/src/state/asset.rs @@ -21,7 +21,6 @@ pub struct AssetV1 { pub key: Key, //1 /// The owner of the asset. pub owner: Pubkey, //32 - //TODO: Fix this for dynamic size /// The update authority of the asset. pub update_authority: UpdateAuthority, //33 /// The name of the asset. @@ -33,6 +32,14 @@ pub struct AssetV1 { } impl AssetV1 { + /// The base length of the asset account with an empty name and uri and no seq. + const BASE_LEN: usize = 1 // Key + + 32 // Owner + + 1 // Update Authority discriminator + + 4 // Name length + + 4 // URI length + + 1; // Seq option + /// Create a new `Asset` with correct `Key` and `seq` of None. pub fn new( owner: Pubkey, @@ -60,9 +67,6 @@ impl AssetV1 { Ok(()) } - /// The base length of the asset account with an empty name and uri and no seq. - pub const BASE_LENGTH: usize = 1 + 32 + 33 + 4 + 4 + 1; - /// Check permissions for the create lifecycle event. pub fn check_create() -> CheckResult { CheckResult::CanApprove @@ -358,12 +362,14 @@ impl AssetV1 { impl Compressible for AssetV1 {} impl DataBlob for AssetV1 { - fn get_initial_size() -> usize { - AssetV1::BASE_LENGTH - } + fn len(&self) -> usize { + let mut size = AssetV1::BASE_LEN + self.name.len() + self.uri.len(); + + if let UpdateAuthority::Address(_) | UpdateAuthority::Collection(_) = self.update_authority + { + size += 32; + } - fn get_size(&self) -> usize { - let mut size = AssetV1::BASE_LENGTH + self.name.len() + self.uri.len(); if self.seq.is_some() { size += size_of::(); } @@ -399,3 +405,42 @@ impl CoreAsset for AssetV1 { &self.owner } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_asset_len() { + let assets = vec![ + AssetV1 { + key: Key::AssetV1, + owner: Pubkey::default(), + update_authority: UpdateAuthority::None, + name: "".to_string(), + uri: "".to_string(), + seq: None, + }, + AssetV1 { + key: Key::AssetV1, + owner: Pubkey::default(), + update_authority: UpdateAuthority::Address(Pubkey::default()), + name: "test".to_string(), + uri: "test".to_string(), + seq: None, + }, + AssetV1 { + key: Key::AssetV1, + owner: Pubkey::default(), + update_authority: UpdateAuthority::Collection(Pubkey::default()), + name: "test2".to_string(), + uri: "test2".to_string(), + seq: Some(1), + }, + ]; + for asset in assets { + let serialized = asset.try_to_vec().unwrap(); + assert_eq!(serialized.len(), asset.len()); + } + } +} diff --git a/programs/mpl-core/src/state/collection.rs b/programs/mpl-core/src/state/collection.rs index f59fd388..0f7443f7 100644 --- a/programs/mpl-core/src/state/collection.rs +++ b/programs/mpl-core/src/state/collection.rs @@ -28,7 +28,12 @@ pub struct CollectionV1 { impl CollectionV1 { /// The base length of the collection account with an empty name and uri. - pub const BASE_LENGTH: usize = 1 + 32 + 4 + 4 + 4 + 4; + const BASE_LEN: usize = 1 // Key + + 32 // Update Authority + + 4 // Name Length + + 4 // URI Length + + 4 // num_minted + + 4; // current_size /// Create a new collection. pub fn new( @@ -351,12 +356,8 @@ impl CollectionV1 { } impl DataBlob for CollectionV1 { - fn get_initial_size() -> usize { - Self::BASE_LENGTH - } - - fn get_size(&self) -> usize { - Self::BASE_LENGTH + self.name.len() + self.uri.len() + fn len(&self) -> usize { + Self::BASE_LEN + self.name.len() + self.uri.len() } } @@ -375,3 +376,34 @@ impl CoreAsset for CollectionV1 { &self.update_authority } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_collection_len() { + let collections = vec![ + CollectionV1 { + key: Key::CollectionV1, + update_authority: Pubkey::default(), + name: "".to_string(), + uri: "".to_string(), + num_minted: 0, + current_size: 0, + }, + CollectionV1 { + key: Key::CollectionV1, + update_authority: Pubkey::default(), + name: "test".to_string(), + uri: "test".to_string(), + num_minted: 1, + current_size: 1, + }, + ]; + for collection in collections { + let serialized = collection.try_to_vec().unwrap(); + assert_eq!(serialized.len(), collection.len()); + } + } +} diff --git a/programs/mpl-core/src/state/hashed_asset.rs b/programs/mpl-core/src/state/hashed_asset.rs index 647062c0..3273a3ce 100644 --- a/programs/mpl-core/src/state/hashed_asset.rs +++ b/programs/mpl-core/src/state/hashed_asset.rs @@ -13,8 +13,8 @@ pub struct HashedAssetV1 { } impl HashedAssetV1 { - /// The length of the hashed asset account. - pub const LENGTH: usize = 1 + 32; + const BASE_LEN: usize = 1 // The Key + + 32; // The hash /// Create a new hashed asset. pub fn new(hash: [u8; 32]) -> Self { @@ -26,12 +26,8 @@ impl HashedAssetV1 { } impl DataBlob for HashedAssetV1 { - fn get_initial_size() -> usize { - HashedAssetV1::LENGTH - } - - fn get_size(&self) -> usize { - HashedAssetV1::LENGTH + fn len(&self) -> usize { + Self::BASE_LEN } } @@ -40,3 +36,15 @@ impl SolanaAccount for HashedAssetV1 { Key::HashedAssetV1 } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_hashed_asset_len() { + let hashed_asset = HashedAssetV1::new([0; 32]); + let serialized = hashed_asset.try_to_vec().unwrap(); + assert_eq!(serialized.len(), hashed_asset.len()); + } +} diff --git a/programs/mpl-core/src/state/mod.rs b/programs/mpl-core/src/state/mod.rs index bd824b36..a3a469e7 100644 --- a/programs/mpl-core/src/state/mod.rs +++ b/programs/mpl-core/src/state/mod.rs @@ -20,6 +20,7 @@ mod hashed_asset; pub use hashed_asset::*; mod traits; +use strum::{EnumCount, EnumIter}; pub use traits::*; mod update_authority; @@ -41,7 +42,9 @@ pub enum DataState { /// Variants representing the different types of authority that can have permissions over plugins. #[repr(u8)] -#[derive(Copy, Clone, BorshSerialize, BorshDeserialize, Debug, Eq, PartialEq, PartialOrd, Ord)] +#[derive( + Copy, Clone, BorshSerialize, BorshDeserialize, Debug, Eq, PartialEq, PartialOrd, Ord, EnumCount, +)] pub enum Authority { /// No authority, used for immutability. None, @@ -56,9 +59,33 @@ pub enum Authority { }, } +impl Authority { + const BASE_LEN: usize = 1; // 1 byte for the discriminator +} + +impl DataBlob for Authority { + fn len(&self) -> usize { + Self::BASE_LEN + + if let Authority::Address { .. } = self { + 32 + } else { + 0 + } + } +} + /// An enum representing account discriminators. #[derive( - Clone, Copy, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq, ToPrimitive, FromPrimitive, + Clone, + Copy, + BorshSerialize, + BorshDeserialize, + Debug, + PartialEq, + Eq, + ToPrimitive, + FromPrimitive, + EnumIter, )] pub enum Key { /// Uninitialized or invalid account. @@ -76,8 +103,47 @@ pub enum Key { } impl Key { - /// Get the size of the Key. - pub fn get_initial_size() -> usize { - 1 + const BASE_LEN: usize = 1; // 1 byte for the discriminator +} + +impl DataBlob for Key { + fn len(&self) -> usize { + Self::BASE_LEN + } +} + +#[cfg(test)] +mod tests { + use strum::IntoEnumIterator; + + use super::*; + + #[test] + fn test_authority_len() { + let authorities = vec![ + Authority::None, + Authority::Owner, + Authority::UpdateAuthority, + Authority::Address { + address: Pubkey::default(), + }, + ]; + assert_eq!( + authorities.len(), + Authority::COUNT, + "Must test all Authority variants" + ); + for authority in authorities { + let serialized = authority.try_to_vec().unwrap(); + assert_eq!(serialized.len(), authority.len()); + } + } + + #[test] + fn test_key_len() { + for key in Key::iter() { + let serialized = key.try_to_vec().unwrap(); + assert_eq!(serialized.len(), key.len()); + } } } diff --git a/programs/mpl-core/src/state/traits.rs b/programs/mpl-core/src/state/traits.rs index 008b6c45..15aa726f 100644 --- a/programs/mpl-core/src/state/traits.rs +++ b/programs/mpl-core/src/state/traits.rs @@ -8,11 +8,10 @@ use solana_program::{ use super::UpdateAuthority; /// A trait for generic blobs of data that have size. +#[allow(clippy::len_without_is_empty)] pub trait DataBlob: BorshSerialize + BorshDeserialize { - /// Get the size of an empty instance of the data blob. - fn get_initial_size() -> usize; - /// Get the current size of the data blob. - fn get_size(&self) -> usize; + /// Get the current length of the data blob. + fn len(&self) -> usize; } /// A trait for Solana accounts. diff --git a/programs/mpl-core/src/utils/compression.rs b/programs/mpl-core/src/utils/compression.rs index 851ab249..09e897db 100644 --- a/programs/mpl-core/src/utils/compression.rs +++ b/programs/mpl-core/src/utils/compression.rs @@ -36,13 +36,14 @@ pub(crate) fn rebuild_account_state_from_proof_data<'a>( // Add the plugins. if !plugins.is_empty() { - let (_, mut plugin_header, mut plugin_registry) = + let (_, header_offset, mut plugin_header, mut plugin_registry) = create_meta_idempotent::(asset_info, payer, system_program)?; for plugin in plugins { initialize_plugin::( &plugin.plugin, &plugin.authority, + header_offset, &mut plugin_header, &mut plugin_registry, asset_info, diff --git a/programs/mpl-core/src/utils/mod.rs b/programs/mpl-core/src/utils/mod.rs index 96fbf998..5438007f 100644 --- a/programs/mpl-core/src/utils/mod.rs +++ b/programs/mpl-core/src/utils/mod.rs @@ -89,8 +89,8 @@ pub fn fetch_core_data( ) -> Result<(T, Option, Option), ProgramError> { let asset = T::load(account, 0)?; - if asset.get_size() != account.data_len() { - let plugin_header = PluginHeaderV1::load(account, asset.get_size())?; + if asset.len() != account.data_len() { + let plugin_header = PluginHeaderV1::load(account, asset.len())?; let plugin_registry = PluginRegistryV1::load(account, plugin_header.plugin_registry_offset)?;