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}