1use 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#[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#[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#[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#[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#[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#[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}