1use std::{
62 collections::HashMap,
63 hash::{DefaultHasher, Hash, Hasher},
64 iter::once,
65};
66
67use alloy_json_abi::ContractObject;
68use proc_macro::TokenStream;
69use proc_macro2::{
70 Delimiter, Group, Ident as Ident2, Punct, Spacing, Span, TokenStream as TokenStream2, TokenTree,
71};
72use quote::{format_ident, quote};
73use syn::{
74 Attribute, Ident, LitStr, Result, Token,
75 parse::{Parse, ParseStream},
76 parse_macro_input,
77};
78use syn_solidity::{
79 File as SolFile, FunctionKind, Item as SolItem, ItemContract, ItemEvent, ItemFunction, Spanned,
80 Type as SolType,
81};
82
83#[proc_macro]
85pub fn tron_sol(input: TokenStream) -> TokenStream {
86 let original = TokenStream2::from(input.clone());
87 let parsed = parse_macro_input!(input as TronSol);
88 match parsed.expand(original) {
89 Ok(ts) => ts.into(),
90 Err(e) => e.to_compile_error().into(),
91 }
92}
93
94struct TronSol {
95 items: Vec<SolItem>,
96 krate: TokenStream2,
97 pre_forwarded: Option<TokenStream2>,
101 include_path: Option<String>,
105}
106
107impl Parse for TronSol {
108 fn parse(input: ParseStream<'_>) -> Result<Self> {
109 {
116 let fork = input.fork();
117 let _ = Attribute::parse_inner(&fork);
118 let _ = Attribute::parse_outer(&fork);
119 if fork.peek(Ident) {
120 let inner = fork.fork();
121 if inner.parse::<Ident>().is_ok() && inner.peek(Token![,]) {
122 return Self::parse_json_abi(input);
123 }
124 }
125 }
126
127 let mut items = Vec::new();
129 while !input.is_empty() {
130 items.push(input.parse::<SolItem>()?);
131 }
132
133 let mut krate: TokenStream2 = quote!(::tronz::contract);
136 for item in &items {
137 let Some(attrs) = item.attrs() else { continue };
138 for attr in attrs {
139 if attr.path().is_ident("tron_sol") {
140 attr.parse_nested_meta(|meta| {
141 if meta.path.is_ident("tronz_crate") {
142 let path: syn::Path = meta.value()?.parse()?;
143 krate = quote!(#path);
144 Ok(())
145 } else {
146 Err(meta.error("unknown `tron_sol` option; expected `tronz_crate`"))
147 }
148 })?;
149 }
150 }
151 }
152
153 Ok(Self { items, krate, pre_forwarded: None, include_path: None })
154 }
155}
156
157impl TronSol {
158 fn parse_json_abi(input: ParseStream<'_>) -> Result<Self> {
160 let inner_attrs = Attribute::parse_inner(input)?;
161 let attrs = Attribute::parse_outer(input)?;
162 let name: Ident = input.parse()?;
163 input.parse::<Token![,]>()?;
164 let path_lit: LitStr = input.parse()?;
165 let _ = input.parse::<Option<Token![,]>>()?;
166
167 let path_str = path_lit.value();
168 let (json, include_path) = {
169 let trimmed = path_str.trim();
170 if trimmed.starts_with('{') || trimmed.starts_with('[') {
171 (path_str.clone(), None)
172 } else {
173 let p = std::path::PathBuf::from(&path_str);
174 let abs = if p.is_relative() {
175 let dir =
176 std::env::var("CARGO_MANIFEST_DIR").map(std::path::PathBuf::from).map_err(
177 |_| syn::Error::new(path_lit.span(), "CARGO_MANIFEST_DIR is not set"),
178 )?;
179 dir.join(p)
180 } else {
181 p
182 };
183 let canonical = std::fs::canonicalize(&abs).map_err(|e| {
186 syn::Error::new(
187 path_lit.span(),
188 format!("failed to canonicalize `{path_str}`: {e}"),
189 )
190 })?;
191 let json = std::fs::read_to_string(&canonical).map_err(|e| {
192 syn::Error::new(path_lit.span(), format!("failed to read `{path_str}`: {e}"))
193 })?;
194 let abs_str = canonical.to_str().ok_or_else(|| {
195 syn::Error::new(path_lit.span(), "ABI file path is not valid UTF-8")
196 })?;
197 (json, Some(abs_str.to_owned()))
198 }
199 };
200
201 let obj = ContractObject::from_json(&json).map_err(|e| {
203 syn::Error::new(path_lit.span(), format!("invalid JSON ABI in `{path_str}`: {e}"))
204 })?;
205 let abi = obj.abi.ok_or_else(|| {
206 syn::Error::new(path_lit.span(), format!("`{path_str}` contains no `abi` field"))
207 })?;
208
209 let sol_ts: TokenStream2 = abi.to_sol(&name.to_string(), None).parse().map_err(|e| {
211 syn::Error::new(
212 path_lit.span(),
213 format!("ABI-generated Solidity failed to tokenize: {e}"),
214 )
215 })?;
216
217 let forwarded = quote! { #(#inner_attrs)* #sol_ts };
222
223 let full_ts = quote! { #(#inner_attrs)* #(#attrs)* #sol_ts };
226 let file: SolFile = syn::parse2(full_ts).map_err(|e| {
227 syn::Error::new(
228 path_lit.span(),
229 format!("failed to parse generated Solidity interface: {e}"),
230 )
231 })?;
232 let items = file.items;
233
234 let mut krate: TokenStream2 = quote!(::tronz::contract);
236 for item in &items {
237 let Some(item_attrs) = item.attrs() else { continue };
238 for attr in item_attrs {
239 if attr.path().is_ident("tron_sol") {
240 attr.parse_nested_meta(|meta| {
241 if meta.path.is_ident("tronz_crate") {
242 let path: syn::Path = meta.value()?.parse()?;
243 krate = quote!(#path);
244 Ok(())
245 } else {
246 Err(meta.error("unknown `tron_sol` option; expected `tronz_crate`"))
247 }
248 })?;
249 }
250 }
251 }
252
253 Ok(Self { items, krate, pre_forwarded: Some(forwarded), include_path })
254 }
255
256 fn expand(&self, original: TokenStream2) -> Result<TokenStream2> {
257 let kpriv = {
260 let k = &self.krate;
261 quote!(#k::__private)
262 };
263 let alloy = quote!(#kpriv::alloy_sol_types);
264
265 let input_hash = {
268 let mut hasher = DefaultHasher::new();
269 original.to_string().hash(&mut hasher);
270 hasher.finish()
271 };
272
273 let forwarded = match self.pre_forwarded.clone() {
277 Some(ts) => ts,
278 None => strip_tron_attrs(original),
279 };
280
281 let include = self.include_path.as_deref().map(|p| {
284 quote! { const _: &'static [u8] = ::core::include_bytes!(#p); }
285 });
286
287 let mut rpc_contracts: Vec<&ItemContract> = Vec::new();
288 for item in &self.items {
289 if let SolItem::Contract(c) = item
290 && contract_opts(&c.attrs)?.rpc
291 {
292 rpc_contracts.push(c);
293 }
294 }
295
296 if rpc_contracts.is_empty() {
299 return Ok(quote! {
300 #include
301 #alloy::sol! {
302 #![sol(alloy_sol_types = #alloy)]
303 #forwarded
304 }
305 });
306 }
307
308 let types_mod = format_ident!("__tron_sol_types_{:x}", input_hash);
317
318 let mut instances = Vec::new();
319 for c in &rpc_contracts {
320 instances.push(self.expand_contract(c, &kpriv, &types_mod)?);
321 }
322
323 Ok(quote! {
324 #include
325
326 #[doc(hidden)]
327 #[allow(non_camel_case_types, non_snake_case, missing_docs, clippy::all)]
328 mod #types_mod {
329 #alloy::sol! {
330 #![sol(alloy_sol_types = #alloy)]
331 #forwarded
332 }
333 }
334
335 #[allow(unused_imports)]
336 pub use #types_mod::*;
337
338 #(#instances)*
339 })
340 }
341
342 fn expand_contract(
345 &self,
346 c: &ItemContract,
347 kpriv: &TokenStream2,
348 types_mod: &Ident,
349 ) -> Result<TokenStream2> {
350 let name = c.name.0.clone();
351 let opts = contract_opts(&c.attrs)?;
352
353 if opts.rename {
354 return Err(syn::Error::new(
355 c.name.span(),
356 "`#[sol(rename/rename_all)]` is not supported together with `#[sol(rpc)]`: \
357 renaming the generated `…Call` types would desync the instance methods",
358 ));
359 }
360
361 let alloy = quote!(#kpriv::alloy_sol_types);
362 let aprim = quote!(#kpriv::alloy_primitives);
363 let taddr = quote!(#kpriv::tronz_primitives::Address);
364 let provider_tr = quote!(#kpriv::tronz_provider::TronProvider);
365 let cinst = quote!(#kpriv::ContractInstance);
366 let tcb = quote!(#kpriv::TronCallBuilder);
367 let tef = quote!(#kpriv::TronEventFilter);
368 let deploy_builder_ty = quote!(#kpriv::DeployBuilder);
369 let result_ty = quote!(#kpriv::Result);
370
371 let mut functions: Vec<ItemFunction> = Vec::new();
375 let mut constructor: Option<ItemFunction> = None;
376 let mut events: Vec<ItemEvent> = Vec::new();
377 for item in &c.body {
378 match item {
379 SolItem::Function(f) => {
380 if matches!(f.kind, FunctionKind::Function(_)) && f.name.is_some() {
381 functions.push(f.clone());
382 } else if matches!(f.kind, FunctionKind::Constructor(_)) {
383 constructor = Some(f.clone());
384 }
385 }
386 SolItem::Variable(v) => {
387 if v.attributes.visibility().is_some_and(|vis| vis.is_public()) {
390 functions.push(ItemFunction::from_variable_definition(v.clone()));
391 }
392 }
393 SolItem::Event(e) => {
394 events.push(e.clone());
395 }
396 _ => {}
397 }
398 }
399
400 let mut counts: HashMap<String, usize> = HashMap::new();
402 for f in &functions {
403 if let Some(n) = &f.name {
404 *counts.entry(n.to_string()).or_default() += 1;
405 }
406 }
407 let mut seen: HashMap<String, usize> = HashMap::new();
408 let methods = functions
409 .iter()
410 .map(|f| {
411 let base = f.name.as_ref().expect("name checked above").to_string();
412 let effective = if counts.get(&base).copied().unwrap_or(0) > 1 {
413 let idx = seen.entry(base.clone()).or_insert(0);
414 let e = format!("{base}_{idx}");
415 *idx += 1;
416 e
417 } else {
418 base
419 };
420 let method_name = if is_reserved_method(&effective) {
421 format!("{effective}_call")
422 } else {
423 effective.clone()
424 };
425 expand_method(f, &effective, &method_name, &alloy, &aprim, &tcb)
426 })
427 .collect::<Result<Vec<_>>>()?;
428
429 let deployed_bytecode_tokens = match &opts.deployed_bytecode {
431 None => quote!(),
432 Some(bytes) => {
433 let byte_vals = bytes.iter().copied();
434 quote! {
435 pub static DEPLOYED_BYTECODE: #aprim::Bytes =
440 #aprim::Bytes::from_static(&[#(#byte_vals),*]);
441 }
442 }
443 };
444
445 let (deploy_tokens, deploy_instance_tokens) = match &opts.bytecode {
447 None => (quote!(), quote!()),
448 Some(bytes) => {
449 let byte_vals = bytes.iter().copied();
450
451 let (ctor_decls, ctor_names, ctor_values) = match &constructor {
452 None => (vec![], vec![], vec![]),
453 Some(c) => collect_params(&c.parameters, &alloy, &aprim)?,
454 };
455
456 let ctor_sig = quote!(#(, #ctor_decls)*);
457 let ctor_fwd = quote!(#(, #ctor_names)*);
458 let bytecode_expr = if ctor_names.is_empty() {
459 quote!(BYTECODE.clone())
460 } else {
461 quote!(#aprim::Bytes::from(
462 [
463 &BYTECODE[..],
464 &#alloy::SolConstructor::abi_encode(&constructorCall {
465 #(#ctor_names: #ctor_values),*
466 })[..]
467 ].concat()
468 ))
469 };
470
471 let free_fns = quote! {
472 pub static BYTECODE: #aprim::Bytes =
474 #aprim::Bytes::from_static(&[#(#byte_vals),*]);
475
476 #[inline]
478 pub fn deploy_builder<P: #provider_tr>(
479 provider: P #ctor_sig
480 ) -> #deploy_builder_ty<P> {
481 Instance::<P>::deploy_builder(provider #ctor_fwd)
482 }
483
484 #[inline]
486 pub async fn deploy<P: #provider_tr + ::core::clone::Clone>(
487 provider: P #ctor_sig
488 ) -> #result_ty<Instance<P>> {
489 Instance::<P>::deploy(provider #ctor_fwd).await
490 }
491 };
492
493 let instance_methods = quote! {
494 impl<P: #provider_tr + ::core::clone::Clone> Instance<P> {
495 #[inline]
497 pub fn deploy_builder(
498 provider: P #ctor_sig
499 ) -> #deploy_builder_ty<P> {
500 #deploy_builder_ty::new(provider, #bytecode_expr)
501 }
502
503 #[inline]
505 pub async fn deploy(
506 provider: P #ctor_sig
507 ) -> #result_ty<Self> {
508 let address = Self::deploy_builder(provider.clone() #ctor_fwd)
509 .deploy()
510 .await?;
511 ::core::result::Result::Ok(new(address, provider))
512 }
513 }
514 };
515
516 (free_fns, instance_methods)
517 }
518 };
519
520 let mut ev_counts: HashMap<String, usize> = HashMap::new();
522 for e in &events {
523 *ev_counts.entry(e.name.to_string()).or_default() += 1;
524 }
525 let mut ev_seen: HashMap<String, usize> = HashMap::new();
526 let event_filter_methods = events
527 .iter()
528 .map(|e| {
529 let base = e.name.to_string();
530 let effective = if ev_counts.get(&base).copied().unwrap_or(0) > 1 {
531 let idx = ev_seen.entry(base.clone()).or_insert(0);
532 let s = format!("{base}_{idx}");
533 *idx += 1;
534 s
535 } else {
536 base
537 };
538 let method_name = format_ident!("{}_filter", effective);
539 let event_ty = format_ident!("{}", effective);
540 let doc = format!("Creates an event filter for the [`{effective}`] event.");
541 quote! {
542 #[doc = #doc]
543 #[allow(non_snake_case)]
544 pub fn #method_name(&self) -> #tef<P, #event_ty> {
545 self.event_filter::<#event_ty>()
546 }
547 }
548 })
549 .collect::<Vec<_>>();
550
551 Ok(quote! {
552 #[allow(non_snake_case, clippy::pub_underscore_fields)]
553 pub mod #name {
554 pub use super::#types_mod::#name::*;
556 #[allow(unused_imports)]
559 use super::#types_mod::*;
560
561 #deployed_bytecode_tokens
562
563 #deploy_tokens
564
565 #[derive(::core::clone::Clone)]
567 pub struct Instance<P: #provider_tr> {
568 inner: #cinst<P>,
569 }
570
571 impl<P: #provider_tr> ::core::fmt::Debug for Instance<P> {
572 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
573 f.debug_struct(::core::stringify!(#name))
574 .field("address", &self.inner.address())
575 .finish()
576 }
577 }
578
579 pub fn new<P: #provider_tr>(address: #taddr, provider: P) -> Instance<P> {
581 Instance { inner: #cinst::new_raw(provider, address) }
582 }
583
584 impl<P: #provider_tr> Instance<P> {
585 #[inline]
587 pub fn address(&self) -> #taddr {
588 self.inner.address()
589 }
590
591 #[inline]
593 pub fn set_address(&mut self, address: #taddr) {
594 self.inner.set_address(address);
595 }
596
597 #[inline]
599 pub fn at(mut self, address: #taddr) -> Self {
600 self.set_address(address);
601 self
602 }
603
604 #[inline]
606 pub fn provider(&self) -> &P {
607 self.inner.provider()
608 }
609
610 #[inline]
613 pub fn call_builder<C: #alloy::SolCall>(&self, call: &C) -> #tcb<P, C> {
614 #tcb::new(self.inner.call_raw(
615 #alloy::SolCall::abi_encode(call).into()
616 ))
617 }
618
619 #(#methods)*
620 }
621
622 impl<P: #provider_tr> Instance<P> {
623 #[inline]
626 pub fn event_filter<E: #alloy::SolEvent>(&self) -> #tef<P, E> {
627 #tef::new(self.inner.provider().clone(), Some(self.inner.address()))
628 }
629
630 #(#event_filter_methods)*
631 }
632
633 #deploy_instance_tokens
634 }
635 })
636 }
637}
638
639#[derive(Default)]
641struct ContractOpts {
642 rpc: bool,
643 bytecode: Option<Vec<u8>>,
644 deployed_bytecode: Option<Vec<u8>>,
645 rename: bool,
647}
648
649fn contract_opts(attrs: &[Attribute]) -> Result<ContractOpts> {
653 let mut opts = ContractOpts::default();
654 for attr in attrs {
655 if !attr.path().is_ident("sol") {
656 continue;
657 }
658 attr.parse_nested_meta(|meta| {
659 if meta.path.is_ident("rpc") {
660 opts.rpc = if meta.input.peek(syn::Token![=]) {
661 meta.value()?.parse::<syn::LitBool>()?.value
662 } else {
663 true
664 };
665 } else if meta.path.is_ident("bytecode") {
666 opts.bytecode = Some(parse_hex(&meta.value()?.parse::<LitStr>()?)?);
667 } else if meta.path.is_ident("deployed_bytecode") {
668 opts.deployed_bytecode = Some(parse_hex(&meta.value()?.parse::<LitStr>()?)?);
669 } else {
670 if meta.path.is_ident("rename") || meta.path.is_ident("rename_all") {
671 opts.rename = true;
672 }
673 if meta.input.peek(syn::Token![=]) {
678 let _: syn::Expr = meta.value()?.parse()?;
679 } else if meta.input.peek(syn::token::Paren) {
680 let content;
681 syn::parenthesized!(content in meta.input);
682 let _: TokenStream2 = content.parse()?;
683 }
684 }
685 Ok(())
686 })?;
687 }
688 Ok(opts)
689}
690
691fn strip_tron_attrs(ts: TokenStream2) -> TokenStream2 {
698 let tokens: Vec<TokenTree> = ts.into_iter().collect();
699 let mut out = TokenStream2::new();
700 let mut i = 0;
701 while i < tokens.len() {
702 if let TokenTree::Group(g) = &tokens[i] {
704 let mut ng = Group::new(g.delimiter(), strip_tron_attrs(g.stream()));
705 ng.set_span(g.span());
706 out.extend(once(TokenTree::Group(ng)));
707 i += 1;
708 continue;
709 }
710
711 if let TokenTree::Punct(p) = &tokens[i]
713 && p.as_char() == '#'
714 {
715 let mut j = i + 1;
716 let bang = matches!(tokens.get(j), Some(TokenTree::Punct(q)) if q.as_char() == '!');
717 if bang {
718 j += 1;
719 }
720 if let Some(TokenTree::Group(g)) = tokens.get(j)
721 && g.delimiter() == Delimiter::Bracket
722 {
723 if let Some(inner) = rewrite_attr(g.stream()) {
725 out.extend(once(tokens[i].clone()));
726 if bang {
727 out.extend(once(tokens[i + 1].clone()));
728 }
729 let mut ng = Group::new(Delimiter::Bracket, inner);
730 ng.set_span(g.span());
731 out.extend(once(TokenTree::Group(ng)));
732 }
733 i = j + 1;
734 continue;
735 }
736 }
737
738 out.extend(once(tokens[i].clone()));
739 i += 1;
740 }
741 out
742}
743
744fn rewrite_attr(inner: TokenStream2) -> Option<TokenStream2> {
749 let toks: Vec<TokenTree> = inner.clone().into_iter().collect();
750 let lead = match toks.first() {
751 Some(TokenTree::Ident(id)) => id.to_string(),
752 _ => return Some(inner),
753 };
754
755 match lead.as_str() {
756 "tron_sol" => None,
758 "sol" => {
760 if let Some(TokenTree::Group(g)) = toks.get(1)
761 && g.delimiter() == Delimiter::Parenthesis
762 {
763 let kept = filter_sol_meta(g.stream());
764 if kept.is_empty() {
765 return None;
766 }
767 let mut grp = Group::new(Delimiter::Parenthesis, kept);
768 grp.set_span(g.span());
769 return Some([toks[0].clone(), TokenTree::Group(grp)].into_iter().collect());
770 }
771 Some(inner)
772 }
773 _ => Some(inner),
775 }
776}
777
778fn filter_sol_meta(stream: TokenStream2) -> TokenStream2 {
781 let mut items: Vec<Vec<TokenTree>> = vec![Vec::new()];
783 for tt in stream {
784 if let TokenTree::Punct(p) = &tt
785 && p.as_char() == ','
786 {
787 items.push(Vec::new());
788 continue;
789 }
790 items.last_mut().expect("non-empty").push(tt);
791 }
792
793 let mut out = TokenStream2::new();
794 let mut first = true;
795 for item in items {
796 if item.is_empty() {
797 continue;
798 }
799 let key = match item.first() {
800 Some(TokenTree::Ident(id)) => id.to_string(),
801 _ => String::new(),
802 };
803 if matches!(key.as_str(), "rpc" | "bytecode" | "deployed_bytecode") {
804 continue;
805 }
806 if !first {
807 out.extend(once(TokenTree::Punct(Punct::new(',', Spacing::Alone))));
808 }
809 first = false;
810 out.extend(item);
811 }
812 out
813}
814
815fn is_reserved_method(name: &str) -> bool {
818 matches!(
819 name,
820 "new"
821 | "deploy"
822 | "deploy_builder"
823 | "address"
824 | "set_address"
825 | "at"
826 | "provider"
827 | "call_builder"
828 | "event_filter"
829 )
830}
831
832fn expand_method(
837 f: &ItemFunction,
838 call_base: &str,
839 method_name: &str,
840 alloy: &TokenStream2,
841 aprim: &TokenStream2,
842 tcb: &TokenStream2,
843) -> Result<TokenStream2> {
844 let fn_ident = format_ident!("{}", method_name);
845 let call_struct = format_ident!("{}Call", call_base);
846
847 let (decls, names, values) = collect_params(&f.parameters, alloy, aprim)?;
848
849 Ok(quote! {
850 #[allow(non_snake_case, clippy::too_many_arguments)]
851 pub fn #fn_ident(&self, #(#decls),*) -> #tcb<P, #call_struct> {
852 self.call_builder(&#call_struct { #(#names: #values),* })
853 }
854 })
855}
856
857fn collect_params(
863 parameters: &syn_solidity::ParameterList,
864 alloy: &TokenStream2,
865 aprim: &TokenStream2,
866) -> Result<(Vec<TokenStream2>, Vec<Ident2>, Vec<TokenStream2>)> {
867 let mut decls = Vec::new();
868 let mut names = Vec::new();
869 let mut values = Vec::new();
870 for (i, var) in parameters.iter().enumerate() {
871 let field: Ident2 = match &var.name {
875 Some(n) => n.0.clone(),
876 None => format_ident!("_{}", i),
877 };
878 match &var.ty {
879 SolType::Address(..) => {
880 decls.push(quote!(#field: impl ::core::convert::Into<#aprim::Address>));
881 names.push(field.clone());
882 values.push(quote!(::core::convert::Into::into(#field)));
883 }
884 other => {
885 let ty = rust_ty(other, alloy, aprim)?;
886 decls.push(quote!(#field: #ty));
887 names.push(field.clone());
888 values.push(quote!(#field));
889 }
890 }
891 }
892 Ok((decls, names, values))
893}
894
895fn rust_ty(ty: &SolType, alloy: &TokenStream2, aprim: &TokenStream2) -> Result<TokenStream2> {
899 let ts = match ty {
900 SolType::Bool(_) => quote!(bool),
901 SolType::Address(..) => quote!(#aprim::Address),
903 SolType::String(_) => quote!(::std::string::String),
904 SolType::Bytes(_) => quote!(#aprim::Bytes),
905 SolType::FixedBytes(_, n) => {
906 let n = n.get() as usize;
907 quote!(#aprim::FixedBytes<#n>)
908 }
909 SolType::Uint(_, size) => int_ty(size.map(|s| s.get()).unwrap_or(256), false, aprim),
910 SolType::Int(_, size) => int_ty(size.map(|s| s.get()).unwrap_or(256), true, aprim),
911 SolType::Array(arr) => {
912 let inner = rust_ty(&arr.ty, alloy, aprim)?;
913 match (&arr.size, arr.size_lit()) {
914 (None, _) => quote!(::std::vec::Vec<#inner>),
915 (Some(_), Some(lit)) => {
916 let n: usize = lit.base10_parse()?;
917 quote!([#inner; #n])
918 }
919 (Some(size), None) => {
923 return Err(syn::Error::new(
924 size.span(),
925 "tron_sol! supports only integer-literal array sizes; \
926 constant expressions are not evaluated here — use a \
927 literal like `uint256[3]`",
928 ));
929 }
930 }
931 }
932 SolType::Tuple(tuple) => {
933 let inners =
934 tuple.types.iter().map(|t| rust_ty(t, alloy, aprim)).collect::<Result<Vec<_>>>()?;
935 quote!((#(#inners,)*))
936 }
937 SolType::Custom(path) => {
938 let id = path.last().0.clone();
942 quote!(<#id as #alloy::SolType>::RustType)
943 }
944 SolType::Function(_) | SolType::Mapping(_) => {
945 return Err(syn::Error::new(
946 ty.span(),
947 "`function` and `mapping` types are not supported as parameters",
948 ));
949 }
950 };
951 Ok(ts)
952}
953
954fn int_ty(bits: u16, signed: bool, aprim: &TokenStream2) -> TokenStream2 {
957 let primitive = matches!(bits, 8 | 16 | 32 | 64 | 128);
958 if primitive {
959 let id = format_ident!("{}{}", if signed { "i" } else { "u" }, bits);
960 quote!(#id)
961 } else {
962 let id = format_ident!("{}{}", if signed { "I" } else { "U" }, bits);
963 quote!(#aprim::aliases::#id)
964 }
965}
966
967fn parse_hex(lit: &LitStr) -> Result<Vec<u8>> {
969 let span = lit.span();
970 let s = lit.value();
971 let s = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")).unwrap_or(&s);
972 if !s.len().is_multiple_of(2) {
973 return Err(syn::Error::new(span, "bytecode hex string has odd length"));
974 }
975 s.as_bytes()
976 .chunks(2)
977 .map(|pair| {
978 let hi = hex_nibble(pair[0], span)?;
979 let lo = hex_nibble(pair[1], span)?;
980 Ok((hi << 4) | lo)
981 })
982 .collect()
983}
984
985fn hex_nibble(b: u8, span: Span) -> Result<u8> {
986 match b {
987 b'0'..=b'9' => Ok(b - b'0'),
988 b'a'..=b'f' => Ok(b - b'a' + 10),
989 b'A'..=b'F' => Ok(b - b'A' + 10),
990 _ => Err(syn::Error::new(span, format!("invalid hex byte '{}'", b as char))),
991 }
992}