// file: kb-lib/src/decoder/metadata/solana_program_metadata/account.rs // version: 2 //! Bounded account decoding for the Solana Program Metadata program. /// Error returned while decoding a Solana Program Metadata account. #[derive(Clone, Debug, Eq, PartialEq)] pub enum DcMetadataSpmAccountDecodeError { /// The account address 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. EmptyAccountData, /// The account carries the published uninitialized discriminator. UninitializedAccount, /// The account exceeds the explicit Solana account-data bound. AccountDataTooLarge { /// Observed byte length. actual_bytes: usize, /// Maximum accepted byte length. max_bytes: usize, }, /// The account is shorter than the fixed header required by its discriminator. TruncatedHeader { /// Observed byte length. actual_bytes: usize, /// Minimum required byte length. minimum_bytes: usize, }, /// A type-specific decoder received another supported account discriminator. UnexpectedAccountDiscriminator { /// Discriminator required by the decoder. expected: crate::DcMetadataSpmAccountDiscriminator, /// Discriminator found on wire. actual: crate::DcMetadataSpmAccountDiscriminator, }, /// The metadata header declares more logical data than the allocated account can hold. LogicalDataOutOfBounds { /// Logical data length declared by the header. declared_bytes: usize, /// Bytes allocated after the fixed header. available_bytes: usize, }, /// The target architecture cannot represent the published `u32` logical length as `usize`. LogicalDataLengthUnsupported { /// Logical data length declared by the header. declared_bytes: u32, }, /// A non-canonical metadata account does not retain its required authority. MissingNonCanonicalAuthority, /// The account address does not match the canonical or non-canonical PDA declared by its header. InvalidMetadataPda, /// URL data is not valid UTF-8. InvalidUrlUtf8, /// A bounded public model conversion failed. Model(crate::DcMetadataSpmModelError), } impl std::fmt::Display for DcMetadataSpmAccountDecodeError { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::InvalidAccountKey => { return formatter.write_str("program metadata account key is invalid"); }, Self::InvalidOwner => { return formatter.write_str("program metadata account owner is invalid"); }, Self::ForeignOwner => { return formatter.write_str("program metadata account is owned by another program"); }, Self::EmptyAccountData => { return formatter.write_str("program metadata account data is empty"); }, Self::UninitializedAccount => { return formatter.write_str("program metadata account is uninitialized"); }, Self::AccountDataTooLarge { actual_bytes, max_bytes } => { return write!( formatter, "program metadata account data length {actual_bytes} exceeds {max_bytes} bytes", ); }, Self::TruncatedHeader { actual_bytes, minimum_bytes } => { return write!( formatter, "program metadata account header length {actual_bytes} is below {minimum_bytes} bytes", ); }, Self::UnexpectedAccountDiscriminator { expected, actual } => { return write!( formatter, "program metadata account discriminator {actual:?} does not match {expected:?}", ); }, Self::LogicalDataOutOfBounds { declared_bytes, available_bytes } => { return write!( formatter, "program metadata logical data length {declared_bytes} exceeds {available_bytes} allocated bytes", ); }, Self::LogicalDataLengthUnsupported { declared_bytes } => { return write!( formatter, "program metadata logical data length {declared_bytes} cannot be represented on this architecture", ); }, Self::MissingNonCanonicalAuthority => { return formatter .write_str("non-canonical program metadata account has no authority"); }, Self::InvalidMetadataPda => { return formatter.write_str("program metadata account PDA is invalid"); }, Self::InvalidUrlUtf8 => { return formatter.write_str("program metadata URL data is not valid UTF-8"); }, Self::Model(error) => { return write!(formatter, "program metadata model conversion failed: {error}"); }, } } } impl std::error::Error for DcMetadataSpmAccountDecodeError {} /// Bounded projection of one Solana Program Metadata Buffer account. #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] pub struct DcMetadataSpmBufferAccountSnapshot { /// Account address supplied by the caller. pub account: String, /// Optional program retained by the zeroable Buffer header. pub program: std::option::Option, /// Optional current Buffer authority. pub authority: std::option::Option, /// Whether the Buffer was declared canonical. pub canonical: bool, /// Exact fixed-size derivation seed retained by the Buffer header. pub seed: crate::DcMetadataSpmSeed, /// Complete bytes allocated after the fixed Buffer header. pub data: std::vec::Vec, /// Number of bytes allocated after the fixed Buffer header. pub allocated_data_bytes: usize, } /// Bounded projection of one initialized Solana Program Metadata account. #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] pub struct DcMetadataSpmMetadataAccountSnapshot { /// Validated metadata PDA address. pub account: String, /// PDA bump derived from the header contract. pub bump: u8, /// Program to which the metadata belongs. pub program: String, /// Optional current metadata authority. pub authority: std::option::Option, /// Whether the metadata account accepts mutations. pub mutable: bool, /// Whether the metadata PDA is derived without a third-party authority seed. pub canonical: bool, /// Exact fixed-size metadata derivation seed. pub seed: crate::DcMetadataSpmSeed, /// Encoding declared for the logical metadata content. pub encoding: crate::DcMetadataSpmEncoding, /// Compression declared for the logical metadata content. pub compression: crate::DcMetadataSpmCompression, /// Format declared for the logical metadata content. pub format: crate::DcMetadataSpmFormat, /// Source declared for the logical metadata content. pub data_source: crate::DcMetadataSpmDataSource, /// Logical data byte length declared by the account header. pub data_length: u32, /// Complete typed on-chain metadata content without off-chain resolution. pub data: crate::DcMetadataSpmData, /// Number of bytes allocated after the fixed Metadata header. pub allocated_data_bytes: usize, /// Allocated capacity remaining after the declared logical content. pub trailing_capacity_bytes: usize, } /// Supported bounded Solana Program Metadata account projection. #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] #[serde(tag = "account_kind", content = "snapshot", rename_all = "snake_case")] pub enum DcMetadataSpmAccountSnapshot { /// Writable staging Buffer account. Buffer(crate::DcMetadataSpmBufferAccountSnapshot), /// Initialized Metadata account. Metadata(crate::DcMetadataSpmMetadataAccountSnapshot), } /// Decodes one supported Solana Program Metadata account by its exact discriminator. pub fn decoder_metadata_solana_program_metadata_decode_account( account: &str, owner: &str, data: &[u8], ) -> std::result::Result { match decoder_metadata_solana_program_metadata_validate_account_context(account, owner, data) { std::result::Result::Ok(_) => {}, std::result::Result::Err(error) => return std::result::Result::Err(error), } let discriminator = match crate::DcMetadataSpmAccountDiscriminator::from_wire_value(data[0]) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::Model(error)); }, }; match discriminator { crate::DcMetadataSpmAccountDiscriminator::Buffer => { let snapshot = match crate::decoder_metadata_solana_program_metadata_decode_buffer_account( account, owner, data, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; return std::result::Result::Ok(crate::DcMetadataSpmAccountSnapshot::Buffer(snapshot)); }, crate::DcMetadataSpmAccountDiscriminator::Metadata => { let snapshot = match crate::decoder_metadata_solana_program_metadata_decode_metadata_account( account, owner, data, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; return std::result::Result::Ok(crate::DcMetadataSpmAccountSnapshot::Metadata( snapshot, )); }, crate::DcMetadataSpmAccountDiscriminator::Empty => { return std::result::Result::Err( crate::DcMetadataSpmAccountDecodeError::UninitializedAccount, ); }, } } /// Decodes one Solana Program Metadata Buffer account without claiming a stable PDA after authority changes. pub fn decoder_metadata_solana_program_metadata_decode_buffer_account( account: &str, owner: &str, data: &[u8], ) -> std::result::Result< crate::DcMetadataSpmBufferAccountSnapshot, crate::DcMetadataSpmAccountDecodeError, > { let (account_key, _) = match decoder_metadata_solana_program_metadata_validate_account_context( account, owner, data, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; if data.len() < crate::DC_METADATA_SPM_BUFFER_HEADER_BYTES { return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::TruncatedHeader { actual_bytes: data.len(), minimum_bytes: crate::DC_METADATA_SPM_BUFFER_HEADER_BYTES, }); } let discriminator = match crate::DcMetadataSpmAccountDiscriminator::from_wire_value(data[0]) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::Model(error)); }, }; if discriminator != crate::DcMetadataSpmAccountDiscriminator::Buffer { return std::result::Result::Err( crate::DcMetadataSpmAccountDecodeError::UnexpectedAccountDiscriminator { expected: crate::DcMetadataSpmAccountDiscriminator::Buffer, actual: discriminator, }, ); } let program = decoder_metadata_solana_program_metadata_parse_zeroable_pubkey(&data[1..33]); let authority = decoder_metadata_solana_program_metadata_parse_zeroable_pubkey(&data[33..65]); let seed = match crate::DcMetadataSpmSeed::from_wire_bytes(&data[66..82]) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::Model(error)); }, }; let program_string = match program { std::option::Option::Some(value) => std::option::Option::Some(value.to_string()), std::option::Option::None => std::option::Option::None, }; let authority_string = match authority { std::option::Option::Some(value) => std::option::Option::Some(value.to_string()), std::option::Option::None => std::option::Option::None, }; let buffer_data = data[crate::DC_METADATA_SPM_BUFFER_HEADER_BYTES..].to_vec(); return std::result::Result::Ok(crate::DcMetadataSpmBufferAccountSnapshot { account: account_key.to_string(), program: program_string, authority: authority_string, canonical: data[65] != 0, seed, allocated_data_bytes: buffer_data.len(), data: buffer_data, }); } /// Decodes one Solana Program Metadata account with exact owner, PDA and logical-data validation. pub fn decoder_metadata_solana_program_metadata_decode_metadata_account( account: &str, owner: &str, data: &[u8], ) -> std::result::Result< crate::DcMetadataSpmMetadataAccountSnapshot, crate::DcMetadataSpmAccountDecodeError, > { let (account_key, metadata_program) = match decoder_metadata_solana_program_metadata_validate_account_context( account, owner, data, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; if data.len() < crate::DC_METADATA_SPM_METADATA_HEADER_BYTES { return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::TruncatedHeader { actual_bytes: data.len(), minimum_bytes: crate::DC_METADATA_SPM_METADATA_HEADER_BYTES, }); } let discriminator = match crate::DcMetadataSpmAccountDiscriminator::from_wire_value(data[0]) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::Model(error)); }, }; if discriminator != crate::DcMetadataSpmAccountDiscriminator::Metadata { return std::result::Result::Err( crate::DcMetadataSpmAccountDecodeError::UnexpectedAccountDiscriminator { expected: crate::DcMetadataSpmAccountDiscriminator::Metadata, actual: discriminator, }, ); } let program = decoder_metadata_solana_program_metadata_parse_required_pubkey(&data[1..33]); let authority = decoder_metadata_solana_program_metadata_parse_zeroable_pubkey(&data[33..65]); let mutable = data[65] != 0; let canonical = data[66] != 0; let seed = match crate::DcMetadataSpmSeed::from_wire_bytes(&data[67..83]) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::Model(error)); }, }; let encoding = match crate::DcMetadataSpmEncoding::from_wire_value(data[83]) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::Model(error)); }, }; let compression = match crate::DcMetadataSpmCompression::from_wire_value(data[84]) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::Model(error)); }, }; let format = match crate::DcMetadataSpmFormat::from_wire_value(data[85]) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::Model(error)); }, }; let data_source = match crate::DcMetadataSpmDataSource::from_wire_value(data[86]) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::Model(error)); }, }; let mut data_length_wire = [0_u8; 4]; data_length_wire.copy_from_slice(&data[87..91]); let data_length = u32::from_le_bytes(data_length_wire); let declared_bytes = match usize::try_from(data_length) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( crate::DcMetadataSpmAccountDecodeError::LogicalDataLengthUnsupported { declared_bytes: data_length, }, ); }, }; let allocated_data_bytes = data.len() - crate::DC_METADATA_SPM_METADATA_HEADER_BYTES; if declared_bytes > allocated_data_bytes { return std::result::Result::Err( crate::DcMetadataSpmAccountDecodeError::LogicalDataOutOfBounds { declared_bytes, available_bytes: allocated_data_bytes, }, ); } let logical_data = &data[crate::DC_METADATA_SPM_METADATA_HEADER_BYTES ..crate::DC_METADATA_SPM_METADATA_HEADER_BYTES + declared_bytes]; let typed_data = match decoder_metadata_solana_program_metadata_decode_typed_data(data_source, logical_data) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let bump = match decoder_metadata_solana_program_metadata_validate_metadata_pda( &account_key, &metadata_program, &program, authority.as_ref(), canonical, &seed, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let authority_string = match authority { std::option::Option::Some(value) => std::option::Option::Some(value.to_string()), std::option::Option::None => std::option::Option::None, }; return std::result::Result::Ok(crate::DcMetadataSpmMetadataAccountSnapshot { account: account_key.to_string(), bump, program: program.to_string(), authority: authority_string, mutable, canonical, seed, encoding, compression, format, data_source, data_length, data: typed_data, allocated_data_bytes, trailing_capacity_bytes: allocated_data_bytes - declared_bytes, }); } fn decoder_metadata_solana_program_metadata_validate_account_context( account: &str, owner: &str, data: &[u8], ) -> std::result::Result< (solana_pubkey::Pubkey, solana_pubkey::Pubkey), crate::DcMetadataSpmAccountDecodeError, > { let account_key = match ::from_str(account) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( crate::DcMetadataSpmAccountDecodeError::InvalidAccountKey, ); }, }; let owner_key = match ::from_str(owner) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::InvalidOwner); }, }; let expected_owner = match ::from_str( kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::InvalidOwner); }, }; if owner_key != expected_owner { return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::ForeignOwner); } if data.is_empty() { return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::EmptyAccountData); } if data.len() > crate::DC_METADATA_SPM_MAX_ACCOUNT_BYTES { return std::result::Result::Err( crate::DcMetadataSpmAccountDecodeError::AccountDataTooLarge { actual_bytes: data.len(), max_bytes: crate::DC_METADATA_SPM_MAX_ACCOUNT_BYTES, }, ); } return std::result::Result::Ok((account_key, expected_owner)); } fn decoder_metadata_solana_program_metadata_parse_zeroable_pubkey( data: &[u8], ) -> std::option::Option { let mut bytes = [0_u8; 32]; bytes.copy_from_slice(data); if bytes == [0_u8; 32] { return std::option::Option::None; } return std::option::Option::Some(solana_pubkey::Pubkey::new_from_array(bytes)); } fn decoder_metadata_solana_program_metadata_parse_required_pubkey( data: &[u8], ) -> solana_pubkey::Pubkey { let mut bytes = [0_u8; 32]; bytes.copy_from_slice(data); return solana_pubkey::Pubkey::new_from_array(bytes); } fn decoder_metadata_solana_program_metadata_decode_typed_data( source: crate::DcMetadataSpmDataSource, data: &[u8], ) -> std::result::Result { let model = match source { crate::DcMetadataSpmDataSource::Direct => crate::DcMetadataSpmData::Direct(data.to_vec()), crate::DcMetadataSpmDataSource::Url => { let url = match std::str::from_utf8(data) { std::result::Result::Ok(value) => value.to_string(), std::result::Result::Err(_) => { return std::result::Result::Err( crate::DcMetadataSpmAccountDecodeError::InvalidUrlUtf8, ); }, }; crate::DcMetadataSpmData::Url(url) }, crate::DcMetadataSpmDataSource::External => { let external = match crate::DcMetadataSpmExternalData::from_wire_bytes(data) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { return std::result::Result::Err( crate::DcMetadataSpmAccountDecodeError::Model(error), ); }, }; crate::DcMetadataSpmData::External(external) }, }; match model.validate() { std::result::Result::Ok(()) => return std::result::Result::Ok(model), std::result::Result::Err(error) => { return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::Model(error)); }, } } fn decoder_metadata_solana_program_metadata_validate_metadata_pda( account: &solana_pubkey::Pubkey, metadata_program: &solana_pubkey::Pubkey, program: &solana_pubkey::Pubkey, authority: std::option::Option<&solana_pubkey::Pubkey>, canonical: bool, seed: &crate::DcMetadataSpmSeed, ) -> std::result::Result { let (expected, bump) = if canonical { solana_pubkey::Pubkey::find_program_address( &[program.as_ref(), seed.as_bytes()], metadata_program, ) } else { let authority_key = match authority { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err( crate::DcMetadataSpmAccountDecodeError::MissingNonCanonicalAuthority, ); }, }; solana_pubkey::Pubkey::find_program_address( &[program.as_ref(), authority_key.as_ref(), seed.as_bytes()], metadata_program, ) }; if account != &expected { return std::result::Result::Err( crate::DcMetadataSpmAccountDecodeError::InvalidMetadataPda, ); } return std::result::Result::Ok(bump); } #[cfg(test)] mod tests { fn metadata_program_id() -> std::option::Option { match ::from_str( kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID, ) { std::result::Result::Ok(value) => return std::option::Option::Some(value), std::result::Result::Err(_) => return std::option::Option::None, } } #[derive(Clone, Copy)] struct MetadataWireSettings { mutable: u8, canonical: u8, encoding: crate::DcMetadataSpmEncoding, compression: crate::DcMetadataSpmCompression, format: crate::DcMetadataSpmFormat, source: crate::DcMetadataSpmDataSource, } fn metadata_wire( program: solana_pubkey::Pubkey, authority: std::option::Option, seed: crate::DcMetadataSpmSeed, settings: MetadataWireSettings, logical_data: &[u8], trailing_capacity: usize, ) -> std::vec::Vec { let mut data = vec![0_u8; crate::DC_METADATA_SPM_METADATA_HEADER_BYTES]; data[0] = crate::DcMetadataSpmAccountDiscriminator::Metadata.wire_value(); data[1..33].copy_from_slice(&program.to_bytes()); if let std::option::Option::Some(value) = authority { data[33..65].copy_from_slice(&value.to_bytes()); } data[65] = settings.mutable; data[66] = settings.canonical; data[67..83].copy_from_slice(seed.as_bytes()); data[83] = settings.encoding.wire_value(); data[84] = settings.compression.wire_value(); data[85] = settings.format.wire_value(); data[86] = settings.source.wire_value(); let logical_length = match u32::try_from(logical_data.len()) { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => return data, }; data[87..91].copy_from_slice(&logical_length.to_le_bytes()); data[91..96].fill(0xf3); data.extend_from_slice(logical_data); data.resize(data.len() + trailing_capacity, 0xa5); return data; } fn metadata_pda( program: &solana_pubkey::Pubkey, authority: std::option::Option<&solana_pubkey::Pubkey>, canonical: bool, seed: &crate::DcMetadataSpmSeed, metadata_program: &solana_pubkey::Pubkey, ) -> (solana_pubkey::Pubkey, u8) { if canonical { return solana_pubkey::Pubkey::find_program_address( &[program.as_ref(), seed.as_bytes()], metadata_program, ); } if let std::option::Option::Some(value) = authority { return solana_pubkey::Pubkey::find_program_address( &[program.as_ref(), value.as_ref(), seed.as_bytes()], metadata_program, ); } return solana_pubkey::Pubkey::find_program_address( &[program.as_ref(), seed.as_bytes()], metadata_program, ); } #[test] fn account_matrix_is_machine_readable_and_matches_compiled_bounds() { let source = include_str!( "../../../../../test-fixtures/contract-matrices/SOLANA_PROGRAM_METADATA_ACCOUNT_MATRIX.json" ); let matrix_result = serde_json::from_str::(source); assert!(matrix_result.is_ok()); if let std::result::Result::Ok(matrix) = matrix_result { assert_eq!( matrix["program_id"], serde_json::Value::String( kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID.to_string(), ), ); assert_eq!( matrix["official_contract"]["account_data_max_bytes"], serde_json::Value::from(crate::DC_METADATA_SPM_MAX_ACCOUNT_BYTES), ); assert_eq!( matrix["accounts"][1]["header_bytes"], serde_json::Value::from(crate::DC_METADATA_SPM_BUFFER_HEADER_BYTES), ); assert_eq!( matrix["accounts"][2]["header_bytes"], serde_json::Value::from(crate::DC_METADATA_SPM_METADATA_HEADER_BYTES), ); assert_eq!( matrix["data_sources"][2]["length"], serde_json::Value::String("exactly_40_bytes".to_string()), ); } } #[test] fn buffer_decodes_zeroable_fields_nonzero_bool_and_all_allocated_bytes() { let account = solana_pubkey::Pubkey::new_from_array([3_u8; 32]); let program = solana_pubkey::Pubkey::new_from_array([4_u8; 32]); let authority = solana_pubkey::Pubkey::new_from_array([5_u8; 32]); let seed = crate::DcMetadataSpmSeed::from_bytes([6_u8; 16]); let mut data = vec![0_u8; crate::DC_METADATA_SPM_BUFFER_HEADER_BYTES]; data[0] = crate::DcMetadataSpmAccountDiscriminator::Buffer.wire_value(); data[1..33].copy_from_slice(&program.to_bytes()); data[33..65].copy_from_slice(&authority.to_bytes()); data[65] = 7; data[66..82].copy_from_slice(seed.as_bytes()); data[82..96].fill(0xff); data.extend_from_slice(&[8_u8, 9_u8]); let result = crate::decoder_metadata_solana_program_metadata_decode_buffer_account( account.to_string().as_str(), kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID, &data, ); assert!(result.is_ok()); if let std::result::Result::Ok(snapshot) = result { assert_eq!(snapshot.account, account.to_string()); assert_eq!(snapshot.program, std::option::Option::Some(program.to_string())); assert_eq!(snapshot.authority, std::option::Option::Some(authority.to_string())); assert!(snapshot.canonical); assert_eq!(snapshot.seed, seed); assert_eq!(snapshot.data, vec![8_u8, 9_u8]); assert_eq!(snapshot.allocated_data_bytes, 2); } let mut zeroable = data; zeroable[1..65].fill(0); let zeroable_result = crate::decoder_metadata_solana_program_metadata_decode_buffer_account( account.to_string().as_str(), kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID, &zeroable, ); assert!(zeroable_result.is_ok()); if let std::result::Result::Ok(snapshot) = zeroable_result { assert_eq!(snapshot.program, std::option::Option::None); assert_eq!(snapshot.authority, std::option::Option::None); } } #[test] fn canonical_direct_metadata_validates_pda_and_preserves_trailing_capacity() { let metadata_program_option: std::option::Option = metadata_program_id(); assert!(metadata_program_option.is_some()); let metadata_program: solana_pubkey::Pubkey; if let std::option::Option::Some(value) = metadata_program_option { metadata_program = value; } else { return; } let program = solana_pubkey::Pubkey::new_from_array([11_u8; 32]); let authority = solana_pubkey::Pubkey::new_from_array([12_u8; 32]); let seed = crate::DcMetadataSpmSeed::from_bytes([13_u8; 16]); let (account, expected_bump) = metadata_pda( &program, std::option::Option::Some(&authority), true, &seed, &metadata_program, ); let data = metadata_wire( program, std::option::Option::Some(authority), seed, MetadataWireSettings { mutable: 9, canonical: 4, encoding: crate::DcMetadataSpmEncoding::Utf8, compression: crate::DcMetadataSpmCompression::Gzip, format: crate::DcMetadataSpmFormat::Json, source: crate::DcMetadataSpmDataSource::Direct, }, b"{}", 7, ); let result = crate::decoder_metadata_solana_program_metadata_decode_metadata_account( account.to_string().as_str(), metadata_program.to_string().as_str(), &data, ); assert!(result.is_ok()); if let std::result::Result::Ok(snapshot) = result { assert_eq!(snapshot.bump, expected_bump); assert_eq!(snapshot.program, program.to_string()); assert_eq!(snapshot.authority, std::option::Option::Some(authority.to_string())); assert!(snapshot.mutable); assert!(snapshot.canonical); assert_eq!(snapshot.data_length, 2); assert_eq!(snapshot.data, crate::DcMetadataSpmData::Direct(b"{}".to_vec())); assert_eq!(snapshot.allocated_data_bytes, 9); assert_eq!(snapshot.trailing_capacity_bytes, 7); } } #[test] fn noncanonical_url_and_external_metadata_decode_exact_sources() { let metadata_program_option: std::option::Option = metadata_program_id(); assert!(metadata_program_option.is_some()); let metadata_program: solana_pubkey::Pubkey; if let std::option::Option::Some(value) = metadata_program_option { metadata_program = value; } else { return; } let program = solana_pubkey::Pubkey::new_from_array([21_u8; 32]); let authority = solana_pubkey::Pubkey::new_from_array([22_u8; 32]); let seed = crate::DcMetadataSpmSeed::from_bytes([23_u8; 16]); let (url_account, _) = metadata_pda( &program, std::option::Option::Some(&authority), false, &seed, &metadata_program, ); let url_data = metadata_wire( program, std::option::Option::Some(authority), seed, MetadataWireSettings { mutable: 1, canonical: 0, encoding: crate::DcMetadataSpmEncoding::Utf8, compression: crate::DcMetadataSpmCompression::None, format: crate::DcMetadataSpmFormat::Json, source: crate::DcMetadataSpmDataSource::Url, }, b"https://example.invalid/idl.json", 0, ); let url_result = crate::decoder_metadata_solana_program_metadata_decode_account( url_account.to_string().as_str(), metadata_program.to_string().as_str(), &url_data, ); assert!(url_result.is_ok()); if let std::result::Result::Ok(crate::DcMetadataSpmAccountSnapshot::Metadata(snapshot)) = url_result { assert_eq!( snapshot.data, crate::DcMetadataSpmData::Url("https://example.invalid/idl.json".to_string()), ); assert!(!snapshot.canonical); } else { return; } let external = crate::DcMetadataSpmExternalData::from_zeroable_length( solana_pubkey::Pubkey::new_from_array([24_u8; 32]), 128, 4096, ); let external_wire = external.to_wire_bytes(); let external_data = metadata_wire( program, std::option::Option::Some(authority), seed, MetadataWireSettings { mutable: 1, canonical: 0, encoding: crate::DcMetadataSpmEncoding::None, compression: crate::DcMetadataSpmCompression::None, format: crate::DcMetadataSpmFormat::None, source: crate::DcMetadataSpmDataSource::External, }, &external_wire, 3, ); let external_result = crate::decoder_metadata_solana_program_metadata_decode_metadata_account( url_account.to_string().as_str(), metadata_program.to_string().as_str(), &external_data, ); assert!(external_result.is_ok()); if let std::result::Result::Ok(snapshot) = external_result { assert_eq!(snapshot.data, crate::DcMetadataSpmData::External(external)); assert_eq!(snapshot.trailing_capacity_bytes, 3); } } #[test] fn malformed_owner_discriminator_headers_and_logical_lengths_fail_closed() { let account = solana_pubkey::Pubkey::new_from_array([31_u8; 32]); assert_eq!( crate::decoder_metadata_solana_program_metadata_decode_account( account.to_string().as_str(), kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID, &[], ), std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::EmptyAccountData), ); let oversized = vec![1_u8; crate::DC_METADATA_SPM_MAX_ACCOUNT_BYTES + 1]; assert_eq!( crate::decoder_metadata_solana_program_metadata_decode_account( account.to_string().as_str(), kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID, &oversized, ), std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::AccountDataTooLarge { actual_bytes: crate::DC_METADATA_SPM_MAX_ACCOUNT_BYTES + 1, max_bytes: crate::DC_METADATA_SPM_MAX_ACCOUNT_BYTES, }), ); assert_eq!( crate::decoder_metadata_solana_program_metadata_decode_account( "invalid", kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID, &[1_u8], ), std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::InvalidAccountKey), ); assert_eq!( crate::decoder_metadata_solana_program_metadata_decode_account( account.to_string().as_str(), solana_pubkey::Pubkey::new_from_array([32_u8; 32]).to_string().as_str(), &[1_u8], ), std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::ForeignOwner), ); assert_eq!( crate::decoder_metadata_solana_program_metadata_decode_account( account.to_string().as_str(), kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID, &[0_u8], ), std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::UninitializedAccount), ); assert_eq!( crate::decoder_metadata_solana_program_metadata_decode_account( account.to_string().as_str(), kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID, &[9_u8], ), std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::Model( crate::DcMetadataSpmModelError::UnknownAccountDiscriminator { value: 9 }, )), ); assert_eq!( crate::decoder_metadata_solana_program_metadata_decode_buffer_account( account.to_string().as_str(), kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID, &[1_u8], ), std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::TruncatedHeader { actual_bytes: 1, minimum_bytes: crate::DC_METADATA_SPM_BUFFER_HEADER_BYTES, }), ); let mut metadata = vec![0_u8; crate::DC_METADATA_SPM_METADATA_HEADER_BYTES]; metadata[0] = crate::DcMetadataSpmAccountDiscriminator::Metadata.wire_value(); metadata[86] = crate::DcMetadataSpmDataSource::Direct.wire_value(); metadata[87..91].copy_from_slice(&4_u32.to_le_bytes()); assert_eq!( crate::decoder_metadata_solana_program_metadata_decode_metadata_account( account.to_string().as_str(), kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID, &metadata, ), std::result::Result::Err( crate::DcMetadataSpmAccountDecodeError::LogicalDataOutOfBounds { declared_bytes: 4, available_bytes: 0, }, ), ); } #[test] fn source_specific_bounds_and_metadata_pda_fail_closed() { let metadata_program_option: std::option::Option = metadata_program_id(); assert!(metadata_program_option.is_some()); let metadata_program: solana_pubkey::Pubkey; if let std::option::Option::Some(value) = metadata_program_option { metadata_program = value; } else { return; } let program = solana_pubkey::Pubkey::new_from_array([41_u8; 32]); let authority = solana_pubkey::Pubkey::new_from_array([42_u8; 32]); let seed = crate::DcMetadataSpmSeed::from_bytes([43_u8; 16]); let (account, _) = metadata_pda( &program, std::option::Option::Some(&authority), true, &seed, &metadata_program, ); let empty = metadata_wire( program, std::option::Option::Some(authority), seed, MetadataWireSettings { mutable: 1, canonical: 1, encoding: crate::DcMetadataSpmEncoding::None, compression: crate::DcMetadataSpmCompression::None, format: crate::DcMetadataSpmFormat::None, source: crate::DcMetadataSpmDataSource::Direct, }, &[], 0, ); assert_eq!( crate::decoder_metadata_solana_program_metadata_decode_metadata_account( account.to_string().as_str(), metadata_program.to_string().as_str(), &empty, ), std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::Model( crate::DcMetadataSpmModelError::EmptyData { source: crate::DcMetadataSpmDataSource::Direct, }, )), ); let invalid_url = metadata_wire( program, std::option::Option::Some(authority), seed, MetadataWireSettings { mutable: 1, canonical: 1, encoding: crate::DcMetadataSpmEncoding::Utf8, compression: crate::DcMetadataSpmCompression::None, format: crate::DcMetadataSpmFormat::None, source: crate::DcMetadataSpmDataSource::Url, }, &[0xff], 0, ); assert_eq!( crate::decoder_metadata_solana_program_metadata_decode_metadata_account( account.to_string().as_str(), metadata_program.to_string().as_str(), &invalid_url, ), std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::InvalidUrlUtf8), ); let invalid_external = metadata_wire( program, std::option::Option::Some(authority), seed, MetadataWireSettings { mutable: 1, canonical: 1, encoding: crate::DcMetadataSpmEncoding::None, compression: crate::DcMetadataSpmCompression::None, format: crate::DcMetadataSpmFormat::None, source: crate::DcMetadataSpmDataSource::External, }, &[0_u8; 39], 0, ); assert_eq!( crate::decoder_metadata_solana_program_metadata_decode_metadata_account( account.to_string().as_str(), metadata_program.to_string().as_str(), &invalid_external, ), std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::Model( crate::DcMetadataSpmModelError::InvalidExternalDataLength { actual_bytes: 39, expected_bytes: crate::DC_METADATA_SPM_EXTERNAL_DATA_BYTES, }, )), ); let wrong_account = solana_pubkey::Pubkey::new_from_array([44_u8; 32]); assert_eq!( crate::decoder_metadata_solana_program_metadata_decode_metadata_account( wrong_account.to_string().as_str(), metadata_program.to_string().as_str(), &invalid_external, ), std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::Model( crate::DcMetadataSpmModelError::InvalidExternalDataLength { actual_bytes: 39, expected_bytes: crate::DC_METADATA_SPM_EXTERNAL_DATA_BYTES, }, )), ); let valid = metadata_wire( program, std::option::Option::Some(authority), seed, MetadataWireSettings { mutable: 1, canonical: 1, encoding: crate::DcMetadataSpmEncoding::None, compression: crate::DcMetadataSpmCompression::None, format: crate::DcMetadataSpmFormat::None, source: crate::DcMetadataSpmDataSource::Direct, }, &[1_u8], 0, ); assert_eq!( crate::decoder_metadata_solana_program_metadata_decode_metadata_account( wrong_account.to_string().as_str(), metadata_program.to_string().as_str(), &valid, ), std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::InvalidMetadataPda), ); let missing_authority = metadata_wire( program, std::option::Option::None, seed, MetadataWireSettings { mutable: 1, canonical: 0, encoding: crate::DcMetadataSpmEncoding::None, compression: crate::DcMetadataSpmCompression::None, format: crate::DcMetadataSpmFormat::None, source: crate::DcMetadataSpmDataSource::Direct, }, &[1_u8], 0, ); assert_eq!( crate::decoder_metadata_solana_program_metadata_decode_metadata_account( wrong_account.to_string().as_str(), metadata_program.to_string().as_str(), &missing_authority, ), std::result::Result::Err( crate::DcMetadataSpmAccountDecodeError::MissingNonCanonicalAuthority, ), ); } }