evm_coder_procedural/
lib.rs

1use inflector::cases;
2use proc_macro::TokenStream;
3use quote::quote;
4use sha3::{Digest, Keccak256};
5use syn::{
6	parse_macro_input, spanned::Spanned, DeriveInput, Ident, ItemImpl, Pat, Path, PathSegment, Type,
7};
8
9mod abi_derive;
10mod solidity_interface;
11#[cfg(feature = "bondrewd")]
12mod structs;
13mod to_log;
14
15fn fn_selector_str(input: &str) -> u32 {
16	let mut hasher = Keccak256::new();
17	hasher.update(input.as_bytes());
18	let result = hasher.finalize();
19
20	let mut selector_bytes = [0; 4];
21	selector_bytes.copy_from_slice(&result[0..4]);
22
23	u32::from_be_bytes(selector_bytes)
24}
25
26/// Returns solidity function selector (first 4 bytes of hash) by its
27/// textual representation
28///
29/// ```ignore
30/// use evm_coder_macros::fn_selector;
31///
32/// assert_eq!(fn_selector!(transfer(address, uint256)), 0xa9059cbb);
33/// ```
34#[proc_macro]
35pub fn fn_selector(input: TokenStream) -> TokenStream {
36	let input = input.to_string().replace(' ', "");
37	let selector = fn_selector_str(&input);
38
39	(quote! {
40		::evm_coder::types::BytesFixed(u32::to_be_bytes(#selector))
41	})
42	.into()
43}
44
45fn event_selector_str(input: &str) -> [u8; 32] {
46	let mut hasher = Keccak256::new();
47	hasher.update(input.as_bytes());
48	let result = hasher.finalize();
49
50	let mut selector_bytes = [0; 32];
51	selector_bytes.copy_from_slice(&result[0..32]);
52	selector_bytes
53}
54
55/// Returns solidity topic (hash) by its textual representation
56///
57/// ```ignore
58/// use evm_coder_macros::event_topic;
59///
60/// assert_eq!(
61///     format!("{:x}", event_topic!(Transfer(address, address, uint256))),
62///     "ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
63/// );
64/// ```
65#[proc_macro]
66pub fn event_topic(stream: TokenStream) -> TokenStream {
67	let input = stream.to_string().replace(' ', "");
68	let selector_bytes = event_selector_str(&input);
69
70	(quote! {
71		::primitive_types::H256([#(
72			#selector_bytes,
73		)*])
74	})
75	.into()
76}
77
78pub(crate) fn parse_path(ty: &Type) -> syn::Result<&Path> {
79	match &ty {
80		syn::Type::Path(pat) => {
81			if let Some(qself) = &pat.qself {
82				return Err(syn::Error::new(qself.ty.span(), "no receiver expected"));
83			}
84			Ok(&pat.path)
85		}
86		_ => Err(syn::Error::new(ty.span(), "expected ty to be path")),
87	}
88}
89
90fn parse_path_segment(path: &Path) -> syn::Result<&PathSegment> {
91	if path.segments.len() != 1 {
92		return Err(syn::Error::new(
93			path.span(),
94			"expected path to have only one segment",
95		));
96	}
97	let last_segment = &path.segments.last().unwrap();
98	Ok(last_segment)
99}
100
101fn parse_ident_from_pat(pat: &Pat) -> syn::Result<&Ident> {
102	match pat {
103		Pat::Ident(i) => Ok(&i.ident),
104		_ => Err(syn::Error::new(pat.span(), "expected pat ident")),
105	}
106}
107
108fn parse_ident_from_segment(segment: &PathSegment, allow_generics: bool) -> syn::Result<&Ident> {
109	if !segment.arguments.is_none() && !allow_generics {
110		return Err(syn::Error::new(
111			segment.arguments.span(),
112			"unexpected generic type",
113		));
114	}
115	Ok(&segment.ident)
116}
117
118fn parse_ident_from_path(path: &Path, allow_generics: bool) -> syn::Result<&Ident> {
119	let segment = parse_path_segment(path)?;
120	parse_ident_from_segment(segment, allow_generics)
121}
122
123fn parse_ident_from_type(ty: &Type, allow_generics: bool) -> syn::Result<&Ident> {
124	let path = parse_path(ty)?;
125	parse_ident_from_path(path, allow_generics)
126}
127
128fn pascal_ident_to_call(ident: &Ident) -> Ident {
129	let name = format!("{ident}Call");
130	Ident::new(&name, ident.span())
131}
132fn snake_ident_to_pascal(ident: &Ident) -> Ident {
133	let name = ident.to_string();
134	let name = cases::pascalcase::to_pascal_case(&name);
135	Ident::new(&name, ident.span())
136}
137fn snake_ident_to_screaming(ident: &Ident) -> Ident {
138	let name = ident.to_string();
139	let name = cases::screamingsnakecase::to_screaming_snake_case(&name);
140	Ident::new(&name, ident.span())
141}
142
143/// See documentation for this proc-macro reexported in `evm-coder` crate
144#[proc_macro_attribute]
145pub fn solidity_interface(args: TokenStream, stream: TokenStream) -> TokenStream {
146	let args = parse_macro_input!(args as solidity_interface::InterfaceInfo);
147
148	let mut input: ItemImpl = match syn::parse(stream) {
149		Ok(t) => t,
150		Err(e) => return e.to_compile_error().into(),
151	};
152
153	let expanded = match solidity_interface::SolidityInterface::try_from(args, &mut input) {
154		Ok(v) => v.expand(),
155		Err(e) => e.to_compile_error(),
156	};
157
158	(quote! {
159		#input
160
161		#expanded
162	})
163	.into()
164}
165
166/// See documentation for this proc-macro reexported in `evm-coder` crate
167#[proc_macro_derive(ToLog, attributes(indexed))]
168pub fn to_log(value: TokenStream) -> TokenStream {
169	let input = parse_macro_input!(value as DeriveInput);
170
171	match to_log::Events::try_from(&input) {
172		Ok(e) => e.expand(),
173		Err(e) => e.to_compile_error(),
174	}
175	.into()
176}
177
178/// See documentation for this proc-macro reexported in `evm-coder` crate
179#[proc_macro_derive(AbiCoder)]
180pub fn abi_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
181	let ast = parse_macro_input!(input as DeriveInput);
182	let ts = match abi_derive::impl_abi_macro(&ast) {
183		Ok(e) => e,
184		Err(e) => e.to_compile_error(),
185	};
186	ts.into()
187}
188
189#[cfg(feature = "bondrewd")]
190/// See documentation for this proc-macro reexported in `evm-coder` crate
191#[proc_macro_derive(AbiCoderFlags, attributes(bondrewd,))]
192pub fn abi_flags_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
193	let ast = parse_macro_input!(input as DeriveInput);
194	let ts = match abi_derive::impl_abi_flags_macro(&ast) {
195		Ok(e) => e,
196		Err(e) => e.to_compile_error(),
197	};
198	ts.into()
199}