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 const DISCRIMINATOR: &'static [u8];
52
53 fn deserialize(data: &[u8]) -> Option<Self>;
54}
55
56/// Extracts a discriminator from the beginning of a byte slice and returns the
57/// discriminator and remaining data.
58///
59/// The `extract_discriminator` function takes a slice of bytes and separates a
60/// portion of it, specified by the `length` parameter, from the rest of the
61/// data. This is commonly used in scenarios where data is prefixed with a
62/// discriminator value, such as Solana transactions and accounts.
63///
64/// # Parameters
65///
66/// - `length`: The length of the discriminator prefix to extract.
67/// - `data`: The full data slice from which to extract the discriminator.
68///
69/// # Returns
70///
71/// Returns an `Option` containing a tuple of slices:
72/// - The first slice is the discriminator of the specified length.
73/// - The second slice is the remaining data following the discriminator.
74/// Returns `None` if the `data` slice is shorter than the specified `length`.
75///
76/// # Notes
77///
78/// - Ensure that `data` is at least as long as `length` to avoid `None` being
79/// returned.
80/// - This function is particularly useful for decoding prefixed data
81/// structures, such as those commonly found in Solana transactions.
82pub fn extract_discriminator(length: usize, data: &[u8]) -> Option<(&[u8], &[u8])> {
83 log::trace!(
84 "extract_discriminator(length: {:?}, data: {:?})",
85 length,
86 data
87 );
88
89 if data.len() < length {
90 return None;
91 }
92
93 Some((&data[..length], &data[length..]))
94}
95
96/// A trait for defining a custom arrangement of Solana account metadata.
97///
98/// The `ArrangeAccounts` trait provides an interface for structuring account
99/// metadata in a custom format.
100///
101/// # Associated Types
102///
103/// - `ArrangedAccounts`: The output type representing the custom arrangement of
104/// accounts.
105pub trait ArrangeAccounts {
106 type ArrangedAccounts;
107
108 fn arrange_accounts(
109 accounts: &[solana_instruction::AccountMeta],
110 ) -> Option<Self::ArrangedAccounts>;
111}
112
113/// A wrapper type for strings that are prefixed with their length.
114
115#[derive(serde::Serialize, serde::Deserialize, Default, PartialEq, Eq, Clone)]
116pub struct PrefixString(pub String);
117
118impl Deref for PrefixString {
119 type Target = String;
120
121 fn deref(&self) -> &Self::Target {
122 &self.0
123 }
124}
125
126impl From<PrefixString> for String {
127 fn from(val: PrefixString) -> Self {
128 val.0
129 }
130}
131
132impl std::fmt::Debug for PrefixString {
133 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134 f.write_fmt(format_args!("{:?}", self.0))
135 }
136}
137
138/// Implements the `CarbonDeserialize` trait for `PrefixString`.
139impl crate::borsh::BorshDeserialize for PrefixString {
140 #[inline]
141 fn deserialize_reader<R: Read>(reader: &mut R) -> Result<Self> {
142 // read the length of the String
143 let mut buffer = vec![0u8; 4];
144 reader.read_exact(&mut buffer)?;
145 let length = u32::deserialize(&mut buffer.as_slice())?;
146 let mut buffer = vec![0u8; length as usize];
147 reader.read_exact(&mut buffer)?;
148
149 Ok(Self(String::from_utf8(buffer).map_err(|_| {
150 Error::new(ErrorKind::InvalidData, "invalid utf8")
151 })?))
152 }
153}
154
155/// A wrapper type for strings that are prefixed with their length.
156
157#[derive(serde::Serialize, Default, serde::Deserialize, PartialEq, Eq, Clone)]
158pub struct U64PrefixString(pub String);
159
160impl Deref for U64PrefixString {
161 type Target = String;
162
163 fn deref(&self) -> &Self::Target {
164 &self.0
165 }
166}
167
168impl From<U64PrefixString> for String {
169 fn from(val: U64PrefixString) -> Self {
170 val.0
171 }
172}
173
174impl std::fmt::Debug for U64PrefixString {
175 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176 f.write_fmt(format_args!("{:?}", self.0))
177 }
178}
179
180/// Implements the `CarbonDeserialize` trait for `U64PrefixString`.
181impl crate::borsh::BorshDeserialize for U64PrefixString {
182 #[inline]
183 fn deserialize_reader<R: Read>(reader: &mut R) -> Result<Self> {
184 // read the length of the String
185 let mut buffer = vec![0u8; 8];
186 reader.read_exact(&mut buffer)?;
187 let length = u64::deserialize(&mut buffer.as_slice())?;
188 let mut buffer = vec![0u8; length as usize];
189 reader.read_exact(&mut buffer)?;
190
191 Ok(Self(String::from_utf8(buffer).map_err(|_| {
192 Error::new(ErrorKind::InvalidData, "invalid utf8")
193 })?))
194 }
195}