reth_codecs_derive/compact/
mod.rs1use proc_macro::TokenStream;
2use proc_macro2::{Ident, TokenStream as TokenStream2};
3use quote::{format_ident, quote};
4use syn::{Data, DeriveInput, Generics};
5
6mod generator;
7use generator::*;
8
9mod enums;
10use enums::*;
11
12mod flags;
13use flags::*;
14
15mod structs;
16use structs::*;
17
18use crate::ZstdConfig;
19
20type FieldType = String;
22type UseAlternative = bool;
28#[derive(Debug, Clone, Eq, PartialEq)]
30pub struct StructFieldDescriptor {
31 name: String,
32 ftype: String,
33 is_compact: bool,
34 use_alt_impl: bool,
35 is_reference: bool,
36}
37type FieldList = Vec<FieldTypes>;
39
40#[derive(Debug, Clone, Eq, PartialEq)]
41pub enum FieldTypes {
42 StructField(StructFieldDescriptor),
43 EnumVariant(String),
44 EnumUnnamedField((FieldType, UseAlternative)),
45}
46
47pub fn derive(input: DeriveInput, zstd: Option<ZstdConfig>) -> TokenStream {
49 let mut output = quote! {};
50
51 let DeriveInput { ident, data, generics, attrs, .. } = input;
52
53 let has_lifetime = has_lifetime(&generics);
54
55 let fields = get_fields(&data);
56 output.extend(generate_flag_struct(&ident, &attrs, has_lifetime, &fields, zstd.is_some()));
57 output.extend(generate_from_to(&ident, &attrs, has_lifetime, &fields, zstd));
58 output.into()
59}
60
61pub fn has_lifetime(generics: &Generics) -> bool {
62 generics.lifetimes().next().is_some()
63}
64
65pub fn get_fields(data: &Data) -> FieldList {
67 let mut fields = vec![];
68
69 match data {
70 Data::Struct(data) => match data.fields {
71 syn::Fields::Named(ref data_fields) => {
72 for field in &data_fields.named {
73 load_field(field, &mut fields, false);
74 }
75 assert_eq!(fields.len(), data_fields.named.len(), "get_fields");
76 }
77 syn::Fields::Unnamed(ref data_fields) => {
78 assert_eq!(
79 data_fields.unnamed.len(),
80 1,
81 "Compact only allows one unnamed field. Consider making it a struct."
82 );
83 load_field(&data_fields.unnamed[0], &mut fields, false);
84 }
85 syn::Fields::Unit => todo!(),
86 },
87 Data::Enum(data) => {
88 for variant in &data.variants {
89 fields.push(FieldTypes::EnumVariant(variant.ident.to_string()));
90
91 match &variant.fields {
92 syn::Fields::Named(_) => {
93 panic!(
94 "Not allowed to have Enum Variants with multiple named fields. Make it a struct instead."
95 )
96 }
97 syn::Fields::Unnamed(data_fields) => {
98 assert_eq!(
99 data_fields.unnamed.len(),
100 1,
101 "Compact only allows one unnamed field. Consider making it a struct."
102 );
103 load_field(&data_fields.unnamed[0], &mut fields, true);
104 }
105 syn::Fields::Unit => (),
106 }
107 }
108 }
109 Data::Union(_) => todo!(),
110 }
111
112 fields
113}
114
115fn load_field(field: &syn::Field, fields: &mut FieldList, is_enum: bool) {
116 match field.ty {
117 syn::Type::Reference(ref reference) => match &*reference.elem {
118 syn::Type::Path(path) => {
119 load_field_from_segments(&path.path.segments, is_enum, fields, field)
120 }
121 _ => unimplemented!("{:?}", &field.ident),
122 },
123 syn::Type::Path(ref path) => {
124 load_field_from_segments(&path.path.segments, is_enum, fields, field)
125 }
126 _ => unimplemented!("{:?}", &field.ident),
127 }
128}
129
130fn load_field_from_segments(
131 segments: &syn::punctuated::Punctuated<syn::PathSegment, syn::token::PathSep>,
132 is_enum: bool,
133 fields: &mut Vec<FieldTypes>,
134 field: &syn::Field,
135) {
136 if !segments.is_empty() {
137 let mut ftype = String::new();
138
139 let mut use_alt_impl: UseAlternative = false;
140
141 for (index, segment) in segments.iter().enumerate() {
142 ftype.push_str(&segment.ident.to_string());
143 if index < segments.len() - 1 {
144 ftype.push_str("::");
145 }
146
147 use_alt_impl = should_use_alt_impl(&ftype, segment);
148 }
149
150 if is_enum {
151 fields.push(FieldTypes::EnumUnnamedField((ftype, use_alt_impl)));
152 } else {
153 let should_compact = is_flag_type(&ftype) ||
154 field.attrs.iter().any(|attr| {
155 attr.path().segments.iter().any(|path| path.ident == "maybe_zero")
156 });
157
158 fields.push(FieldTypes::StructField(StructFieldDescriptor {
159 name: field.ident.as_ref().map(|i| i.to_string()).unwrap_or_default(),
160 ftype,
161 is_compact: should_compact,
162 use_alt_impl,
163 is_reference: matches!(field.ty, syn::Type::Reference(_)),
164 }));
165 }
166 }
167}
168
169fn should_use_alt_impl(ftype: &str, segment: &syn::PathSegment) -> bool {
174 if ftype == "Vec" || ftype == "Option" {
175 if let syn::PathArguments::AngleBracketed(ref args) = segment.arguments {
176 if let Some(syn::GenericArgument::Type(syn::Type::Path(arg_path))) = args.args.last() {
177 if let (Some(path), 1) =
178 (arg_path.path.segments.first(), arg_path.path.segments.len())
179 {
180 if [
181 "B256",
182 "Address",
183 "Address",
184 "Bloom",
185 "TxHash",
186 "BlockHash",
187 "CompactPlaceholder",
188 ]
189 .contains(&path.ident.to_string().as_str())
190 {
191 return true
192 }
193 }
194 }
195 }
196 }
197 false
198}
199
200pub fn get_bit_size(ftype: &str) -> u8 {
203 match ftype {
204 "TransactionKind" | "TxKind" | "bool" | "Option" | "Signature" => 1,
205 "TxType" | "OpTxType" | "SeismicTxType" => 2,
206 "u64" | "BlockNumber" | "TxNumber" | "ChainId" | "NumTransactions" => 4,
207 "u128" => 5,
208 "U256" => 6,
209 "u8" => 1,
210 _ => 0,
211 }
212}
213
214pub fn is_flag_type(ftype: &str) -> bool {
217 get_bit_size(ftype) > 0
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223 use similar_asserts::assert_eq;
224 use syn::parse2;
225
226 #[test]
227 fn compact_codec() {
228 let f_struct = quote! {
229 #[derive(Debug, PartialEq, Clone)]
230 pub struct TestStruct {
231 f_u64: u64,
232 f_u256: U256,
233 f_bool_t: bool,
234 f_bool_f: bool,
235 f_option_none: Option<U256>,
236 f_option_some: Option<B256>,
237 f_option_some_u64: Option<u64>,
238 f_vec_empty: Vec<U256>,
239 f_vec_some: Vec<Address>,
240 }
241 };
242
243 let mut output = quote! {};
245 let DeriveInput { ident, data, attrs, .. } = parse2(f_struct).unwrap();
246 let fields = get_fields(&data);
247 output.extend(generate_flag_struct(&ident, &attrs, false, &fields, false));
248 output.extend(generate_from_to(&ident, &attrs, false, &fields, None));
249
250 let should_output = quote! {
252 impl TestStruct {
253 #[doc = "Used bytes by [`TestStructFlags`]"]
254 pub const fn bitflag_encoded_bytes() -> usize {
255 2u8 as usize
256 }
257 #[doc = "Unused bits for new fields by [`TestStructFlags`]"]
258 pub const fn bitflag_unused_bits() -> usize {
259 1u8 as usize
260 }
261 }
262
263 pub use TestStruct_flags::TestStructFlags;
264
265 #[expect(non_snake_case)]
266 mod TestStruct_flags {
267 use reth_codecs::__private::Buf;
268 use reth_codecs::__private::modular_bitfield;
269 use reth_codecs::__private::modular_bitfield::prelude::*;
270 #[doc = "Fieldset that facilitates compacting the parent type. Used bytes: 2 | Unused bits: 1"]
271 #[bitfield]
272 #[derive(Clone, Copy, Debug, Default)]
273 pub struct TestStructFlags {
274 pub f_u64_len: B4,
275 pub f_u256_len: B6,
276 pub f_bool_t_len: B1,
277 pub f_bool_f_len: B1,
278 pub f_option_none_len: B1,
279 pub f_option_some_len: B1,
280 pub f_option_some_u64_len: B1,
281 #[skip]
282 unused: B1,
283 }
284 impl TestStructFlags {
285 #[doc = r" Deserializes this fieldset and returns it, alongside the original slice in an advanced position."]
286 pub fn from(mut buf: &[u8]) -> (Self, &[u8]) {
287 (
288 TestStructFlags::from_bytes([buf.get_u8(), buf.get_u8(),]),
289 buf
290 )
291 }
292 }
293 }
294 #[cfg(test)]
295 #[expect(dead_code)]
296 #[test_fuzz::test_fuzz]
297 fn fuzz_test_test_struct(obj: TestStruct) {
298 use reth_codecs::Compact;
299 let mut buf = vec![];
300 let len = obj.clone().to_compact(&mut buf);
301 let (same_obj, buf) = TestStruct::from_compact(buf.as_ref(), len);
302 assert_eq!(obj, same_obj);
303 }
304 #[test]
305 #[expect(missing_docs)]
306 pub fn fuzz_test_struct() {
307 fuzz_test_test_struct(TestStruct::default())
308 }
309 impl reth_codecs::Compact for TestStruct {
310 fn to_compact<B>(&self, buf: &mut B) -> usize where B: reth_codecs::__private::bytes::BufMut + AsMut<[u8]> {
311 let mut flags = TestStructFlags::default();
312 let mut total_length = 0;
313 let mut buffer = reth_codecs::__private::bytes::BytesMut::new();
314 let f_u64_len = self.f_u64.to_compact(&mut buffer);
315 flags.set_f_u64_len(f_u64_len as u8);
316 let f_u256_len = self.f_u256.to_compact(&mut buffer);
317 flags.set_f_u256_len(f_u256_len as u8);
318 let f_bool_t_len = self.f_bool_t.to_compact(&mut buffer);
319 flags.set_f_bool_t_len(f_bool_t_len as u8);
320 let f_bool_f_len = self.f_bool_f.to_compact(&mut buffer);
321 flags.set_f_bool_f_len(f_bool_f_len as u8);
322 let f_option_none_len = self.f_option_none.to_compact(&mut buffer);
323 flags.set_f_option_none_len(f_option_none_len as u8);
324 let f_option_some_len = self.f_option_some.specialized_to_compact(&mut buffer);
325 flags.set_f_option_some_len(f_option_some_len as u8);
326 let f_option_some_u64_len = self.f_option_some_u64.to_compact(&mut buffer);
327 flags.set_f_option_some_u64_len(f_option_some_u64_len as u8);
328 let f_vec_empty_len = self.f_vec_empty.to_compact(&mut buffer);
329 let f_vec_some_len = self.f_vec_some.specialized_to_compact(&mut buffer);
330 let flags = flags.into_bytes();
331 total_length += flags.len() + buffer.len();
332 buf.put_slice(&flags);
333 buf.put(buffer);
334 total_length
335 }
336 fn from_compact(mut buf: &[u8], len: usize) -> (Self, &[u8]) {
337 let (flags, mut buf) = TestStructFlags::from(buf);
338 let (f_u64, new_buf) = u64::from_compact(buf, flags.f_u64_len() as usize);
339 buf = new_buf;
340 let (f_u256, new_buf) = U256::from_compact(buf, flags.f_u256_len() as usize);
341 buf = new_buf;
342 let (f_bool_t, new_buf) = bool::from_compact(buf, flags.f_bool_t_len() as usize);
343 buf = new_buf;
344 let (f_bool_f, new_buf) = bool::from_compact(buf, flags.f_bool_f_len() as usize);
345 buf = new_buf;
346 let (f_option_none, new_buf) = Option::from_compact(buf, flags.f_option_none_len() as usize);
347 buf = new_buf;
348 let (f_option_some, new_buf) = Option::specialized_from_compact(buf, flags.f_option_some_len() as usize);
349 buf = new_buf;
350 let (f_option_some_u64, new_buf) = Option::from_compact(buf, flags.f_option_some_u64_len() as usize);
351 buf = new_buf;
352 let (f_vec_empty, new_buf) = Vec::from_compact(buf, buf.len());
353 buf = new_buf;
354 let (f_vec_some, new_buf) = Vec::specialized_from_compact(buf, buf.len());
355 buf = new_buf;
356 let obj = TestStruct {
357 f_u64: f_u64,
358 f_u256: f_u256,
359 f_bool_t: f_bool_t,
360 f_bool_f: f_bool_f,
361 f_option_none: f_option_none,
362 f_option_some: f_option_some,
363 f_option_some_u64: f_option_some_u64,
364 f_vec_empty: f_vec_empty,
365 f_vec_some: f_vec_some,
366 };
367 (obj, buf)
368 }
369 }
370 };
371
372 assert_eq!(
373 syn::parse2::<syn::File>(output).unwrap(),
374 syn::parse2::<syn::File>(should_output).unwrap()
375 );
376 }
377}