1113 lines
46 KiB
Rust
1113 lines
46 KiB
Rust
// file: ks-lib/src/decoder/metadata/solana_program_metadata/account.rs
|
|
// version: 4
|
|
|
|
//! 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 {
|
|
return match self {
|
|
Self::InvalidAccountKey => {
|
|
formatter.write_str("program metadata account key is invalid")
|
|
},
|
|
Self::InvalidOwner => formatter.write_str("program metadata account owner is invalid"),
|
|
Self::ForeignOwner => {
|
|
formatter.write_str("program metadata account is owned by another program")
|
|
},
|
|
Self::EmptyAccountData => formatter.write_str("program metadata account data is empty"),
|
|
Self::UninitializedAccount => {
|
|
formatter.write_str("program metadata account is uninitialized")
|
|
},
|
|
Self::AccountDataTooLarge { actual_bytes, max_bytes } => {
|
|
write!(
|
|
formatter,
|
|
"program metadata account data length {actual_bytes} exceeds {max_bytes} bytes",
|
|
)
|
|
},
|
|
Self::TruncatedHeader { actual_bytes, minimum_bytes } => {
|
|
write!(
|
|
formatter,
|
|
"program metadata account header length {actual_bytes} is below {minimum_bytes} bytes",
|
|
)
|
|
},
|
|
Self::UnexpectedAccountDiscriminator { expected, actual } => {
|
|
write!(
|
|
formatter,
|
|
"program metadata account discriminator {actual:?} does not match {expected:?}",
|
|
)
|
|
},
|
|
Self::LogicalDataOutOfBounds { declared_bytes, available_bytes } => {
|
|
write!(
|
|
formatter,
|
|
"program metadata logical data length {declared_bytes} exceeds {available_bytes} allocated bytes",
|
|
)
|
|
},
|
|
Self::LogicalDataLengthUnsupported { declared_bytes } => {
|
|
write!(
|
|
formatter,
|
|
"program metadata logical data length {declared_bytes} cannot be represented on this architecture",
|
|
)
|
|
},
|
|
Self::MissingNonCanonicalAuthority => {
|
|
formatter.write_str("non-canonical program metadata account has no authority")
|
|
},
|
|
Self::InvalidMetadataPda => {
|
|
formatter.write_str("program metadata account PDA is invalid")
|
|
},
|
|
Self::InvalidUrlUtf8 => {
|
|
formatter.write_str("program metadata URL data is not valid UTF-8")
|
|
},
|
|
Self::Model(error) => {
|
|
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<String>,
|
|
/// Optional current Buffer authority.
|
|
pub authority: std::option::Option<String>,
|
|
/// 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<u8>,
|
|
/// 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<String>,
|
|
/// 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<crate::DcMetadataSpmAccountSnapshot, crate::DcMetadataSpmAccountDecodeError>
|
|
{
|
|
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));
|
|
},
|
|
};
|
|
return 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),
|
|
};
|
|
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),
|
|
};
|
|
std::result::Result::Ok(crate::DcMetadataSpmAccountSnapshot::Metadata(snapshot))
|
|
},
|
|
crate::DcMetadataSpmAccountDiscriminator::Empty => {
|
|
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 <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(
|
|
crate::DcMetadataSpmAccountDecodeError::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(crate::DcMetadataSpmAccountDecodeError::InvalidOwner);
|
|
},
|
|
};
|
|
let expected_owner = match <solana_pubkey::Pubkey as std::str::FromStr>::from_str(
|
|
ks_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<solana_pubkey::Pubkey> {
|
|
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<crate::DcMetadataSpmData, crate::DcMetadataSpmAccountDecodeError> {
|
|
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)
|
|
},
|
|
};
|
|
return match model.validate() {
|
|
std::result::Result::Ok(()) => std::result::Result::Ok(model),
|
|
std::result::Result::Err(error) => {
|
|
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<u8, crate::DcMetadataSpmAccountDecodeError> {
|
|
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<solana_pubkey::Pubkey> {
|
|
return match <solana_pubkey::Pubkey as std::str::FromStr>::from_str(
|
|
ks_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID,
|
|
) {
|
|
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
|
std::result::Result::Err(_) => 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<solana_pubkey::Pubkey>,
|
|
seed: crate::DcMetadataSpmSeed,
|
|
settings: MetadataWireSettings,
|
|
logical_data: &[u8],
|
|
trailing_capacity: usize,
|
|
) -> std::vec::Vec<u8> {
|
|
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::<serde_json::Value>(source);
|
|
assert!(matrix_result.is_ok());
|
|
if let std::result::Result::Ok(matrix) = matrix_result {
|
|
assert_eq!(
|
|
matrix["program_id"],
|
|
serde_json::Value::String(
|
|
ks_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(),
|
|
ks_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(),
|
|
ks_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<solana_pubkey::Pubkey> =
|
|
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<solana_pubkey::Pubkey> =
|
|
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(),
|
|
ks_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(),
|
|
ks_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",
|
|
ks_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(),
|
|
ks_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(),
|
|
ks_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(),
|
|
ks_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(),
|
|
ks_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<solana_pubkey::Pubkey> =
|
|
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,
|
|
),
|
|
);
|
|
}
|
|
}
|