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}