use super::*;
use crate::{
assertions::{
collection::assert_collection_update_is_valid, metadata::assert_data_valid,
uses::assert_valid_use,
},
instruction::{CollectionDetailsToggle, CollectionToggle, RuleSetToggle, UpdateArgs},
utils::{clean_write_metadata, puff_out_data_fields},
};
pub const MAX_NAME_LENGTH: usize = 32;
pub const MAX_SYMBOL_LENGTH: usize = 10;
pub const MAX_URI_LENGTH: usize = 200;
pub const MAX_METADATA_LEN: usize = 1 + 32 + 32 + MAX_DATA_SIZE
+ 1 + 1 + 9 + 2 + 34 + 18 + 10 + 33 + 75; pub const MAX_DATA_SIZE: usize = 4
+ MAX_NAME_LENGTH
+ 4
+ MAX_SYMBOL_LENGTH
+ 4
+ MAX_URI_LENGTH
+ 2
+ 1
+ 4
+ MAX_CREATOR_LIMIT * MAX_CREATOR_LEN;
#[macro_export]
macro_rules! metadata_seeds {
($mint:expr) => {{
let path = vec!["metadata".as_bytes(), $crate::ID.as_ref(), $mint.as_ref()];
let (_, bump) = Pubkey::find_program_address(&path, &$crate::ID);
&[
"metadata".as_bytes(),
$crate::ID.as_ref(),
$mint.as_ref(),
&[bump],
]
}};
}
#[repr(C)]
#[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))]
#[derive(Clone, BorshSerialize, Debug, PartialEq, Eq, ShankAccount)]
pub struct Metadata {
pub key: Key,
#[cfg_attr(feature = "serde-feature", serde(with = "As::<DisplayFromStr>"))]
pub update_authority: Pubkey,
#[cfg_attr(feature = "serde-feature", serde(with = "As::<DisplayFromStr>"))]
pub mint: Pubkey,
pub data: Data,
pub primary_sale_happened: bool,
pub is_mutable: bool,
pub edition_nonce: Option<u8>,
pub token_standard: Option<TokenStandard>,
pub collection: Option<Collection>,
pub uses: Option<Uses>,
pub collection_details: Option<CollectionDetails>,
pub programmable_config: Option<ProgrammableConfig>,
}
impl Metadata {
pub fn save(&self, data: &mut [u8]) -> Result<(), BorshError> {
let mut bytes = Vec::with_capacity(MAX_METADATA_LEN);
BorshSerialize::serialize(&self, &mut bytes)?;
data[..bytes.len()].copy_from_slice(&bytes);
Ok(())
}
pub(crate) fn update_v1<'a>(
&mut self,
args: UpdateArgs,
update_authority: &AccountInfo<'a>,
metadata: &AccountInfo<'a>,
token: Option<TokenAccount>,
token_standard: TokenStandard,
) -> ProgramResult {
self.token_standard = Some(token_standard);
match &args {
UpdateArgs::V1 {
uses,
collection_details,
..
}
| UpdateArgs::AsUpdateAuthorityV2 {
uses,
collection_details,
..
} => {
if uses.is_some() {
let uses_option = uses.clone().to_option();
assert_valid_use(&uses_option, &self.uses)?;
self.uses = uses_option;
}
if let CollectionDetailsToggle::Set(collection_details) = collection_details {
if self.collection_details.is_some() {
return Err(MetadataError::SizedCollection.into());
}
self.collection_details = Some(collection_details.clone());
}
}
_ => (),
}
match &args {
UpdateArgs::V1 { data, .. }
| UpdateArgs::AsUpdateAuthorityV2 { data, .. }
| UpdateArgs::AsDataDelegateV2 { data, .. }
| UpdateArgs::AsDataItemDelegateV2 { data, .. } => {
if let Some(data) = data {
if !self.is_mutable {
return Err(MetadataError::DataIsImmutable.into());
}
assert_data_valid(
data,
update_authority.key,
self,
false,
update_authority.is_signer,
)?;
self.data = data.clone();
}
}
_ => (),
}
match &args {
UpdateArgs::V1 {
new_update_authority,
primary_sale_happened,
is_mutable,
..
}
| UpdateArgs::AsUpdateAuthorityV2 {
new_update_authority,
primary_sale_happened,
is_mutable,
..
}
| UpdateArgs::AsAuthorityItemDelegateV2 {
new_update_authority,
primary_sale_happened,
is_mutable,
..
} => {
if let Some(authority) = new_update_authority {
self.update_authority = *authority;
}
if let Some(primary_sale) = primary_sale_happened {
if *primary_sale || !self.primary_sale_happened {
self.primary_sale_happened = *primary_sale
} else {
return Err(MetadataError::PrimarySaleCanOnlyBeFlippedToTrue.into());
}
}
if let Some(mutable) = is_mutable {
if !mutable || self.is_mutable {
self.is_mutable = *mutable
} else {
return Err(MetadataError::IsMutableCanOnlyBeFlippedToFalse.into());
}
}
}
_ => (),
}
match &args {
UpdateArgs::V1 { collection, .. }
| UpdateArgs::AsUpdateAuthorityV2 { collection, .. }
| UpdateArgs::AsCollectionDelegateV2 { collection, .. }
| UpdateArgs::AsCollectionItemDelegateV2 { collection, .. } => match collection {
CollectionToggle::Set(_) => {
let collection_option = collection.clone().to_option();
assert_collection_update_is_valid(false, &self.collection, &collection_option)?;
self.collection = collection_option;
}
CollectionToggle::Clear => {
if let Some(current_collection) = self.collection.as_ref() {
if current_collection.verified {
return Err(MetadataError::CannotUpdateVerifiedCollection.into());
}
self.collection = None;
}
}
CollectionToggle::None => { }
},
_ => (),
};
match &args {
UpdateArgs::V1 { rule_set, .. }
| UpdateArgs::AsUpdateAuthorityV2 { rule_set, .. }
| UpdateArgs::AsProgrammableConfigDelegateV2 { rule_set, .. }
| UpdateArgs::AsProgrammableConfigItemDelegateV2 { rule_set, .. } => {
if matches!(rule_set, RuleSetToggle::Clear | RuleSetToggle::Set(_)) {
if token_standard != TokenStandard::ProgrammableNonFungible {
return Err(MetadataError::InvalidTokenStandard.into());
}
let token = token.ok_or(MetadataError::MissingTokenAccount)?;
if token.delegate.is_some() {
return Err(MetadataError::CannotUpdateAssetWithDelegate.into());
}
self.programmable_config =
rule_set
.clone()
.to_option()
.map(|rule_set| ProgrammableConfig::V1 {
rule_set: Some(rule_set),
});
}
}
_ => (),
};
puff_out_data_fields(self);
clean_write_metadata(self, metadata)
}
pub fn into_asset_data(self) -> AssetData {
let mut asset_data = AssetData::new(
self.token_standard.unwrap_or(TokenStandard::NonFungible),
self.data.name,
self.data.symbol,
self.data.uri,
);
asset_data.seller_fee_basis_points = self.data.seller_fee_basis_points;
asset_data.creators = self.data.creators;
asset_data.primary_sale_happened = self.primary_sale_happened;
asset_data.is_mutable = self.is_mutable;
asset_data.collection = self.collection;
asset_data.uses = self.uses;
asset_data.collection_details = self.collection_details;
asset_data.rule_set =
if let Some(ProgrammableConfig::V1 { rule_set }) = self.programmable_config {
rule_set
} else {
None
};
asset_data
}
}
impl Default for Metadata {
fn default() -> Self {
Metadata {
key: Key::MetadataV1,
update_authority: Pubkey::default(),
mint: Pubkey::default(),
data: Data::default(),
primary_sale_happened: false,
is_mutable: false,
edition_nonce: None,
token_standard: None,
collection: None,
uses: None,
collection_details: None,
programmable_config: None,
}
}
}
impl TokenMetadataAccount for Metadata {
fn key() -> Key {
Key::MetadataV1
}
fn size() -> usize {
MAX_METADATA_LEN
}
}
impl borsh::de::BorshDeserialize for Metadata {
fn deserialize(buf: &mut &[u8]) -> ::core::result::Result<Self, BorshError> {
let md = meta_deser_unchecked(buf)?;
Ok(md)
}
}
#[repr(C)]
#[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))]
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)]
pub enum PrintSupply {
Zero,
Limited(u64),
Unlimited,
}
impl PrintSupply {
pub fn to_option(&self) -> Option<u64> {
match self {
PrintSupply::Zero => Some(0),
PrintSupply::Limited(supply) => Some(*supply),
PrintSupply::Unlimited => None,
}
}
}
#[repr(C)]
#[cfg_attr(feature = "serde-feature", derive(Serialize, Deserialize))]
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)]
pub enum ProgrammableConfig {
V1 {
#[cfg_attr(
feature = "serde-feature",
serde(
deserialize_with = "deser_option_pubkey",
serialize_with = "ser_option_pubkey"
)
)]
rule_set: Option<Pubkey>,
},
}
#[cfg(test)]
mod tests {
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::account_info::AccountInfo;
use solana_sdk::{signature::Keypair, signer::Signer};
use crate::{
error::MetadataError,
state::{
CollectionAuthorityRecord, Edition, EditionMarker, Key, MasterEditionV2, Metadata,
TokenMetadataAccount, UseAuthorityRecord, MAX_METADATA_LEN,
},
utils::metadata::tests::{expected_pesky_metadata, pesky_data},
ID,
};
fn pad_metadata_length(metadata: &mut Vec<u8>) {
let padding_length = MAX_METADATA_LEN - metadata.len();
metadata.extend(vec![0; padding_length]);
}
#[test]
fn successfully_deserialize_corrupted_metadata() {
let expected_metadata = expected_pesky_metadata();
let mut corrupted_data = pesky_data();
let metadata = Metadata::deserialize(&mut corrupted_data).unwrap();
assert_eq!(metadata, expected_metadata);
}
#[test]
fn successfully_deserialize_metadata() {
let expected_metadata = expected_pesky_metadata();
let mut buf = Vec::new();
expected_metadata.serialize(&mut buf).unwrap();
pad_metadata_length(&mut buf);
let pubkey = Keypair::new().pubkey();
let owner = &ID;
let mut lamports = 1_000_000_000;
let mut data = buf.clone();
let md_account_info = AccountInfo::new(
&pubkey,
false,
true,
&mut lamports,
&mut data,
owner,
false,
1_000_000_000,
);
let md = Metadata::from_account_info(&md_account_info).unwrap();
assert_eq!(md.key, Key::MetadataV1);
assert_eq!(md, expected_metadata);
}
#[test]
fn fail_to_deserialize_metadata_with_wrong_owner() {
let expected_metadata = expected_pesky_metadata();
let mut buf = Vec::new();
expected_metadata.serialize(&mut buf).unwrap();
pad_metadata_length(&mut buf);
let pubkey = Keypair::new().pubkey();
let invalid_owner = Keypair::new().pubkey();
let mut lamports = 1_000_000_000;
let mut data = buf.clone();
let md_account_info = AccountInfo::new(
&pubkey,
false,
true,
&mut lamports,
&mut data,
&invalid_owner,
false,
1_000_000_000,
);
let error = Metadata::from_account_info(&md_account_info).unwrap_err();
assert_eq!(error, MetadataError::IncorrectOwner.into());
}
#[test]
fn fail_to_deserialize_metadata_with_wrong_size() {
let expected_metadata = expected_pesky_metadata();
let mut buf = Vec::new();
expected_metadata.serialize(&mut buf).unwrap();
let pubkey = Keypair::new().pubkey();
let owner = ID;
let mut lamports = 1_000_000_000;
let mut data = buf.clone();
let account_info = AccountInfo::new(
&pubkey,
false,
true,
&mut lamports,
&mut data,
&owner,
false,
1_000_000_000,
);
let error = Metadata::from_account_info(&account_info).unwrap_err();
assert_eq!(error, MetadataError::DataTypeMismatch.into());
}
#[test]
fn fail_to_deserialize_master_edition_into_metadata() {
let master_edition = MasterEditionV2 {
key: Key::MasterEditionV2,
supply: 0,
max_supply: Some(0),
};
let mut buf = Vec::new();
master_edition.serialize(&mut buf).unwrap();
let pubkey = Keypair::new().pubkey();
let owner = &ID;
let mut lamports = 1_000_000_000;
let mut data = buf.clone();
let account_info = AccountInfo::new(
&pubkey,
false,
true,
&mut lamports,
&mut data,
owner,
false,
1_000_000_000,
);
let err = Metadata::from_account_info(&account_info).unwrap_err();
assert_eq!(err, MetadataError::DataTypeMismatch.into());
}
#[test]
fn fail_to_deserialize_edition_into_metadata() {
let parent = Keypair::new().pubkey();
let edition = 1;
let edition = Edition {
key: Key::EditionV1,
parent,
edition,
};
let mut buf = Vec::new();
edition.serialize(&mut buf).unwrap();
let pubkey = Keypair::new().pubkey();
let owner = &ID;
let mut lamports = 1_000_000_000;
let mut data = buf.clone();
let account_info = AccountInfo::new(
&pubkey,
false,
true,
&mut lamports,
&mut data,
owner,
false,
1_000_000_000,
);
let err = Metadata::from_account_info(&account_info).unwrap_err();
assert_eq!(err, MetadataError::DataTypeMismatch.into());
}
#[test]
fn fail_to_deserialize_use_authority_record_into_metadata() {
let use_record = UseAuthorityRecord {
key: Key::UseAuthorityRecord,
allowed_uses: 14,
bump: 255,
};
let mut buf = Vec::new();
use_record.serialize(&mut buf).unwrap();
let pubkey = Keypair::new().pubkey();
let owner = &ID;
let mut lamports = 1_000_000_000;
let mut data = buf.clone();
let account_info = AccountInfo::new(
&pubkey,
false,
true,
&mut lamports,
&mut data,
owner,
false,
1_000_000_000,
);
let err = Metadata::from_account_info(&account_info).unwrap_err();
assert_eq!(err, MetadataError::DataTypeMismatch.into());
}
#[test]
fn fail_to_deserialize_collection_authority_record_into_metadata() {
let collection_record = CollectionAuthorityRecord {
key: Key::CollectionAuthorityRecord,
bump: 255,
update_authority: None,
};
let mut buf = Vec::new();
collection_record.serialize(&mut buf).unwrap();
let pubkey = Keypair::new().pubkey();
let owner = &ID;
let mut lamports = 1_000_000_000;
let mut data = buf.clone();
let account_info = AccountInfo::new(
&pubkey,
false,
true,
&mut lamports,
&mut data,
owner,
false,
1_000_000_000,
);
let err = Metadata::from_account_info(&account_info).unwrap_err();
assert_eq!(err, MetadataError::DataTypeMismatch.into());
}
#[test]
fn fail_to_deserialize_edition_marker_into_metadata() {
let edition_marker = EditionMarker {
key: Key::EditionMarker,
ledger: [0; 31],
};
let mut buf = Vec::new();
edition_marker.serialize(&mut buf).unwrap();
let pubkey = Keypair::new().pubkey();
let owner = &ID;
let mut lamports = 1_000_000_000;
let mut data = buf.clone();
let account_info = AccountInfo::new(
&pubkey,
false,
true,
&mut lamports,
&mut data,
owner,
false,
1_000_000_000,
);
let err = Metadata::from_account_info(&account_info).unwrap_err();
assert_eq!(err, MetadataError::DataTypeMismatch.into());
}
}