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_instruction::AccountMeta],
108    ) -> Option<Self::ArrangedAccounts>;
109}
110
111/// A wrapper type for strings that are prefixed with their length.
112
113#[derive(serde::Serialize, serde::Deserialize, Default, PartialEq, Eq, Clone)]
114pub struct PrefixString(pub String);
115
116impl Deref for PrefixString {
117    type Target = String;
118
119    fn deref(&self) -> &Self::Target {
120        &self.0
121    }
122}
123
124impl From<PrefixString> for String {
125    fn from(val: PrefixString) -> Self {
126        val.0
127    }
128}
129
130impl std::fmt::Debug for PrefixString {
131    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132        f.write_fmt(format_args!("{:?}", self.0))
133    }
134}
135
136/// Implements the `CarbonDeserialize` trait for `PrefixString`.
137impl crate::borsh::BorshDeserialize for PrefixString {
138    #[inline]
139    fn deserialize_reader<R: Read>(reader: &mut R) -> Result<Self> {
140        // read the length of the String
141        let mut buffer = vec![0u8; 4];
142        reader.read_exact(&mut buffer)?;
143        let length = u32::deserialize(&mut buffer.as_slice())?;
144        let mut buffer = vec![0u8; length as usize];
145        reader.read_exact(&mut buffer)?;
146
147        Ok(Self(String::from_utf8(buffer).map_err(|_| {
148            Error::new(ErrorKind::InvalidData, "invalid utf8")
149        })?))
150    }
151}
152
153/// A wrapper type for strings that are prefixed with their length.
154
155#[derive(serde::Serialize, Default, serde::Deserialize, PartialEq, Eq, Clone)]
156pub struct U64PrefixString(pub String);
157
158impl Deref for U64PrefixString {
159    type Target = String;
160
161    fn deref(&self) -> &Self::Target {
162        &self.0
163    }
164}
165
166impl From<U64PrefixString> for String {
167    fn from(val: U64PrefixString) -> Self {
168        val.0
169    }
170}
171
172impl std::fmt::Debug for U64PrefixString {
173    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
174        f.write_fmt(format_args!("{:?}", self.0))
175    }
176}
177
178/// Implements the `CarbonDeserialize` trait for `U64PrefixString`.
179impl crate::borsh::BorshDeserialize for U64PrefixString {
180    #[inline]
181    fn deserialize_reader<R: Read>(reader: &mut R) -> Result<Self> {
182        // read the length of the String
183        let mut buffer = vec![0u8; 8];
184        reader.read_exact(&mut buffer)?;
185        let length = u64::deserialize(&mut buffer.as_slice())?;
186        let mut buffer = vec![0u8; length as usize];
187        reader.read_exact(&mut buffer)?;
188
189        Ok(Self(String::from_utf8(buffer).map_err(|_| {
190            Error::new(ErrorKind::InvalidData, "invalid utf8")
191        })?))
192    }
193}