Skip to main content

truthlinked_sdk_macros/
lib.rs

1//! Procedural macros for the TruthLinked SDK.
2//!
3//! This crate provides derive macros and attribute macros for smart contract development:
4//!
5//! - `#[derive(Event)]` - Generates event emission code
6//! - `#[derive(BytesCodec)]` - Generates variable-length encoding/decoding
7//! - `#[derive(Codec32)]` - Generates 32-byte encoding/decoding
8//! - `#[derive(Manifest)]` - Generates storage access manifests
9//! - `#[error_code]` - Defines error code constants
10//! - `#[require]` - Assertion macro with custom error codes
11
12use proc_macro::TokenStream;
13use proc_macro2::TokenStream as TokenStream2;
14use quote::{quote, ToTokens};
15use syn::{
16    parse::Parse, parse::ParseStream, parse_macro_input, spanned::Spanned, Attribute, Data,
17    DeriveInput, Expr, Fields, ItemEnum, ItemFn, LitInt, LitStr, Path, Token,
18};
19
20/// Derives the `Event` trait for structured logging.
21///
22/// # Attributes
23///
24/// - `#[event(name = "EventName")]` - Sets the event name
25/// - `#[event(anonymous)]` - Creates an anonymous event (no signature topic)
26/// - `#[topic]` - Marks a field as indexed
27/// - `#[event(skip)]` - Excludes a field from the event
28///
29/// # Example
30///
31/// ```ignore
32/// #[derive(Event)]
33/// #[event(name = "Transfer")]
34/// struct Transfer {
35///     #[topic]
36///     from: [u8; 32],
37///     #[topic]
38///     to: [u8; 32],
39///     amount: u128,
40/// }
41/// ```
42#[proc_macro_derive(Event, attributes(event, topic))]
43pub fn derive_event(input: TokenStream) -> TokenStream {
44    let input = parse_macro_input!(input as DeriveInput);
45    let name = input.ident;
46
47    let options = match parse_event_options(&name.to_string(), &input.attrs) {
48        Ok(v) => v,
49        Err(e) => return e,
50    };
51
52    let fields = match event_fields(&input.data) {
53        Ok(v) => v,
54        Err(e) => return e,
55    };
56
57    let topic_count = fields.iter().filter(|f| f.is_topic && !f.skip).count();
58    let max_topics = if options.anonymous { 8 } else { 7 };
59    if topic_count > max_topics {
60        let message = format!(
61            "event declares {} indexed fields; maximum is {} ({})",
62            topic_count,
63            max_topics,
64            if options.anonymous {
65                "anonymous event"
66            } else {
67                "topic0 reserved for signature"
68            }
69        );
70        return syn::Error::new(name.span(), message)
71            .to_compile_error()
72            .into();
73    }
74
75    let mut topic_pushes = Vec::new();
76    let mut data_pushes = Vec::new();
77    let mut type_names = Vec::new();
78
79    for field in fields {
80        if field.skip {
81            continue;
82        }
83
84        let access = field.access;
85        if field.is_topic {
86            if let Some(path) = field.with_topic {
87                topic_pushes.push(quote! {
88                    topics.push(#path(&#access));
89                });
90            } else {
91                topic_pushes.push(quote! {
92                    topics.push(::truthlinked_sdk::log::EventTopic::to_topic(&#access));
93                });
94            }
95        } else if let Some(path) = field.with_data {
96            data_pushes.push(quote! {
97                #path(&#access, &mut encoder);
98            });
99        } else {
100            data_pushes.push(quote! {
101                ::truthlinked_sdk::log::EventData::encode_event_data(&#access, &mut encoder);
102            });
103        }
104
105        let ty_name = field
106            .signature_type
107            .unwrap_or_else(|| normalize_type_name(&field.ty.to_token_stream().to_string()));
108        type_names.push(ty_name);
109    }
110
111    let signature = options
112        .signature
113        .unwrap_or_else(|| format!("{}({})", options.name, type_names.join(",")));
114    let event_name = options.name;
115    let anonymous = options.anonymous;
116
117    let expanded = quote! {
118        impl ::truthlinked_sdk::log::Event for #name {
119            fn event_name() -> &'static str {
120                #event_name
121            }
122
123            fn is_anonymous() -> bool {
124                #anonymous
125            }
126
127            fn event_signature() -> [u8; 32] {
128                ::truthlinked_sdk::log::event_signature(#signature)
129            }
130
131            fn event_topics(&self) -> ::truthlinked_sdk::log::__private::Vec<[u8; 32]> {
132                let mut topics = ::truthlinked_sdk::log::__private::Vec::new();
133                if !Self::is_anonymous() {
134                    topics.push(Self::event_signature());
135                }
136                #(#topic_pushes)*
137                topics
138            }
139
140            fn event_data(&self) -> ::truthlinked_sdk::log::__private::Vec<u8> {
141                let mut encoder = ::truthlinked_sdk::codec::Encoder::new();
142                #(#data_pushes)*
143                encoder.into_vec()
144            }
145        }
146    };
147
148    TokenStream::from(expanded)
149}
150
151/// Derives the `Manifest` trait for storage access declarations.
152///
153/// # Attributes
154///
155/// - `#[manifest(read)]` - Declares read access to a slot
156/// - `#[manifest(write)]` - Declares write access to a slot
157/// - `#[manifest(commutative)]` - Declares commutative access
158///
159/// # Example
160///
161/// ```ignore
162/// #[derive(Manifest)]
163/// struct Counter {
164///     #[manifest(read, write)]
165///     value: Slot,
166/// }
167/// ```
168#[proc_macro_derive(Manifest, attributes(manifest))]
169pub fn derive_manifest(input: TokenStream) -> TokenStream {
170    let input = parse_macro_input!(input as DeriveInput);
171    let name = input.ident;
172
173    let entries = match parse_manifest_attrs(&input.attrs) {
174        Ok(v) => v,
175        Err(e) => return e,
176    };
177
178    let mut statements = Vec::new();
179
180    for entry in entries {
181        match entry {
182            ManifestEntry::ReadSlot(slot) => statements.push(quote! {
183                manifest.add_read_slot(#slot);
184            }),
185            ManifestEntry::WriteSlot(slot) => statements.push(quote! {
186                manifest.add_write_slot(#slot);
187            }),
188            ManifestEntry::CommutativeSlot(slot) => statements.push(quote! {
189                manifest.add_commutative_key(#slot);
190            }),
191            ManifestEntry::ReadSlotExpr(path) => statements.push(quote! {
192                manifest.add_read_slot(#path);
193            }),
194            ManifestEntry::WriteSlotExpr(path) => statements.push(quote! {
195                manifest.add_write_slot(#path);
196            }),
197            ManifestEntry::CommutativeSlotExpr(path) => statements.push(quote! {
198                manifest.add_commutative_key(#path);
199            }),
200            ManifestEntry::ReadLabel(label) => statements.push(quote! {
201                manifest.add_read_slot(::truthlinked_sdk::storage::Slot::from_label(#label).0);
202            }),
203            ManifestEntry::WriteLabel(label) => statements.push(quote! {
204                manifest.add_write_slot(::truthlinked_sdk::storage::Slot::from_label(#label).0);
205            }),
206            ManifestEntry::CommutativeLabel(label) => statements.push(quote! {
207                manifest.add_commutative_key(::truthlinked_sdk::storage::Slot::from_label(#label).0);
208            }),
209            ManifestEntry::ReadDerived { namespace, key } => {
210                let slot = derived_slot_expr(&namespace, quote! { #key.as_bytes() });
211                statements.push(quote! {
212                    manifest.add_read_slot(#slot);
213                });
214            }
215            ManifestEntry::WriteDerived { namespace, key } => {
216                let slot = derived_slot_expr(&namespace, quote! { #key.as_bytes() });
217                statements.push(quote! {
218                    manifest.add_write_slot(#slot);
219                });
220            }
221            ManifestEntry::CommutativeDerived { namespace, key } => {
222                let slot = derived_slot_expr(&namespace, quote! { #key.as_bytes() });
223                statements.push(quote! {
224                    manifest.add_commutative_key(#slot);
225                });
226            }
227            ManifestEntry::ReadMap { namespace, key } => {
228                let exists = prefixed_slot_expr(&namespace, "map:exists", quote! { #key.as_bytes() });
229                let value = prefixed_slot_expr(&namespace, "map:value", quote! { #key.as_bytes() });
230                statements.push(quote! {
231                    manifest.add_read_slot(#exists);
232                    manifest.add_read_slot(#value);
233                });
234            }
235            ManifestEntry::WriteMap { namespace, key } => {
236                let exists = prefixed_slot_expr(&namespace, "map:exists", quote! { #key.as_bytes() });
237                let value = prefixed_slot_expr(&namespace, "map:value", quote! { #key.as_bytes() });
238                statements.push(quote! {
239                    manifest.add_write_slot(#exists);
240                    manifest.add_write_slot(#value);
241                });
242            }
243            ManifestEntry::ReadVecLen { namespace } => {
244                let len_slot = vec_len_slot_expr(&namespace);
245                statements.push(quote! {
246                    manifest.add_read_slot(#len_slot);
247                });
248            }
249            ManifestEntry::WriteVecLen { namespace } => {
250                let len_slot = vec_len_slot_expr(&namespace);
251                statements.push(quote! {
252                    manifest.add_write_slot(#len_slot);
253                });
254            }
255            ManifestEntry::ReadVecIndex { namespace, index } => {
256                let len_slot = vec_len_slot_expr(&namespace);
257                let elem_slot = vec_elem_slot_expr(&namespace, index);
258                statements.push(quote! {
259                    manifest.add_read_slot(#len_slot);
260                    manifest.add_read_slot(#elem_slot);
261                });
262            }
263            ManifestEntry::WriteVecIndex { namespace, index } => {
264                let len_slot = vec_len_slot_expr(&namespace);
265                let elem_slot = vec_elem_slot_expr(&namespace, index);
266                statements.push(quote! {
267                    manifest.add_read_slot(#len_slot);
268                    manifest.add_write_slot(#elem_slot);
269                });
270            }
271            ManifestEntry::ReadBlobChunk { namespace, chunk } => {
272                let len_slot = blob_len_slot_expr(&namespace);
273                let chunk_slot = blob_chunk_slot_expr(&namespace, chunk);
274                statements.push(quote! {
275                    manifest.add_read_slot(#len_slot);
276                    manifest.add_read_slot(#chunk_slot);
277                });
278            }
279            ManifestEntry::WriteBlobChunk { namespace, chunk } => {
280                let len_slot = blob_len_slot_expr(&namespace);
281                let chunk_slot = blob_chunk_slot_expr(&namespace, chunk);
282                statements.push(quote! {
283                    manifest.add_write_slot(#len_slot);
284                    manifest.add_write_slot(#chunk_slot);
285                });
286            }
287            ManifestEntry::StorageKeySpec { offset, len } => {
288                statements.push(quote! {
289                    manifest.add_storage_key_spec(::truthlinked_sdk::manifest::StorageKeySpec { offset: #offset, len: #len });
290                });
291            }
292        }
293    }
294
295    let expanded = quote! {
296        impl ::truthlinked_sdk::manifest::Manifest for #name {
297            fn manifest() -> ::truthlinked_sdk::manifest::ContractManifest {
298                let mut manifest = ::truthlinked_sdk::manifest::ContractManifest::new();
299                #(#statements)*
300                manifest.normalize();
301                manifest
302            }
303        }
304    };
305
306    TokenStream::from(expanded)
307}
308
309/// Derives the `BytesCodec` trait for variable-length encoding.
310///
311/// Automatically implements encoding and decoding for structs and enums.
312///
313/// # Example
314///
315/// ```ignore
316/// #[derive(BytesCodec)]
317/// struct Record {
318///     id: u64,
319///     name: String,
320/// }
321/// ```
322#[proc_macro_derive(BytesCodec)]
323pub fn derive_bytes_codec(input: TokenStream) -> TokenStream {
324    let input = parse_macro_input!(input as DeriveInput);
325    let name = input.ident;
326    let generated = match bytescodec_impl(&name, &input.data) {
327        Ok(tokens) => tokens,
328        Err(err) => return err.to_compile_error().into(),
329    };
330    TokenStream::from(generated)
331}
332
333/// Derives the `Codec32` trait for 32-byte fixed-size encoding.
334///
335/// Requires the type to also implement `BytesCodec`.
336///
337/// # Example
338///
339/// ```ignore
340/// #[derive(BytesCodec, Codec32)]
341/// struct SmallRecord {
342///     id: u16,
343///     enabled: bool,
344/// }
345/// ```
346#[proc_macro_derive(Codec32)]
347pub fn derive_codec32(input: TokenStream) -> TokenStream {
348    let input = parse_macro_input!(input as DeriveInput);
349    let name = input.ident;
350
351    let generated = quote! {
352        impl ::truthlinked_sdk::codec::Codec32 for #name
353        where
354            #name: ::truthlinked_sdk::codec::BytesCodec,
355        {
356            fn encode_32(&self) -> [u8; 32] {
357                let payload = ::truthlinked_sdk::codec::BytesCodec::encode_bytes(self);
358                if payload.len() > 31 {
359                    panic!("Codec32 derive overflow: encoded payload > 31 bytes");
360                }
361                let mut out = [0u8; 32];
362                out[0] = payload.len() as u8;
363                out[1..1 + payload.len()].copy_from_slice(&payload);
364                out
365            }
366
367            fn decode_32(bytes: &[u8; 32]) -> ::truthlinked_sdk::Result<Self> {
368                let len = bytes[0] as usize;
369                if len > 31 {
370                    return Err(::truthlinked_sdk::Error::new(::truthlinked_sdk::codec::ERR_CODEC));
371                }
372                <#name as ::truthlinked_sdk::codec::BytesCodec>::decode_bytes(&bytes[1..1 + len])
373            }
374        }
375    };
376    TokenStream::from(generated)
377}
378
379/// Attribute macro for defining error code enums.
380///
381/// # Example
382///
383/// ```ignore
384/// #[error_code(base = -100)]
385/// enum MyError {
386///     NotFound,      // -100
387///     #[code = -50]
388///     Custom,        // -50
389///     Invalid,       // -101
390/// }
391/// ```
392#[proc_macro_attribute]
393pub fn error_code(attr: TokenStream, item: TokenStream) -> TokenStream {
394    let args = parse_macro_input!(attr as ErrorCodeArgs);
395    let enum_item = parse_macro_input!(item as ItemEnum);
396
397    let mut next_auto = args.base;
398    let mut mappings = Vec::<(syn::Ident, i32)>::new();
399
400    for variant in &enum_item.variants {
401        if !matches!(variant.fields, Fields::Unit) {
402            return syn::Error::new_spanned(variant, "#[error_code] only supports unit variants")
403                .to_compile_error()
404                .into();
405        }
406
407        let code = if let Some((_, expr)) = &variant.discriminant {
408            match parse_i32_expr(expr) {
409                Ok(v) => v,
410                Err(err) => return err.to_compile_error().into(),
411            }
412        } else if let Some(next) = next_auto {
413            let current = next;
414            next_auto = next.checked_add(1);
415            current
416        } else {
417            return syn::Error::new_spanned(
418                variant,
419                "missing discriminant; use explicit `= code` or #[error_code(base = N)]",
420            )
421            .to_compile_error()
422            .into();
423        };
424
425        mappings.push((variant.ident.clone(), code));
426    }
427
428    let enum_name = enum_item.ident.clone();
429    let variants = mappings.iter().map(|(ident, _)| ident);
430    let codes = mappings.iter().map(|(_, code)| code);
431
432    let expanded = quote! {
433        #enum_item
434
435        impl #enum_name {
436            pub const fn code(self) -> i32 {
437                match self {
438                    #(Self::#variants => #codes,)*
439                }
440            }
441        }
442
443        impl From<#enum_name> for ::truthlinked_sdk::Error {
444            fn from(value: #enum_name) -> Self {
445                ::truthlinked_sdk::Error::new(value.code())
446            }
447        }
448
449        impl From<#enum_name> for i32 {
450            fn from(value: #enum_name) -> Self {
451                value.code()
452            }
453        }
454    };
455
456    TokenStream::from(expanded)
457}
458
459/// Attribute macro for adding precondition checks to functions.
460///
461/// # Example
462///
463/// ```ignore
464/// #[require(amount > 0, error = MyError::InvalidAmount)]
465/// fn transfer(amount: u64) -> Result<()> {
466///     // Function body
467///     Ok(())
468/// }
469/// ```
470#[proc_macro_attribute]
471pub fn require(attr: TokenStream, item: TokenStream) -> TokenStream {
472    let args = parse_macro_input!(attr as RequireArgs);
473    let mut function = parse_macro_input!(item as ItemFn);
474
475    let condition = args.condition;
476    let error_expr = if let Some(expr) = args.error {
477        quote! { #expr }
478    } else {
479        quote! { ::truthlinked_sdk::Error::new(::truthlinked_sdk::error::ERR_REQUIRE) }
480    };
481
482    let guard_stmt: syn::Stmt = match syn::parse2(quote! {
483        if !(#condition) {
484            return core::result::Result::Err((#error_expr).into());
485        }
486    }) {
487        Ok(stmt) => stmt,
488        Err(err) => return err.to_compile_error().into(),
489    };
490
491    function.block.stmts.insert(0, guard_stmt);
492    TokenStream::from(quote! { #function })
493}
494
495struct EventOptions {
496    name: String,
497    signature: Option<String>,
498    anonymous: bool,
499}
500
501fn parse_event_options(
502    default_name: &str,
503    attrs: &[Attribute],
504) -> Result<EventOptions, TokenStream> {
505    let mut options = EventOptions {
506        name: default_name.to_string(),
507        signature: None,
508        anonymous: false,
509    };
510
511    for attr in attrs {
512        if !attr.path().is_ident("event") {
513            continue;
514        }
515        let parse_result = attr.parse_nested_meta(|meta| {
516            if meta.path.is_ident("name") {
517                let value: LitStr = meta.value()?.parse()?;
518                options.name = value.value();
519                return Ok(());
520            }
521            if meta.path.is_ident("signature") {
522                let value: LitStr = meta.value()?.parse()?;
523                options.signature = Some(value.value());
524                return Ok(());
525            }
526            if meta.path.is_ident("anonymous") {
527                options.anonymous = true;
528                return Ok(());
529            }
530            Err(meta.error("unsupported event option"))
531        });
532        if let Err(err) = parse_result {
533            return Err(err.to_compile_error().into());
534        }
535    }
536
537    Ok(options)
538}
539
540struct EventField {
541    access: proc_macro2::TokenStream,
542    ty: syn::Type,
543    is_topic: bool,
544    skip: bool,
545    signature_type: Option<String>,
546    with_topic: Option<Path>,
547    with_data: Option<Path>,
548}
549
550fn event_fields(data: &Data) -> Result<Vec<EventField>, TokenStream> {
551    let mut fields_out = Vec::new();
552    match data {
553        Data::Struct(data_struct) => {
554            match &data_struct.fields {
555                Fields::Named(named) => {
556                    for field in &named.named {
557                        let options = match parse_event_field_options(field) {
558                            Ok(v) => v,
559                            Err(e) => return Err(e.to_compile_error().into()),
560                        };
561                        let ident = field.ident.clone().expect("named field must have ident");
562                        fields_out.push(EventField {
563                            access: quote! { self.#ident },
564                            ty: field.ty.clone(),
565                            is_topic: options.is_topic,
566                            skip: options.skip,
567                            signature_type: options.signature_type,
568                            with_topic: options.with_topic,
569                            with_data: options.with_data,
570                        });
571                    }
572                }
573                Fields::Unnamed(unnamed) => {
574                    for (idx, field) in unnamed.unnamed.iter().enumerate() {
575                        let options = match parse_event_field_options(field) {
576                            Ok(v) => v,
577                            Err(e) => return Err(e.to_compile_error().into()),
578                        };
579                        let index = syn::Index::from(idx);
580                        fields_out.push(EventField {
581                            access: quote! { self.#index },
582                            ty: field.ty.clone(),
583                            is_topic: options.is_topic,
584                            skip: options.skip,
585                            signature_type: options.signature_type,
586                            with_topic: options.with_topic,
587                            with_data: options.with_data,
588                        });
589                    }
590                }
591                Fields::Unit => {}
592            }
593            Ok(fields_out)
594        }
595        _ => Err(syn::Error::new(
596            proc_macro2::Span::call_site(),
597            "Event can only be derived for structs",
598        )
599        .to_compile_error()
600        .into()),
601    }
602}
603
604struct EventFieldOptions {
605    is_topic: bool,
606    skip: bool,
607    signature_type: Option<String>,
608    with_topic: Option<Path>,
609    with_data: Option<Path>,
610}
611
612fn parse_event_field_options(field: &syn::Field) -> syn::Result<EventFieldOptions> {
613    let mut out = EventFieldOptions {
614        is_topic: false,
615        skip: false,
616        signature_type: None,
617        with_topic: None,
618        with_data: None,
619    };
620
621    for attr in &field.attrs {
622        if attr.path().is_ident("topic") {
623            out.is_topic = true;
624            continue;
625        }
626
627        if !attr.path().is_ident("event") {
628            continue;
629        }
630
631        attr.parse_nested_meta(|meta| {
632            if meta.path.is_ident("topic") || meta.path.is_ident("indexed") {
633                out.is_topic = true;
634                return Ok(());
635            }
636            if meta.path.is_ident("skip") {
637                out.skip = true;
638                return Ok(());
639            }
640            if meta.path.is_ident("type") {
641                let value: LitStr = meta.value()?.parse()?;
642                out.signature_type = Some(value.value());
643                return Ok(());
644            }
645            if meta.path.is_ident("with_topic") {
646                let value: LitStr = meta.value()?.parse()?;
647                let path = syn::parse_str::<Path>(&value.value())
648                    .map_err(|_| meta.error("with_topic must be a valid path string"))?;
649                out.with_topic = Some(path);
650                out.is_topic = true;
651                return Ok(());
652            }
653            if meta.path.is_ident("with_data") {
654                let value: LitStr = meta.value()?.parse()?;
655                let path = syn::parse_str::<Path>(&value.value())
656                    .map_err(|_| meta.error("with_data must be a valid path string"))?;
657                out.with_data = Some(path);
658                return Ok(());
659            }
660            Err(meta.error("unsupported field event option"))
661        })?;
662    }
663
664    if out.skip && out.is_topic {
665        return Err(syn::Error::new(
666            field.span(),
667            "field cannot be both #[topic] and #[event(skip)]",
668        ));
669    }
670    if out.skip && out.with_data.is_some() {
671        return Err(syn::Error::new(
672            field.span(),
673            "field cannot be both #[event(skip)] and #[event(with_data = ...)]",
674        ));
675    }
676    if out.is_topic && out.with_data.is_some() {
677        return Err(syn::Error::new(
678            field.span(),
679            "topic fields cannot define #[event(with_data = ...)]",
680        ));
681    }
682
683    Ok(out)
684}
685
686enum ManifestEntry {
687    ReadSlot(proc_macro2::TokenStream),
688    WriteSlot(proc_macro2::TokenStream),
689    CommutativeSlot(proc_macro2::TokenStream),
690    ReadSlotExpr(Path),
691    WriteSlotExpr(Path),
692    CommutativeSlotExpr(Path),
693    ReadLabel(String),
694    WriteLabel(String),
695    CommutativeLabel(String),
696    ReadDerived { namespace: String, key: String },
697    WriteDerived { namespace: String, key: String },
698    CommutativeDerived { namespace: String, key: String },
699    ReadMap { namespace: String, key: String },
700    WriteMap { namespace: String, key: String },
701    ReadVecLen { namespace: String },
702    WriteVecLen { namespace: String },
703    ReadVecIndex { namespace: String, index: u64 },
704    WriteVecIndex { namespace: String, index: u64 },
705    ReadBlobChunk { namespace: String, chunk: u64 },
706    WriteBlobChunk { namespace: String, chunk: u64 },
707    StorageKeySpec { offset: usize, len: usize },
708}
709
710fn parse_manifest_attrs(attrs: &[Attribute]) -> Result<Vec<ManifestEntry>, TokenStream> {
711    let mut out = Vec::new();
712
713    for attr in attrs {
714        if !attr.path().is_ident("manifest") {
715            continue;
716        }
717
718        let parse_result = attr.parse_nested_meta(|meta| {
719            if meta.path.is_ident("read_slot") {
720                let value: LitStr = meta.value()?.parse()?;
721                let slot = parse_slot_hex_literal(&value)?;
722                out.push(ManifestEntry::ReadSlot(slot));
723                return Ok(());
724            }
725            if meta.path.is_ident("write_slot") {
726                let value: LitStr = meta.value()?.parse()?;
727                let slot = parse_slot_hex_literal(&value)?;
728                out.push(ManifestEntry::WriteSlot(slot));
729                return Ok(());
730            }
731            if meta.path.is_ident("commutative_slot") {
732                let value: LitStr = meta.value()?.parse()?;
733                let slot = parse_slot_hex_literal(&value)?;
734                out.push(ManifestEntry::CommutativeSlot(slot));
735                return Ok(());
736            }
737
738            if meta.path.is_ident("read_slot_expr") {
739                let value: LitStr = meta.value()?.parse()?;
740                let path = parse_path_literal(&value)?;
741                out.push(ManifestEntry::ReadSlotExpr(path));
742                return Ok(());
743            }
744            if meta.path.is_ident("write_slot_expr") {
745                let value: LitStr = meta.value()?.parse()?;
746                let path = parse_path_literal(&value)?;
747                out.push(ManifestEntry::WriteSlotExpr(path));
748                return Ok(());
749            }
750            if meta.path.is_ident("commutative_slot_expr") {
751                let value: LitStr = meta.value()?.parse()?;
752                let path = parse_path_literal(&value)?;
753                out.push(ManifestEntry::CommutativeSlotExpr(path));
754                return Ok(());
755            }
756
757            if meta.path.is_ident("read_label") {
758                let value: LitStr = meta.value()?.parse()?;
759                out.push(ManifestEntry::ReadLabel(value.value()));
760                return Ok(());
761            }
762            if meta.path.is_ident("write_label") {
763                let value: LitStr = meta.value()?.parse()?;
764                out.push(ManifestEntry::WriteLabel(value.value()));
765                return Ok(());
766            }
767            if meta.path.is_ident("commutative_label") {
768                let value: LitStr = meta.value()?.parse()?;
769                out.push(ManifestEntry::CommutativeLabel(value.value()));
770                return Ok(());
771            }
772
773            if meta.path.is_ident("read_derived") {
774                let (namespace, key) = parse_namespace_key(meta)?;
775                out.push(ManifestEntry::ReadDerived { namespace, key });
776                return Ok(());
777            }
778            if meta.path.is_ident("write_derived") {
779                let (namespace, key) = parse_namespace_key(meta)?;
780                out.push(ManifestEntry::WriteDerived { namespace, key });
781                return Ok(());
782            }
783            if meta.path.is_ident("commutative_derived") {
784                let (namespace, key) = parse_namespace_key(meta)?;
785                out.push(ManifestEntry::CommutativeDerived { namespace, key });
786                return Ok(());
787            }
788
789            if meta.path.is_ident("read_map") {
790                let (namespace, key) = parse_namespace_key(meta)?;
791                out.push(ManifestEntry::ReadMap { namespace, key });
792                return Ok(());
793            }
794            if meta.path.is_ident("write_map") {
795                let (namespace, key) = parse_namespace_key(meta)?;
796                out.push(ManifestEntry::WriteMap { namespace, key });
797                return Ok(());
798            }
799
800            if meta.path.is_ident("read_vec_len") {
801                let namespace = parse_namespace(meta)?;
802                out.push(ManifestEntry::ReadVecLen { namespace });
803                return Ok(());
804            }
805            if meta.path.is_ident("write_vec_len") {
806                let namespace = parse_namespace(meta)?;
807                out.push(ManifestEntry::WriteVecLen { namespace });
808                return Ok(());
809            }
810            if meta.path.is_ident("read_vec_index") {
811                let (namespace, index) = parse_namespace_index(meta, "index")?;
812                out.push(ManifestEntry::ReadVecIndex { namespace, index });
813                return Ok(());
814            }
815            if meta.path.is_ident("write_vec_index") {
816                let (namespace, index) = parse_namespace_index(meta, "index")?;
817                out.push(ManifestEntry::WriteVecIndex { namespace, index });
818                return Ok(());
819            }
820
821            if meta.path.is_ident("read_blob_chunk") {
822                let (namespace, chunk) = parse_namespace_index(meta, "chunk")?;
823                out.push(ManifestEntry::ReadBlobChunk { namespace, chunk });
824                return Ok(());
825            }
826            if meta.path.is_ident("write_blob_chunk") {
827                let (namespace, chunk) = parse_namespace_index(meta, "chunk")?;
828                out.push(ManifestEntry::WriteBlobChunk { namespace, chunk });
829                return Ok(());
830            }
831
832            if meta.path.is_ident("key_spec") {
833                let mut offset = None;
834                let mut len = None;
835                meta.parse_nested_meta(|nested| {
836                    if nested.path.is_ident("offset") {
837                        let expr: Expr = nested.value()?.parse()?;
838                        offset = Some(expr_to_usize(&expr)?);
839                        return Ok(());
840                    }
841                    if nested.path.is_ident("len") {
842                        let expr: Expr = nested.value()?.parse()?;
843                        len = Some(expr_to_usize(&expr)?);
844                        return Ok(());
845                    }
846                    Err(nested.error("unsupported key_spec argument"))
847                })?;
848
849                let offset = offset.ok_or_else(|| meta.error("key_spec missing offset"))?;
850                let len = len.ok_or_else(|| meta.error("key_spec missing len"))?;
851                out.push(ManifestEntry::StorageKeySpec { offset, len });
852                return Ok(());
853            }
854
855            Err(meta.error("unsupported manifest attribute"))
856        });
857
858        if let Err(err) = parse_result {
859            return Err(err.to_compile_error().into());
860        }
861    }
862
863    Ok(out)
864}
865
866fn parse_namespace(meta: syn::meta::ParseNestedMeta<'_>) -> syn::Result<String> {
867    let mut namespace = None;
868    meta.parse_nested_meta(|nested| {
869        if nested.path.is_ident("namespace") {
870            let value: LitStr = nested.value()?.parse()?;
871            namespace = Some(value.value());
872            return Ok(());
873        }
874        Err(nested.error("expected namespace=..."))
875    })?;
876
877    namespace.ok_or_else(|| meta.error("missing namespace"))
878}
879
880fn parse_namespace_key(meta: syn::meta::ParseNestedMeta<'_>) -> syn::Result<(String, String)> {
881    let mut namespace = None;
882    let mut key = None;
883
884    meta.parse_nested_meta(|nested| {
885        if nested.path.is_ident("namespace") {
886            let value: LitStr = nested.value()?.parse()?;
887            namespace = Some(value.value());
888            return Ok(());
889        }
890        if nested.path.is_ident("key") {
891            let value: LitStr = nested.value()?.parse()?;
892            key = Some(value.value());
893            return Ok(());
894        }
895        Err(nested.error("expected namespace=... or key=..."))
896    })?;
897
898    let namespace = namespace.ok_or_else(|| meta.error("derived spec missing namespace"))?;
899    let key = key.ok_or_else(|| meta.error("derived spec missing key"))?;
900    Ok((namespace, key))
901}
902
903fn parse_namespace_index(
904    meta: syn::meta::ParseNestedMeta<'_>,
905    key_name: &str,
906) -> syn::Result<(String, u64)> {
907    let mut namespace = None;
908    let mut index = None;
909
910    meta.parse_nested_meta(|nested| {
911        if nested.path.is_ident("namespace") {
912            let value: LitStr = nested.value()?.parse()?;
913            namespace = Some(value.value());
914            return Ok(());
915        }
916        if nested.path.is_ident(key_name) {
917            let expr: Expr = nested.value()?.parse()?;
918            index = Some(expr_to_u64(&expr)?);
919            return Ok(());
920        }
921        Err(nested.error("unexpected argument"))
922    })?;
923
924    let namespace = namespace.ok_or_else(|| meta.error("missing namespace"))?;
925    let index = index.ok_or_else(|| meta.error(format!("missing {}", key_name)))?;
926    Ok((namespace, index))
927}
928
929fn expr_to_usize(expr: &Expr) -> syn::Result<usize> {
930    if let Expr::Lit(expr_lit) = expr {
931        if let syn::Lit::Int(int_lit) = &expr_lit.lit {
932            return int_lit.base10_parse::<usize>();
933        }
934    }
935    Err(syn::Error::new_spanned(expr, "expected integer literal"))
936}
937
938fn expr_to_u64(expr: &Expr) -> syn::Result<u64> {
939    if let Expr::Lit(expr_lit) = expr {
940        if let syn::Lit::Int(int_lit) = &expr_lit.lit {
941            return int_lit.base10_parse::<u64>();
942        }
943    }
944    Err(syn::Error::new_spanned(expr, "expected integer literal"))
945}
946
947fn parse_slot_hex_literal(value: &LitStr) -> syn::Result<proc_macro2::TokenStream> {
948    let mut text = value.value();
949    if let Some(stripped) = text.strip_prefix("0x") {
950        text = stripped.to_string();
951    }
952
953    if text.len() != 64 {
954        return Err(syn::Error::new_spanned(
955            value,
956            "slot hex must be exactly 32 bytes (64 hex chars)",
957        ));
958    }
959
960    let mut bytes = [0u8; 32];
961    for i in 0..32 {
962        let pair = &text[i * 2..i * 2 + 2];
963        bytes[i] = u8::from_str_radix(pair, 16)
964            .map_err(|_| syn::Error::new_spanned(value, "slot hex contains non-hex characters"))?;
965    }
966
967    let values = bytes.iter();
968    Ok(quote! { [#(#values),*] })
969}
970
971fn parse_path_literal(value: &LitStr) -> syn::Result<Path> {
972    syn::parse_str::<Path>(&value.value())
973        .map_err(|_| syn::Error::new_spanned(value, "expected a valid Rust path string"))
974}
975
976fn derived_slot_expr(namespace: &str, part: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
977    quote!({
978        let __ns = ::truthlinked_sdk::storage::namespace(#namespace);
979        ::truthlinked_sdk::hashing::derive_slot(&__ns, &[#part])
980    })
981}
982
983fn prefixed_slot_expr(
984    namespace: &str,
985    prefix: &str,
986    part: proc_macro2::TokenStream,
987) -> proc_macro2::TokenStream {
988    let prefix_lit = syn::LitByteStr::new(prefix.as_bytes(), proc_macro2::Span::call_site());
989    quote!({
990        let __ns = ::truthlinked_sdk::storage::namespace(#namespace);
991        ::truthlinked_sdk::hashing::derive_slot(&__ns, &[#prefix_lit, #part])
992    })
993}
994
995fn vec_len_slot_expr(namespace: &str) -> proc_macro2::TokenStream {
996    quote!({
997        let __ns = ::truthlinked_sdk::storage::namespace(#namespace);
998        ::truthlinked_sdk::hashing::derive_slot(&__ns, &[b"vec:len"])
999    })
1000}
1001
1002fn vec_elem_slot_expr(namespace: &str, index: u64) -> proc_macro2::TokenStream {
1003    quote!({
1004        let __ns = ::truthlinked_sdk::storage::namespace(#namespace);
1005        let __idx = (#index as u64).to_le_bytes();
1006        ::truthlinked_sdk::hashing::derive_slot(&__ns, &[b"vec:elem", &__idx])
1007    })
1008}
1009
1010fn blob_len_slot_expr(namespace: &str) -> proc_macro2::TokenStream {
1011    quote!({
1012        let __ns = ::truthlinked_sdk::storage::namespace(#namespace);
1013        ::truthlinked_sdk::hashing::derive_slot(&__ns, &[b"blob:len"])
1014    })
1015}
1016
1017fn blob_chunk_slot_expr(namespace: &str, chunk: u64) -> proc_macro2::TokenStream {
1018    quote!({
1019        let __ns = ::truthlinked_sdk::storage::namespace(#namespace);
1020        let __idx = (#chunk as u64).to_le_bytes();
1021        ::truthlinked_sdk::hashing::derive_slot(&__ns, &[b"blob:chunk", &__idx])
1022    })
1023}
1024
1025fn normalize_type_name(input: &str) -> String {
1026    input.chars().filter(|c| !c.is_whitespace()).collect()
1027}
1028
1029fn bytescodec_impl(name: &syn::Ident, data: &Data) -> syn::Result<TokenStream2> {
1030    match data {
1031        Data::Struct(data_struct) => bytescodec_struct_impl(name, data_struct),
1032        Data::Enum(data_enum) => bytescodec_enum_impl(name, data_enum),
1033        _ => Err(syn::Error::new(
1034            proc_macro2::Span::call_site(),
1035            "BytesCodec can only be derived for structs or enums",
1036        )),
1037    }
1038}
1039
1040fn bytescodec_struct_impl(
1041    name: &syn::Ident,
1042    data_struct: &syn::DataStruct,
1043) -> syn::Result<TokenStream2> {
1044    match &data_struct.fields {
1045        Fields::Named(named) => {
1046            let fields: Vec<_> = named
1047                .named
1048                .iter()
1049                .map(|field| {
1050                    let ident = field.ident.clone().expect("named field");
1051                    let ty = field.ty.clone();
1052                    (ident, ty)
1053                })
1054                .collect();
1055
1056            let encode_fields = fields
1057                .iter()
1058                .map(|(ident, _)| encode_field_expr(quote! { &self.#ident }));
1059            let decode_fields = fields
1060                .iter()
1061                .map(|(ident, ty)| decode_field_expr(quote! { #ident }, ty));
1062            let idents = fields.iter().map(|(ident, _)| ident);
1063
1064            Ok(quote! {
1065                impl ::truthlinked_sdk::codec::BytesCodec for #name {
1066                    fn encode_bytes(&self) -> ::truthlinked_sdk::log::__private::Vec<u8> {
1067                        let mut __out = ::truthlinked_sdk::log::__private::Vec::new();
1068                        #(#encode_fields)*
1069                        __out
1070                    }
1071
1072                    fn decode_bytes(bytes: &[u8]) -> ::truthlinked_sdk::Result<Self> {
1073                        let mut __cursor = 0usize;
1074                        #(#decode_fields)*
1075                        if __cursor != bytes.len() {
1076                            return Err(::truthlinked_sdk::Error::new(::truthlinked_sdk::codec::ERR_CODEC));
1077                        }
1078                        Ok(Self { #(#idents),* })
1079                    }
1080                }
1081            })
1082        }
1083        Fields::Unnamed(unnamed) => {
1084            let fields: Vec<_> = unnamed
1085                .unnamed
1086                .iter()
1087                .enumerate()
1088                .map(|(idx, field)| {
1089                    (
1090                        syn::Index::from(idx),
1091                        field.ty.clone(),
1092                        syn::Ident::new(&format!("__f{}", idx), field.span()),
1093                    )
1094                })
1095                .collect();
1096
1097            let encode_fields = fields
1098                .iter()
1099                .map(|(index, _, _)| encode_field_expr(quote! { &self.#index }));
1100            let decode_fields = fields
1101                .iter()
1102                .map(|(_, ty, ident)| decode_field_expr(quote! { #ident }, ty));
1103            let idents = fields.iter().map(|(_, _, ident)| ident);
1104
1105            Ok(quote! {
1106                impl ::truthlinked_sdk::codec::BytesCodec for #name {
1107                    fn encode_bytes(&self) -> ::truthlinked_sdk::log::__private::Vec<u8> {
1108                        let mut __out = ::truthlinked_sdk::log::__private::Vec::new();
1109                        #(#encode_fields)*
1110                        __out
1111                    }
1112
1113                    fn decode_bytes(bytes: &[u8]) -> ::truthlinked_sdk::Result<Self> {
1114                        let mut __cursor = 0usize;
1115                        #(#decode_fields)*
1116                        if __cursor != bytes.len() {
1117                            return Err(::truthlinked_sdk::Error::new(::truthlinked_sdk::codec::ERR_CODEC));
1118                        }
1119                        Ok(Self(#(#idents),*))
1120                    }
1121                }
1122            })
1123        }
1124        Fields::Unit => Ok(quote! {
1125            impl ::truthlinked_sdk::codec::BytesCodec for #name {
1126                fn encode_bytes(&self) -> ::truthlinked_sdk::log::__private::Vec<u8> {
1127                    ::truthlinked_sdk::log::__private::Vec::new()
1128                }
1129
1130                fn decode_bytes(bytes: &[u8]) -> ::truthlinked_sdk::Result<Self> {
1131                    if !bytes.is_empty() {
1132                        return Err(::truthlinked_sdk::Error::new(::truthlinked_sdk::codec::ERR_CODEC));
1133                    }
1134                    Ok(Self)
1135                }
1136            }
1137        }),
1138    }
1139}
1140
1141fn bytescodec_enum_impl(name: &syn::Ident, data_enum: &syn::DataEnum) -> syn::Result<TokenStream2> {
1142    let mut encode_arms = Vec::new();
1143    let mut decode_arms = Vec::new();
1144
1145    for (variant_idx, variant) in data_enum.variants.iter().enumerate() {
1146        let discriminant = variant_idx as u32;
1147        let variant_ident = &variant.ident;
1148
1149        match &variant.fields {
1150            Fields::Unit => {
1151                encode_arms.push(quote! {
1152                    Self::#variant_ident => {
1153                        __out.extend_from_slice(&#discriminant.to_le_bytes());
1154                    }
1155                });
1156
1157                decode_arms.push(quote! {
1158                    #discriminant => {
1159                        if __cursor != bytes.len() {
1160                            return Err(::truthlinked_sdk::Error::new(::truthlinked_sdk::codec::ERR_CODEC));
1161                        }
1162                        Ok(Self::#variant_ident)
1163                    }
1164                });
1165            }
1166            Fields::Unnamed(unnamed) => {
1167                let bindings: Vec<_> = unnamed
1168                    .unnamed
1169                    .iter()
1170                    .enumerate()
1171                    .map(|(idx, field)| {
1172                        (
1173                            syn::Ident::new(&format!("__v{}", idx), field.span()),
1174                            field.ty.clone(),
1175                        )
1176                    })
1177                    .collect();
1178
1179                let pattern_idents = bindings.iter().map(|(ident, _)| ident);
1180                let encode_fields = bindings
1181                    .iter()
1182                    .map(|(ident, _)| encode_field_expr(quote! { #ident }));
1183
1184                let decode_bindings = bindings
1185                    .iter()
1186                    .map(|(ident, ty)| decode_field_expr(quote! { #ident }, ty));
1187                let construct_idents = bindings.iter().map(|(ident, _)| ident);
1188
1189                encode_arms.push(quote! {
1190                    Self::#variant_ident(#(#pattern_idents),*) => {
1191                        __out.extend_from_slice(&#discriminant.to_le_bytes());
1192                        #(#encode_fields)*
1193                    }
1194                });
1195
1196                decode_arms.push(quote! {
1197                    #discriminant => {
1198                        #(#decode_bindings)*
1199                        if __cursor != bytes.len() {
1200                            return Err(::truthlinked_sdk::Error::new(::truthlinked_sdk::codec::ERR_CODEC));
1201                        }
1202                        Ok(Self::#variant_ident(#(#construct_idents),*))
1203                    }
1204                });
1205            }
1206            Fields::Named(named) => {
1207                let bindings: Vec<_> = named
1208                    .named
1209                    .iter()
1210                    .map(|field| (field.ident.clone().expect("named field"), field.ty.clone()))
1211                    .collect();
1212
1213                let pattern_idents = bindings.iter().map(|(ident, _)| ident);
1214                let encode_fields = bindings
1215                    .iter()
1216                    .map(|(ident, _)| encode_field_expr(quote! { #ident }));
1217                let decode_bindings = bindings
1218                    .iter()
1219                    .map(|(ident, ty)| decode_field_expr(quote! { #ident }, ty));
1220                let construct_idents = bindings.iter().map(|(ident, _)| ident);
1221
1222                encode_arms.push(quote! {
1223                    Self::#variant_ident { #(#pattern_idents),* } => {
1224                        __out.extend_from_slice(&#discriminant.to_le_bytes());
1225                        #(#encode_fields)*
1226                    }
1227                });
1228
1229                decode_arms.push(quote! {
1230                    #discriminant => {
1231                        #(#decode_bindings)*
1232                        if __cursor != bytes.len() {
1233                            return Err(::truthlinked_sdk::Error::new(::truthlinked_sdk::codec::ERR_CODEC));
1234                        }
1235                        Ok(Self::#variant_ident { #(#construct_idents),* })
1236                    }
1237                });
1238            }
1239        }
1240    }
1241
1242    Ok(quote! {
1243        impl ::truthlinked_sdk::codec::BytesCodec for #name {
1244            fn encode_bytes(&self) -> ::truthlinked_sdk::log::__private::Vec<u8> {
1245                let mut __out = ::truthlinked_sdk::log::__private::Vec::new();
1246                match self {
1247                    #(#encode_arms),*
1248                }
1249                __out
1250            }
1251
1252            fn decode_bytes(bytes: &[u8]) -> ::truthlinked_sdk::Result<Self> {
1253                if bytes.len() < 4 {
1254                    return Err(::truthlinked_sdk::Error::new(::truthlinked_sdk::codec::ERR_CODEC_EOF));
1255                }
1256                let mut __cursor = 0usize;
1257                let mut __discriminant_raw = [0u8; 4];
1258                __discriminant_raw.copy_from_slice(&bytes[..4]);
1259                __cursor = 4;
1260                let __discriminant = u32::from_le_bytes(__discriminant_raw);
1261                match __discriminant {
1262                    #(#decode_arms,)*
1263                    _ => Err(::truthlinked_sdk::Error::new(::truthlinked_sdk::codec::ERR_CODEC)),
1264                }
1265            }
1266        }
1267    })
1268}
1269
1270fn encode_field_expr(access: TokenStream2) -> TokenStream2 {
1271    quote! {
1272        let __field = ::truthlinked_sdk::codec::BytesCodec::encode_bytes(#access);
1273        let __field_len = (__field.len() as u32).to_le_bytes();
1274        __out.extend_from_slice(&__field_len);
1275        __out.extend_from_slice(&__field);
1276    }
1277}
1278
1279fn decode_field_expr(binding: TokenStream2, ty: &syn::Type) -> TokenStream2 {
1280    quote! {
1281        let #binding = {
1282            let __end_len = __cursor
1283                .checked_add(4)
1284                .ok_or_else(|| ::truthlinked_sdk::Error::new(::truthlinked_sdk::codec::ERR_CODEC_EOF))?;
1285            if __end_len > bytes.len() {
1286                return Err(::truthlinked_sdk::Error::new(::truthlinked_sdk::codec::ERR_CODEC_EOF));
1287            }
1288            let mut __len_raw = [0u8; 4];
1289            __len_raw.copy_from_slice(&bytes[__cursor..__end_len]);
1290            __cursor = __end_len;
1291            let __field_len = u32::from_le_bytes(__len_raw) as usize;
1292            let __end_field = __cursor
1293                .checked_add(__field_len)
1294                .ok_or_else(|| ::truthlinked_sdk::Error::new(::truthlinked_sdk::codec::ERR_CODEC_EOF))?;
1295            if __end_field > bytes.len() {
1296                return Err(::truthlinked_sdk::Error::new(::truthlinked_sdk::codec::ERR_CODEC_EOF));
1297            }
1298            let __field_value = <#ty as ::truthlinked_sdk::codec::BytesCodec>::decode_bytes(
1299                &bytes[__cursor..__end_field],
1300            )?;
1301            __cursor = __end_field;
1302            __field_value
1303        };
1304    }
1305}
1306
1307struct ErrorCodeArgs {
1308    base: Option<i32>,
1309}
1310
1311impl Parse for ErrorCodeArgs {
1312    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
1313        if input.is_empty() {
1314            return Ok(Self { base: None });
1315        }
1316        let ident: syn::Ident = input.parse()?;
1317        if ident != "base" {
1318            return Err(syn::Error::new(ident.span(), "expected `base = <i32>`"));
1319        }
1320        input.parse::<Token![=]>()?;
1321        let base: LitInt = input.parse()?;
1322        let base_value = base.base10_parse::<i32>()?;
1323        Ok(Self {
1324            base: Some(base_value),
1325        })
1326    }
1327}
1328
1329fn parse_i32_expr(expr: &Expr) -> syn::Result<i32> {
1330    if let Expr::Lit(expr_lit) = expr {
1331        if let syn::Lit::Int(int_lit) = &expr_lit.lit {
1332            return int_lit.base10_parse::<i32>();
1333        }
1334    }
1335    Err(syn::Error::new_spanned(
1336        expr,
1337        "error code discriminant must be i32 literal",
1338    ))
1339}
1340
1341struct RequireArgs {
1342    condition: Expr,
1343    error: Option<Expr>,
1344}
1345
1346impl Parse for RequireArgs {
1347    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
1348        let condition: Expr = input.parse()?;
1349        let error = if input.peek(Token![,]) {
1350            input.parse::<Token![,]>()?;
1351            Some(input.parse::<Expr>()?)
1352        } else {
1353            None
1354        };
1355        Ok(Self { condition, error })
1356    }
1357}