carbon_core/
deserialize.rs

1//! Provides traits and utility functions for deserialization and account
2//! arrangement within the `carbon-core` framework.
3//!
4//! This module includes the `CarbonDeserialize` trait for custom
5//! deserialization of data types, the `extract_discriminator` function for
6//! splitting data slices by a discriminator length, and the `ArrangeAccounts`
7//! trait for defining of Solana account metadata.
8//!
9//! # Overview
10//!
11//! - **`CarbonDeserialize`**: A trait for custom deserialization of data
12//!   structures from byte slices.
13//! - **`extract_discriminator`**: A function that separates a discriminator
14//!   from the rest of a byte slice, used for parsing data with prefixed
15//!   discriminators.
16//! - **`ArrangeAccounts`**: A trait that allows for defining a specific
17//!   arrangement of accounts, suitable for handling Solana account metadata in
18//!   a customized way.
19//!
20//! # Notes
21//!
22//! - The `CarbonDeserialize` trait requires implementers to also implement
23//!   `borsh::BorshDeserialize`.
24//! - Ensure that `extract_discriminator` is used with data slices large enough
25//!   to avoid runtime errors.
26//! - Implement `ArrangeAccounts` when you need to access account metadata for
27//!   Solana instructions.
28
29use std::{
30    io::{Error, ErrorKind, Read, Result},
31    ops::Deref,
32};
33/// A trait for custom deserialization of types from byte slices.
34///
35/// The `CarbonDeserialize` trait provides a method for deserializing instances
36/// of a type from raw byte slices. This is essential for parsing binary data
37/// into structured types within the `carbon-core` framework. Types implementing
38/// this trait should also implement `BorshDeserialize` to support Borsh-based
39/// serialization.
40///
41/// # Notes
42///
43/// - Implementing this trait enables custom deserialization logic for types,
44///   which is useful for processing raw blockchain data.
45/// - Ensure the data slice passed to `deserialize` is valid and of appropriate
46///   length to avoid errors.
47pub trait CarbonDeserialize
48where
49    Self: Sized + crate::borsh::BorshDeserialize,
50{
51    fn deserialize(data: &[u8]) -> Option<Self>;
52}
53
54/// Extracts a discriminator from the beginning of a byte slice and returns the
55/// discriminator and remaining data.
56///
57/// The `extract_discriminator` function takes a slice of bytes and separates a
58/// portion of it, specified by the `length` parameter, from the rest of the
59/// data. This is commonly used in scenarios where data is prefixed with a
60/// discriminator value, such as Solana transactions and accounts.
61///
62/// # Parameters
63///
64/// - `length`: The length of the discriminator prefix to extract.
65/// - `data`: The full data slice from which to extract the discriminator.
66///
67/// # Returns
68///
69/// Returns an `Option` containing a tuple of slices:
70/// - The first slice is the discriminator of the specified length.
71/// - The second slice is the remaining data following the discriminator.
72///   Returns `None` if the `data` slice is shorter than the specified `length`.
73///
74/// # Notes
75///
76/// - Ensure that `data` is at least as long as `length` to avoid `None` being
77///   returned.
78/// - This function is particularly useful for decoding prefixed data
79///   structures, such as those commonly found in Solana transactions.
80pub fn extract_discriminator(length: usize, data: &[u8]) -> Option<(&[u8], &[u8])> {
81    log::trace!(
82        "extract_discriminator(length: {:?}, data: {:?})",
83        length,
84        data
85    );
86
87    if data.len() < length {
88        return None;
89    }
90
91    Some((&data[..length], &data[length..]))
92}
93
94/// A trait for defining a custom arrangement of Solana account metadata.
95///
96/// The `ArrangeAccounts` trait provides an interface for structuring account
97/// metadata in a custom format.
98///
99/// # Associated Types
100///
101/// - `ArrangedAccounts`: The output type representing the custom arrangement of
102///   accounts.
103pub trait ArrangeAccounts {
104    type ArrangedAccounts;
105
106    fn arrange_accounts(
107        accounts: &[solana_sdk::instruction::AccountMeta],
108    ) -> Option<Self::ArrangedAccounts>;
109}
110
111/// A wrapper type for strings that are prefixed with their length.
112#[derive(serde::Serialize, serde::Deserialize, PartialEq, Eq, Clone)]
113pub struct PrefixString(pub String);
114
115impl Default for PrefixString {
116    fn default() -> Self {
117        Self(String::default())
118    }
119}
120
121impl Deref for PrefixString {
122    type Target = String;
123
124    fn deref(&self) -> &Self::Target {
125        &self.0
126    }
127}
128
129impl From<PrefixString> for String {
130    fn from(val: PrefixString) -> Self {
131        val.0
132    }
133}
134
135impl std::fmt::Debug for PrefixString {
136    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137        f.write_fmt(format_args!("{:?}", self.0))
138    }
139}
140
141/// Implements the `CarbonDeserialize` trait for `PrefixString`.
142impl crate::borsh::BorshDeserialize for PrefixString {
143    #[inline]
144    fn deserialize_reader<R: Read>(reader: &mut R) -> Result<Self> {
145        // read the length of the String
146        let mut buffer = vec![0u8; 4];
147        reader.read_exact(&mut buffer)?;
148        let length = u32::deserialize(&mut buffer.as_slice())?;
149        let mut buffer = vec![0u8; length as usize];
150        reader.read_exact(&mut buffer)?;
151
152        Ok(Self(String::from_utf8(buffer).map_err(|_| {
153            Error::new(ErrorKind::InvalidData, "invalid utf8")
154        })?))
155    }
156}