Files
khadhroony-bot3/ks-lib/src/decoder/metadata/metaplex_token_metadata/account.rs
2026-08-09 19:34:08 +02:00

3158 lines
129 KiB
Rust

// 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 <solana_pubkey::Pubkey as std::str::FromStr>::from_str(account) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(
DcMetadataMtmMetadataAccountDecodeError::InvalidAccountKey,
);
},
};
let owner_key = match <solana_pubkey::Pubkey as std::str::FromStr>::from_str(owner) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(DcMetadataMtmMetadataAccountDecodeError::InvalidOwner);
},
};
let expected_owner = match <solana_pubkey::Pubkey as std::str::FromStr>::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<u64>,
/// Optional maximum supply for a master edition.
pub max_supply: std::option::Option<u64>,
/// Parent master edition for a printed edition.
pub parent: std::option::Option<String>,
/// Printed edition number.
pub edition: std::option::Option<u64>,
/// Historical printing mint carried by Master Edition V1.
pub printing_mint: std::option::Option<String>,
/// Historical one-time printing authorization mint carried by Master Edition V1.
pub one_time_printing_authorization_mint: std::option::Option<String>,
/// 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<DcMetadataMtmEditionAccountSnapshot, DcMetadataMtmMetadataAccountDecodeError>
{
let account_key = match <solana_pubkey::Pubkey as std::str::FromStr>::from_str(account) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(
DcMetadataMtmMetadataAccountDecodeError::InvalidAccountKey,
);
},
};
let owner_key = match <solana_pubkey::Pubkey as std::str::FromStr>::from_str(owner) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(DcMetadataMtmMetadataAccountDecodeError::InvalidOwner);
},
};
let expected_owner = match <solana_pubkey::Pubkey as std::str::FromStr>::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<u64>,
/// 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<u8>,
/// 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 <solana_pubkey::Pubkey as std::str::FromStr>::from_str(account) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(
DcMetadataMtmMetadataAccountDecodeError::InvalidAccountKey,
);
},
};
let owner_key = match <solana_pubkey::Pubkey as std::str::FromStr>::from_str(owner) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(DcMetadataMtmMetadataAccountDecodeError::InvalidOwner);
},
};
let program_key = match <solana_pubkey::Pubkey as std::str::FromStr>::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 <solana_pubkey::Pubkey as std::str::FromStr>::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<u64>,
/// Optional active delegate.
pub delegate: std::option::Option<String>,
/// Optional active delegate role.
pub delegate_role: std::option::Option<String>,
/// Optional locked-transfer destination.
pub locked_transfer: std::option::Option<String>,
/// 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 <solana_pubkey::Pubkey as std::str::FromStr>::from_str(account) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(
DcMetadataMtmMetadataAccountDecodeError::InvalidAccountKey,
);
},
};
let owner_key = match <solana_pubkey::Pubkey as std::str::FromStr>::from_str(owner) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(DcMetadataMtmMetadataAccountDecodeError::InvalidOwner);
},
};
let expected_owner = match <solana_pubkey::Pubkey as std::str::FromStr>::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 <solana_pubkey::Pubkey as std::str::FromStr>::from_str(mint) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(
DcMetadataMtmMetadataAccountDecodeError::InvalidAccountKey,
);
},
};
let token_key = match <solana_pubkey::Pubkey as std::str::FromStr>::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 <solana_pubkey::Pubkey as std::str::FromStr>::from_str(account) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(
DcMetadataMtmMetadataAccountDecodeError::InvalidAccountKey,
);
},
};
let owner_key = match <solana_pubkey::Pubkey as std::str::FromStr>::from_str(owner) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(DcMetadataMtmMetadataAccountDecodeError::InvalidOwner);
},
};
let expected_owner = match <solana_pubkey::Pubkey as std::str::FromStr>::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 <solana_pubkey::Pubkey as std::str::FromStr>::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 <solana_pubkey::Pubkey as std::str::FromStr>::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 <solana_pubkey::Pubkey as std::str::FromStr>::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 <solana_pubkey::Pubkey as std::str::FromStr>::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 <solana_pubkey::Pubkey as std::str::FromStr>::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 <solana_pubkey::Pubkey as std::str::FromStr>::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<String>,
/// Optional remaining use allowance stored by Use Authority Record.
pub allowed_uses: std::option::Option<u64>,
/// 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 <solana_pubkey::Pubkey as std::str::FromStr>::from_str(mint) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(
DcMetadataMtmMetadataAccountDecodeError::InvalidAccountKey,
);
},
};
let authority_key = match <solana_pubkey::Pubkey as std::str::FromStr>::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 <solana_pubkey::Pubkey as std::str::FromStr>::from_str(mint) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(
DcMetadataMtmMetadataAccountDecodeError::InvalidAccountKey,
);
},
};
let authority_key = match <solana_pubkey::Pubkey as std::str::FromStr>::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<String>,
/// 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 <solana_pubkey::Pubkey as std::str::FromStr>::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 <solana_pubkey::Pubkey as std::str::FromStr>::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<u64>,
/// Bounded reservation entries.
pub reservations: std::vec::Vec<DcMetadataMtmReservationEntrySnapshot>,
/// 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<u64> {
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<solana_pubkey::Pubkey> {
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 <solana_pubkey::Pubkey as std::str::FromStr>::from_str(account) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(
DcMetadataMtmMetadataAccountDecodeError::InvalidAccountKey,
);
},
};
let owner_key = match <solana_pubkey::Pubkey as std::str::FromStr>::from_str(owner) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(DcMetadataMtmMetadataAccountDecodeError::InvalidOwner);
},
};
let resource_key = match <solana_pubkey::Pubkey as std::str::FromStr>::from_str(resource) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(
DcMetadataMtmMetadataAccountDecodeError::InvalidAccountKey,
);
},
};
let program_key = match <solana_pubkey::Pubkey as std::str::FromStr>::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::<std::vec::Vec<_>>(),
"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 <solana_pubkey::Pubkey as std::str::FromStr>::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 <solana_pubkey::Pubkey as std::str::FromStr>::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 <solana_pubkey::Pubkey as std::str::FromStr>::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 <solana_pubkey::Pubkey as std::str::FromStr>::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 <solana_pubkey::Pubkey as std::str::FromStr>::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 <solana_pubkey::Pubkey as std::str::FromStr>::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 <solana_pubkey::Pubkey as std::str::FromStr>::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 <solana_pubkey::Pubkey as std::str::FromStr>::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 <solana_pubkey::Pubkey as std::str::FromStr>::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 <solana_pubkey::Pubkey as std::str::FromStr>::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 <solana_pubkey::Pubkey as std::str::FromStr>::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 <solana_pubkey::Pubkey as std::str::FromStr>::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
)
);
}
}