Files
khadhroony-bot3/kb-lib/src/decoder/metadata/solana_program_metadata/account.rs
2026-08-05 17:46:56 +02:00

1122 lines
46 KiB
Rust

// 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<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));
},
};
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 <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(
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<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)
},
};
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<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> {
match <solana_pubkey::Pubkey as std::str::FromStr>::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<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(
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<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(),
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<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,
),
);
}
}