1use std::{
49 collections::HashMap,
50 hash::{DefaultHasher, Hash, Hasher},
51 iter::once,
52};
53
54use proc_macro::TokenStream;
55use proc_macro2::{
56 Delimiter, Group, Ident as Ident2, Punct, Spacing, Span, TokenStream as TokenStream2, TokenTree,
57};
58use quote::{format_ident, quote};
59use syn::{
60 Attribute, Ident, LitStr, Result,
61 parse::{Parse, ParseStream},
62 parse_macro_input,
63};
64use syn_solidity::{
65 FunctionKind, Item as SolItem, ItemContract, ItemEvent, ItemFunction, Spanned, Type as SolType,
66};
67
68#[proc_macro]
70pub fn tron_sol(input: TokenStream) -> TokenStream {
71 let original = TokenStream2::from(input.clone());
72 let parsed = parse_macro_input!(input as TronSol);
73 match parsed.expand(original) {
74 Ok(ts) => ts.into(),
75 Err(e) => e.to_compile_error().into(),
76 }
77}
78
79struct TronSol {
80 items: Vec<SolItem>,
81 krate: TokenStream2,
82}
83
84impl Parse for TronSol {
85 fn parse(input: ParseStream<'_>) -> Result<Self> {
86 let mut items = Vec::new();
87 while !input.is_empty() {
88 items.push(input.parse::<SolItem>()?);
89 }
90
91 let mut krate: TokenStream2 = quote!(::tronz::contract);
94 for item in &items {
95 let Some(attrs) = item.attrs() else { continue };
96 for attr in attrs {
97 if attr.path().is_ident("tron_sol") {
98 attr.parse_nested_meta(|meta| {
99 if meta.path.is_ident("tronz_crate") {
100 let path: syn::Path = meta.value()?.parse()?;
101 krate = quote!(#path);
102 Ok(())
103 } else {
104 Err(meta.error("unknown `tron_sol` option; expected `tronz_crate`"))
105 }
106 })?;
107 }
108 }
109 }
110
111 Ok(Self { items, krate })
112 }
113}
114
115impl TronSol {
116 fn expand(&self, original: TokenStream2) -> Result<TokenStream2> {
117 let kpriv = {
120 let k = &self.krate;
121 quote!(#k::__private)
122 };
123 let alloy = quote!(#kpriv::alloy_sol_types);
124
125 let input_hash = {
128 let mut hasher = DefaultHasher::new();
129 original.to_string().hash(&mut hasher);
130 hasher.finish()
131 };
132
133 let forwarded = strip_tron_attrs(original);
136
137 let mut rpc_contracts: Vec<&ItemContract> = Vec::new();
138 for item in &self.items {
139 if let SolItem::Contract(c) = item {
140 if contract_opts(&c.attrs)?.rpc {
141 rpc_contracts.push(c);
142 }
143 }
144 }
145
146 if rpc_contracts.is_empty() {
149 return Ok(quote! {
150 #alloy::sol! {
151 #![sol(alloy_sol_types = #alloy)]
152 #forwarded
153 }
154 });
155 }
156
157 let types_mod = format_ident!("__tron_sol_types_{:x}", input_hash);
166
167 let mut instances = Vec::new();
168 for c in &rpc_contracts {
169 instances.push(self.expand_contract(c, &kpriv, &types_mod)?);
170 }
171
172 Ok(quote! {
173 #[doc(hidden)]
174 #[allow(non_camel_case_types, non_snake_case, missing_docs, clippy::all)]
175 mod #types_mod {
176 #alloy::sol! {
177 #![sol(alloy_sol_types = #alloy)]
178 #forwarded
179 }
180 }
181
182 #[allow(unused_imports)]
183 pub use #types_mod::*;
184
185 #(#instances)*
186 })
187 }
188
189 fn expand_contract(
192 &self,
193 c: &ItemContract,
194 kpriv: &TokenStream2,
195 types_mod: &Ident,
196 ) -> Result<TokenStream2> {
197 let name = c.name.0.clone();
198 let opts = contract_opts(&c.attrs)?;
199
200 if opts.rename {
201 return Err(syn::Error::new(
202 c.name.span(),
203 "`#[sol(rename/rename_all)]` is not supported together with `#[sol(rpc)]`: \
204 renaming the generated `…Call` types would desync the instance methods",
205 ));
206 }
207
208 let alloy = quote!(#kpriv::alloy_sol_types);
209 let aprim = quote!(#kpriv::alloy_primitives);
210 let taddr = quote!(#kpriv::tronz_primitives::Address);
211 let provider_tr = quote!(#kpriv::tronz_provider::TronProvider);
212 let cinst = quote!(#kpriv::ContractInstance);
213 let tcb = quote!(#kpriv::TronCallBuilder);
214 let tef = quote!(#kpriv::TronEventFilter);
215 let deploy_builder_ty = quote!(#kpriv::DeployBuilder);
216 let result_ty = quote!(#kpriv::Result);
217
218 let mut functions: Vec<ItemFunction> = Vec::new();
222 let mut constructor: Option<ItemFunction> = None;
223 let mut events: Vec<ItemEvent> = Vec::new();
224 for item in &c.body {
225 match item {
226 SolItem::Function(f) => {
227 if matches!(f.kind, FunctionKind::Function(_)) && f.name.is_some() {
228 functions.push(f.clone());
229 } else if matches!(f.kind, FunctionKind::Constructor(_)) {
230 constructor = Some(f.clone());
231 }
232 }
233 SolItem::Variable(v) => {
234 if v.attributes.visibility().is_some_and(|vis| vis.is_public()) {
237 functions.push(ItemFunction::from_variable_definition(v.clone()));
238 }
239 }
240 SolItem::Event(e) => {
241 events.push(e.clone());
242 }
243 _ => {}
244 }
245 }
246
247 let mut counts: HashMap<String, usize> = HashMap::new();
249 for f in &functions {
250 if let Some(n) = &f.name {
251 *counts.entry(n.to_string()).or_default() += 1;
252 }
253 }
254 let mut seen: HashMap<String, usize> = HashMap::new();
255 let methods = functions
256 .iter()
257 .map(|f| {
258 let base = f.name.as_ref().expect("name checked above").to_string();
259 let effective = if counts.get(&base).copied().unwrap_or(0) > 1 {
260 let idx = seen.entry(base.clone()).or_insert(0);
261 let e = format!("{base}_{idx}");
262 *idx += 1;
263 e
264 } else {
265 base
266 };
267 let method_name = if is_reserved_method(&effective) {
268 format!("{effective}_call")
269 } else {
270 effective.clone()
271 };
272 expand_method(f, &effective, &method_name, &alloy, &aprim, &tcb)
273 })
274 .collect::<Result<Vec<_>>>()?;
275
276 let deployed_bytecode_tokens = match &opts.deployed_bytecode {
278 None => quote!(),
279 Some(bytes) => {
280 let byte_vals = bytes.iter().copied();
281 quote! {
282 pub static DEPLOYED_BYTECODE: #aprim::Bytes =
287 #aprim::Bytes::from_static(&[#(#byte_vals),*]);
288 }
289 }
290 };
291
292 let (deploy_tokens, deploy_instance_tokens) = match &opts.bytecode {
294 None => (quote!(), quote!()),
295 Some(bytes) => {
296 let byte_vals = bytes.iter().copied();
297
298 let (ctor_decls, ctor_names, ctor_values) = match &constructor {
299 None => (vec![], vec![], vec![]),
300 Some(c) => collect_params(&c.parameters, &alloy, &aprim)?,
301 };
302
303 let ctor_sig = quote!(#(, #ctor_decls)*);
304 let ctor_fwd = quote!(#(, #ctor_names)*);
305 let bytecode_expr = if ctor_names.is_empty() {
306 quote!(BYTECODE.clone())
307 } else {
308 quote!(#aprim::Bytes::from(
309 [
310 &BYTECODE[..],
311 &#alloy::SolConstructor::abi_encode(&constructorCall {
312 #(#ctor_names: #ctor_values),*
313 })[..]
314 ].concat()
315 ))
316 };
317
318 let free_fns = quote! {
319 pub static BYTECODE: #aprim::Bytes =
321 #aprim::Bytes::from_static(&[#(#byte_vals),*]);
322
323 #[inline]
325 pub fn deploy_builder<P: #provider_tr>(
326 provider: P #ctor_sig
327 ) -> #deploy_builder_ty<P> {
328 Instance::<P>::deploy_builder(provider #ctor_fwd)
329 }
330
331 #[inline]
333 pub async fn deploy<P: #provider_tr + ::core::clone::Clone>(
334 provider: P #ctor_sig
335 ) -> #result_ty<Instance<P>> {
336 Instance::<P>::deploy(provider #ctor_fwd).await
337 }
338 };
339
340 let instance_methods = quote! {
341 impl<P: #provider_tr + ::core::clone::Clone> Instance<P> {
342 #[inline]
344 pub fn deploy_builder(
345 provider: P #ctor_sig
346 ) -> #deploy_builder_ty<P> {
347 #deploy_builder_ty::new(provider, #bytecode_expr)
348 }
349
350 #[inline]
352 pub async fn deploy(
353 provider: P #ctor_sig
354 ) -> #result_ty<Self> {
355 let address = Self::deploy_builder(provider.clone() #ctor_fwd)
356 .deploy()
357 .await?;
358 ::core::result::Result::Ok(new(address, provider))
359 }
360 }
361 };
362
363 (free_fns, instance_methods)
364 }
365 };
366
367 let mut ev_counts: HashMap<String, usize> = HashMap::new();
369 for e in &events {
370 *ev_counts.entry(e.name.to_string()).or_default() += 1;
371 }
372 let mut ev_seen: HashMap<String, usize> = HashMap::new();
373 let event_filter_methods = events
374 .iter()
375 .map(|e| {
376 let base = e.name.to_string();
377 let effective = if ev_counts.get(&base).copied().unwrap_or(0) > 1 {
378 let idx = ev_seen.entry(base.clone()).or_insert(0);
379 let s = format!("{base}_{idx}");
380 *idx += 1;
381 s
382 } else {
383 base
384 };
385 let method_name = format_ident!("{}_filter", effective);
386 let event_ty = format_ident!("{}", effective);
387 let doc = format!("Creates an event filter for the [`{effective}`] event.");
388 quote! {
389 #[doc = #doc]
390 #[allow(non_snake_case)]
391 pub fn #method_name(&self) -> #tef<P, #event_ty> {
392 self.event_filter::<#event_ty>()
393 }
394 }
395 })
396 .collect::<Vec<_>>();
397
398 Ok(quote! {
399 #[allow(non_snake_case, clippy::pub_underscore_fields)]
400 pub mod #name {
401 pub use super::#types_mod::#name::*;
403 #[allow(unused_imports)]
406 use super::#types_mod::*;
407
408 #deployed_bytecode_tokens
409
410 #deploy_tokens
411
412 #[derive(::core::clone::Clone)]
414 pub struct Instance<P: #provider_tr> {
415 inner: #cinst<P>,
416 }
417
418 impl<P: #provider_tr> ::core::fmt::Debug for Instance<P> {
419 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
420 f.debug_struct(::core::stringify!(#name))
421 .field("address", &self.inner.address())
422 .finish()
423 }
424 }
425
426 pub fn new<P: #provider_tr>(address: #taddr, provider: P) -> Instance<P> {
428 Instance { inner: #cinst::new_raw(provider, address) }
429 }
430
431 impl<P: #provider_tr> Instance<P> {
432 #[inline]
434 pub fn address(&self) -> #taddr {
435 self.inner.address()
436 }
437
438 #[inline]
440 pub fn set_address(&mut self, address: #taddr) {
441 self.inner.set_address(address);
442 }
443
444 #[inline]
446 pub fn at(mut self, address: #taddr) -> Self {
447 self.set_address(address);
448 self
449 }
450
451 #[inline]
453 pub fn provider(&self) -> &P {
454 self.inner.provider()
455 }
456
457 #[inline]
460 pub fn call_builder<C: #alloy::SolCall>(&self, call: &C) -> #tcb<P, C> {
461 #tcb::new(self.inner.call_raw(
462 #alloy::SolCall::abi_encode(call).into()
463 ))
464 }
465
466 #(#methods)*
467 }
468
469 impl<P: #provider_tr> Instance<P> {
470 #[inline]
473 pub fn event_filter<E: #alloy::SolEvent>(&self) -> #tef<P, E> {
474 #tef::new(self.inner.provider().clone(), Some(self.inner.address()))
475 }
476
477 #(#event_filter_methods)*
478 }
479
480 #deploy_instance_tokens
481 }
482 })
483 }
484}
485
486#[derive(Default)]
488struct ContractOpts {
489 rpc: bool,
490 bytecode: Option<Vec<u8>>,
491 deployed_bytecode: Option<Vec<u8>>,
492 rename: bool,
494}
495
496fn contract_opts(attrs: &[Attribute]) -> Result<ContractOpts> {
500 let mut opts = ContractOpts::default();
501 for attr in attrs {
502 if !attr.path().is_ident("sol") {
503 continue;
504 }
505 attr.parse_nested_meta(|meta| {
506 if meta.path.is_ident("rpc") {
507 opts.rpc = if meta.input.peek(syn::Token![=]) {
508 meta.value()?.parse::<syn::LitBool>()?.value
509 } else {
510 true
511 };
512 } else if meta.path.is_ident("bytecode") {
513 opts.bytecode = Some(parse_hex(&meta.value()?.parse::<LitStr>()?)?);
514 } else if meta.path.is_ident("deployed_bytecode") {
515 opts.deployed_bytecode = Some(parse_hex(&meta.value()?.parse::<LitStr>()?)?);
516 } else {
517 if meta.path.is_ident("rename") || meta.path.is_ident("rename_all") {
518 opts.rename = true;
519 }
520 if meta.input.peek(syn::Token![=]) {
525 let _: syn::Expr = meta.value()?.parse()?;
526 } else if meta.input.peek(syn::token::Paren) {
527 let content;
528 syn::parenthesized!(content in meta.input);
529 let _: TokenStream2 = content.parse()?;
530 }
531 }
532 Ok(())
533 })?;
534 }
535 Ok(opts)
536}
537
538fn strip_tron_attrs(ts: TokenStream2) -> TokenStream2 {
545 let tokens: Vec<TokenTree> = ts.into_iter().collect();
546 let mut out = TokenStream2::new();
547 let mut i = 0;
548 while i < tokens.len() {
549 if let TokenTree::Group(g) = &tokens[i] {
551 let mut ng = Group::new(g.delimiter(), strip_tron_attrs(g.stream()));
552 ng.set_span(g.span());
553 out.extend(once(TokenTree::Group(ng)));
554 i += 1;
555 continue;
556 }
557
558 if let TokenTree::Punct(p) = &tokens[i] {
560 if p.as_char() == '#' {
561 let mut j = i + 1;
562 let bang = matches!(tokens.get(j), Some(TokenTree::Punct(q)) if q.as_char() == '!');
563 if bang {
564 j += 1;
565 }
566 if let Some(TokenTree::Group(g)) = tokens.get(j) {
567 if g.delimiter() == Delimiter::Bracket {
568 if let Some(inner) = rewrite_attr(g.stream()) {
570 out.extend(once(tokens[i].clone()));
571 if bang {
572 out.extend(once(tokens[i + 1].clone()));
573 }
574 let mut ng = Group::new(Delimiter::Bracket, inner);
575 ng.set_span(g.span());
576 out.extend(once(TokenTree::Group(ng)));
577 }
578 i = j + 1;
579 continue;
580 }
581 }
582 }
583 }
584
585 out.extend(once(tokens[i].clone()));
586 i += 1;
587 }
588 out
589}
590
591fn rewrite_attr(inner: TokenStream2) -> Option<TokenStream2> {
596 let toks: Vec<TokenTree> = inner.clone().into_iter().collect();
597 let lead = match toks.first() {
598 Some(TokenTree::Ident(id)) => id.to_string(),
599 _ => return Some(inner),
600 };
601
602 match lead.as_str() {
603 "tron_sol" => None,
605 "sol" => {
607 if let Some(TokenTree::Group(g)) = toks.get(1) {
608 if g.delimiter() == Delimiter::Parenthesis {
609 let kept = filter_sol_meta(g.stream());
610 if kept.is_empty() {
611 return None;
612 }
613 let mut grp = Group::new(Delimiter::Parenthesis, kept);
614 grp.set_span(g.span());
615 return Some(
616 [toks[0].clone(), TokenTree::Group(grp)]
617 .into_iter()
618 .collect(),
619 );
620 }
621 }
622 Some(inner)
623 }
624 _ => Some(inner),
626 }
627}
628
629fn filter_sol_meta(stream: TokenStream2) -> TokenStream2 {
632 let mut items: Vec<Vec<TokenTree>> = vec![Vec::new()];
634 for tt in stream {
635 if let TokenTree::Punct(p) = &tt {
636 if p.as_char() == ',' {
637 items.push(Vec::new());
638 continue;
639 }
640 }
641 items.last_mut().expect("non-empty").push(tt);
642 }
643
644 let mut out = TokenStream2::new();
645 let mut first = true;
646 for item in items {
647 if item.is_empty() {
648 continue;
649 }
650 let key = match item.first() {
651 Some(TokenTree::Ident(id)) => id.to_string(),
652 _ => String::new(),
653 };
654 if matches!(key.as_str(), "rpc" | "bytecode" | "deployed_bytecode") {
655 continue;
656 }
657 if !first {
658 out.extend(once(TokenTree::Punct(Punct::new(',', Spacing::Alone))));
659 }
660 first = false;
661 out.extend(item);
662 }
663 out
664}
665
666fn is_reserved_method(name: &str) -> bool {
669 matches!(
670 name,
671 "new"
672 | "deploy"
673 | "deploy_builder"
674 | "address"
675 | "set_address"
676 | "at"
677 | "provider"
678 | "call_builder"
679 | "event_filter"
680 )
681}
682
683fn expand_method(
688 f: &ItemFunction,
689 call_base: &str,
690 method_name: &str,
691 alloy: &TokenStream2,
692 aprim: &TokenStream2,
693 tcb: &TokenStream2,
694) -> Result<TokenStream2> {
695 let fn_ident = format_ident!("{}", method_name);
696 let call_struct = format_ident!("{}Call", call_base);
697
698 let (decls, names, values) = collect_params(&f.parameters, alloy, aprim)?;
699
700 Ok(quote! {
701 #[allow(non_snake_case, clippy::too_many_arguments)]
702 pub fn #fn_ident(&self, #(#decls),*) -> #tcb<P, #call_struct> {
703 self.call_builder(&#call_struct { #(#names: #values),* })
704 }
705 })
706}
707
708fn collect_params(
714 parameters: &syn_solidity::ParameterList,
715 alloy: &TokenStream2,
716 aprim: &TokenStream2,
717) -> Result<(Vec<TokenStream2>, Vec<Ident2>, Vec<TokenStream2>)> {
718 let mut decls = Vec::new();
719 let mut names = Vec::new();
720 let mut values = Vec::new();
721 for (i, var) in parameters.iter().enumerate() {
722 let field: Ident2 = match &var.name {
726 Some(n) => n.0.clone(),
727 None => format_ident!("_{}", i),
728 };
729 match &var.ty {
730 SolType::Address(..) => {
731 decls.push(quote!(#field: impl ::core::convert::Into<#aprim::Address>));
732 names.push(field.clone());
733 values.push(quote!(::core::convert::Into::into(#field)));
734 }
735 other => {
736 let ty = rust_ty(other, alloy, aprim)?;
737 decls.push(quote!(#field: #ty));
738 names.push(field.clone());
739 values.push(quote!(#field));
740 }
741 }
742 }
743 Ok((decls, names, values))
744}
745
746fn rust_ty(ty: &SolType, alloy: &TokenStream2, aprim: &TokenStream2) -> Result<TokenStream2> {
750 let ts = match ty {
751 SolType::Bool(_) => quote!(bool),
752 SolType::Address(..) => quote!(#aprim::Address),
754 SolType::String(_) => quote!(::std::string::String),
755 SolType::Bytes(_) => quote!(#aprim::Bytes),
756 SolType::FixedBytes(_, n) => {
757 let n = n.get() as usize;
758 quote!(#aprim::FixedBytes<#n>)
759 }
760 SolType::Uint(_, size) => int_ty(size.map(|s| s.get()).unwrap_or(256), false, aprim),
761 SolType::Int(_, size) => int_ty(size.map(|s| s.get()).unwrap_or(256), true, aprim),
762 SolType::Array(arr) => {
763 let inner = rust_ty(&arr.ty, alloy, aprim)?;
764 match (&arr.size, arr.size_lit()) {
765 (None, _) => quote!(::std::vec::Vec<#inner>),
766 (Some(_), Some(lit)) => {
767 let n: usize = lit.base10_parse()?;
768 quote!([#inner; #n])
769 }
770 (Some(size), None) => {
774 return Err(syn::Error::new(
775 size.span(),
776 "tron_sol! supports only integer-literal array sizes; \
777 constant expressions are not evaluated here — use a \
778 literal like `uint256[3]`",
779 ));
780 }
781 }
782 }
783 SolType::Tuple(tuple) => {
784 let inners = tuple
785 .types
786 .iter()
787 .map(|t| rust_ty(t, alloy, aprim))
788 .collect::<Result<Vec<_>>>()?;
789 quote!((#(#inners,)*))
790 }
791 SolType::Custom(path) => {
792 let id = path.last().0.clone();
796 quote!(<#id as #alloy::SolType>::RustType)
797 }
798 SolType::Function(_) | SolType::Mapping(_) => {
799 return Err(syn::Error::new(
800 ty.span(),
801 "`function` and `mapping` types are not supported as parameters",
802 ));
803 }
804 };
805 Ok(ts)
806}
807
808fn int_ty(bits: u16, signed: bool, aprim: &TokenStream2) -> TokenStream2 {
811 let primitive = matches!(bits, 8 | 16 | 32 | 64 | 128);
812 if primitive {
813 let id = format_ident!("{}{}", if signed { "i" } else { "u" }, bits);
814 quote!(#id)
815 } else {
816 let id = format_ident!("{}{}", if signed { "I" } else { "U" }, bits);
817 quote!(#aprim::aliases::#id)
818 }
819}
820
821fn parse_hex(lit: &LitStr) -> Result<Vec<u8>> {
823 let span = lit.span();
824 let s = lit.value();
825 let s = s
826 .strip_prefix("0x")
827 .or_else(|| s.strip_prefix("0X"))
828 .unwrap_or(&s);
829 if s.len() % 2 != 0 {
830 return Err(syn::Error::new(span, "bytecode hex string has odd length"));
831 }
832 s.as_bytes()
833 .chunks(2)
834 .map(|pair| {
835 let hi = hex_nibble(pair[0], span)?;
836 let lo = hex_nibble(pair[1], span)?;
837 Ok((hi << 4) | lo)
838 })
839 .collect()
840}
841
842fn hex_nibble(b: u8, span: Span) -> Result<u8> {
843 match b {
844 b'0'..=b'9' => Ok(b - b'0'),
845 b'a'..=b'f' => Ok(b - b'a' + 10),
846 b'A'..=b'F' => Ok(b - b'A' + 10),
847 _ => Err(syn::Error::new(
848 span,
849 format!("invalid hex byte '{}'", b as char),
850 )),
851 }
852}