v0.4.8-pre.003

This commit is contained in:
2026-08-05 15:04:27 +02:00
parent 0078956d48
commit de1062b2d3
13 changed files with 1060 additions and 108 deletions

View File

@@ -1,5 +1,5 @@
// file: kb-lib/src/decoder.rs
// version: 12
// version: 13
//! Consolidated decoder modules.
@@ -332,6 +332,14 @@ pub(crate) use self::metadata::DC_METADATA_MTM_VERIFY_COLLECTION_DISCRIMINATOR;
pub(crate) use self::metadata::DC_METADATA_MTM_VERIFY_DISCRIMINATOR;
/// Verify Sized Collection Item discriminator.
pub(crate) use self::metadata::DC_METADATA_MTM_VERIFY_SIZED_COLLECTION_ITEM_DISCRIMINATOR;
/// Exact fixed byte length of one Buffer account header.
pub use self::metadata::DC_METADATA_SPM_BUFFER_HEADER_BYTES;
/// Exact byte length of one external-account data reference.
pub use self::metadata::DC_METADATA_SPM_EXTERNAL_DATA_BYTES;
/// Exact fixed byte length of one Metadata account header.
pub use self::metadata::DC_METADATA_SPM_METADATA_HEADER_BYTES;
/// Exact byte length of one Solana Program Metadata seed.
pub use self::metadata::DC_METADATA_SPM_SEED_BYTES;
/// Exact decoder for the Metaplex Token Metadata program.
pub use self::metadata::DcMetadataMetaplexTokenMetadataDecoder;
/// Canonical projection shared by Collection and Use Authority records.
@@ -384,6 +392,26 @@ pub use self::metadata::DcMetadataMtmTokenRecordAccountSnapshot;
pub use self::metadata::DcMetadataSolanaProgramMetadataDecoder;
/// Reserved `kb-lib.decoder.metadata.spl_name_service` decoder.
pub use self::metadata::DcMetadataSplNameServiceDecoder;
/// Solana Program Metadata account discriminator.
pub use self::metadata::DcMetadataSpmAccountDiscriminator;
/// Compression declared for Solana Program Metadata content.
pub use self::metadata::DcMetadataSpmCompression;
/// Typed Solana Program Metadata content without off-chain resolution.
pub use self::metadata::DcMetadataSpmData;
/// On-chain source used for Solana Program Metadata content.
pub use self::metadata::DcMetadataSpmDataSource;
/// Encoding declared for Solana Program Metadata content.
pub use self::metadata::DcMetadataSpmEncoding;
/// Fixed external-account data reference.
pub use self::metadata::DcMetadataSpmExternalData;
/// Format declared for Solana Program Metadata content.
pub use self::metadata::DcMetadataSpmFormat;
/// Bounded Solana Program Metadata model conversion error.
pub use self::metadata::DcMetadataSpmModelError;
/// Official Solana Program Metadata custom error.
pub use self::metadata::DcMetadataSpmProgramError;
/// Exact fixed-size Solana Program Metadata seed.
pub use self::metadata::DcMetadataSpmSeed;
/// Canonical tracing target for this crate.
pub(crate) use self::metadata::TRACING_TARGET_DECODER_METADATA_METAPLEX_TOKEN_METADATA;
/// Crate-root access to `decode` from `instruction`.

View File

@@ -1,5 +1,5 @@
// file: kb-lib/src/decoder/metadata.rs
// version: 7
// version: 8
//! `metadata` decoder family.
@@ -77,8 +77,36 @@ pub use self::metaplex_token_metadata::decoder_metadata_metaplex_token_metadata_
pub use self::metaplex_token_metadata::decoder_metadata_metaplex_token_metadata_decode_token_record_account;
/// Decodes one Use Authority Record account.
pub use self::metaplex_token_metadata::decoder_metadata_metaplex_token_metadata_decode_use_authority_record_account;
/// Exact fixed byte length of one Buffer account header.
pub use self::solana_program_metadata::DC_METADATA_SPM_BUFFER_HEADER_BYTES;
/// Exact byte length of one external-account data reference.
pub use self::solana_program_metadata::DC_METADATA_SPM_EXTERNAL_DATA_BYTES;
/// Exact fixed byte length of one Metadata account header.
pub use self::solana_program_metadata::DC_METADATA_SPM_METADATA_HEADER_BYTES;
/// Exact byte length of one Solana Program Metadata seed.
pub use self::solana_program_metadata::DC_METADATA_SPM_SEED_BYTES;
/// Reserved `kb-lib.decoder.metadata.solana_program_metadata` decoder.
pub use self::solana_program_metadata::DcMetadataSolanaProgramMetadataDecoder;
/// Solana Program Metadata account discriminator.
pub use self::solana_program_metadata::DcMetadataSpmAccountDiscriminator;
/// Compression declared for Solana Program Metadata content.
pub use self::solana_program_metadata::DcMetadataSpmCompression;
/// Typed Solana Program Metadata content without off-chain resolution.
pub use self::solana_program_metadata::DcMetadataSpmData;
/// On-chain source used for Solana Program Metadata content.
pub use self::solana_program_metadata::DcMetadataSpmDataSource;
/// Encoding declared for Solana Program Metadata content.
pub use self::solana_program_metadata::DcMetadataSpmEncoding;
/// Fixed external-account data reference.
pub use self::solana_program_metadata::DcMetadataSpmExternalData;
/// Format declared for Solana Program Metadata content.
pub use self::solana_program_metadata::DcMetadataSpmFormat;
/// Bounded Solana Program Metadata model conversion error.
pub use self::solana_program_metadata::DcMetadataSpmModelError;
/// Official Solana Program Metadata custom error.
pub use self::solana_program_metadata::DcMetadataSpmProgramError;
/// Exact fixed-size Solana Program Metadata seed.
pub use self::solana_program_metadata::DcMetadataSpmSeed;
/// Reserved `kb-lib.decoder.metadata.spl_name_service` decoder.
pub use self::spl_name_service::DcMetadataSplNameServiceDecoder;

View File

@@ -1,7 +1,39 @@
// file: kb-lib/src/decoder/metadata/solana_program_metadata.rs
// version: 1
// version: 2
//! Reserved protocol decoder for `metadata_solana_program_metadata`.
//! Public Solana Program Metadata models and reserved protocol decoder.
mod constants;
mod model;
/// Exact fixed byte length of one Buffer account header.
pub use self::constants::DC_METADATA_SPM_BUFFER_HEADER_BYTES;
/// Exact byte length of one external-account data reference.
pub use self::constants::DC_METADATA_SPM_EXTERNAL_DATA_BYTES;
/// Exact fixed byte length of one Metadata account header.
pub use self::constants::DC_METADATA_SPM_METADATA_HEADER_BYTES;
/// Exact byte length of one Solana Program Metadata seed.
pub use self::constants::DC_METADATA_SPM_SEED_BYTES;
/// Solana Program Metadata account discriminator.
pub use self::model::DcMetadataSpmAccountDiscriminator;
/// Compression declared for metadata content.
pub use self::model::DcMetadataSpmCompression;
/// Typed on-chain metadata content without off-chain resolution.
pub use self::model::DcMetadataSpmData;
/// On-chain source used for metadata content.
pub use self::model::DcMetadataSpmDataSource;
/// Encoding declared for metadata content.
pub use self::model::DcMetadataSpmEncoding;
/// Fixed external-account data reference.
pub use self::model::DcMetadataSpmExternalData;
/// Format declared for metadata content.
pub use self::model::DcMetadataSpmFormat;
/// Bounded model conversion error.
pub use self::model::DcMetadataSpmModelError;
/// Official Solana Program Metadata custom error.
pub use self::model::DcMetadataSpmProgramError;
/// Exact fixed-size metadata seed.
pub use self::model::DcMetadataSpmSeed;
/// Reserved decoder for the `metadata_solana_program_metadata` program surface.
#[derive(Clone, Debug, Default)]

View File

@@ -0,0 +1,13 @@
// file: kb-lib/src/decoder/metadata/solana_program_metadata/constants.rs
// version: 1
//! Bounded Solana Program Metadata wire constants.
/// Exact byte length of one Solana Program Metadata seed.
pub const DC_METADATA_SPM_SEED_BYTES: usize = 16;
/// Exact fixed byte length of one Buffer account header.
pub const DC_METADATA_SPM_BUFFER_HEADER_BYTES: usize = 96;
/// Exact fixed byte length of one Metadata account header.
pub const DC_METADATA_SPM_METADATA_HEADER_BYTES: usize = 96;
/// Exact byte length of one external-account data reference.
pub const DC_METADATA_SPM_EXTERNAL_DATA_BYTES: usize = 40;

View File

@@ -0,0 +1,740 @@
// file: kb-lib/src/decoder/metadata/solana_program_metadata/model.rs
// version: 1
//! Public bounded models for the Solana Program Metadata wire contract.
/// Solana Program Metadata account discriminator.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
#[repr(u8)]
#[serde(rename_all = "snake_case")]
pub enum DcMetadataSpmAccountDiscriminator {
/// Uninitialized account data.
Empty = 0,
/// Writable staging buffer.
Buffer = 1,
/// Initialized program metadata account.
Metadata = 2,
}
impl DcMetadataSpmAccountDiscriminator {
/// Returns the exact wire discriminant.
pub const fn wire_value(self) -> u8 {
return self as u8;
}
/// Resolves an exact wire discriminant.
pub fn from_wire_value(value: u8) -> std::result::Result<Self, crate::DcMetadataSpmModelError> {
match value {
0 => return std::result::Result::Ok(Self::Empty),
1 => return std::result::Result::Ok(Self::Buffer),
2 => return std::result::Result::Ok(Self::Metadata),
_ => {
return std::result::Result::Err(
crate::DcMetadataSpmModelError::UnknownAccountDiscriminator { value },
);
},
}
}
}
/// Encoding declared for metadata content.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
#[repr(u8)]
#[serde(rename_all = "snake_case")]
pub enum DcMetadataSpmEncoding {
/// No content encoding is declared.
None = 0,
/// UTF-8 content encoding.
Utf8 = 1,
/// Base58 content encoding.
Base58 = 2,
/// Base64 content encoding.
Base64 = 3,
}
impl DcMetadataSpmEncoding {
/// Returns the exact wire discriminant.
pub const fn wire_value(self) -> u8 {
return self as u8;
}
/// Resolves an exact wire discriminant.
pub fn from_wire_value(value: u8) -> std::result::Result<Self, crate::DcMetadataSpmModelError> {
match value {
0 => return std::result::Result::Ok(Self::None),
1 => return std::result::Result::Ok(Self::Utf8),
2 => return std::result::Result::Ok(Self::Base58),
3 => return std::result::Result::Ok(Self::Base64),
_ => {
return std::result::Result::Err(crate::DcMetadataSpmModelError::UnknownEncoding {
value,
});
},
}
}
}
/// Compression declared for metadata content.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
#[repr(u8)]
#[serde(rename_all = "snake_case")]
pub enum DcMetadataSpmCompression {
/// No content compression is declared.
None = 0,
/// Gzip content compression.
Gzip = 1,
/// Zlib content compression.
Zlib = 2,
}
impl DcMetadataSpmCompression {
/// Returns the exact wire discriminant.
pub const fn wire_value(self) -> u8 {
return self as u8;
}
/// Resolves an exact wire discriminant.
pub fn from_wire_value(value: u8) -> std::result::Result<Self, crate::DcMetadataSpmModelError> {
match value {
0 => return std::result::Result::Ok(Self::None),
1 => return std::result::Result::Ok(Self::Gzip),
2 => return std::result::Result::Ok(Self::Zlib),
_ => {
return std::result::Result::Err(
crate::DcMetadataSpmModelError::UnknownCompression { value },
);
},
}
}
}
/// Format declared for metadata content.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
#[repr(u8)]
#[serde(rename_all = "snake_case")]
pub enum DcMetadataSpmFormat {
/// No structured content format is declared.
None = 0,
/// JSON content format.
Json = 1,
/// YAML content format.
Yaml = 2,
/// TOML content format.
Toml = 3,
}
impl DcMetadataSpmFormat {
/// Returns the exact wire discriminant.
pub const fn wire_value(self) -> u8 {
return self as u8;
}
/// Resolves an exact wire discriminant.
pub fn from_wire_value(value: u8) -> std::result::Result<Self, crate::DcMetadataSpmModelError> {
match value {
0 => return std::result::Result::Ok(Self::None),
1 => return std::result::Result::Ok(Self::Json),
2 => return std::result::Result::Ok(Self::Yaml),
3 => return std::result::Result::Ok(Self::Toml),
_ => {
return std::result::Result::Err(crate::DcMetadataSpmModelError::UnknownFormat {
value,
});
},
}
}
}
/// On-chain source used for metadata content.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
#[repr(u8)]
#[serde(rename_all = "snake_case")]
pub enum DcMetadataSpmDataSource {
/// Content bytes are stored directly after the account header.
Direct = 0,
/// A UTF-8 URL is stored after the account header.
Url = 1,
/// A fixed external-account reference is stored after the account header.
External = 2,
}
impl DcMetadataSpmDataSource {
/// Returns the exact wire discriminant.
pub const fn wire_value(self) -> u8 {
return self as u8;
}
/// Resolves an exact wire discriminant.
pub fn from_wire_value(value: u8) -> std::result::Result<Self, crate::DcMetadataSpmModelError> {
match value {
0 => return std::result::Result::Ok(Self::Direct),
1 => return std::result::Result::Ok(Self::Url),
2 => return std::result::Result::Ok(Self::External),
_ => {
return std::result::Result::Err(
crate::DcMetadataSpmModelError::UnknownDataSource { value },
);
},
}
}
}
/// Official custom error returned by the Solana Program Metadata program.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
#[repr(u32)]
#[serde(rename_all = "snake_case")]
pub enum DcMetadataSpmProgramError {
/// The referenced program account is not executable.
NotExecutableAccount = 0,
/// The referenced upgradeable-program state is invalid.
InvalidProgramState = 1,
/// The referenced program-data account is invalid.
InvalidProgramDataAccount = 2,
/// The metadata account is immutable.
ImmutableMetadataAccount = 3,
/// The metadata data length is invalid for its source.
InvalidDataLength = 4,
}
impl DcMetadataSpmProgramError {
/// Returns the exact custom program error code.
pub const fn code(self) -> u32 {
return self as u32;
}
/// Resolves an exact custom program error code.
pub fn from_code(value: u32) -> std::result::Result<Self, crate::DcMetadataSpmModelError> {
match value {
0 => return std::result::Result::Ok(Self::NotExecutableAccount),
1 => return std::result::Result::Ok(Self::InvalidProgramState),
2 => return std::result::Result::Ok(Self::InvalidProgramDataAccount),
3 => return std::result::Result::Ok(Self::ImmutableMetadataAccount),
4 => return std::result::Result::Ok(Self::InvalidDataLength),
_ => {
return std::result::Result::Err(
crate::DcMetadataSpmModelError::UnknownProgramError { value },
);
},
}
}
}
/// Exact fixed-size seed carried by PDA derivations and account headers.
#[derive(
Clone, Copy, Debug, Default, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize,
)]
#[serde(transparent)]
pub struct DcMetadataSpmSeed([u8; crate::DC_METADATA_SPM_SEED_BYTES]);
impl DcMetadataSpmSeed {
/// Creates a seed from its exact fixed-size wire representation.
pub const fn from_bytes(bytes: [u8; crate::DC_METADATA_SPM_SEED_BYTES]) -> Self {
return Self(bytes);
}
/// Creates a zero-padded fixed-size seed from one UTF-8 value.
pub fn from_utf8(value: &str) -> std::result::Result<Self, crate::DcMetadataSpmModelError> {
let source = value.as_bytes();
if source.len() > crate::DC_METADATA_SPM_SEED_BYTES {
return std::result::Result::Err(crate::DcMetadataSpmModelError::SeedUtf8TooLong {
actual_bytes: source.len(),
max_bytes: crate::DC_METADATA_SPM_SEED_BYTES,
});
}
let mut bytes = [0_u8; crate::DC_METADATA_SPM_SEED_BYTES];
bytes[..source.len()].copy_from_slice(source);
return std::result::Result::Ok(Self(bytes));
}
/// Parses an exact fixed-size wire slice.
pub fn from_wire_bytes(
bytes: &[u8],
) -> std::result::Result<Self, crate::DcMetadataSpmModelError> {
if bytes.len() != crate::DC_METADATA_SPM_SEED_BYTES {
return std::result::Result::Err(crate::DcMetadataSpmModelError::InvalidSeedLength {
actual_bytes: bytes.len(),
expected_bytes: crate::DC_METADATA_SPM_SEED_BYTES,
});
}
let mut seed = [0_u8; crate::DC_METADATA_SPM_SEED_BYTES];
seed.copy_from_slice(bytes);
return std::result::Result::Ok(Self(seed));
}
/// Returns the exact fixed-size wire representation.
pub const fn as_bytes(&self) -> &[u8; crate::DC_METADATA_SPM_SEED_BYTES] {
return &self.0;
}
/// Returns the UTF-8 value after removing only terminal zero padding.
pub fn trimmed_utf8(&self) -> std::result::Result<&str, crate::DcMetadataSpmModelError> {
let mut length = self.0.len();
while length > 0 && self.0[length - 1] == 0 {
length -= 1;
}
match std::str::from_utf8(&self.0[..length]) {
std::result::Result::Ok(value) => return std::result::Result::Ok(value),
std::result::Result::Err(_) => {
return std::result::Result::Err(crate::DcMetadataSpmModelError::InvalidSeedUtf8);
},
}
}
}
/// Fixed external-account data reference used by the `External` data source.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct DcMetadataSpmExternalData {
/// Account that stores the referenced data.
pub address: solana_pubkey::Pubkey,
/// Byte offset inside the external account.
pub offset: u32,
/// Optional non-zero byte length; `None` is encoded as zero and means the whole account.
pub length: std::option::Option<std::num::NonZeroU32>,
}
impl DcMetadataSpmExternalData {
/// Creates an exact external-account reference.
pub const fn new(
address: solana_pubkey::Pubkey,
offset: u32,
length: std::option::Option<std::num::NonZeroU32>,
) -> Self {
return Self { address, offset, length };
}
/// Creates an external-account reference from a zeroable optional length.
pub fn from_zeroable_length(address: solana_pubkey::Pubkey, offset: u32, length: u32) -> Self {
return Self::new(address, offset, std::num::NonZeroU32::new(length));
}
/// Serializes the exact 40-byte little-endian wire representation.
pub fn to_wire_bytes(&self) -> [u8; crate::DC_METADATA_SPM_EXTERNAL_DATA_BYTES] {
let mut bytes = [0_u8; crate::DC_METADATA_SPM_EXTERNAL_DATA_BYTES];
bytes[..32].copy_from_slice(&self.address.to_bytes());
bytes[32..36].copy_from_slice(&self.offset.to_le_bytes());
let length = match self.length {
std::option::Option::Some(value) => value.get(),
std::option::Option::None => 0,
};
bytes[36..40].copy_from_slice(&length.to_le_bytes());
return bytes;
}
/// Parses the exact 40-byte little-endian wire representation.
pub fn from_wire_bytes(
bytes: &[u8],
) -> std::result::Result<Self, crate::DcMetadataSpmModelError> {
if bytes.len() != crate::DC_METADATA_SPM_EXTERNAL_DATA_BYTES {
return std::result::Result::Err(
crate::DcMetadataSpmModelError::InvalidExternalDataLength {
actual_bytes: bytes.len(),
expected_bytes: crate::DC_METADATA_SPM_EXTERNAL_DATA_BYTES,
},
);
}
let mut address = [0_u8; 32];
address.copy_from_slice(&bytes[..32]);
let mut offset = [0_u8; 4];
offset.copy_from_slice(&bytes[32..36]);
let mut length = [0_u8; 4];
length.copy_from_slice(&bytes[36..40]);
return std::result::Result::Ok(Self::from_zeroable_length(
solana_pubkey::Pubkey::new_from_array(address),
u32::from_le_bytes(offset),
u32::from_le_bytes(length),
));
}
}
/// Typed on-chain metadata content without off-chain resolution.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(tag = "source", content = "value", rename_all = "snake_case")]
pub enum DcMetadataSpmData {
/// Raw content stored directly in the metadata account.
Direct(std::vec::Vec<u8>),
/// UTF-8 URL stored directly in the metadata account.
Url(String),
/// Fixed reference to data stored in another account.
External(crate::DcMetadataSpmExternalData),
}
impl DcMetadataSpmData {
/// Returns the source discriminator associated with this content.
pub const fn source(&self) -> crate::DcMetadataSpmDataSource {
match self {
Self::Direct(_) => return crate::DcMetadataSpmDataSource::Direct,
Self::Url(_) => return crate::DcMetadataSpmDataSource::Url,
Self::External(_) => return crate::DcMetadataSpmDataSource::External,
}
}
/// Validates source-specific length requirements and the `u32` header bound.
pub fn validate(&self) -> std::result::Result<(), crate::DcMetadataSpmModelError> {
let actual_bytes = self.wire_byte_len();
match self {
Self::Direct(_) | Self::Url(_) if actual_bytes == 0 => {
return std::result::Result::Err(crate::DcMetadataSpmModelError::EmptyData {
source: self.source(),
});
},
Self::Direct(_) | Self::Url(_) | Self::External(_) => {},
}
if u32::try_from(actual_bytes).is_err() {
return std::result::Result::Err(
crate::DcMetadataSpmModelError::DataLengthExceedsU32 { actual_bytes },
);
}
return std::result::Result::Ok(());
}
/// Returns the exact number of bytes represented on wire.
pub fn wire_byte_len(&self) -> usize {
match self {
Self::Direct(value) => return value.len(),
Self::Url(value) => return value.len(),
Self::External(_) => return crate::DC_METADATA_SPM_EXTERNAL_DATA_BYTES,
}
}
/// Returns the exact little-endian header length after validation.
pub fn wire_length_u32(&self) -> std::result::Result<u32, crate::DcMetadataSpmModelError> {
match self.validate() {
std::result::Result::Ok(()) => {},
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
let actual_bytes = self.wire_byte_len();
match u32::try_from(actual_bytes) {
std::result::Result::Ok(value) => return std::result::Result::Ok(value),
std::result::Result::Err(_) => {
return std::result::Result::Err(
crate::DcMetadataSpmModelError::DataLengthExceedsU32 { actual_bytes },
);
},
}
}
/// Serializes the exact content bytes without fetching URL or external-account data.
pub fn to_wire_bytes(
&self,
) -> std::result::Result<std::vec::Vec<u8>, crate::DcMetadataSpmModelError> {
match self.validate() {
std::result::Result::Ok(()) => {},
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
match self {
Self::Direct(value) => return std::result::Result::Ok(value.clone()),
Self::Url(value) => {
return std::result::Result::Ok(value.as_bytes().to_vec());
},
Self::External(value) => {
return std::result::Result::Ok(value.to_wire_bytes().to_vec());
},
}
}
}
/// Bounded error for Solana Program Metadata public model conversions.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum DcMetadataSpmModelError {
/// An account discriminator is outside the published closed enum.
UnknownAccountDiscriminator {
/// Rejected wire value.
value: u8,
},
/// An encoding discriminator is outside the published closed enum.
UnknownEncoding {
/// Rejected wire value.
value: u8,
},
/// A compression discriminator is outside the published closed enum.
UnknownCompression {
/// Rejected wire value.
value: u8,
},
/// A format discriminator is outside the published closed enum.
UnknownFormat {
/// Rejected wire value.
value: u8,
},
/// A data-source discriminator is outside the published closed enum.
UnknownDataSource {
/// Rejected wire value.
value: u8,
},
/// A custom program error code is outside the published closed enum.
UnknownProgramError {
/// Rejected custom error code.
value: u32,
},
/// One UTF-8 seed exceeds the exact fixed-size wire bound.
SeedUtf8TooLong {
/// Actual UTF-8 byte length.
actual_bytes: usize,
/// Maximum accepted UTF-8 byte length.
max_bytes: usize,
},
/// One seed slice does not have the exact fixed-size wire length.
InvalidSeedLength {
/// Actual slice length.
actual_bytes: usize,
/// Required fixed length.
expected_bytes: usize,
},
/// The non-padding seed bytes are not valid UTF-8.
InvalidSeedUtf8,
/// One external-data slice does not have the exact fixed-size wire length.
InvalidExternalDataLength {
/// Actual slice length.
actual_bytes: usize,
/// Required fixed length.
expected_bytes: usize,
},
/// Direct or URL content is empty.
EmptyData {
/// Source whose data is empty.
source: crate::DcMetadataSpmDataSource,
},
/// Content cannot be represented by the `u32` length stored in the account header.
DataLengthExceedsU32 {
/// Actual content byte length.
actual_bytes: usize,
},
}
impl std::fmt::Display for DcMetadataSpmModelError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UnknownAccountDiscriminator { value } => {
return write!(formatter, "unknown account discriminator {value}");
},
Self::UnknownEncoding { value } => {
return write!(formatter, "unknown encoding discriminator {value}");
},
Self::UnknownCompression { value } => {
return write!(formatter, "unknown compression discriminator {value}");
},
Self::UnknownFormat { value } => {
return write!(formatter, "unknown format discriminator {value}");
},
Self::UnknownDataSource { value } => {
return write!(formatter, "unknown data-source discriminator {value}");
},
Self::UnknownProgramError { value } => {
return write!(formatter, "unknown program metadata error code {value}");
},
Self::SeedUtf8TooLong { actual_bytes, max_bytes } => {
return write!(
formatter,
"seed UTF-8 length {actual_bytes} exceeds {max_bytes} bytes",
);
},
Self::InvalidSeedLength { actual_bytes, expected_bytes } => {
return write!(
formatter,
"seed length {actual_bytes} does not equal {expected_bytes} bytes",
);
},
Self::InvalidSeedUtf8 => {
return formatter.write_str("seed contains invalid UTF-8 before terminal padding");
},
Self::InvalidExternalDataLength { actual_bytes, expected_bytes } => {
return write!(
formatter,
"external data length {actual_bytes} does not equal {expected_bytes} bytes",
);
},
Self::EmptyData { source } => {
return write!(formatter, "metadata data source {source:?} cannot be empty");
},
Self::DataLengthExceedsU32 { actual_bytes } => {
return write!(
formatter,
"metadata data length {actual_bytes} exceeds the u32 wire bound",
);
},
}
}
}
impl std::error::Error for DcMetadataSpmModelError {}
#[cfg(test)]
mod tests {
#[test]
fn published_wire_values_are_exact_and_closed() {
assert_eq!(crate::DcMetadataSpmAccountDiscriminator::Empty.wire_value(), 0);
assert_eq!(crate::DcMetadataSpmAccountDiscriminator::Buffer.wire_value(), 1);
assert_eq!(crate::DcMetadataSpmAccountDiscriminator::Metadata.wire_value(), 2);
assert_eq!(crate::DcMetadataSpmEncoding::None.wire_value(), 0);
assert_eq!(crate::DcMetadataSpmEncoding::Utf8.wire_value(), 1);
assert_eq!(crate::DcMetadataSpmEncoding::Base58.wire_value(), 2);
assert_eq!(crate::DcMetadataSpmEncoding::Base64.wire_value(), 3);
assert_eq!(crate::DcMetadataSpmCompression::None.wire_value(), 0);
assert_eq!(crate::DcMetadataSpmCompression::Gzip.wire_value(), 1);
assert_eq!(crate::DcMetadataSpmCompression::Zlib.wire_value(), 2);
assert_eq!(crate::DcMetadataSpmFormat::None.wire_value(), 0);
assert_eq!(crate::DcMetadataSpmFormat::Json.wire_value(), 1);
assert_eq!(crate::DcMetadataSpmFormat::Yaml.wire_value(), 2);
assert_eq!(crate::DcMetadataSpmFormat::Toml.wire_value(), 3);
assert_eq!(crate::DcMetadataSpmDataSource::Direct.wire_value(), 0);
assert_eq!(crate::DcMetadataSpmDataSource::Url.wire_value(), 1);
assert_eq!(crate::DcMetadataSpmDataSource::External.wire_value(), 2);
assert_eq!(crate::DcMetadataSpmProgramError::NotExecutableAccount.code(), 0);
assert_eq!(crate::DcMetadataSpmProgramError::InvalidProgramState.code(), 1);
assert_eq!(crate::DcMetadataSpmProgramError::InvalidProgramDataAccount.code(), 2);
assert_eq!(crate::DcMetadataSpmProgramError::ImmutableMetadataAccount.code(), 3);
assert_eq!(crate::DcMetadataSpmProgramError::InvalidDataLength.code(), 4);
assert!(crate::DcMetadataSpmAccountDiscriminator::from_wire_value(3).is_err());
assert!(crate::DcMetadataSpmEncoding::from_wire_value(4).is_err());
assert!(crate::DcMetadataSpmCompression::from_wire_value(3).is_err());
assert!(crate::DcMetadataSpmFormat::from_wire_value(4).is_err());
assert!(crate::DcMetadataSpmDataSource::from_wire_value(3).is_err());
assert!(crate::DcMetadataSpmProgramError::from_code(5).is_err());
}
#[test]
fn seed_preserves_exact_bytes_and_applies_the_utf8_byte_bound() {
let seed_result = crate::DcMetadataSpmSeed::from_utf8("sécurité");
assert!(seed_result.is_ok());
let seed;
if let std::result::Result::Ok(value) = seed_result {
seed = value;
} else {
return;
}
assert_eq!(seed.as_bytes().len(), crate::DC_METADATA_SPM_SEED_BYTES);
assert_eq!(seed.trimmed_utf8(), std::result::Result::Ok("sécurité"));
assert!(crate::DcMetadataSpmSeed::from_utf8("éééééééé").is_ok());
assert_eq!(
crate::DcMetadataSpmSeed::from_utf8("ééééééééé"),
std::result::Result::Err(crate::DcMetadataSpmModelError::SeedUtf8TooLong {
actual_bytes: 18,
max_bytes: crate::DC_METADATA_SPM_SEED_BYTES,
}),
);
assert!(crate::DcMetadataSpmSeed::from_wire_bytes(&[0_u8; 15]).is_err());
let invalid = crate::DcMetadataSpmSeed::from_bytes([
0xff, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8,
0_u8, 0_u8,
]);
assert_eq!(
invalid.trimmed_utf8(),
std::result::Result::Err(crate::DcMetadataSpmModelError::InvalidSeedUtf8),
);
}
#[test]
fn external_data_round_trips_the_exact_zeroable_layout() {
let address = solana_pubkey::Pubkey::new_from_array([7_u8; 32]);
let length_option = std::num::NonZeroU32::new(9);
assert!(length_option.is_some());
let length;
if let std::option::Option::Some(value) = length_option {
length = value;
} else {
return;
}
let model = crate::DcMetadataSpmExternalData::new(
address,
0x1122_3344,
std::option::Option::Some(length),
);
let bytes = model.to_wire_bytes();
assert_eq!(bytes.len(), crate::DC_METADATA_SPM_EXTERNAL_DATA_BYTES);
assert_eq!(&bytes[..32], &[7_u8; 32]);
assert_eq!(&bytes[32..36], &0x1122_3344_u32.to_le_bytes());
assert_eq!(&bytes[36..40], &9_u32.to_le_bytes());
assert_eq!(
crate::DcMetadataSpmExternalData::from_wire_bytes(&bytes),
std::result::Result::Ok(model),
);
let zeroable = crate::DcMetadataSpmExternalData::from_zeroable_length(address, 3, 0);
assert_eq!(zeroable.length, std::option::Option::None);
assert_eq!(&zeroable.to_wire_bytes()[36..40], &0_u32.to_le_bytes());
assert!(crate::DcMetadataSpmExternalData::from_wire_bytes(&bytes[..39]).is_err());
}
#[test]
fn typed_data_keeps_url_and_external_content_on_chain_only() {
let direct = crate::DcMetadataSpmData::Direct(vec![1_u8, 2_u8, 3_u8]);
assert_eq!(direct.source(), crate::DcMetadataSpmDataSource::Direct);
assert_eq!(direct.wire_length_u32(), std::result::Result::Ok(3));
assert_eq!(direct.to_wire_bytes(), std::result::Result::Ok(vec![1_u8, 2_u8, 3_u8]));
let url = crate::DcMetadataSpmData::Url("https://example.invalid/idl.json".to_string());
assert_eq!(url.source(), crate::DcMetadataSpmDataSource::Url);
assert_eq!(
url.to_wire_bytes(),
std::result::Result::Ok(b"https://example.invalid/idl.json".to_vec()),
);
let external = crate::DcMetadataSpmData::External(
crate::DcMetadataSpmExternalData::from_zeroable_length(
solana_pubkey::Pubkey::new_from_array([8_u8; 32]),
12,
20,
),
);
assert_eq!(external.source(), crate::DcMetadataSpmDataSource::External);
assert_eq!(external.wire_length_u32(), std::result::Result::Ok(40),);
assert_eq!(
external.to_wire_bytes().map(|value| return value.len()),
std::result::Result::Ok(crate::DC_METADATA_SPM_EXTERNAL_DATA_BYTES),
);
assert!(crate::DcMetadataSpmData::Direct(std::vec::Vec::new()).validate().is_err());
assert!(crate::DcMetadataSpmData::Url(String::new()).validate().is_err());
}
#[test]
fn public_json_representation_is_stable_and_typed() {
let encoding_result = serde_json::to_value(crate::DcMetadataSpmEncoding::Base64);
assert!(encoding_result.is_ok());
let encoding;
if let std::result::Result::Ok(value) = encoding_result {
encoding = value;
} else {
return;
}
assert_eq!(encoding, serde_json::json!("base64"));
let url_result = serde_json::to_value(crate::DcMetadataSpmData::Url(
"https://example.invalid/metadata.json".to_string(),
));
assert!(url_result.is_ok());
let url;
if let std::result::Result::Ok(value) = url_result {
url = value;
} else {
return;
}
assert_eq!(
url,
serde_json::json!({
"source": "url",
"value": "https://example.invalid/metadata.json",
}),
);
let seed_result = crate::DcMetadataSpmSeed::from_utf8("idl");
assert!(seed_result.is_ok());
let seed;
if let std::result::Result::Ok(value) = seed_result {
seed = value;
} else {
return;
}
let json_result = serde_json::to_value(seed);
assert!(json_result.is_ok());
let json;
if let std::result::Result::Ok(value) = json_result {
json = value;
} else {
return;
}
assert_eq!(json.as_array().map(std::vec::Vec::len), std::option::Option::Some(16));
}
#[test]
fn published_fixed_sizes_match_the_audited_contract() {
assert_eq!(crate::DC_METADATA_SPM_SEED_BYTES, 16);
assert_eq!(crate::DC_METADATA_SPM_BUFFER_HEADER_BYTES, 96);
assert_eq!(crate::DC_METADATA_SPM_METADATA_HEADER_BYTES, 96);
assert_eq!(crate::DC_METADATA_SPM_EXTERNAL_DATA_BYTES, 40);
}
}

View File

@@ -1,5 +1,5 @@
// file: kb-lib/src/lib.rs
// version: 29
// version: 30
//! Consolidated decoder, executor, materializer and shared model library.
#![warn(missing_docs)]
@@ -15,6 +15,14 @@ mod model;
pub use self::decoder::DC_API_CORE_INSTRUCTION_INPUT_CONTRACT_VERSION;
/// Exhaustive published Metaplex account-key inventory.
pub use self::decoder::DC_METADATA_MTM_METADATA_ACCOUNT_LAYOUT_AUDIT;
/// Exposes the exact fixed byte length of one Buffer account header.
pub use self::decoder::DC_METADATA_SPM_BUFFER_HEADER_BYTES;
/// Exposes the exact byte length of one external-account data reference.
pub use self::decoder::DC_METADATA_SPM_EXTERNAL_DATA_BYTES;
/// Exposes the exact fixed byte length of one Metadata account header.
pub use self::decoder::DC_METADATA_SPM_METADATA_HEADER_BYTES;
/// Exposes the exact byte length of one Solana Program Metadata seed.
pub use self::decoder::DC_METADATA_SPM_SEED_BYTES;
/// Exact byte length of one SPL ElGamal registry account.
pub use self::decoder::DC_SPL_ELGAMAL_REGISTRY_ACCOUNT_LEN;
/// Reserved `kb-lib.decoder.adapter.saber_decimal_wrapper` decoder.
@@ -226,6 +234,26 @@ pub use self::decoder::DcMetadataMtmTokenRecordAccountSnapshot;
pub use self::decoder::DcMetadataSolanaProgramMetadataDecoder;
/// Reserved `kb-lib.decoder.metadata.spl_name_service` decoder.
pub use self::decoder::DcMetadataSplNameServiceDecoder;
/// Solana Program Metadata account discriminator.
pub use self::decoder::DcMetadataSpmAccountDiscriminator;
/// Compression declared for Solana Program Metadata content.
pub use self::decoder::DcMetadataSpmCompression;
/// Typed Solana Program Metadata content without off-chain resolution.
pub use self::decoder::DcMetadataSpmData;
/// On-chain source used for Solana Program Metadata content.
pub use self::decoder::DcMetadataSpmDataSource;
/// Encoding declared for Solana Program Metadata content.
pub use self::decoder::DcMetadataSpmEncoding;
/// Fixed external-account data reference.
pub use self::decoder::DcMetadataSpmExternalData;
/// Format declared for Solana Program Metadata content.
pub use self::decoder::DcMetadataSpmFormat;
/// Bounded Solana Program Metadata model conversion error.
pub use self::decoder::DcMetadataSpmModelError;
/// Official Solana Program Metadata custom error.
pub use self::decoder::DcMetadataSpmProgramError;
/// Exact fixed-size Solana Program Metadata seed.
pub use self::decoder::DcMetadataSpmSeed;
/// Reserved `kb-lib.decoder.nft.metaplex_bubblegum` decoder.
pub use self::decoder::DcNftMetaplexBubblegumDecoder;
/// Exposes the reserved `DcNftTensorCnftDecoder` implementation.