// file: ks-lib/src/decoder/metadata/metaplex_token_metadata/account.rs // version: 16 //! Bounded decoding and PDA validation for Metaplex Token Metadata accounts. /// Exhaustive inventory of every published Metaplex Token Metadata `Key` variant. pub const DC_METADATA_MTM_METADATA_ACCOUNT_LAYOUT_AUDIT: &[DcMetadataMtmMetadataAccountLayoutAuditEntry] = &[ DcMetadataMtmMetadataAccountLayoutAuditEntry { key_name: "Uninitialized", discriminant: 0, decoder: "reject_uninitialized", lifecycle: "sentinel", }, DcMetadataMtmMetadataAccountLayoutAuditEntry { key_name: "EditionV1", discriminant: 1, decoder: "decode_edition_account", lifecycle: "active", }, DcMetadataMtmMetadataAccountLayoutAuditEntry { key_name: "MasterEditionV1", discriminant: 2, decoder: "decode_edition_account", lifecycle: "historical_materializable", }, DcMetadataMtmMetadataAccountLayoutAuditEntry { key_name: "ReservationListV1", discriminant: 3, decoder: "decode_reservation_list_account", lifecycle: "historical_materializable", }, DcMetadataMtmMetadataAccountLayoutAuditEntry { key_name: "MetadataV1", discriminant: 4, decoder: "decode_metadata_account", lifecycle: "active", }, DcMetadataMtmMetadataAccountLayoutAuditEntry { key_name: "ReservationListV2", discriminant: 5, decoder: "decode_reservation_list_account", lifecycle: "historical_materializable", }, DcMetadataMtmMetadataAccountLayoutAuditEntry { key_name: "MasterEditionV2", discriminant: 6, decoder: "decode_edition_account", lifecycle: "active", }, DcMetadataMtmMetadataAccountLayoutAuditEntry { key_name: "EditionMarker", discriminant: 7, decoder: "decode_edition_marker_account", lifecycle: "active", }, DcMetadataMtmMetadataAccountLayoutAuditEntry { key_name: "UseAuthorityRecord", discriminant: 8, decoder: "decode_use_authority_record_account", lifecycle: "active", }, DcMetadataMtmMetadataAccountLayoutAuditEntry { key_name: "CollectionAuthorityRecord", discriminant: 9, decoder: "decode_collection_authority_record_account", lifecycle: "active", }, DcMetadataMtmMetadataAccountLayoutAuditEntry { key_name: "TokenOwnedEscrow", discriminant: 10, decoder: "decode_token_owned_escrow_account", lifecycle: "active", }, DcMetadataMtmMetadataAccountLayoutAuditEntry { key_name: "TokenRecord", discriminant: 11, decoder: "decode_token_record_account", lifecycle: "active", }, DcMetadataMtmMetadataAccountLayoutAuditEntry { key_name: "MetadataDelegate", discriminant: 12, decoder: "decode_metadata_delegate_record_account", lifecycle: "active", }, DcMetadataMtmMetadataAccountLayoutAuditEntry { key_name: "EditionMarkerV2", discriminant: 13, decoder: "decode_edition_marker_account", lifecycle: "active_experimental", }, DcMetadataMtmMetadataAccountLayoutAuditEntry { key_name: "HolderDelegate", discriminant: 14, decoder: "decode_holder_delegate_record_account", lifecycle: "active", }, ]; const MAX_METADATA_ACCOUNT_BYTES: usize = 1_024; const MAX_NAME_BYTES: usize = 32; const MAX_SYMBOL_BYTES: usize = 10; const MAX_URI_BYTES: usize = 200; const MAX_CREATORS: usize = 5; const COMPACT_MASTER_EDITION_ACCOUNT_BYTES: usize = 20; const LEGACY_MASTER_EDITION_ACCOUNT_BYTES: usize = 282; const COMPACT_EDITION_ACCOUNT_BYTES: usize = 42; const LEGACY_EDITION_ACCOUNT_BYTES: usize = 241; const EDITION_MARKER_V1_BITS: u64 = 248; const MAX_EDITION_MARKER_V2_LEDGER_BYTES: usize = 1_048_576; /// Bounded error returned while decoding a Metadata account. #[derive(Clone, Debug, Eq, PartialEq)] pub enum DcMetadataMtmMetadataAccountDecodeError { /// The account key is not a valid Solana public key. InvalidAccountKey, /// The account owner is not a valid Solana public key. InvalidOwner, /// The account is owned by another program. ForeignOwner, /// The account data is empty or exceeds the explicit bound. InvalidDataLength, /// The account payload is not a supported Metadata layout. InvalidMetadataLayout, /// One bounded string or vector exceeds the official maximum. MetadataBoundsExceeded, /// The supplied account key is not the canonical Metadata PDA for the parsed mint. InvalidMetadataPda, /// The parsed account key discriminant is not Metadata. InvalidMetadataKey, /// The supplied account key is not the canonical Edition Marker PDA. InvalidEditionMarkerPda, /// The supplied account key is not the canonical Token Record PDA. InvalidTokenRecordPda, /// The parsed account key discriminant is not Token Record. InvalidTokenRecordKey, /// The supplied account key is not the canonical delegate or authority record PDA. InvalidAuthorityRecordPda, /// The supplied account key is not the canonical Token Owned Escrow PDA. InvalidTokenOwnedEscrowPda, /// The parsed account key discriminant is not Token Owned Escrow. InvalidTokenOwnedEscrowKey, /// The parsed account key discriminant is not the expected delegate or authority record. InvalidAuthorityRecordKey, /// The supplied account key is not the canonical historical Reservation List PDA. InvalidReservationListPda, /// The parsed account key discriminant is not Reservation List V1 or V2. InvalidReservationListKey, /// The historical reservation vector or padded payload exceeds official bounds. ReservationListBoundsExceeded, /// The Edition Marker ledger exceeds the explicit decoder bound. EditionMarkerBoundsExceeded, /// The parsed account could not be projected to bounded JSON. ProjectionFailed, } impl std::fmt::Display for DcMetadataMtmMetadataAccountDecodeError { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let message = match self { Self::InvalidAccountKey => "metadata account key is invalid", Self::InvalidOwner => "metadata account owner is invalid", Self::ForeignOwner => "metadata account is owned by another program", Self::InvalidDataLength => "metadata account data length is invalid", Self::InvalidMetadataLayout => "metadata account layout is invalid", Self::MetadataBoundsExceeded => "metadata account exceeds bounded fields", Self::InvalidMetadataPda => "metadata account PDA is invalid", Self::InvalidMetadataKey => "metadata account key discriminant is invalid", Self::InvalidEditionMarkerPda => "edition marker account PDA is invalid", Self::InvalidTokenRecordPda => "token record account PDA is invalid", Self::InvalidTokenRecordKey => "token record account key discriminant is invalid", Self::InvalidAuthorityRecordPda => "delegate or authority record PDA is invalid", Self::InvalidTokenOwnedEscrowPda => "token owned escrow PDA is invalid", Self::InvalidTokenOwnedEscrowKey => "token owned escrow key discriminant is invalid", Self::InvalidAuthorityRecordKey => { "delegate or authority record key discriminant is invalid" }, Self::InvalidReservationListPda => "reservation list account PDA is invalid", Self::InvalidReservationListKey => { "reservation list account key discriminant is invalid" }, Self::ReservationListBoundsExceeded => { "reservation list account exceeds official bounds" }, Self::EditionMarkerBoundsExceeded => "edition marker ledger exceeds bounded fields", Self::ProjectionFailed => "metadata account JSON projection failed", }; return formatter.write_str(message); } } impl std::error::Error for DcMetadataMtmMetadataAccountDecodeError {} /// One audited Metaplex account-key layout. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct DcMetadataMtmMetadataAccountLayoutAuditEntry { /// Published `Key` variant name. pub key_name: &'static str, /// Exact Borsh discriminant byte. pub discriminant: u8, /// Decoder entry point or explicit rejection policy. pub decoder: &'static str, /// Lifecycle classification used by later materialization and execution layers. pub lifecycle: &'static str, } /// Canonical bounded projection of one Metadata account. #[derive(Clone, Debug, PartialEq)] pub struct DcMetadataMtmMetadataAccountSnapshot { /// Canonical account address. pub account: String, /// Canonical Metadata PDA bump. pub bump: u8, /// Mint carried by the account. pub mint: String, /// Current update authority. pub update_authority: String, /// Trimmed metadata name. pub name: String, /// Trimmed metadata symbol. pub symbol: String, /// Trimmed metadata URI. pub uri: String, /// Seller fee in basis points. pub seller_fee_basis_points: u16, /// Whether primary sale happened. pub primary_sale_happened: bool, /// Whether the metadata is mutable. pub is_mutable: bool, /// Complete bounded account projection. pub payload_json: serde_json::Value, } /// Decodes one Metadata account after validating owner, bounds and canonical PDA. pub fn decoder_metadata_metaplex_token_metadata_decode_metadata_account( account: &str, owner: &str, data: &[u8], ) -> std::result::Result< DcMetadataMtmMetadataAccountSnapshot, DcMetadataMtmMetadataAccountDecodeError, > { let account_key = match ::from_str(account) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidAccountKey, ); }, }; let owner_key = match ::from_str(owner) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err(DcMetadataMtmMetadataAccountDecodeError::InvalidOwner); }, }; let expected_owner = match ::from_str( ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err(DcMetadataMtmMetadataAccountDecodeError::InvalidOwner); }, }; if owner_key != expected_owner { return std::result::Result::Err(DcMetadataMtmMetadataAccountDecodeError::ForeignOwner); } if data.is_empty() || data.len() > MAX_METADATA_ACCOUNT_BYTES { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidDataLength, ); } let metadata = match mpl_token_metadata::accounts::Metadata::from_bytes(data) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); }, }; if metadata.key != mpl_token_metadata::types::Key::MetadataV1 { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataKey, ); } let name = metadata.name.trim_end_matches('\0').to_string(); let symbol = metadata.symbol.trim_end_matches('\0').to_string(); let uri = metadata.uri.trim_end_matches('\0').to_string(); if name.len() > MAX_NAME_BYTES || symbol.len() > MAX_SYMBOL_BYTES || uri.len() > MAX_URI_BYTES { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::MetadataBoundsExceeded, ); } if metadata .creators .as_ref() .is_some_and(|creators| return creators.len() > MAX_CREATORS) { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::MetadataBoundsExceeded, ); } let (expected_pda, bump) = mpl_token_metadata::accounts::Metadata::find_pda(&metadata.mint); if account_key.to_string() != expected_pda.to_string() { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataPda, ); } let payload_json = match serde_json::to_value(&metadata) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::ProjectionFailed, ); }, }; return std::result::Result::Ok(DcMetadataMtmMetadataAccountSnapshot { account: account_key.to_string(), bump, mint: metadata.mint.to_string(), update_authority: metadata.update_authority.to_string(), name, symbol, uri, seller_fee_basis_points: metadata.seller_fee_basis_points, primary_sale_happened: metadata.primary_sale_happened, is_mutable: metadata.is_mutable, payload_json, }); } fn validate_known_account_allocation( data: &[u8], serialized_len: usize, padded_account_lengths: &[usize], ) -> std::result::Result<(), DcMetadataMtmMetadataAccountDecodeError> { if data.len() == serialized_len { return std::result::Result::Ok(()); } if data.len() < serialized_len || !padded_account_lengths.contains(&data.len()) { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidDataLength, ); } if data[serialized_len..].iter().any(|byte| return *byte != 0) { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); } return std::result::Result::Ok(()); } fn validate_master_edition_allocation( data: &[u8], serialized_len: usize, padded_account_lengths: &[usize], ) -> std::result::Result<(), DcMetadataMtmMetadataAccountDecodeError> { if data.len() == serialized_len { return std::result::Result::Ok(()); } if data.len() < serialized_len || !padded_account_lengths.contains(&data.len()) { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidDataLength, ); } let trailer_start = match data.len().checked_sub(2) { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidDataLength, ); }, }; if trailer_start < serialized_len { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidDataLength, ); } if data[serialized_len..trailer_start].iter().any(|byte| return *byte != 0) { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); } let fee_flag = data[trailer_start]; if fee_flag > 1 { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); } let token_standard = data[trailer_start + 1]; if token_standard != mpl_token_metadata::types::TokenStandard::NonFungible as u8 && token_standard != mpl_token_metadata::types::TokenStandard::ProgrammableNonFungible as u8 { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); } return std::result::Result::Ok(()); } /// Supported edition account layout. #[derive(Clone, Debug, Eq, PartialEq)] pub enum DcMetadataMtmEditionAccountKind { /// Historical Master Edition V1 layout. MasterEditionV1, /// Current Master Edition V2 layout. MasterEditionV2, /// Printed Edition V1 layout. EditionV1, } /// Canonical bounded projection of one edition or master-edition account. #[derive(Clone, Debug, PartialEq)] pub struct DcMetadataMtmEditionAccountSnapshot { /// Canonical account address. pub account: String, /// Canonical edition PDA bump. pub bump: u8, /// Mint used to derive the edition PDA. pub mint: String, /// Decoded account layout. pub kind: DcMetadataMtmEditionAccountKind, /// Current printed supply for a master edition. pub supply: std::option::Option, /// Optional maximum supply for a master edition. pub max_supply: std::option::Option, /// Parent master edition for a printed edition. pub parent: std::option::Option, /// Printed edition number. pub edition: std::option::Option, /// Historical printing mint carried by Master Edition V1. pub printing_mint: std::option::Option, /// Historical one-time printing authorization mint carried by Master Edition V1. pub one_time_printing_authorization_mint: std::option::Option, /// Complete bounded account projection. pub payload_json: serde_json::Value, } /// Decodes one MasterEditionV1, MasterEditionV2 or Edition account with exact owner and PDA validation. pub fn decoder_metadata_metaplex_token_metadata_decode_edition_account( account: &str, owner: &str, mint: &str, data: &[u8], ) -> std::result::Result { let account_key = match ::from_str(account) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidAccountKey, ); }, }; let owner_key = match ::from_str(owner) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err(DcMetadataMtmMetadataAccountDecodeError::InvalidOwner); }, }; let expected_owner = match ::from_str( ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err(DcMetadataMtmMetadataAccountDecodeError::InvalidOwner); }, }; if owner_key != expected_owner { return std::result::Result::Err(DcMetadataMtmMetadataAccountDecodeError::ForeignOwner); } if data.is_empty() || data.len() > LEGACY_MASTER_EDITION_ACCOUNT_BYTES { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidDataLength, ); } let (expected_pda, bump) = match mint.parse() { std::result::Result::Ok(mint_key) => { mpl_token_metadata::accounts::MasterEdition::find_pda(&mint_key) }, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidAccountKey, ); }, }; if account_key.to_string() != expected_pda.to_string() { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataPda, ); } let key = data[0]; if key == mpl_token_metadata::types::Key::MasterEditionV1 as u8 { let value = match mpl_token_metadata::accounts::DeprecatedMasterEditionV1::from_bytes(data) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); }, }; let serialized_len = if value.max_supply.is_some() { 82 } else { 74 }; if let std::result::Result::Err(error) = validate_master_edition_allocation( data, serialized_len, &[LEGACY_MASTER_EDITION_ACCOUNT_BYTES], ) { return std::result::Result::Err(error); } let payload_json = match serde_json::to_value(&value) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::ProjectionFailed, ); }, }; return std::result::Result::Ok(DcMetadataMtmEditionAccountSnapshot { account: account_key.to_string(), bump, mint: mint.to_string(), kind: DcMetadataMtmEditionAccountKind::MasterEditionV1, supply: std::option::Option::Some(value.supply), max_supply: value.max_supply, parent: std::option::Option::None, edition: std::option::Option::None, printing_mint: std::option::Option::Some(value.printing_mint.to_string()), one_time_printing_authorization_mint: std::option::Option::Some( value.one_time_printing_authorization_mint.to_string(), ), payload_json, }); } if key == mpl_token_metadata::types::Key::MasterEditionV2 as u8 { let value = match mpl_token_metadata::accounts::MasterEdition::from_bytes(data) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); }, }; let serialized_len = if value.max_supply.is_some() { 18 } else { 10 }; if let std::result::Result::Err(error) = validate_master_edition_allocation( data, serialized_len, &[COMPACT_MASTER_EDITION_ACCOUNT_BYTES, LEGACY_MASTER_EDITION_ACCOUNT_BYTES], ) { return std::result::Result::Err(error); } let payload_json = match serde_json::to_value(&value) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::ProjectionFailed, ); }, }; return std::result::Result::Ok(DcMetadataMtmEditionAccountSnapshot { account: account_key.to_string(), bump, mint: mint.to_string(), kind: DcMetadataMtmEditionAccountKind::MasterEditionV2, supply: std::option::Option::Some(value.supply), max_supply: value.max_supply, parent: std::option::Option::None, edition: std::option::Option::None, printing_mint: std::option::Option::None, one_time_printing_authorization_mint: std::option::Option::None, payload_json, }); } if key == mpl_token_metadata::types::Key::EditionV1 as u8 { let value = match mpl_token_metadata::accounts::Edition::from_bytes(data) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); }, }; if let std::result::Result::Err(error) = validate_known_account_allocation( data, mpl_token_metadata::accounts::Edition::LEN, &[COMPACT_EDITION_ACCOUNT_BYTES, LEGACY_EDITION_ACCOUNT_BYTES], ) { return std::result::Result::Err(error); } let payload_json = match serde_json::to_value(&value) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::ProjectionFailed, ); }, }; return std::result::Result::Ok(DcMetadataMtmEditionAccountSnapshot { account: account_key.to_string(), bump, mint: mint.to_string(), kind: DcMetadataMtmEditionAccountKind::EditionV1, supply: std::option::Option::None, max_supply: std::option::Option::None, parent: std::option::Option::Some(value.parent.to_string()), edition: std::option::Option::Some(value.edition), printing_mint: std::option::Option::None, one_time_printing_authorization_mint: std::option::Option::None, payload_json, }); } return std::result::Result::Err(DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataKey); } /// Supported Edition Marker account layout. #[derive(Clone, Debug, Eq, PartialEq)] pub enum DcMetadataMtmEditionMarkerAccountKind { /// Fixed 248-edition historical/current marker layout. EditionMarkerV1, /// Extensible marker layout with one PDA per mint. EditionMarkerV2, } /// Canonical bounded projection of one Edition Marker account. #[derive(Clone, Debug, PartialEq)] pub struct DcMetadataMtmEditionMarkerAccountSnapshot { /// Canonical account address. pub account: String, /// Canonical PDA bump. pub bump: u8, /// Mint used to derive the marker PDA. pub mint: String, /// Decoded marker layout. pub kind: DcMetadataMtmEditionMarkerAccountKind, /// V1 group number (`edition / 248`), absent for V2. pub marker_group: std::option::Option, /// Requested edition used to inspect the bitmap. pub edition: u64, /// Byte index inside the ledger. pub byte_index: usize, /// Big-endian bit mask inside the selected byte. pub bit_mask: u8, /// Whether the requested edition is already marked. pub edition_taken: bool, /// Exact bounded ledger bytes. pub ledger: std::vec::Vec, /// Complete bounded account projection. pub payload_json: serde_json::Value, } /// Decodes EditionMarker or EditionMarkerV2 with exact owner, bounds, bitmap and PDA validation. pub fn decoder_metadata_metaplex_token_metadata_decode_edition_marker_account( account: &str, owner: &str, mint: &str, edition: u64, data: &[u8], ) -> std::result::Result< DcMetadataMtmEditionMarkerAccountSnapshot, DcMetadataMtmMetadataAccountDecodeError, > { let account_key = match ::from_str(account) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidAccountKey, ); }, }; let owner_key = match ::from_str(owner) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err(DcMetadataMtmMetadataAccountDecodeError::InvalidOwner); }, }; let program_key = match ::from_str( ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err(DcMetadataMtmMetadataAccountDecodeError::InvalidOwner); }, }; if owner_key != program_key { return std::result::Result::Err(DcMetadataMtmMetadataAccountDecodeError::ForeignOwner); } let mint_key = match ::from_str(mint) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidAccountKey, ); }, }; if data.is_empty() { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidDataLength, ); } if data[0] == mpl_token_metadata::types::Key::EditionMarker as u8 { if data.len() != mpl_token_metadata::accounts::EditionMarker::LEN { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidDataLength, ); } let marker_group = edition / EDITION_MARKER_V1_BITS; let marker_seed = marker_group.to_string(); let (expected_pda, bump) = solana_pubkey::Pubkey::find_program_address( &[ b"metadata", program_key.as_ref(), mint_key.as_ref(), b"edition", marker_seed.as_bytes(), ], &program_key, ); if account_key != expected_pda { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidEditionMarkerPda, ); } let value = match mpl_token_metadata::accounts::EditionMarker::from_bytes(data) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); }, }; let offset = (edition % EDITION_MARKER_V1_BITS) as usize; let byte_index = offset / 8; let bit_mask = 1u8 << (7 - (offset % 8)); let payload_json = match serde_json::to_value(&value) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::ProjectionFailed, ); }, }; return std::result::Result::Ok(DcMetadataMtmEditionMarkerAccountSnapshot { account: account_key.to_string(), bump, mint: mint_key.to_string(), kind: DcMetadataMtmEditionMarkerAccountKind::EditionMarkerV1, marker_group: std::option::Option::Some(marker_group), edition, byte_index, bit_mask, edition_taken: value.ledger[byte_index] & bit_mask != 0, ledger: value.ledger.to_vec(), payload_json, }); } if data[0] == mpl_token_metadata::types::Key::EditionMarkerV2 as u8 { if data.len() < 5 || data.len() > MAX_EDITION_MARKER_V2_LEDGER_BYTES + 5 { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidDataLength, ); } let (expected_pda, bump) = solana_pubkey::Pubkey::find_program_address( &[b"metadata", program_key.as_ref(), mint_key.as_ref(), b"edition", b"marker"], &program_key, ); if account_key != expected_pda { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidEditionMarkerPda, ); } let value = match mpl_token_metadata::accounts::EditionMarkerV2::from_bytes(data) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); }, }; if value.ledger.len() > MAX_EDITION_MARKER_V2_LEDGER_BYTES { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::EditionMarkerBoundsExceeded, ); } if data.len() != value.ledger.len() + 5 { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidDataLength, ); } let byte_index_u64 = edition / 8; let byte_index = match usize::try_from(byte_index_u64) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::EditionMarkerBoundsExceeded, ); }, }; let bit_mask = 1u8 << (7 - (edition % 8) as usize); let edition_taken = value.ledger.get(byte_index).is_some_and(|entry| return entry & bit_mask != 0); let payload_json = match serde_json::to_value(&value) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::ProjectionFailed, ); }, }; return std::result::Result::Ok(DcMetadataMtmEditionMarkerAccountSnapshot { account: account_key.to_string(), bump, mint: mint_key.to_string(), kind: DcMetadataMtmEditionMarkerAccountKind::EditionMarkerV2, marker_group: std::option::Option::None, edition, byte_index, bit_mask, edition_taken, ledger: value.ledger, payload_json, }); } return std::result::Result::Err(DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataKey); } /// Canonical bounded projection of one programmable Token Record account. #[derive(Clone, Debug, PartialEq)] pub struct DcMetadataMtmTokenRecordAccountSnapshot { /// Canonical Token Record account address. pub account: String, /// Canonical Token Record PDA bump computed from mint and token account. pub bump: u8, /// Mint used to derive the Token Record PDA. pub mint: String, /// Token account used to derive the Token Record PDA. pub token: String, /// Bump stored by the on-chain Token Record layout. pub stored_bump: u8, /// Current programmable token state. pub state: String, /// Optional Token Authorization Rules revision. pub rule_set_revision: std::option::Option, /// Optional active delegate. pub delegate: std::option::Option, /// Optional active delegate role. pub delegate_role: std::option::Option, /// Optional locked-transfer destination. pub locked_transfer: std::option::Option, /// Whether the token is currently locked. pub locked: bool, /// Complete bounded account projection. pub payload_json: serde_json::Value, } /// Decodes one Token Record account after exact owner, length, discriminant and PDA validation. pub fn decoder_metadata_metaplex_token_metadata_decode_token_record_account( account: &str, owner: &str, mint: &str, token: &str, data: &[u8], ) -> std::result::Result< DcMetadataMtmTokenRecordAccountSnapshot, DcMetadataMtmMetadataAccountDecodeError, > { let account_key = match ::from_str(account) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidAccountKey, ); }, }; let owner_key = match ::from_str(owner) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err(DcMetadataMtmMetadataAccountDecodeError::InvalidOwner); }, }; let expected_owner = match ::from_str( ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err(DcMetadataMtmMetadataAccountDecodeError::InvalidOwner); }, }; if owner_key != expected_owner { return std::result::Result::Err(DcMetadataMtmMetadataAccountDecodeError::ForeignOwner); } if data.len() != mpl_token_metadata::accounts::TokenRecord::LEN { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidDataLength, ); } let mint_key = match ::from_str(mint) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidAccountKey, ); }, }; let token_key = match ::from_str(token) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidAccountKey, ); }, }; let (expected_pda, bump) = solana_pubkey::Pubkey::find_program_address( &[ b"metadata", expected_owner.as_ref(), mint_key.as_ref(), b"token_record", token_key.as_ref(), ], &expected_owner, ); if account_key != expected_pda { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidTokenRecordPda, ); } let value = match mpl_token_metadata::accounts::TokenRecord::from_bytes(data) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); }, }; if value.key != mpl_token_metadata::types::Key::TokenRecord { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidTokenRecordKey, ); } if value.bump != bump { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidTokenRecordPda, ); } let state = format!("{:?}", value.state); let delegate = value.delegate.map(|delegate| return delegate.to_string()); let delegate_role = value.delegate_role.as_ref().map(|role| return format!("{role:?}")); let locked_transfer = value.locked_transfer.map(|locked_transfer| return locked_transfer.to_string()); let locked = value.state == mpl_token_metadata::types::TokenState::Locked; let payload_json = serde_json::json!({ "key": "TokenRecord", "bump": value.bump, "state": &state, "rule_set_revision": value.rule_set_revision, "delegate": &delegate, "delegate_role": &delegate_role, "locked_transfer": &locked_transfer, }); return std::result::Result::Ok(DcMetadataMtmTokenRecordAccountSnapshot { account: account_key.to_string(), bump, mint: mint_key.to_string(), token: token_key.to_string(), stored_bump: value.bump, state, rule_set_revision: value.rule_set_revision, delegate, delegate_role, locked_transfer, locked, payload_json, }); } /// Canonical projection shared by Metadata and Holder delegate records. #[derive(Clone, Debug, PartialEq)] pub struct DcMetadataMtmDelegateRecordAccountSnapshot { /// Canonical delegate-record account address. pub account: String, /// Canonical PDA bump. pub bump: u8, /// Delegate-record family. pub kind: String, /// Mint carried by the record. pub mint: String, /// Role seed used by the PDA. pub role: String, /// Authority or holder used by the PDA. pub authority: String, /// Delegate carried by the record. pub delegate: String, /// Update authority stored by the record. pub update_authority: String, /// Bump stored by the account. pub stored_bump: u8, /// Complete bounded account projection. pub payload_json: serde_json::Value, } fn parse_record_boundary( account: &str, owner: &str, ) -> std::result::Result< (solana_pubkey::Pubkey, solana_pubkey::Pubkey), DcMetadataMtmMetadataAccountDecodeError, > { let account_key = match ::from_str(account) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidAccountKey, ); }, }; let owner_key = match ::from_str(owner) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err(DcMetadataMtmMetadataAccountDecodeError::InvalidOwner); }, }; let expected_owner = match ::from_str( ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err(DcMetadataMtmMetadataAccountDecodeError::InvalidOwner); }, }; if owner_key != expected_owner { return std::result::Result::Err(DcMetadataMtmMetadataAccountDecodeError::ForeignOwner); } return std::result::Result::Ok((account_key, expected_owner)); } /// Decodes a Metadata Delegate Record with exact role, authority, delegate and bump validation. pub fn decoder_metadata_metaplex_token_metadata_decode_metadata_delegate_record_account( account: &str, owner: &str, role: mpl_token_metadata::types::MetadataDelegateRole, data: &[u8], ) -> std::result::Result< DcMetadataMtmDelegateRecordAccountSnapshot, DcMetadataMtmMetadataAccountDecodeError, > { let (account_key, program) = match parse_record_boundary(account, owner) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; if data.len() != mpl_token_metadata::accounts::MetadataDelegateRecord::LEN { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidDataLength, ); } let value = match mpl_token_metadata::accounts::MetadataDelegateRecord::from_bytes(data) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); }, }; if value.key != mpl_token_metadata::types::Key::MetadataDelegate { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidAuthorityRecordKey, ); } let role_seed = role.to_string(); let mint = match ::from_str(&value.mint.to_string()) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); }, }; let authority = match ::from_str( &value.update_authority.to_string(), ) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); }, }; let delegate = match ::from_str(&value.delegate.to_string()) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); }, }; let (expected_pda, bump) = solana_pubkey::Pubkey::find_program_address( &[ b"metadata", program.as_ref(), mint.as_ref(), role_seed.as_bytes(), authority.as_ref(), delegate.as_ref(), ], &program, ); if account_key != expected_pda || value.bump != bump { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidAuthorityRecordPda, ); } let payload_json = match serde_json::to_value(&value) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::ProjectionFailed, ); }, }; return std::result::Result::Ok(DcMetadataMtmDelegateRecordAccountSnapshot { account: account_key.to_string(), bump, kind: "MetadataDelegateRecord".to_string(), mint: mint.to_string(), role: role_seed, authority: authority.to_string(), delegate: delegate.to_string(), update_authority: value.update_authority.to_string(), stored_bump: value.bump, payload_json, }); } /// Decodes a Holder Delegate Record with exact role, holder, delegate and bump validation. pub fn decoder_metadata_metaplex_token_metadata_decode_holder_delegate_record_account( account: &str, owner: &str, holder: &str, role: mpl_token_metadata::types::HolderDelegateRole, data: &[u8], ) -> std::result::Result< DcMetadataMtmDelegateRecordAccountSnapshot, DcMetadataMtmMetadataAccountDecodeError, > { let (account_key, program) = match parse_record_boundary(account, owner) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; if data.len() != mpl_token_metadata::accounts::HolderDelegateRecord::LEN { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidDataLength, ); } let value = match mpl_token_metadata::accounts::HolderDelegateRecord::from_bytes(data) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); }, }; if value.key != mpl_token_metadata::types::Key::HolderDelegate { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidAuthorityRecordKey, ); } let holder_key = match ::from_str(holder) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidAccountKey, ); }, }; let role_seed = role.to_string(); let mint = match ::from_str(&value.mint.to_string()) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); }, }; let delegate = match ::from_str(&value.delegate.to_string()) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); }, }; let (expected_pda, bump) = solana_pubkey::Pubkey::find_program_address( &[ b"metadata", program.as_ref(), mint.as_ref(), role_seed.as_bytes(), holder_key.as_ref(), delegate.as_ref(), ], &program, ); if account_key != expected_pda || value.bump != bump { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidAuthorityRecordPda, ); } let payload_json = match serde_json::to_value(&value) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::ProjectionFailed, ); }, }; return std::result::Result::Ok(DcMetadataMtmDelegateRecordAccountSnapshot { account: account_key.to_string(), bump, kind: "HolderDelegateRecord".to_string(), mint: mint.to_string(), role: role_seed, authority: holder_key.to_string(), delegate: delegate.to_string(), update_authority: value.update_authority.to_string(), stored_bump: value.bump, payload_json, }); } /// Canonical projection of Collection Authority and Use Authority records. #[derive(Clone, Debug, PartialEq)] pub struct DcMetadataMtmAuthorityRecordAccountSnapshot { /// Canonical record account address. pub account: String, /// Canonical PDA bump. pub bump: u8, /// Record family. pub kind: String, /// Mint used by the PDA. pub mint: String, /// Authority used by the PDA. pub authority: String, /// Optional update authority stored by Collection Authority Record. pub update_authority: std::option::Option, /// Optional remaining use allowance stored by Use Authority Record. pub allowed_uses: std::option::Option, /// Bump stored by the account. pub stored_bump: u8, /// Complete bounded account projection. pub payload_json: serde_json::Value, } /// Decodes a Collection Authority Record with exact PDA and bump validation. pub fn decoder_metadata_metaplex_token_metadata_decode_collection_authority_record_account( account: &str, owner: &str, mint: &str, authority: &str, data: &[u8], ) -> std::result::Result< DcMetadataMtmAuthorityRecordAccountSnapshot, DcMetadataMtmMetadataAccountDecodeError, > { let (account_key, program) = match parse_record_boundary(account, owner) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; if data.len() != 3 && data.len() != 35 { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidDataLength, ); } let mint_key = match ::from_str(mint) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidAccountKey, ); }, }; let authority_key = match ::from_str(authority) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidAccountKey, ); }, }; let (expected_pda, bump) = solana_pubkey::Pubkey::find_program_address( &[ b"metadata", program.as_ref(), mint_key.as_ref(), b"collection_authority", authority_key.as_ref(), ], &program, ); if account_key != expected_pda { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidAuthorityRecordPda, ); } let value = match mpl_token_metadata::accounts::CollectionAuthorityRecord::from_bytes(data) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); }, }; if value.key != mpl_token_metadata::types::Key::CollectionAuthorityRecord { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidAuthorityRecordKey, ); } if value.bump != bump { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidAuthorityRecordPda, ); } let payload_json = match serde_json::to_value(&value) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::ProjectionFailed, ); }, }; return std::result::Result::Ok(DcMetadataMtmAuthorityRecordAccountSnapshot { account: account_key.to_string(), bump, kind: "CollectionAuthorityRecord".to_string(), mint: mint_key.to_string(), authority: authority_key.to_string(), update_authority: value.update_authority.map(|key| return key.to_string()), allowed_uses: std::option::Option::None, stored_bump: value.bump, payload_json, }); } /// Decodes a Use Authority Record with exact length, PDA and bump validation. pub fn decoder_metadata_metaplex_token_metadata_decode_use_authority_record_account( account: &str, owner: &str, mint: &str, authority: &str, data: &[u8], ) -> std::result::Result< DcMetadataMtmAuthorityRecordAccountSnapshot, DcMetadataMtmMetadataAccountDecodeError, > { let (account_key, program) = match parse_record_boundary(account, owner) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; if data.len() != mpl_token_metadata::accounts::UseAuthorityRecord::LEN { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidDataLength, ); } let mint_key = match ::from_str(mint) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidAccountKey, ); }, }; let authority_key = match ::from_str(authority) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidAccountKey, ); }, }; let (expected_pda, bump) = solana_pubkey::Pubkey::find_program_address( &[ b"metadata", program.as_ref(), mint_key.as_ref(), b"user", authority_key.as_ref(), ], &program, ); if account_key != expected_pda { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidAuthorityRecordPda, ); } let value = match mpl_token_metadata::accounts::UseAuthorityRecord::from_bytes(data) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); }, }; if value.key != mpl_token_metadata::types::Key::UseAuthorityRecord { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidAuthorityRecordKey, ); } if value.bump != bump { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidAuthorityRecordPda, ); } let payload_json = match serde_json::to_value(&value) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::ProjectionFailed, ); }, }; return std::result::Result::Ok(DcMetadataMtmAuthorityRecordAccountSnapshot { account: account_key.to_string(), bump, kind: "UseAuthorityRecord".to_string(), mint: mint_key.to_string(), authority: authority_key.to_string(), update_authority: std::option::Option::None, allowed_uses: std::option::Option::Some(value.allowed_uses), stored_bump: value.bump, payload_json, }); } /// Authority form used by one Token Owned Escrow account. #[derive(Clone, Debug, Eq, PartialEq)] pub enum DcMetadataMtmTokenOwnedEscrowAuthorityKind { /// Escrow controlled by the owner of the base token. TokenOwner, /// Escrow controlled by an explicit creator key. Creator, } /// Canonical bounded projection of one Token Owned Escrow account. #[derive(Clone, Debug, PartialEq)] pub struct DcMetadataMtmTokenOwnedEscrowAccountSnapshot { /// Canonical escrow account address. pub account: String, /// Canonical PDA bump. pub bump: u8, /// Base token mint stored by the account. pub base_token: String, /// Authority form encoded by the account. pub authority_kind: DcMetadataMtmTokenOwnedEscrowAuthorityKind, /// Optional creator authority. pub creator: std::option::Option, /// Bump stored by the account. pub stored_bump: u8, /// Complete bounded account projection. pub payload_json: serde_json::Value, } /// Decodes one Token Owned Escrow account with exact owner, layout, authority seeds and bump validation. pub fn decoder_metadata_metaplex_token_metadata_decode_token_owned_escrow_account( account: &str, owner: &str, data: &[u8], ) -> std::result::Result< DcMetadataMtmTokenOwnedEscrowAccountSnapshot, DcMetadataMtmMetadataAccountDecodeError, > { let (account_key, program) = match parse_record_boundary(account, owner) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; if data.len() != 35 && data.len() != 67 { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidDataLength, ); } let value = match mpl_token_metadata::accounts::TokenOwnedEscrow::from_bytes(data) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); }, }; if value.key != mpl_token_metadata::types::Key::TokenOwnedEscrow { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidTokenOwnedEscrowKey, ); } let base_token = match ::from_str(&value.base_token.to_string()) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidAccountKey, ); }, }; let (authority_kind, creator, expected_pda, bump) = match &value.authority { mpl_token_metadata::types::EscrowAuthority::TokenOwner => { let (expected_pda, bump) = solana_pubkey::Pubkey::find_program_address( &[b"metadata", program.as_ref(), base_token.as_ref(), &[0], b"escrow"], &program, ); ( DcMetadataMtmTokenOwnedEscrowAuthorityKind::TokenOwner, std::option::Option::None, expected_pda, bump, ) }, mpl_token_metadata::types::EscrowAuthority::Creator(creator_key) => { let creator_text = creator_key.to_string(); let creator_key = match ::from_str(&creator_text) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidAccountKey, ); }, }; let (expected_pda, bump) = solana_pubkey::Pubkey::find_program_address( &[ b"metadata", program.as_ref(), base_token.as_ref(), &[1], creator_key.as_ref(), b"escrow", ], &program, ); ( DcMetadataMtmTokenOwnedEscrowAuthorityKind::Creator, std::option::Option::Some(creator_text), expected_pda, bump, ) }, }; if account_key != expected_pda || value.bump != bump { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidTokenOwnedEscrowPda, ); } let payload_json = match serde_json::to_value(&value) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::ProjectionFailed, ); }, }; return std::result::Result::Ok(DcMetadataMtmTokenOwnedEscrowAccountSnapshot { account: account_key.to_string(), bump, base_token: value.base_token.to_string(), authority_kind, creator, stored_bump: value.bump, payload_json, }); } /// Historical Reservation List account generation. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum DcMetadataMtmReservationListAccountKind { /// Legacy layout with `u8` spot counters. V1, /// Later historical layout with `u64` spot counters and cached totals. V2, } /// One bounded historical reservation entry. #[derive(Clone, Debug, Eq, PartialEq)] pub struct DcMetadataMtmReservationEntrySnapshot { /// Reserved address. pub address: String, /// Spots still available for the address. pub spots_remaining: u64, /// Total spots assigned to the address. pub total_spots: u64, } /// Canonical decode-only projection of a historical Reservation List account. #[derive(Clone, Debug, PartialEq)] pub struct DcMetadataMtmReservationListAccountSnapshot { /// Canonical account address. pub account: String, /// Canonical Reservation List PDA bump. pub bump: u8, /// Historical account generation. pub kind: DcMetadataMtmReservationListAccountKind, /// Master Edition stored by the account. pub master_edition: String, /// Resource key used by the PDA derivation. pub resource: String, /// Master Edition supply captured when reservations were created. pub supply_snapshot: std::option::Option, /// Bounded reservation entries. pub reservations: std::vec::Vec, /// Total reservation spots. V1 derives this value from entries. pub total_reservation_spots: u64, /// Current reservation spots. V1 derives this value from entries. pub current_reservation_spots: u64, /// Complete bounded JSON projection. pub payload_json: serde_json::Value, } fn read_reservation_u64(data: &[u8], offset: &mut usize) -> std::option::Option { let end = match offset.checked_add(8) { std::option::Option::Some(value) => value, std::option::Option::None => return std::option::Option::None, }; let bytes = match data.get(*offset..end) { std::option::Option::Some(value) => value, std::option::Option::None => return std::option::Option::None, }; let array = match <[u8; 8]>::try_from(bytes) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => return std::option::Option::None, }; *offset = end; return std::option::Option::Some(u64::from_le_bytes(array)); } fn read_reservation_pubkey( data: &[u8], offset: &mut usize, ) -> std::option::Option { let end = match offset.checked_add(32) { std::option::Option::Some(value) => value, std::option::Option::None => return std::option::Option::None, }; let bytes = match data.get(*offset..end) { std::option::Option::Some(value) => value, std::option::Option::None => return std::option::Option::None, }; let array = match <[u8; 32]>::try_from(bytes) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => return std::option::Option::None, }; *offset = end; return std::option::Option::Some(solana_pubkey::Pubkey::new_from_array(array)); } /// Decodes a historical Reservation List V1 or V2 account as decode-only state. pub fn decoder_metadata_metaplex_token_metadata_decode_reservation_list_account( account: &str, owner: &str, resource: &str, data: &[u8], ) -> std::result::Result< DcMetadataMtmReservationListAccountSnapshot, DcMetadataMtmMetadataAccountDecodeError, > { const MAX_RESERVATIONS: usize = 200; const MAX_V1_BYTES: usize = 6_949; const MAX_V2_BYTES: usize = 9_749; let account_key = match ::from_str(account) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidAccountKey, ); }, }; let owner_key = match ::from_str(owner) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err(DcMetadataMtmMetadataAccountDecodeError::InvalidOwner); }, }; let resource_key = match ::from_str(resource) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidAccountKey, ); }, }; let program_key = match ::from_str( ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err(DcMetadataMtmMetadataAccountDecodeError::InvalidOwner); }, }; if owner_key != program_key { return std::result::Result::Err(DcMetadataMtmMetadataAccountDecodeError::ForeignOwner); } let key = match data.first() { std::option::Option::Some(value) => *value, std::option::Option::None => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidDataLength, ); }, }; let (kind, maximum, entry_bytes) = match key { 3 => (DcMetadataMtmReservationListAccountKind::V1, MAX_V1_BYTES, 34usize), 5 => (DcMetadataMtmReservationListAccountKind::V2, MAX_V2_BYTES, 48usize), _ => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidReservationListKey, ); }, }; if data.len() > maximum { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::ReservationListBoundsExceeded, ); } let mut offset = 1usize; let master_edition = match read_reservation_pubkey(data, &mut offset) { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); }, }; let option_tag = match data.get(offset) { std::option::Option::Some(value) => *value, std::option::Option::None => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); }, }; offset += 1; let supply_snapshot = match option_tag { 0 => std::option::Option::None, 1 => match read_reservation_u64(data, &mut offset) { std::option::Option::Some(value) => std::option::Option::Some(value), std::option::Option::None => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); }, }, _ => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); }, }; let count_end = match offset.checked_add(4) { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); }, }; let count_bytes = match data.get(offset..count_end) { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); }, }; let count_array = match <[u8; 4]>::try_from(count_bytes) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); }, }; let count = u32::from_le_bytes(count_array) as usize; offset = count_end; if count > MAX_RESERVATIONS { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::ReservationListBoundsExceeded, ); } let required_entries = match count.checked_mul(entry_bytes) { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::ReservationListBoundsExceeded, ); }, }; if data.len().saturating_sub(offset) < required_entries { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); } let mut reservations = std::vec::Vec::with_capacity(count); for _ in 0..count { let address = match read_reservation_pubkey(data, &mut offset) { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); }, }; let (spots_remaining, total_spots) = match kind { DcMetadataMtmReservationListAccountKind::V1 => { let remaining = match data.get(offset) { std::option::Option::Some(value) => *value as u64, std::option::Option::None => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); }, }; let total = match data.get(offset + 1) { std::option::Option::Some(value) => *value as u64, std::option::Option::None => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); }, }; offset += 2; (remaining, total) }, DcMetadataMtmReservationListAccountKind::V2 => { let remaining = match read_reservation_u64(data, &mut offset) { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); }, }; let total = match read_reservation_u64(data, &mut offset) { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); }, }; (remaining, total) }, }; reservations.push(DcMetadataMtmReservationEntrySnapshot { address: address.to_string(), spots_remaining, total_spots, }); } let derived_total = reservations .iter() .fold(0u64, |sum, entry| return sum.saturating_add(entry.total_spots)); let (total_reservation_spots, current_reservation_spots) = match kind { DcMetadataMtmReservationListAccountKind::V1 => (derived_total, derived_total), DcMetadataMtmReservationListAccountKind::V2 => { let total = match read_reservation_u64(data, &mut offset) { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); }, }; let current = match read_reservation_u64(data, &mut offset) { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); }, }; (total, current) }, }; if data .get(offset..) .is_some_and(|suffix| return suffix.iter().any(|byte| return *byte != 0)) { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout, ); } let (expected_pda, bump) = solana_pubkey::Pubkey::find_program_address( &[ b"metadata", program_key.as_ref(), master_edition.as_ref(), b"reservation", resource_key.as_ref(), ], &program_key, ); if account_key != expected_pda { return std::result::Result::Err( DcMetadataMtmMetadataAccountDecodeError::InvalidReservationListPda, ); } let payload_json = serde_json::json!({ "kind": match kind { DcMetadataMtmReservationListAccountKind::V1 => "reservation_list_v1", DcMetadataMtmReservationListAccountKind::V2 => "reservation_list_v2" }, "master_edition": master_edition.to_string(), "resource": resource_key.to_string(), "supply_snapshot": supply_snapshot, "reservations": reservations.iter().map(|entry| return serde_json::json!({"address": entry.address.clone(), "spots_remaining": entry.spots_remaining, "total_spots": entry.total_spots})).collect::>(), "total_reservation_spots": total_reservation_spots, "current_reservation_spots": current_reservation_spots, }); return std::result::Result::Ok(DcMetadataMtmReservationListAccountSnapshot { account: account_key.to_string(), bump, kind, master_edition: master_edition.to_string(), resource: resource_key.to_string(), supply_snapshot, reservations, total_reservation_spots, current_reservation_spots, payload_json, }); } #[cfg(test)] mod tests { fn metadata() -> mpl_token_metadata::accounts::Metadata { return mpl_token_metadata::accounts::Metadata { key: mpl_token_metadata::types::Key::MetadataV1, update_authority: mpl_token_metadata::ID, mint: mpl_token_metadata::ID, name: "Khadhroony".to_string(), symbol: "KB".to_string(), uri: "https://example.invalid/metadata.json".to_string(), seller_fee_basis_points: 500, creators: std::option::Option::None, primary_sale_happened: false, is_mutable: true, edition_nonce: std::option::Option::None, token_standard: std::option::Option::None, collection: std::option::Option::None, uses: std::option::Option::None, collection_details: std::option::Option::None, programmable_config: std::option::Option::None, }; } #[test] fn metadata_account_decodes_with_exact_owner_and_pda() { let metadata = metadata(); let (account, expected_bump) = mpl_token_metadata::accounts::Metadata::find_pda(&metadata.mint); let data = match borsh_0_10::to_vec(&metadata) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("metadata serialization failed: {error}"), }; let snapshot = match crate::decoder_metadata_metaplex_token_metadata_decode_metadata_account( &account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &data, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("metadata decode failed: {error}"), }; assert_eq!(snapshot.bump, expected_bump); assert_eq!(snapshot.mint, metadata.mint.to_string()); assert_eq!(snapshot.name, "Khadhroony"); assert_eq!(snapshot.symbol, "KB"); assert_eq!(snapshot.seller_fee_basis_points, 500); assert!(snapshot.is_mutable); assert!(!snapshot.primary_sale_happened); } #[test] fn metadata_account_rejects_foreign_owner_wrong_pda_and_bounds() { let metadata = metadata(); let (account, _) = mpl_token_metadata::accounts::Metadata::find_pda(&metadata.mint); let data = match borsh_0_10::to_vec(&metadata) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("metadata serialization failed: {error}"), }; assert_eq!( crate::decoder_metadata_metaplex_token_metadata_decode_metadata_account( &account.to_string(), &solana_pubkey::Pubkey::new_unique().to_string(), &data ), std::result::Result::Err(crate::DcMetadataMtmMetadataAccountDecodeError::ForeignOwner), ); assert_eq!( crate::decoder_metadata_metaplex_token_metadata_decode_metadata_account( &solana_pubkey::Pubkey::new_unique().to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &data ), std::result::Result::Err( crate::DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataPda ), ); assert_eq!( crate::decoder_metadata_metaplex_token_metadata_decode_metadata_account( &account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &[] ), std::result::Result::Err( crate::DcMetadataMtmMetadataAccountDecodeError::InvalidDataLength ), ); } #[test] fn edition_and_master_edition_accounts_decode_with_exact_pda() { let mint = mpl_token_metadata::ID; let (account, expected_bump) = mpl_token_metadata::accounts::MasterEdition::find_pda(&mint); let master_v2 = mpl_token_metadata::accounts::MasterEdition { key: mpl_token_metadata::types::Key::MasterEditionV2, supply: 7, max_supply: std::option::Option::Some(100), }; let master_v2_data = match borsh_0_10::to_vec(&master_v2) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { panic!("master edition serialization failed: {error}") }, }; let master_v2_snapshot = match crate::decoder_metadata_metaplex_token_metadata_decode_edition_account( &account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &mint.to_string(), &master_v2_data, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("master edition decode failed: {error}"), }; assert_eq!(master_v2_snapshot.bump, expected_bump); assert_eq!( master_v2_snapshot.kind, crate::DcMetadataMtmEditionAccountKind::MasterEditionV2 ); assert_eq!(master_v2_snapshot.supply, std::option::Option::Some(7)); assert_eq!(master_v2_snapshot.max_supply, std::option::Option::Some(100)); let edition = mpl_token_metadata::accounts::Edition { key: mpl_token_metadata::types::Key::EditionV1, parent: account, edition: u64::MAX, }; let edition_data = match borsh_0_10::to_vec(&edition) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("edition serialization failed: {error}"), }; let edition_snapshot = match crate::decoder_metadata_metaplex_token_metadata_decode_edition_account( &account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &mint.to_string(), &edition_data, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("edition decode failed: {error}"), }; assert_eq!(edition_snapshot.kind, crate::DcMetadataMtmEditionAccountKind::EditionV1); assert_eq!(edition_snapshot.parent, std::option::Option::Some(account.to_string())); assert_eq!(edition_snapshot.edition, std::option::Option::Some(u64::MAX)); } #[test] fn edition_and_master_edition_accept_only_known_allocations() { let mint = mpl_token_metadata::ID; let (account, _) = mpl_token_metadata::accounts::MasterEdition::find_pda(&mint); let master = mpl_token_metadata::accounts::MasterEdition { key: mpl_token_metadata::types::Key::MasterEditionV2, supply: 1, max_supply: std::option::Option::Some(1), }; let master_data = match borsh_0_10::to_vec(&master) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { panic!("master edition serialization failed: {error}") }, }; for allocated_len in [ super::COMPACT_MASTER_EDITION_ACCOUNT_BYTES, super::LEGACY_MASTER_EDITION_ACCOUNT_BYTES, ] { let mut padded = master_data.clone(); padded.resize(allocated_len, 0); assert!( crate::decoder_metadata_metaplex_token_metadata_decode_edition_account( &account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &mint.to_string(), padded.as_slice(), ) .is_ok() ); } let edition = mpl_token_metadata::accounts::Edition { key: mpl_token_metadata::types::Key::EditionV1, parent: account, edition: 1, }; let edition_data = match borsh_0_10::to_vec(&edition) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("edition serialization failed: {error}"), }; for allocated_len in [super::COMPACT_EDITION_ACCOUNT_BYTES, super::LEGACY_EDITION_ACCOUNT_BYTES] { let mut padded = edition_data.clone(); padded.resize(allocated_len, 0); assert!( crate::decoder_metadata_metaplex_token_metadata_decode_edition_account( &account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &mint.to_string(), padded.as_slice(), ) .is_ok() ); } let mut unexpected_len = edition_data.clone(); unexpected_len.resize(super::COMPACT_EDITION_ACCOUNT_BYTES + 1, 0); assert_eq!( crate::decoder_metadata_metaplex_token_metadata_decode_edition_account( &account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &mint.to_string(), unexpected_len.as_slice(), ), std::result::Result::Err( crate::DcMetadataMtmMetadataAccountDecodeError::InvalidDataLength ), ); let mut non_zero_padding = edition_data; non_zero_padding.resize(super::COMPACT_EDITION_ACCOUNT_BYTES, 1); assert_eq!( crate::decoder_metadata_metaplex_token_metadata_decode_edition_account( &account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &mint.to_string(), non_zero_padding.as_slice(), ), std::result::Result::Err( crate::DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout ), ); } #[test] fn compact_master_edition_trailer_accepts_pnft_and_rejects_invalid_flags() { let mint = mpl_token_metadata::ID; let (account, _) = mpl_token_metadata::accounts::MasterEdition::find_pda(&mint); let master = mpl_token_metadata::accounts::MasterEdition { key: mpl_token_metadata::types::Key::MasterEditionV2, supply: 0, max_supply: std::option::Option::Some(0), }; let serialized = match borsh_0_10::to_vec(&master) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { panic!("master edition serialization failed: {error}") }, }; let mut pnft = serialized.clone(); pnft.resize(super::COMPACT_MASTER_EDITION_ACCOUNT_BYTES, 0); pnft[super::COMPACT_MASTER_EDITION_ACCOUNT_BYTES - 2] = 1; pnft[super::COMPACT_MASTER_EDITION_ACCOUNT_BYTES - 1] = mpl_token_metadata::types::TokenStandard::ProgrammableNonFungible as u8; let snapshot = match crate::decoder_metadata_metaplex_token_metadata_decode_edition_account( &account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &mint.to_string(), pnft.as_slice(), ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { panic!("compact pNFT master edition decode failed: {error}") }, }; assert_eq!(snapshot.kind, crate::DcMetadataMtmEditionAccountKind::MasterEditionV2); assert_eq!(snapshot.supply, std::option::Option::Some(0)); assert_eq!(snapshot.max_supply, std::option::Option::Some(0)); let mut invalid_fee_flag = pnft.clone(); invalid_fee_flag[super::COMPACT_MASTER_EDITION_ACCOUNT_BYTES - 2] = 2; assert_eq!( crate::decoder_metadata_metaplex_token_metadata_decode_edition_account( &account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &mint.to_string(), invalid_fee_flag.as_slice(), ), std::result::Result::Err( crate::DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout ), ); let mut invalid_token_standard = pnft; invalid_token_standard[super::COMPACT_MASTER_EDITION_ACCOUNT_BYTES - 1] = mpl_token_metadata::types::TokenStandard::Fungible as u8; assert_eq!( crate::decoder_metadata_metaplex_token_metadata_decode_edition_account( &account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &mint.to_string(), invalid_token_standard.as_slice(), ), std::result::Result::Err( crate::DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout ), ); let master_without_limit = mpl_token_metadata::accounts::MasterEdition { key: mpl_token_metadata::types::Key::MasterEditionV2, supply: 0, max_supply: std::option::Option::None, }; let mut invalid_reserved = match borsh_0_10::to_vec(&master_without_limit) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { panic!("master edition serialization failed: {error}") }, }; let serialized_len = invalid_reserved.len(); invalid_reserved.resize(super::COMPACT_MASTER_EDITION_ACCOUNT_BYTES, 0); invalid_reserved[serialized_len] = 1; invalid_reserved[super::COMPACT_MASTER_EDITION_ACCOUNT_BYTES - 1] = mpl_token_metadata::types::TokenStandard::ProgrammableNonFungible as u8; assert_eq!( crate::decoder_metadata_metaplex_token_metadata_decode_edition_account( &account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &mint.to_string(), invalid_reserved.as_slice(), ), std::result::Result::Err( crate::DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout ), ); } #[test] fn historical_master_edition_v1_and_invalid_boundaries_are_explicit() { let mint = mpl_token_metadata::ID; let (account, _) = mpl_token_metadata::accounts::MasterEdition::find_pda(&mint); let historical = mpl_token_metadata::accounts::DeprecatedMasterEditionV1 { key: mpl_token_metadata::types::Key::MasterEditionV1, supply: 1, max_supply: std::option::Option::None, printing_mint: mpl_token_metadata::ID, one_time_printing_authorization_mint: mpl_token_metadata::ID, }; let data = match borsh_0_10::to_vec(&historical) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { panic!("historical master edition serialization failed: {error}") }, }; let snapshot = match crate::decoder_metadata_metaplex_token_metadata_decode_edition_account( &account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &mint.to_string(), &data, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { panic!("historical master edition decode failed: {error}") }, }; assert_eq!(snapshot.kind, crate::DcMetadataMtmEditionAccountKind::MasterEditionV1); assert_eq!( snapshot.printing_mint, std::option::Option::Some(mpl_token_metadata::ID.to_string()) ); assert_eq!( crate::decoder_metadata_metaplex_token_metadata_decode_edition_account( &solana_pubkey::Pubkey::new_unique().to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &mint.to_string(), &data ), std::result::Result::Err( crate::DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataPda ), ); assert_eq!( crate::decoder_metadata_metaplex_token_metadata_decode_edition_account( &account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &mint.to_string(), &[255] ), std::result::Result::Err( crate::DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataKey ), ); } #[test] fn edition_marker_v1_decodes_group_bitmap_and_exact_pda() { let mint = mpl_token_metadata::ID.to_string(); let program = match ::from_str( ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("program parse failed: {error}"), }; let mint_key = match ::from_str(&mint) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("mint parse failed: {error}"), }; let edition = 249u64; let group = edition / 248; let group_seed = group.to_string(); let (account, expected_bump) = solana_pubkey::Pubkey::find_program_address( &[ b"metadata", program.as_ref(), mint_key.as_ref(), b"edition", group_seed.as_bytes(), ], &program, ); let mut ledger = [0u8; 31]; ledger[0] = 64; let marker = mpl_token_metadata::accounts::EditionMarker { key: mpl_token_metadata::types::Key::EditionMarker, ledger, }; let data = match borsh_0_10::to_vec(&marker) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { panic!("edition marker serialization failed: {error}") }, }; let snapshot = match crate::decoder_metadata_metaplex_token_metadata_decode_edition_marker_account( &account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &mint, edition, &data, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("edition marker decode failed: {error}"), }; assert_eq!(snapshot.kind, crate::DcMetadataMtmEditionMarkerAccountKind::EditionMarkerV1); assert_eq!(snapshot.bump, expected_bump); assert_eq!(snapshot.marker_group, std::option::Option::Some(1)); assert_eq!(snapshot.byte_index, 0); assert_eq!(snapshot.bit_mask, 64); assert!(snapshot.edition_taken); } #[test] fn edition_marker_v2_decodes_extensible_bitmap_and_rejects_boundaries() { let mint = mpl_token_metadata::ID.to_string(); let program = match ::from_str( ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("program parse failed: {error}"), }; let mint_key = match ::from_str(&mint) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("mint parse failed: {error}"), }; let (account, expected_bump) = solana_pubkey::Pubkey::find_program_address( &[b"metadata", program.as_ref(), mint_key.as_ref(), b"edition", b"marker"], &program, ); let marker = mpl_token_metadata::accounts::EditionMarkerV2 { key: mpl_token_metadata::types::Key::EditionMarkerV2, ledger: vec![0, 64], }; let data = match borsh_0_10::to_vec(&marker) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { panic!("edition marker v2 serialization failed: {error}") }, }; let snapshot = match crate::decoder_metadata_metaplex_token_metadata_decode_edition_marker_account( &account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &mint, 9, &data, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { panic!("edition marker v2 decode failed: {error}") }, }; assert_eq!(snapshot.kind, crate::DcMetadataMtmEditionMarkerAccountKind::EditionMarkerV2); assert_eq!(snapshot.bump, expected_bump); assert_eq!(snapshot.marker_group, std::option::Option::None); assert_eq!(snapshot.byte_index, 1); assert_eq!(snapshot.bit_mask, 64); assert!(snapshot.edition_taken); let mut suffixed = data.clone(); suffixed.push(0); assert_eq!( crate::decoder_metadata_metaplex_token_metadata_decode_edition_marker_account( &account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &mint, 9, &suffixed ), std::result::Result::Err( crate::DcMetadataMtmMetadataAccountDecodeError::InvalidDataLength ), ); assert_eq!( crate::decoder_metadata_metaplex_token_metadata_decode_edition_marker_account( &solana_pubkey::Pubkey::new_unique().to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &mint, 9, &data ), std::result::Result::Err( crate::DcMetadataMtmMetadataAccountDecodeError::InvalidEditionMarkerPda ), ); } #[test] fn token_record_decodes_programmable_state_delegate_and_exact_pda() { let mint = solana_pubkey::Pubkey::new_unique(); let token = solana_pubkey::Pubkey::new_unique(); let program = match ::from_str( ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("program parse failed: {error}"), }; let (account, bump) = solana_pubkey::Pubkey::find_program_address( &[b"metadata", program.as_ref(), mint.as_ref(), b"token_record", token.as_ref()], &program, ); let value = mpl_token_metadata::accounts::TokenRecord { key: mpl_token_metadata::types::Key::TokenRecord, bump, state: mpl_token_metadata::types::TokenState::Locked, rule_set_revision: std::option::Option::Some(u64::MAX), delegate: std::option::Option::Some(mpl_token_metadata::ID), delegate_role: std::option::Option::Some( mpl_token_metadata::types::TokenDelegateRole::Transfer, ), locked_transfer: std::option::Option::Some(mpl_token_metadata::ID), }; let mut data = match borsh_0_10::to_vec(&value) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("token record serialization failed: {error}"), }; data.resize(mpl_token_metadata::accounts::TokenRecord::LEN, 0); let snapshot = match crate::decoder_metadata_metaplex_token_metadata_decode_token_record_account( &account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &mint.to_string(), &token.to_string(), &data, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("token record decode failed: {error}"), }; assert_eq!(snapshot.bump, bump); assert_eq!(snapshot.stored_bump, bump); assert_eq!(snapshot.state, "Locked"); assert!(snapshot.locked); assert_eq!(snapshot.rule_set_revision, std::option::Option::Some(u64::MAX)); assert_eq!(snapshot.delegate_role, std::option::Option::Some("Transfer".to_string())); assert_eq!( snapshot.delegate, std::option::Option::Some(mpl_token_metadata::ID.to_string()) ); assert_eq!( snapshot.locked_transfer, std::option::Option::Some(mpl_token_metadata::ID.to_string()) ); assert_eq!( snapshot.payload_json["delegate"], serde_json::json!(mpl_token_metadata::ID.to_string()) ); assert_eq!(snapshot.payload_json["delegate_role"], serde_json::json!("Transfer")); assert_eq!( snapshot.payload_json["locked_transfer"], serde_json::json!(mpl_token_metadata::ID.to_string()) ); } #[test] fn token_record_rejects_foreign_owner_wrong_pda_bump_key_and_length() { let mint = solana_pubkey::Pubkey::new_unique(); let token = solana_pubkey::Pubkey::new_unique(); let program = match ::from_str( ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("program parse failed: {error}"), }; let (account, bump) = solana_pubkey::Pubkey::find_program_address( &[b"metadata", program.as_ref(), mint.as_ref(), b"token_record", token.as_ref()], &program, ); let make_data = |key, stored_bump| { let value = mpl_token_metadata::accounts::TokenRecord { key, bump: stored_bump, state: mpl_token_metadata::types::TokenState::Unlocked, rule_set_revision: std::option::Option::None, delegate: std::option::Option::None, delegate_role: std::option::Option::None, locked_transfer: std::option::Option::None, }; let mut bytes = match borsh_0_10::to_vec(&value) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { panic!("token record serialization failed: {error}") }, }; bytes.resize(mpl_token_metadata::accounts::TokenRecord::LEN, 0); return bytes; }; let data = make_data(mpl_token_metadata::types::Key::TokenRecord, bump); assert_eq!( crate::decoder_metadata_metaplex_token_metadata_decode_token_record_account( &account.to_string(), &solana_pubkey::Pubkey::new_unique().to_string(), &mint.to_string(), &token.to_string(), &data ), std::result::Result::Err(crate::DcMetadataMtmMetadataAccountDecodeError::ForeignOwner), ); assert_eq!( crate::decoder_metadata_metaplex_token_metadata_decode_token_record_account( &solana_pubkey::Pubkey::new_unique().to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &mint.to_string(), &token.to_string(), &data ), std::result::Result::Err( crate::DcMetadataMtmMetadataAccountDecodeError::InvalidTokenRecordPda ), ); let wrong_bump = make_data(mpl_token_metadata::types::Key::TokenRecord, bump.wrapping_add(1)); assert_eq!( crate::decoder_metadata_metaplex_token_metadata_decode_token_record_account( &account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &mint.to_string(), &token.to_string(), &wrong_bump ), std::result::Result::Err( crate::DcMetadataMtmMetadataAccountDecodeError::InvalidTokenRecordPda ), ); let wrong_key = make_data(mpl_token_metadata::types::Key::MetadataV1, bump); assert_eq!( crate::decoder_metadata_metaplex_token_metadata_decode_token_record_account( &account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &mint.to_string(), &token.to_string(), &wrong_key ), std::result::Result::Err( crate::DcMetadataMtmMetadataAccountDecodeError::InvalidTokenRecordKey ), ); assert_eq!( crate::decoder_metadata_metaplex_token_metadata_decode_token_record_account( &account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &mint.to_string(), &token.to_string(), &data[..data.len() - 1] ), std::result::Result::Err( crate::DcMetadataMtmMetadataAccountDecodeError::InvalidDataLength ), ); } #[test] fn delegate_records_decode_exact_role_seeds_and_pdas() { let program = match ::from_str( ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("program parse failed: {error}"), }; let mint = mpl_token_metadata::ID; let authority = mpl_token_metadata::ID; let delegate = mpl_token_metadata::ID; let role = mpl_token_metadata::types::MetadataDelegateRole::ProgrammableConfigItem; let role_seed = role.to_string(); let (account, bump) = solana_pubkey::Pubkey::find_program_address( &[ b"metadata", program.as_ref(), program.as_ref(), role_seed.as_bytes(), program.as_ref(), program.as_ref(), ], &program, ); let record = mpl_token_metadata::accounts::MetadataDelegateRecord { key: mpl_token_metadata::types::Key::MetadataDelegate, bump, mint, delegate, update_authority: authority, }; let data = match borsh_0_10::to_vec(&record) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { panic!("metadata delegate serialization failed: {error}") }, }; assert_eq!(data.len(), mpl_token_metadata::accounts::MetadataDelegateRecord::LEN); let snapshot = match crate::decoder_metadata_metaplex_token_metadata_decode_metadata_delegate_record_account( &account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, role, &data, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("metadata delegate decode failed: {error}"), }; assert_eq!(snapshot.role, "prog_config_item_delegate"); assert_eq!(snapshot.stored_bump, bump); let holder_role = mpl_token_metadata::types::HolderDelegateRole::PrintDelegate; let holder_seed = holder_role.to_string(); let (holder_account, holder_bump) = solana_pubkey::Pubkey::find_program_address( &[ b"metadata", program.as_ref(), program.as_ref(), holder_seed.as_bytes(), program.as_ref(), program.as_ref(), ], &program, ); let holder_record = mpl_token_metadata::accounts::HolderDelegateRecord { key: mpl_token_metadata::types::Key::HolderDelegate, bump: holder_bump, mint, delegate, update_authority: authority, }; let holder_data = match borsh_0_10::to_vec(&holder_record) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { panic!("holder delegate serialization failed: {error}") }, }; let holder_snapshot = match crate::decoder_metadata_metaplex_token_metadata_decode_holder_delegate_record_account( &holder_account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &authority.to_string(), holder_role, &holder_data, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("holder delegate decode failed: {error}"), }; assert_eq!(holder_snapshot.role, "print_delegate"); assert_eq!(holder_snapshot.stored_bump, holder_bump); } #[test] fn authority_records_decode_exact_values_and_reject_invalid_boundaries() { let program = match ::from_str( ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("program parse failed: {error}"), }; let mint = mpl_token_metadata::ID; let authority = mpl_token_metadata::ID; let (collection_account, collection_bump) = solana_pubkey::Pubkey::find_program_address( &[ b"metadata", program.as_ref(), program.as_ref(), b"collection_authority", program.as_ref(), ], &program, ); let collection = mpl_token_metadata::accounts::CollectionAuthorityRecord { key: mpl_token_metadata::types::Key::CollectionAuthorityRecord, bump: collection_bump, update_authority: std::option::Option::Some(mpl_token_metadata::ID), }; let collection_data = match borsh_0_10::to_vec(&collection) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { panic!("collection authority serialization failed: {error}") }, }; let collection_snapshot = match crate::decoder_metadata_metaplex_token_metadata_decode_collection_authority_record_account( &collection_account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &mint.to_string(), &authority.to_string(), &collection_data, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { panic!("collection authority decode failed: {error}") }, }; assert_eq!( collection_snapshot.update_authority, std::option::Option::Some(mpl_token_metadata::ID.to_string()) ); let (use_account, use_bump) = solana_pubkey::Pubkey::find_program_address( &[b"metadata", program.as_ref(), program.as_ref(), b"user", program.as_ref()], &program, ); let use_record = mpl_token_metadata::accounts::UseAuthorityRecord { key: mpl_token_metadata::types::Key::UseAuthorityRecord, allowed_uses: u64::MAX, bump: use_bump, }; let use_data = match borsh_0_10::to_vec(&use_record) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { panic!("use authority serialization failed: {error}") }, }; let use_snapshot = match crate::decoder_metadata_metaplex_token_metadata_decode_use_authority_record_account( &use_account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &mint.to_string(), &authority.to_string(), &use_data, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("use authority decode failed: {error}"), }; assert_eq!(use_snapshot.allowed_uses, std::option::Option::Some(u64::MAX)); assert_eq!( crate::decoder_metadata_metaplex_token_metadata_decode_use_authority_record_account( &solana_pubkey::Pubkey::new_unique().to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &mint.to_string(), &authority.to_string(), &use_data ), std::result::Result::Err( crate::DcMetadataMtmMetadataAccountDecodeError::InvalidAuthorityRecordPda ) ); assert_eq!( crate::decoder_metadata_metaplex_token_metadata_decode_use_authority_record_account( &use_account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &mint.to_string(), &authority.to_string(), &use_data[..9] ), std::result::Result::Err( crate::DcMetadataMtmMetadataAccountDecodeError::InvalidDataLength ) ); } #[test] fn token_owned_escrow_decodes_token_owner_and_creator_authorities() { let program = match ::from_str( ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("program parse failed: {error}"), }; let base_token = mpl_token_metadata::ID; let base_key = program; let (owner_account, owner_bump) = solana_pubkey::Pubkey::find_program_address( &[b"metadata", program.as_ref(), base_key.as_ref(), &[0], b"escrow"], &program, ); let owner_value = mpl_token_metadata::accounts::TokenOwnedEscrow { key: mpl_token_metadata::types::Key::TokenOwnedEscrow, base_token, authority: mpl_token_metadata::types::EscrowAuthority::TokenOwner, bump: owner_bump, }; let owner_data = match borsh_0_10::to_vec(&owner_value) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("escrow serialization failed: {error}"), }; assert_eq!(owner_data.len(), 35); let owner_snapshot = match crate::decoder_metadata_metaplex_token_metadata_decode_token_owned_escrow_account( &owner_account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &owner_data, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("escrow decode failed: {error}"), }; assert_eq!( owner_snapshot.authority_kind, crate::DcMetadataMtmTokenOwnedEscrowAuthorityKind::TokenOwner ); assert_eq!(owner_snapshot.creator, std::option::Option::None); let creator = mpl_token_metadata::ID; let (creator_account, creator_bump) = solana_pubkey::Pubkey::find_program_address( &[ b"metadata", program.as_ref(), base_key.as_ref(), &[1], program.as_ref(), b"escrow", ], &program, ); let creator_value = mpl_token_metadata::accounts::TokenOwnedEscrow { key: mpl_token_metadata::types::Key::TokenOwnedEscrow, base_token, authority: mpl_token_metadata::types::EscrowAuthority::Creator(creator), bump: creator_bump, }; let creator_data = match borsh_0_10::to_vec(&creator_value) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { panic!("creator escrow serialization failed: {error}") }, }; assert_eq!(creator_data.len(), 67); let creator_snapshot = match crate::decoder_metadata_metaplex_token_metadata_decode_token_owned_escrow_account( &creator_account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &creator_data, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("creator escrow decode failed: {error}"), }; assert_eq!( creator_snapshot.authority_kind, crate::DcMetadataMtmTokenOwnedEscrowAuthorityKind::Creator ); assert_eq!( creator_snapshot.creator, std::option::Option::Some(mpl_token_metadata::ID.to_string()) ); } #[test] fn token_owned_escrow_rejects_wrong_pda_bump_key_and_length() { let program = match ::from_str( ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("program parse failed: {error}"), }; let (account, bump) = solana_pubkey::Pubkey::find_program_address( &[b"metadata", program.as_ref(), program.as_ref(), &[0], b"escrow"], &program, ); let make = |key, stored_bump| { let value = mpl_token_metadata::accounts::TokenOwnedEscrow { key, base_token: mpl_token_metadata::ID, authority: mpl_token_metadata::types::EscrowAuthority::TokenOwner, bump: stored_bump, }; return match borsh_0_10::to_vec(&value) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("escrow serialization failed: {error}"), }; }; let data = make(mpl_token_metadata::types::Key::TokenOwnedEscrow, bump); assert_eq!( crate::decoder_metadata_metaplex_token_metadata_decode_token_owned_escrow_account( &solana_pubkey::Pubkey::new_unique().to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &data ), std::result::Result::Err( crate::DcMetadataMtmMetadataAccountDecodeError::InvalidTokenOwnedEscrowPda ) ); let wrong_bump = make(mpl_token_metadata::types::Key::TokenOwnedEscrow, bump.wrapping_add(1)); assert_eq!( crate::decoder_metadata_metaplex_token_metadata_decode_token_owned_escrow_account( &account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &wrong_bump ), std::result::Result::Err( crate::DcMetadataMtmMetadataAccountDecodeError::InvalidTokenOwnedEscrowPda ) ); let wrong_key = make(mpl_token_metadata::types::Key::MetadataV1, bump); assert_eq!( crate::decoder_metadata_metaplex_token_metadata_decode_token_owned_escrow_account( &account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &wrong_key ), std::result::Result::Err( crate::DcMetadataMtmMetadataAccountDecodeError::InvalidTokenOwnedEscrowKey ) ); assert_eq!( crate::decoder_metadata_metaplex_token_metadata_decode_token_owned_escrow_account( &account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &data[..34] ), std::result::Result::Err( crate::DcMetadataMtmMetadataAccountDecodeError::InvalidDataLength ) ); } #[test] fn historical_reservation_lists_decode_v1_and_v2_exactly() { let program = match ::from_str( ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("program parse failed: {error}"), }; let master = solana_pubkey::Pubkey::new_unique(); let resource = solana_pubkey::Pubkey::new_unique(); let reserved = solana_pubkey::Pubkey::new_unique(); let (account, _) = solana_pubkey::Pubkey::find_program_address( &[ b"metadata", program.as_ref(), master.as_ref(), b"reservation", resource.as_ref(), ], &program, ); let mut v1 = vec![3u8]; v1.extend_from_slice(master.as_ref()); v1.push(1); v1.extend_from_slice(&9u64.to_le_bytes()); v1.extend_from_slice(&1u32.to_le_bytes()); v1.extend_from_slice(reserved.as_ref()); v1.push(2); v1.push(3); let snapshot = match crate::decoder_metadata_metaplex_token_metadata_decode_reservation_list_account( &account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &resource.to_string(), &v1, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("reservation v1 decode failed: {error}"), }; assert_eq!(snapshot.kind, crate::DcMetadataMtmReservationListAccountKind::V1); assert_eq!(snapshot.supply_snapshot, std::option::Option::Some(9)); assert_eq!(snapshot.total_reservation_spots, 3); let mut v2 = vec![5u8]; v2.extend_from_slice(master.as_ref()); v2.push(0); v2.extend_from_slice(&1u32.to_le_bytes()); v2.extend_from_slice(reserved.as_ref()); v2.extend_from_slice(&u64::MAX.to_le_bytes()); v2.extend_from_slice(&7u64.to_le_bytes()); v2.extend_from_slice(&11u64.to_le_bytes()); v2.extend_from_slice(&5u64.to_le_bytes()); let snapshot = match crate::decoder_metadata_metaplex_token_metadata_decode_reservation_list_account( &account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &resource.to_string(), &v2, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("reservation v2 decode failed: {error}"), }; assert_eq!(snapshot.kind, crate::DcMetadataMtmReservationListAccountKind::V2); assert_eq!(snapshot.reservations[0].spots_remaining, u64::MAX); assert_eq!(snapshot.total_reservation_spots, 11); assert_eq!(snapshot.current_reservation_spots, 5); } #[test] fn published_account_key_inventory_is_exact_and_exhaustive() { let variants = [ mpl_token_metadata::types::Key::Uninitialized, mpl_token_metadata::types::Key::EditionV1, mpl_token_metadata::types::Key::MasterEditionV1, mpl_token_metadata::types::Key::ReservationListV1, mpl_token_metadata::types::Key::MetadataV1, mpl_token_metadata::types::Key::ReservationListV2, mpl_token_metadata::types::Key::MasterEditionV2, mpl_token_metadata::types::Key::EditionMarker, mpl_token_metadata::types::Key::UseAuthorityRecord, mpl_token_metadata::types::Key::CollectionAuthorityRecord, mpl_token_metadata::types::Key::TokenOwnedEscrow, mpl_token_metadata::types::Key::TokenRecord, mpl_token_metadata::types::Key::MetadataDelegate, mpl_token_metadata::types::Key::EditionMarkerV2, mpl_token_metadata::types::Key::HolderDelegate, ]; assert_eq!(crate::DC_METADATA_MTM_METADATA_ACCOUNT_LAYOUT_AUDIT.len(), variants.len()); for (index, variant) in variants.iter().enumerate() { let bytes = match borsh_0_10::to_vec(variant) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("key serialization failed: {error}"), }; assert_eq!(bytes, vec![index as u8]); let entry = crate::DC_METADATA_MTM_METADATA_ACCOUNT_LAYOUT_AUDIT[index]; assert_eq!(entry.discriminant, index as u8); assert!(!entry.key_name.is_empty()); assert!(!entry.decoder.is_empty()); assert!(!entry.lifecycle.is_empty()); } } #[test] fn account_audit_matrix_covers_every_key_without_duplicate_discriminants() { let mut discriminants = std::collections::BTreeSet::new(); let mut names = std::collections::BTreeSet::new(); for entry in crate::DC_METADATA_MTM_METADATA_ACCOUNT_LAYOUT_AUDIT { assert!(discriminants.insert(entry.discriminant)); assert!(names.insert(entry.key_name)); if entry.discriminant == 0 { assert_eq!(entry.decoder, "reject_uninitialized"); } else { assert_ne!(entry.decoder, "reject_uninitialized"); } } let matrix: serde_json::Value = match serde_json::from_str(include_str!( "../../../../../test-fixtures/contract-matrices/METAPLEX_TOKEN_METADATA_MATRIX.json" )) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("matrix parse failed: {error}"), }; let inventory = match matrix.get("account_key_inventory").and_then(serde_json::Value::as_array) { std::option::Option::Some(value) => value, std::option::Option::None => panic!("account_key_inventory missing"), }; assert_eq!(inventory.len(), crate::DC_METADATA_MTM_METADATA_ACCOUNT_LAYOUT_AUDIT.len()); for (index, item) in inventory.iter().enumerate() { assert_eq!( item.get("discriminant").and_then(serde_json::Value::as_u64), std::option::Option::Some(index as u64) ); assert_eq!( item.get("key").and_then(serde_json::Value::as_str), std::option::Option::Some( crate::DC_METADATA_MTM_METADATA_ACCOUNT_LAYOUT_AUDIT[index].key_name ) ); assert_eq!( item.get("decoder").and_then(serde_json::Value::as_str), std::option::Option::Some( crate::DC_METADATA_MTM_METADATA_ACCOUNT_LAYOUT_AUDIT[index].decoder ) ); } } #[test] fn historical_reservation_lists_reject_wrong_pda_key_bounds_and_suffix() { let program = match ::from_str( ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("program parse failed: {error}"), }; let master = solana_pubkey::Pubkey::new_unique(); let resource = solana_pubkey::Pubkey::new_unique(); let (account, _) = solana_pubkey::Pubkey::find_program_address( &[ b"metadata", program.as_ref(), master.as_ref(), b"reservation", resource.as_ref(), ], &program, ); let mut data = vec![3u8]; data.extend_from_slice(master.as_ref()); data.push(0); data.extend_from_slice(&0u32.to_le_bytes()); assert_eq!( crate::decoder_metadata_metaplex_token_metadata_decode_reservation_list_account( &solana_pubkey::Pubkey::new_unique().to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &resource.to_string(), &data ), std::result::Result::Err( crate::DcMetadataMtmMetadataAccountDecodeError::InvalidReservationListPda ) ); let mut wrong_key = data.clone(); wrong_key[0] = 4; assert_eq!( crate::decoder_metadata_metaplex_token_metadata_decode_reservation_list_account( &account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &resource.to_string(), &wrong_key ), std::result::Result::Err( crate::DcMetadataMtmMetadataAccountDecodeError::InvalidReservationListKey ) ); let mut too_many = data.clone(); too_many[34..38].copy_from_slice(&201u32.to_le_bytes()); assert_eq!( crate::decoder_metadata_metaplex_token_metadata_decode_reservation_list_account( &account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &resource.to_string(), &too_many ), std::result::Result::Err( crate::DcMetadataMtmMetadataAccountDecodeError::ReservationListBoundsExceeded ) ); let mut suffix = data.clone(); suffix.push(1); assert_eq!( crate::decoder_metadata_metaplex_token_metadata_decode_reservation_list_account( &account.to_string(), ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID, &resource.to_string(), &suffix ), std::result::Result::Err( crate::DcMetadataMtmMetadataAccountDecodeError::InvalidMetadataLayout ) ); } }