git.delta.rocks / unique-network / refs/commits / f0dde168a42b

difftreelog

feat(evm-coder) parse #[doc] for interface types

Yaroslav Bolyukin2022-07-12parent: #7d5b680.patch.diff
in: master
Previously, only documentation on items was parsed and preserved in
generated code, now you can have documentation on interface itself

10 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2172,7 +2172,7 @@
 version = "0.1.1"
 dependencies = [
  "ethereum",
- "evm-coder-macros",
+ "evm-coder-procedural",
  "evm-core",
  "hex",
  "hex-literal",
@@ -2181,7 +2181,7 @@
 ]
 
 [[package]]
-name = "evm-coder-macros"
+name = "evm-coder-procedural"
 version = "0.2.0"
 dependencies = [
  "Inflector",
deletedcrates/evm-coder-macros/Cargo.tomldiffbeforeafterboth
--- a/crates/evm-coder-macros/Cargo.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-[package]
-name = "evm-coder-macros"
-version = "0.2.0"
-license = "GPLv3"
-edition = "2021"
-
-[lib]
-proc-macro = true
-
-[dependencies]
-sha3 = "0.10.1"
-quote = "1.0"
-proc-macro2 = "1.0"
-syn = { version = "1.0", features = ["full"] }
-hex = "0.4.3"
-Inflector = "0.11.4"
deletedcrates/evm-coder-macros/src/lib.rsdiffbeforeafterboth
--- a/crates/evm-coder-macros/src/lib.rs
+++ /dev/null
@@ -1,298 +0,0 @@
-// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
-// This file is part of Unique Network.
-
-// Unique Network is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Unique Network is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-
-#![allow(dead_code)]
-
-use inflector::cases;
-use proc_macro::TokenStream;
-use quote::quote;
-use sha3::{Digest, Keccak256};
-use syn::{
-	DeriveInput, GenericArgument, Ident, ItemImpl, Pat, Path, PathArguments,
-	PathSegment, Type, parse_macro_input, spanned::Spanned,
-};
-
-mod solidity_interface;
-mod to_log;
-
-fn fn_selector_str(input: &str) -> u32 {
-	let mut hasher = Keccak256::new();
-	hasher.update(input.as_bytes());
-	let result = hasher.finalize();
-
-	let mut selector_bytes = [0; 4];
-	selector_bytes.copy_from_slice(&result[0..4]);
-
-	u32::from_be_bytes(selector_bytes)
-}
-
-/// Returns solidity function selector (first 4 bytes of hash) by its
-/// textual representation
-///
-/// ```ignore
-/// use evm_coder_macros::fn_selector;
-///
-/// assert_eq!(fn_selector!(transfer(address, uint256)), 0xa9059cbb);
-/// ```
-#[proc_macro]
-pub fn fn_selector(input: TokenStream) -> TokenStream {
-	let input = input.to_string().replace(' ', "");
-	let selector = fn_selector_str(&input);
-
-	(quote! {
-		#selector
-	})
-	.into()
-}
-
-fn event_selector_str(input: &str) -> [u8; 32] {
-	let mut hasher = Keccak256::new();
-	hasher.update(input.as_bytes());
-	let result = hasher.finalize();
-
-	let mut selector_bytes = [0; 32];
-	selector_bytes.copy_from_slice(&result[0..32]);
-	selector_bytes
-}
-
-/// Returns solidity topic (hash) by its textual representation
-///
-/// ```ignore
-/// use evm_coder_macros::event_topic;
-///
-/// assert_eq!(
-///     format!("{:x}", event_topic!(Transfer(address, address, uint256))),
-///     "ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
-/// );
-/// ```
-#[proc_macro]
-pub fn event_topic(stream: TokenStream) -> TokenStream {
-	let input = stream.to_string().replace(' ', "");
-	let selector_bytes = event_selector_str(&input);
-
-	(quote! {
-		::primitive_types::H256([#(
-			#selector_bytes,
-		)*])
-	})
-	.into()
-}
-
-fn parse_path(ty: &Type) -> syn::Result<&Path> {
-	match &ty {
-		syn::Type::Path(pat) => {
-			if let Some(qself) = &pat.qself {
-				return Err(syn::Error::new(qself.ty.span(), "no receiver expected"));
-			}
-			Ok(&pat.path)
-		}
-		_ => Err(syn::Error::new(ty.span(), "expected ty to be path")),
-	}
-}
-
-fn parse_path_segment(path: &Path) -> syn::Result<&PathSegment> {
-	if path.segments.len() != 1 {
-		return Err(syn::Error::new(
-			path.span(),
-			"expected path to have only segment",
-		));
-	}
-	let last_segment = &path.segments.last().unwrap();
-	Ok(last_segment)
-}
-
-fn parse_ident_from_pat(pat: &Pat) -> syn::Result<&Ident> {
-	match pat {
-		Pat::Ident(i) => Ok(&i.ident),
-		_ => Err(syn::Error::new(pat.span(), "expected pat ident")),
-	}
-}
-
-fn parse_ident_from_segment(segment: &PathSegment, allow_generics: bool) -> syn::Result<&Ident> {
-	if segment.arguments != PathArguments::None && !allow_generics {
-		return Err(syn::Error::new(
-			segment.arguments.span(),
-			"unexpected generic type",
-		));
-	}
-	Ok(&segment.ident)
-}
-
-fn parse_ident_from_path(path: &Path, allow_generics: bool) -> syn::Result<&Ident> {
-	let segment = parse_path_segment(path)?;
-	parse_ident_from_segment(segment, allow_generics)
-}
-
-fn parse_ident_from_type(ty: &Type, allow_generics: bool) -> syn::Result<&Ident> {
-	let path = parse_path(ty)?;
-	parse_ident_from_path(path, allow_generics)
-}
-
-// Gets T out of Result<T>
-fn parse_result_ok(ty: &Type) -> syn::Result<&Type> {
-	let path = parse_path(ty)?;
-	let segment = parse_path_segment(path)?;
-
-	if segment.ident != "Result" {
-		return Err(syn::Error::new(
-			ty.span(),
-			"expected Result as return type (no renamed aliases allowed)",
-		));
-	}
-	let args = match &segment.arguments {
-		PathArguments::AngleBracketed(e) => e,
-		_ => {
-			return Err(syn::Error::new(
-				segment.arguments.span(),
-				"missing Result generics",
-			))
-		}
-	};
-
-	let args = &args.args;
-	let arg = args.first().unwrap();
-
-	let ty = match arg {
-		GenericArgument::Type(ty) => ty,
-		_ => {
-			return Err(syn::Error::new(
-				arg.span(),
-				"expected first generic to be type",
-			))
-		}
-	};
-
-	Ok(ty)
-}
-
-fn pascal_ident_to_call(ident: &Ident) -> Ident {
-	let name = format!("{}Call", ident);
-	Ident::new(&name, ident.span())
-}
-fn snake_ident_to_pascal(ident: &Ident) -> Ident {
-	let name = ident.to_string();
-	let name = cases::pascalcase::to_pascal_case(&name);
-	Ident::new(&name, ident.span())
-}
-fn snake_ident_to_screaming(ident: &Ident) -> Ident {
-	let name = ident.to_string();
-	let name = cases::screamingsnakecase::to_screaming_snake_case(&name);
-	Ident::new(&name, ident.span())
-}
-fn pascal_ident_to_snake_call(ident: &Ident) -> Ident {
-	let name = ident.to_string();
-	let name = cases::snakecase::to_snake_case(&name);
-	let name = format!("call_{}", name);
-	Ident::new(&name, ident.span())
-}
-
-/// Derives call enum implementing [`evm_coder::Callable`], [`evm_coder::Weighted`]
-/// and [`evm_coder::Call`] from impl block
-///
-/// ## Macro syntax
-///
-/// `#[solidity_interface(name, is, inline_is, events)]`
-/// - *name*: used in generated code, and for Call enum name
-/// - *is*: used to provide call inheritance, not found methods will be delegated to all contracts
-/// specified in is/inline_is
-/// - *inline_is*: same as is, but selectors for passed contracts will be used by derived ERC165
-/// implementation
-///
-/// `#[weight(value)]`
-/// Can be added to every method of impl block, used for deriving [`evm_coder::Weighted`], which
-/// is used by substrate bridge
-/// - *value*: expression, which evaluates to weight required to call this method.
-/// This expression can use call arguments to calculate non-constant execution time.
-/// This expression should evaluate faster than actual execution does, and may provide worser case
-/// than one is called
-///
-/// `#[solidity_interface(rename_selector)]`
-/// - *rename_selector*: by default, selector name will be generated by transforming method name
-/// from snake_case to camelCase. Use this option, if other naming convention is required.
-/// I.e: method `token_uri` will be automatically renamed to `tokenUri` in selector, but name
-/// required by ERC721 standard is `tokenURI`, thus we need to specify `rename_selector = "tokenURI"`
-/// explicitly
-///
-/// Also, any contract method may have doc comments, which will be automatically added to generated
-/// solidity interface definitions
-///
-/// ## Example
-///
-/// ```ignore
-/// struct SuperContract;
-/// struct InlineContract;
-/// struct Contract;
-///
-/// #[derive(ToLog)]
-/// enum ContractEvents {
-///     Event(#[indexed] uint32),
-/// }
-///
-/// #[solidity_interface(name = MyContract, is(SuperContract), inline_is(InlineContract))]
-/// impl Contract {
-///     /// Multiply two numbers
-///     #[weight(200 + a + b)]
-///     #[solidity_interface(rename_selector = "mul")]
-///     fn mul(&mut self, a: uint32, b: uint32) -> Result<uint32> {
-///         Ok(a.checked_mul(b).ok_or("overflow")?)
-///     }
-/// }
-/// ```
-#[proc_macro_attribute]
-pub fn solidity_interface(args: TokenStream, stream: TokenStream) -> TokenStream {
-	let args = parse_macro_input!(args as solidity_interface::InterfaceInfo);
-
-	let input: ItemImpl = match syn::parse(stream) {
-		Ok(t) => t,
-		Err(e) => return e.to_compile_error().into(),
-	};
-
-	let expanded = match solidity_interface::SolidityInterface::try_from(args, &input) {
-		Ok(v) => v.expand(),
-		Err(e) => e.to_compile_error(),
-	};
-
-	(quote! {
-		#input
-
-		#expanded
-	})
-	.into()
-}
-
-#[proc_macro_attribute]
-pub fn solidity(_args: TokenStream, stream: TokenStream) -> TokenStream {
-	stream
-}
-#[proc_macro_attribute]
-pub fn weight(_args: TokenStream, stream: TokenStream) -> TokenStream {
-	stream
-}
-
-/// ## Syntax
-///
-/// `#[indexed]`
-/// Marks this field as indexed, so it will appear in [`ethereum::Log`] topics instead of data
-#[proc_macro_derive(ToLog, attributes(indexed))]
-pub fn to_log(value: TokenStream) -> TokenStream {
-	let input = parse_macro_input!(value as DeriveInput);
-
-	match to_log::Events::try_from(&input) {
-		Ok(e) => e.expand(),
-		Err(e) => e.to_compile_error(),
-	}
-	.into()
-}
deletedcrates/evm-coder-macros/src/solidity_interface.rsdiffbeforeafterboth
--- a/crates/evm-coder-macros/src/solidity_interface.rs
+++ /dev/null
@@ -1,1076 +0,0 @@
-// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
-// This file is part of Unique Network.
-
-// Unique Network is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Unique Network is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-
-#![allow(dead_code)]
-
-use quote::{quote, ToTokens};
-use inflector::cases;
-use std::fmt::Write;
-use syn::{
-	Expr, FnArg, GenericArgument, Generics, Ident, ImplItem, ImplItemMethod, ItemImpl, Lit, Meta,
-	MetaNameValue, PatType, PathArguments, ReturnType, Type,
-	spanned::Spanned,
-	parse::{Parse, ParseStream},
-	parenthesized, Token, LitInt, LitStr,
-};
-
-use crate::{
-	fn_selector_str, parse_ident_from_pat, parse_ident_from_path, parse_path, parse_path_segment,
-	parse_result_ok, pascal_ident_to_call, pascal_ident_to_snake_call, snake_ident_to_pascal,
-	snake_ident_to_screaming,
-};
-
-struct Is {
-	name: Ident,
-	pascal_call_name: Ident,
-	snake_call_name: Ident,
-	via: Option<(Type, Ident)>,
-}
-impl Is {
-	fn expand_call_def(&self, gen_ref: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {
-		let name = &self.name;
-		let pascal_call_name = &self.pascal_call_name;
-		quote! {
-			#name(#pascal_call_name #gen_ref)
-		}
-	}
-
-	fn expand_interface_id(&self) -> proc_macro2::TokenStream {
-		let pascal_call_name = &self.pascal_call_name;
-		quote! {
-			interface_id ^= u32::from_be_bytes(#pascal_call_name::interface_id());
-		}
-	}
-
-	fn expand_supports_interface(
-		&self,
-		generics: &proc_macro2::TokenStream,
-	) -> proc_macro2::TokenStream {
-		let pascal_call_name = &self.pascal_call_name;
-		quote! {
-			<#pascal_call_name #generics>::supports_interface(interface_id)
-		}
-	}
-
-	fn expand_variant_weight(&self) -> proc_macro2::TokenStream {
-		let name = &self.name;
-		quote! {
-			Self::#name(call) => call.weight()
-		}
-	}
-
-	fn expand_variant_call(
-		&self,
-		call_name: &proc_macro2::Ident,
-		generics: &proc_macro2::TokenStream,
-	) -> proc_macro2::TokenStream {
-		let name = &self.name;
-		let pascal_call_name = &self.pascal_call_name;
-		let via_typ = self
-			.via
-			.as_ref()
-			.map(|(t, _)| quote! {#t})
-			.unwrap_or_else(|| quote! {Self});
-		let via_map = self
-			.via
-			.as_ref()
-			.map(|(_, i)| quote! {.#i()})
-			.unwrap_or_default();
-		quote! {
-			#call_name::#name(call) => return <#via_typ as ::evm_coder::Callable<#pascal_call_name #generics>>::call(self #via_map, Msg {
-				call,
-				caller: c.caller,
-				value: c.value,
-			})
-		}
-	}
-
-	fn expand_parse(&self, generics: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {
-		let name = &self.name;
-		let pascal_call_name = &self.pascal_call_name;
-		quote! {
-			if let Some(parsed_call) = <#pascal_call_name #generics>::parse(method_id, reader)? {
-				return Ok(Some(Self::#name(parsed_call)))
-			}
-		}
-	}
-
-	fn expand_generator(&self, generics: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {
-		let pascal_call_name = &self.pascal_call_name;
-		quote! {
-			<#pascal_call_name #generics>::generate_solidity_interface(tc, is_impl);
-		}
-	}
-
-	fn expand_event_generator(&self) -> proc_macro2::TokenStream {
-		let name = &self.name;
-		quote! {
-			#name::generate_solidity_interface(tc, is_impl);
-		}
-	}
-}
-
-#[derive(Default)]
-struct IsList(Vec<Is>);
-impl Parse for IsList {
-	fn parse(input: ParseStream) -> syn::Result<Self> {
-		let mut out = vec![];
-		loop {
-			if input.is_empty() {
-				break;
-			}
-			let name = input.parse::<Ident>()?;
-			let lookahead = input.lookahead1();
-			let via = if lookahead.peek(syn::token::Paren) {
-				let contents;
-				parenthesized!(contents in input);
-				let method = contents.parse::<Ident>()?;
-				contents.parse::<Token![,]>()?;
-				let ty = contents.parse::<Type>()?;
-				Some((ty, method))
-			} else if lookahead.peek(Token![,]) {
-				None
-			} else if input.is_empty() {
-				None
-			} else {
-				return Err(lookahead.error());
-			};
-			out.push(Is {
-				pascal_call_name: pascal_ident_to_call(&name),
-				snake_call_name: pascal_ident_to_snake_call(&name),
-				name,
-				via,
-			});
-			if input.peek(Token![,]) {
-				input.parse::<Token![,]>()?;
-				continue;
-			} else {
-				break;
-			}
-		}
-		Ok(Self(out))
-	}
-}
-
-pub struct InterfaceInfo {
-	name: Ident,
-	is: IsList,
-	inline_is: IsList,
-	events: IsList,
-	expect_selector: Option<u32>,
-}
-impl Parse for InterfaceInfo {
-	fn parse(input: ParseStream) -> syn::Result<Self> {
-		let mut name = None;
-		let mut is = None;
-		let mut inline_is = None;
-		let mut events = None;
-		let mut expect_selector = None;
-		// TODO: create proc-macro to optimize proc-macro boilerplate? :D
-		loop {
-			let lookahead = input.lookahead1();
-			if lookahead.peek(kw::name) {
-				let k = input.parse::<kw::name>()?;
-				input.parse::<Token![=]>()?;
-				if name.replace(input.parse::<Ident>()?).is_some() {
-					return Err(syn::Error::new(k.span(), "name is already set"));
-				}
-			} else if lookahead.peek(kw::is) {
-				let k = input.parse::<kw::is>()?;
-				let contents;
-				parenthesized!(contents in input);
-				if is.replace(contents.parse::<IsList>()?).is_some() {
-					return Err(syn::Error::new(k.span(), "is is already set"));
-				}
-			} else if lookahead.peek(kw::inline_is) {
-				let k = input.parse::<kw::inline_is>()?;
-				let contents;
-				parenthesized!(contents in input);
-				if inline_is.replace(contents.parse::<IsList>()?).is_some() {
-					return Err(syn::Error::new(k.span(), "inline_is is already set"));
-				}
-			} else if lookahead.peek(kw::events) {
-				let k = input.parse::<kw::events>()?;
-				let contents;
-				parenthesized!(contents in input);
-				if events.replace(contents.parse::<IsList>()?).is_some() {
-					return Err(syn::Error::new(k.span(), "events is already set"));
-				}
-			} else if lookahead.peek(kw::expect_selector) {
-				let k = input.parse::<kw::expect_selector>()?;
-				input.parse::<Token![=]>()?;
-				let value = input.parse::<LitInt>()?;
-				if expect_selector
-					.replace(value.base10_parse::<u32>()?)
-					.is_some()
-				{
-					return Err(syn::Error::new(k.span(), "expect_selector is already set"));
-				}
-			} else if input.is_empty() {
-				break;
-			} else {
-				return Err(lookahead.error());
-			}
-			if input.peek(Token![,]) {
-				input.parse::<Token![,]>()?;
-			} else {
-				break;
-			}
-		}
-		Ok(Self {
-			name: name.ok_or_else(|| syn::Error::new(input.span(), "missing name"))?,
-			is: is.unwrap_or_default(),
-			inline_is: inline_is.unwrap_or_default(),
-			events: events.unwrap_or_default(),
-			expect_selector,
-		})
-	}
-}
-
-struct MethodInfo {
-	rename_selector: Option<String>,
-}
-impl Parse for MethodInfo {
-	fn parse(input: ParseStream) -> syn::Result<Self> {
-		let mut rename_selector = None;
-		let lookahead = input.lookahead1();
-		if lookahead.peek(kw::rename_selector) {
-			let k = input.parse::<kw::rename_selector>()?;
-			input.parse::<Token![=]>()?;
-			if rename_selector
-				.replace(input.parse::<LitStr>()?.value())
-				.is_some()
-			{
-				return Err(syn::Error::new(k.span(), "rename_selector is already set"));
-			}
-		}
-		Ok(Self { rename_selector })
-	}
-}
-
-enum AbiType {
-	// type
-	Plain(Ident),
-	// (type1,type2)
-	Tuple(Vec<AbiType>),
-	// type[]
-	Vec(Box<AbiType>),
-	// type[20]
-	Array(Box<AbiType>, usize),
-}
-impl AbiType {
-	fn try_from(value: &Type) -> syn::Result<Self> {
-		let value = Self::try_maybe_special_from(value)?;
-		if value.is_special() {
-			return Err(syn::Error::new(value.span(), "unexpected special type"));
-		}
-		Ok(value)
-	}
-	fn try_maybe_special_from(value: &Type) -> syn::Result<Self> {
-		match value {
-			Type::Array(arr) => {
-				let wrapped = AbiType::try_from(&arr.elem)?;
-				match &arr.len {
-					Expr::Lit(l) => match &l.lit {
-						Lit::Int(i) => {
-							let num = i.base10_parse::<usize>()?;
-							Ok(AbiType::Array(Box::new(wrapped), num as usize))
-						}
-						_ => Err(syn::Error::new(arr.len.span(), "should be int literal")),
-					},
-					_ => Err(syn::Error::new(arr.len.span(), "should be literal")),
-				}
-			}
-			Type::Path(_) => {
-				let path = parse_path(value)?;
-				let segment = parse_path_segment(path)?;
-				if segment.ident == "Vec" {
-					let args = match &segment.arguments {
-						PathArguments::AngleBracketed(e) => e,
-						_ => {
-							return Err(syn::Error::new(
-								segment.arguments.span(),
-								"missing Vec generic",
-							))
-						}
-					};
-					let args = &args.args;
-					if args.len() != 1 {
-						return Err(syn::Error::new(
-							args.span(),
-							"expected only one generic for vec",
-						));
-					}
-					let arg = args.first().expect("first arg");
-
-					let ty = match arg {
-						GenericArgument::Type(ty) => ty,
-						_ => {
-							return Err(syn::Error::new(
-								arg.span(),
-								"expected first generic to be type",
-							))
-						}
-					};
-
-					let wrapped = AbiType::try_from(ty)?;
-					Ok(Self::Vec(Box::new(wrapped)))
-				} else {
-					if !segment.arguments.is_empty() {
-						return Err(syn::Error::new(
-							segment.arguments.span(),
-							"unexpected generic arguments for non-vec type",
-						));
-					}
-					Ok(Self::Plain(segment.ident.clone()))
-				}
-			}
-			Type::Tuple(t) => {
-				let mut out = Vec::with_capacity(t.elems.len());
-				for el in t.elems.iter() {
-					out.push(AbiType::try_from(el)?)
-				}
-				Ok(Self::Tuple(out))
-			}
-			_ => Err(syn::Error::new(
-				value.span(),
-				"unexpected type, only arrays, plain types and tuples are supported",
-			)),
-		}
-	}
-	fn is_value(&self) -> bool {
-		matches!(self, Self::Plain(v) if v == "value")
-	}
-	fn is_caller(&self) -> bool {
-		matches!(self, Self::Plain(v) if v == "caller")
-	}
-	fn is_special(&self) -> bool {
-		self.is_caller() || self.is_value()
-	}
-	fn selector_ty_buf(&self, buf: &mut String) -> std::fmt::Result {
-		match self {
-			AbiType::Plain(t) => {
-				write!(buf, "{}", t)
-			}
-			AbiType::Tuple(t) => {
-				write!(buf, "(")?;
-				for (i, t) in t.iter().enumerate() {
-					if i != 0 {
-						write!(buf, ",")?;
-					}
-					t.selector_ty_buf(buf)?;
-				}
-				write!(buf, ")")
-			}
-			AbiType::Vec(v) => {
-				v.selector_ty_buf(buf)?;
-				write!(buf, "[]")
-			}
-			AbiType::Array(v, len) => {
-				v.selector_ty_buf(buf)?;
-				write!(buf, "[{}]", len)
-			}
-		}
-	}
-	fn selector_ty(&self) -> String {
-		let mut out = String::new();
-		self.selector_ty_buf(&mut out).expect("no fmt error");
-		out
-	}
-}
-impl ToTokens for AbiType {
-	fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
-		match self {
-			AbiType::Plain(t) => tokens.extend(quote! {#t}),
-			AbiType::Tuple(t) => {
-				tokens.extend(quote! {(
-					#(#t),*
-				)});
-			}
-			AbiType::Vec(v) => tokens.extend(quote! {Vec<#v>}),
-			AbiType::Array(v, l) => tokens.extend(quote! {[#v; #l]}),
-		}
-	}
-}
-
-struct MethodArg {
-	name: Ident,
-	camel_name: String,
-	ty: AbiType,
-}
-impl MethodArg {
-	fn try_from(value: &PatType) -> syn::Result<Self> {
-		let name = parse_ident_from_pat(&value.pat)?.clone();
-		Ok(Self {
-			camel_name: cases::camelcase::to_camel_case(&name.to_string()),
-			name,
-			ty: AbiType::try_maybe_special_from(&value.ty)?,
-		})
-	}
-	fn is_value(&self) -> bool {
-		self.ty.is_value()
-	}
-	fn is_caller(&self) -> bool {
-		self.ty.is_caller()
-	}
-	fn is_special(&self) -> bool {
-		self.ty.is_special()
-	}
-	fn selector_ty(&self) -> String {
-		assert!(!self.is_special());
-		self.ty.selector_ty()
-	}
-
-	fn expand_call_def(&self) -> proc_macro2::TokenStream {
-		assert!(!self.is_special());
-		let name = &self.name;
-		let ty = &self.ty;
-
-		quote! {
-			#name: #ty
-		}
-	}
-
-	fn expand_parse(&self) -> proc_macro2::TokenStream {
-		assert!(!self.is_special());
-		let name = &self.name;
-		quote! {
-			#name: reader.abi_read()?
-		}
-	}
-
-	fn expand_call_arg(&self) -> proc_macro2::TokenStream {
-		if self.is_value() {
-			quote! {
-				c.value.clone()
-			}
-		} else if self.is_caller() {
-			quote! {
-				c.caller.clone()
-			}
-		} else {
-			let name = &self.name;
-			quote! {
-				#name
-			}
-		}
-	}
-
-	fn expand_solidity_argument(&self) -> proc_macro2::TokenStream {
-		let camel_name = &self.camel_name.to_string();
-		let ty = &self.ty;
-		quote! {
-			<NamedArgument<#ty>>::new(#camel_name)
-		}
-	}
-}
-
-#[derive(PartialEq)]
-enum Mutability {
-	Mutable,
-	View,
-	Pure,
-}
-
-mod kw {
-	syn::custom_keyword!(weight);
-
-	syn::custom_keyword!(via);
-	syn::custom_keyword!(name);
-	syn::custom_keyword!(is);
-	syn::custom_keyword!(inline_is);
-	syn::custom_keyword!(events);
-	syn::custom_keyword!(expect_selector);
-
-	syn::custom_keyword!(rename_selector);
-}
-
-struct Method {
-	name: Ident,
-	camel_name: String,
-	pascal_name: Ident,
-	screaming_name: Ident,
-	selector_str: String,
-	selector: u32,
-	args: Vec<MethodArg>,
-	has_normal_args: bool,
-	mutability: Mutability,
-	result: Type,
-	weight: Option<Expr>,
-	docs: Vec<String>,
-}
-impl Method {
-	fn try_from(value: &ImplItemMethod) -> syn::Result<Self> {
-		let mut info = MethodInfo {
-			rename_selector: None,
-		};
-		let mut docs = Vec::new();
-		let mut weight = None;
-		for attr in &value.attrs {
-			let ident = parse_ident_from_path(&attr.path, false)?;
-			if ident == "solidity" {
-				info = attr.parse_args::<MethodInfo>()?;
-			} else if ident == "doc" {
-				let args = attr.parse_meta().unwrap();
-				let value = match args {
-					Meta::NameValue(MetaNameValue {
-						lit: Lit::Str(str), ..
-					}) => str.value(),
-					_ => unreachable!(),
-				};
-				docs.push(value);
-			} else if ident == "weight" {
-				weight = Some(attr.parse_args::<Expr>()?);
-			}
-		}
-		let ident = &value.sig.ident;
-		let ident_str = ident.to_string();
-		if !cases::snakecase::is_snake_case(&ident_str) {
-			return Err(syn::Error::new(ident.span(), "method name should be snake_cased\nif alternative solidity name needs to be set - use #[solidity] attribute"));
-		}
-
-		let mut mutability = Mutability::Pure;
-
-		if let Some(FnArg::Receiver(receiver)) = value
-			.sig
-			.inputs
-			.iter()
-			.find(|arg| matches!(arg, FnArg::Receiver(_)))
-		{
-			if receiver.reference.is_none() {
-				return Err(syn::Error::new(
-					receiver.span(),
-					"receiver should be by ref",
-				));
-			}
-			if receiver.mutability.is_some() {
-				mutability = Mutability::Mutable;
-			} else {
-				mutability = Mutability::View;
-			}
-		}
-		let mut args = Vec::new();
-		for typ in value
-			.sig
-			.inputs
-			.iter()
-			.filter(|arg| matches!(arg, FnArg::Typed(_)))
-		{
-			let typ = match typ {
-				FnArg::Typed(typ) => typ,
-				_ => unreachable!(),
-			};
-			args.push(MethodArg::try_from(typ)?);
-		}
-
-		if mutability != Mutability::Mutable && args.iter().any(|arg| arg.is_value()) {
-			return Err(syn::Error::new(
-				args.iter().find(|arg| arg.is_value()).unwrap().ty.span(),
-				"payable function should be mutable",
-			));
-		}
-
-		let result = match &value.sig.output {
-			ReturnType::Type(_, ty) => ty,
-			_ => return Err(syn::Error::new(value.sig.output.span(), "interface method should return Result<value>\nif there is no value to return - specify void (which is alias to unit)")),
-		};
-		let result = parse_result_ok(result)?;
-
-		let camel_name = info
-			.rename_selector
-			.unwrap_or_else(|| cases::camelcase::to_camel_case(&ident.to_string()));
-		let mut selector_str = camel_name.clone();
-		selector_str.push('(');
-		let mut has_normal_args = false;
-		for (i, arg) in args.iter().filter(|arg| !arg.is_special()).enumerate() {
-			if i != 0 {
-				selector_str.push(',');
-			}
-			write!(selector_str, "{}", arg.selector_ty()).unwrap();
-			has_normal_args = true;
-		}
-		selector_str.push(')');
-		let selector = fn_selector_str(&selector_str);
-
-		Ok(Self {
-			name: ident.clone(),
-			camel_name,
-			pascal_name: snake_ident_to_pascal(ident),
-			screaming_name: snake_ident_to_screaming(ident),
-			selector_str,
-			selector,
-			args,
-			has_normal_args,
-			mutability,
-			result: result.clone(),
-			weight,
-			docs,
-		})
-	}
-	fn expand_call_def(&self) -> proc_macro2::TokenStream {
-		let defs = self
-			.args
-			.iter()
-			.filter(|a| !a.is_special())
-			.map(|a| a.expand_call_def());
-		let pascal_name = &self.pascal_name;
-
-		if self.has_normal_args {
-			quote! {
-				#pascal_name {
-					#(
-						#defs,
-					)*
-				}
-			}
-		} else {
-			quote! {#pascal_name}
-		}
-	}
-
-	fn expand_const(&self) -> proc_macro2::TokenStream {
-		let screaming_name = &self.screaming_name;
-		let selector = u32::to_be_bytes(self.selector);
-		let selector_str = &self.selector_str;
-		quote! {
-			#[doc = #selector_str]
-			const #screaming_name: ::evm_coder::types::bytes4 = [#(#selector,)*];
-		}
-	}
-
-	fn expand_interface_id(&self) -> proc_macro2::TokenStream {
-		let screaming_name = &self.screaming_name;
-		quote! {
-			interface_id ^= u32::from_be_bytes(Self::#screaming_name);
-		}
-	}
-
-	fn expand_parse(&self) -> proc_macro2::TokenStream {
-		let pascal_name = &self.pascal_name;
-		let screaming_name = &self.screaming_name;
-		if self.has_normal_args {
-			let parsers = self
-				.args
-				.iter()
-				.filter(|a| !a.is_special())
-				.map(|a| a.expand_parse());
-			quote! {
-				Self::#screaming_name => return Ok(Some(Self::#pascal_name {
-					#(
-						#parsers,
-					)*
-				}))
-			}
-		} else {
-			quote! { Self::#screaming_name => return Ok(Some(Self::#pascal_name)) }
-		}
-	}
-
-	fn expand_variant_call(&self, call_name: &proc_macro2::Ident) -> proc_macro2::TokenStream {
-		let pascal_name = &self.pascal_name;
-		let name = &self.name;
-
-		let matcher = if self.has_normal_args {
-			let names = self
-				.args
-				.iter()
-				.filter(|a| !a.is_special())
-				.map(|a| &a.name);
-
-			quote! {{
-				#(
-					#names,
-				)*
-			}}
-		} else {
-			quote! {}
-		};
-
-		let receiver = match self.mutability {
-			Mutability::Mutable | Mutability::View => quote! {self.},
-			Mutability::Pure => quote! {Self::},
-		};
-		let args = self.args.iter().map(|a| a.expand_call_arg());
-
-		quote! {
-			#call_name::#pascal_name #matcher => {
-				let result = #receiver #name(
-					#(
-						#args,
-					)*
-				)?;
-				(&result).to_result()
-			}
-		}
-	}
-
-	fn expand_variant_weight(&self) -> proc_macro2::TokenStream {
-		let pascal_name = &self.pascal_name;
-		if let Some(weight) = &self.weight {
-			let matcher = if self.has_normal_args {
-				let names = self
-					.args
-					.iter()
-					.filter(|a| !a.is_special())
-					.map(|a| &a.name);
-
-				quote! {{
-					#(
-						#names,
-					)*
-				}}
-			} else {
-				quote! {}
-			};
-			quote! {
-				Self::#pascal_name #matcher => (#weight).into()
-			}
-		} else {
-			let matcher = if self.has_normal_args {
-				quote! {{..}}
-			} else {
-				quote! {}
-			};
-			quote! {
-				Self::#pascal_name #matcher => ().into()
-			}
-		}
-	}
-
-	fn expand_solidity_function(&self) -> proc_macro2::TokenStream {
-		let camel_name = &self.camel_name;
-		let mutability = match self.mutability {
-			Mutability::Mutable => quote! {SolidityMutability::Mutable},
-			Mutability::View => quote! { SolidityMutability::View },
-			Mutability::Pure => quote! {SolidityMutability::Pure},
-		};
-		let result = &self.result;
-
-		let args = self
-			.args
-			.iter()
-			.filter(|a| !a.is_special())
-			.map(MethodArg::expand_solidity_argument);
-		let docs = self.docs.iter();
-		let selector_str = &self.selector_str;
-		let selector = self.selector;
-
-		quote! {
-			SolidityFunction {
-				docs: &[#(#docs),*],
-				selector_str: #selector_str,
-				selector: #selector,
-				name: #camel_name,
-				mutability: #mutability,
-				args: (
-					#(
-						#args,
-					)*
-				),
-				result: <UnnamedArgument<#result>>::default(),
-			}
-		}
-	}
-}
-
-fn generics_list(gen: &Generics) -> proc_macro2::TokenStream {
-	if gen.params.is_empty() {
-		return quote! {};
-	}
-	let params = gen.params.iter().map(|p| match p {
-		syn::GenericParam::Type(id) => {
-			let v = &id.ident;
-			quote! {#v}
-		}
-		syn::GenericParam::Lifetime(lt) => {
-			let v = &lt.lifetime;
-			quote! {#v}
-		}
-		syn::GenericParam::Const(c) => {
-			let i = &c.ident;
-			quote! {#i}
-		}
-	});
-	quote! { #(#params),* }
-}
-fn generics_reference(gen: &Generics) -> proc_macro2::TokenStream {
-	if gen.params.is_empty() {
-		return quote! {};
-	}
-	let list = generics_list(gen);
-	quote! { <#list> }
-}
-fn generics_data(gen: &Generics) -> proc_macro2::TokenStream {
-	let list = generics_list(gen);
-	if gen.params.len() == 1 {
-		quote! {#list}
-	} else {
-		quote! { (#list) }
-	}
-}
-
-pub struct SolidityInterface {
-	generics: Generics,
-	name: Box<syn::Type>,
-	info: InterfaceInfo,
-	methods: Vec<Method>,
-}
-impl SolidityInterface {
-	pub fn try_from(info: InterfaceInfo, value: &ItemImpl) -> syn::Result<Self> {
-		let mut methods = Vec::new();
-
-		for item in &value.items {
-			if let ImplItem::Method(method) = item {
-				methods.push(Method::try_from(method)?)
-			}
-		}
-		Ok(Self {
-			generics: value.generics.clone(),
-			name: value.self_ty.clone(),
-			info,
-			methods,
-		})
-	}
-	pub fn expand(self) -> proc_macro2::TokenStream {
-		let name = self.name;
-
-		let solidity_name = self.info.name.to_string();
-		let call_name = pascal_ident_to_call(&self.info.name);
-		let generics = self.generics;
-		let gen_ref = generics_reference(&generics);
-		let gen_data = generics_data(&generics);
-		let gen_where = &generics.where_clause;
-
-		let call_sub = self
-			.info
-			.inline_is
-			.0
-			.iter()
-			.chain(self.info.is.0.iter())
-			.map(|c| Is::expand_call_def(c, &gen_ref));
-		let call_parse = self
-			.info
-			.inline_is
-			.0
-			.iter()
-			.chain(self.info.is.0.iter())
-			.map(|is| Is::expand_parse(is, &gen_ref));
-		let call_variants = self
-			.info
-			.inline_is
-			.0
-			.iter()
-			.chain(self.info.is.0.iter())
-			.map(|c| Is::expand_variant_call(c, &call_name, &gen_ref));
-		let weight_variants = self
-			.info
-			.inline_is
-			.0
-			.iter()
-			.chain(self.info.is.0.iter())
-			.map(Is::expand_variant_weight);
-
-		let inline_interface_id = self.info.inline_is.0.iter().map(Is::expand_interface_id);
-		let supports_interface = self
-			.info
-			.is
-			.0
-			.iter()
-			.map(|is| Is::expand_supports_interface(is, &gen_ref));
-
-		let calls = self.methods.iter().map(Method::expand_call_def);
-		let consts = self.methods.iter().map(Method::expand_const);
-		let interface_id = self.methods.iter().map(Method::expand_interface_id);
-		let parsers = self.methods.iter().map(Method::expand_parse);
-		let call_variants_this = self
-			.methods
-			.iter()
-			.map(|m| Method::expand_variant_call(m, &call_name));
-		let weight_variants_this = self.methods.iter().map(Method::expand_variant_weight);
-		let solidity_functions = self.methods.iter().map(Method::expand_solidity_function);
-
-		// TODO: Inline inline_is
-		let solidity_is = self
-			.info
-			.is
-			.0
-			.iter()
-			.chain(self.info.inline_is.0.iter())
-			.map(|is| is.name.to_string());
-		let solidity_events_is = self.info.events.0.iter().map(|is| is.name.to_string());
-		let solidity_generators = self
-			.info
-			.is
-			.0
-			.iter()
-			.chain(self.info.inline_is.0.iter())
-			.map(|is| Is::expand_generator(is, &gen_ref));
-		let solidity_event_generators = self.info.events.0.iter().map(Is::expand_event_generator);
-
-		if let Some(expect_selector) = &self.info.expect_selector {
-			if !self.info.inline_is.0.is_empty() {
-				return syn::Error::new(
-					name.span(),
-					"expect_selector is not compatible with inline_is",
-				)
-				.to_compile_error();
-			}
-			let selector = self
-				.methods
-				.iter()
-				.map(|m| m.selector)
-				.fold(0, |a, b| a ^ b);
-
-			if *expect_selector != selector {
-				let mut methods = String::new();
-				for meth in self.methods.iter() {
-					write!(methods, "\n- {}", meth.selector_str).expect("write to string");
-				}
-				return syn::Error::new(name.span(), format!("expected selector mismatch, expected {expect_selector:0>8x}, but implementation has {selector:0>8x}{methods}")).to_compile_error();
-			}
-		}
-		// let methods = self.methods.iter().map(Method::solidity_def);
-
-		quote! {
-			#[derive(Debug)]
-			pub enum #call_name #gen_ref {
-				ERC165Call(::evm_coder::ERC165Call, ::core::marker::PhantomData<#gen_data>),
-				#(
-					#calls,
-				)*
-				#(
-					#call_sub,
-				)*
-			}
-			impl #gen_ref #call_name #gen_ref {
-				#(
-					#consts
-				)*
-				pub fn interface_id() -> ::evm_coder::types::bytes4 {
-					let mut interface_id = 0;
-					#(#interface_id)*
-					#(#inline_interface_id)*
-					u32::to_be_bytes(interface_id)
-				}
-				pub fn supports_interface(interface_id: ::evm_coder::types::bytes4) -> bool {
-					interface_id != u32::to_be_bytes(0xffffff) && (
-						interface_id == ::evm_coder::ERC165Call::INTERFACE_ID ||
-						interface_id == Self::interface_id()
-						#(
-							|| #supports_interface
-						)*
-					)
-				}
-				pub fn generate_solidity_interface(tc: &evm_coder::solidity::TypeCollector, is_impl: bool) {
-					use evm_coder::solidity::*;
-					use core::fmt::Write;
-					let interface = SolidityInterface {
-						name: #solidity_name,
-						selector: Self::interface_id(),
-						is: &["Dummy", "ERC165", #(
-							#solidity_is,
-						)* #(
-							#solidity_events_is,
-						)* ],
-						functions: (#(
-							#solidity_functions,
-						)*),
-					};
-
-					let mut out = string::new();
-					if #solidity_name.starts_with("Inline") {
-						out.push_str("/// @dev inlined interface\n");
-					}
-					let _ = interface.format(is_impl, &mut out, tc);
-					tc.collect(out);
-					#(
-						#solidity_event_generators
-					)*
-					#(
-						#solidity_generators
-					)*
-					if is_impl {
-						tc.collect("/// @dev common stubs holder\ncontract Dummy {\n\tuint8 dummy;\n\tstring stub_error = \"this contract is implemented in native\";\n}\ncontract ERC165 is Dummy {\n\tfunction supportsInterface(bytes4 interfaceID) external view returns (bool) {\n\t\trequire(false, stub_error);\n\t\tinterfaceID;\n\t\treturn true;\n\t}\n}\n".into());
-					} else {
-						tc.collect("/// @dev common stubs holder\ninterface Dummy {\n}\ninterface ERC165 is Dummy {\n\tfunction supportsInterface(bytes4 interfaceID) external view returns (bool);\n}\n".into());
-					}
-				}
-			}
-			impl #gen_ref ::evm_coder::Call for #call_name #gen_ref {
-				fn parse(method_id: ::evm_coder::types::bytes4, reader: &mut ::evm_coder::abi::AbiReader) -> ::evm_coder::execution::Result<Option<Self>> {
-					use ::evm_coder::abi::AbiRead;
-					match method_id {
-						::evm_coder::ERC165Call::INTERFACE_ID => return Ok(
-							::evm_coder::ERC165Call::parse(method_id, reader)?
-							.map(|c| Self::ERC165Call(c, ::core::marker::PhantomData))
-						),
-						#(
-							#parsers,
-						)*
-						_ => {},
-					}
-					#(
-						#call_parse
-					)else*
-					return Ok(None);
-				}
-			}
-			impl #generics ::evm_coder::Weighted for #call_name #gen_ref
-			#gen_where
-			{
-				#[allow(unused_variables)]
-				fn weight(&self) -> ::evm_coder::execution::DispatchInfo {
-					match self {
-						#(
-							#weight_variants,
-						)*
-						// TODO: It should be very cheap, but not free
-						Self::ERC165Call(::evm_coder::ERC165Call::SupportsInterface {..}, _) => 100u64.into(),
-						#(
-							#weight_variants_this,
-						)*
-					}
-				}
-			}
-			impl #generics ::evm_coder::Callable<#call_name #gen_ref> for #name
-			#gen_where
-			{
-				#[allow(unreachable_code)] // In case of no inner calls
-				fn call(&mut self, c: Msg<#call_name #gen_ref>) -> ::evm_coder::execution::ResultWithPostInfo<::evm_coder::abi::AbiWriter> {
-					use ::evm_coder::abi::AbiWrite;
-					match c.call {
-						#(
-							#call_variants,
-						)*
-						#call_name::ERC165Call(::evm_coder::ERC165Call::SupportsInterface {interface_id}, _) => {
-							let mut writer = ::evm_coder::abi::AbiWriter::default();
-							writer.bool(&<#call_name #gen_ref>::supports_interface(interface_id));
-							return Ok(writer.into());
-						}
-						_ => {},
-					}
-					let mut writer = ::evm_coder::abi::AbiWriter::default();
-					match c.call {
-						#(
-							#call_variants_this,
-						)*
-						_ => unreachable!()
-					}
-				}
-			}
-		}
-	}
-}
deletedcrates/evm-coder-macros/src/to_log.rsdiffbeforeafterboth
--- a/crates/evm-coder-macros/src/to_log.rs
+++ /dev/null
@@ -1,237 +0,0 @@
-// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
-// This file is part of Unique Network.
-
-// Unique Network is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Unique Network is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-
-use inflector::cases;
-use syn::{Data, DeriveInput, Field, Fields, Ident, Variant, spanned::Spanned};
-use std::fmt::Write;
-use quote::quote;
-
-use crate::{parse_ident_from_path, parse_ident_from_type, snake_ident_to_screaming};
-
-struct EventField {
-	name: Ident,
-	camel_name: String,
-	ty: Ident,
-	indexed: bool,
-}
-
-impl EventField {
-	fn try_from(field: &Field) -> syn::Result<Self> {
-		let name = field.ident.as_ref().unwrap();
-		let ty = parse_ident_from_type(&field.ty, false)?;
-		let mut indexed = false;
-		for attr in &field.attrs {
-			if let Ok(ident) = parse_ident_from_path(&attr.path, false) {
-				if ident == "indexed" {
-					indexed = true;
-				}
-			}
-		}
-		Ok(Self {
-			name: name.to_owned(),
-			camel_name: cases::camelcase::to_camel_case(&name.to_string()),
-			ty: ty.to_owned(),
-			indexed,
-		})
-	}
-	fn expand_solidity_argument(&self) -> proc_macro2::TokenStream {
-		let camel_name = &self.camel_name;
-		let ty = &self.ty;
-		let indexed = self.indexed;
-		quote! {
-			<SolidityEventArgument<#ty>>::new(#indexed, #camel_name)
-		}
-	}
-}
-
-struct Event {
-	name: Ident,
-	name_screaming: Ident,
-	fields: Vec<EventField>,
-	selector: [u8; 32],
-	selector_str: String,
-}
-
-impl Event {
-	fn try_from(variant: &Variant) -> syn::Result<Self> {
-		let name = &variant.ident;
-		let name_screaming = snake_ident_to_screaming(name);
-
-		let named = match &variant.fields {
-			Fields::Named(named) => named,
-			_ => {
-				return Err(syn::Error::new(
-					variant.fields.span(),
-					"expected named fields",
-				))
-			}
-		};
-		let mut fields = Vec::new();
-		for field in &named.named {
-			fields.push(EventField::try_from(field)?);
-		}
-		if fields.iter().filter(|f| f.indexed).count() > 3 {
-			return Err(syn::Error::new(
-				variant.fields.span(),
-				"events can have at most 4 indexed fields (1 indexed field is reserved for event signature)"
-			));
-		}
-		let mut selector_str = format!("{}(", name);
-		for (i, arg) in fields.iter().enumerate() {
-			if i != 0 {
-				write!(selector_str, ",").unwrap();
-			}
-			write!(selector_str, "{}", arg.ty).unwrap();
-		}
-		selector_str.push(')');
-		let selector = crate::event_selector_str(&selector_str);
-
-		Ok(Self {
-			name: name.to_owned(),
-			name_screaming,
-			fields,
-			selector,
-			selector_str,
-		})
-	}
-
-	fn expand_serializers(&self) -> proc_macro2::TokenStream {
-		let name = &self.name;
-		let name_screaming = &self.name_screaming;
-		let fields = self.fields.iter().map(|f| &f.name);
-
-		let indexed = self.fields.iter().filter(|f| f.indexed).map(|f| &f.name);
-		let plain = self.fields.iter().filter(|f| !f.indexed).map(|f| &f.name);
-
-		quote! {
-			Self::#name {#(
-				#fields,
-			)*} => {
-				topics.push(topic::from(Self::#name_screaming));
-				#(
-					topics.push(#indexed.to_topic());
-				)*
-				#(
-					#plain.abi_write(&mut writer);
-				)*
-			}
-		}
-	}
-
-	fn expand_consts(&self) -> proc_macro2::TokenStream {
-		let name_screaming = &self.name_screaming;
-		let selector_str = &self.selector_str;
-		let selector = &self.selector;
-
-		quote! {
-			#[doc = #selector_str]
-			const #name_screaming: [u8; 32] = [#(
-				#selector,
-			)*];
-		}
-	}
-
-	fn expand_solidity_function(&self) -> proc_macro2::TokenStream {
-		let name = self.name.to_string();
-		let args = self.fields.iter().map(EventField::expand_solidity_argument);
-		quote! {
-			SolidityEvent {
-				name: #name,
-				args: (
-					#(
-						#args,
-					)*
-				),
-			}
-		}
-	}
-}
-
-pub struct Events {
-	name: Ident,
-	events: Vec<Event>,
-}
-
-impl Events {
-	pub fn try_from(data: &DeriveInput) -> syn::Result<Self> {
-		let name = &data.ident;
-		let en = match &data.data {
-			Data::Enum(en) => en,
-			_ => return Err(syn::Error::new(data.span(), "expected enum")),
-		};
-		let mut events = Vec::new();
-		for variant in &en.variants {
-			events.push(Event::try_from(variant)?);
-		}
-		Ok(Self {
-			name: name.to_owned(),
-			events,
-		})
-	}
-	pub fn expand(&self) -> proc_macro2::TokenStream {
-		let name = &self.name;
-
-		let consts = self.events.iter().map(Event::expand_consts);
-		let serializers = self.events.iter().map(Event::expand_serializers);
-		let solidity_name = self.name.to_string();
-		let solidity_functions = self.events.iter().map(Event::expand_solidity_function);
-
-		quote! {
-			impl #name {
-				#(
-					#consts
-				)*
-
-				pub fn generate_solidity_interface(tc: &evm_coder::solidity::TypeCollector, is_impl: bool) {
-					use evm_coder::solidity::*;
-					use core::fmt::Write;
-					let interface = SolidityInterface {
-						selector: [0; 4],
-						name: #solidity_name,
-						is: &[],
-						functions: (#(
-							#solidity_functions,
-						)*),
-					};
-					let mut out = string::new();
-					out.push_str("/// @dev inlined interface\n");
-					let _ = interface.format(is_impl, &mut out, tc);
-					tc.collect(out);
-				}
-			}
-
-			#[automatically_derived]
-			impl ::evm_coder::events::ToLog for #name {
-				fn to_log(&self, contract: address) -> ::ethereum::Log {
-					use ::evm_coder::events::ToTopic;
-					use ::evm_coder::abi::AbiWrite;
-					let mut writer = ::evm_coder::abi::AbiWriter::new();
-					let mut topics = Vec::new();
-					match self {
-						#(
-							#serializers,
-						)*
-					}
-					::ethereum::Log {
-						address: contract,
-						topics,
-						data: writer.finish(),
-					}
-				}
-			}
-		}
-	}
-}
addedcrates/evm-coder/procedural/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/crates/evm-coder/procedural/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "evm-coder-procedural"
+version = "0.2.0"
+license = "GPLv3"
+edition = "2021"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+sha3 = "0.10.1"
+quote = "1.0"
+proc-macro2 = "1.0"
+syn = { version = "1.0", features = ["full"] }
+hex = "0.4.3"
+Inflector = "0.11.4"
addedcrates/evm-coder/procedural/src/lib.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/evm-coder/procedural/src/lib.rs
@@ -0,0 +1,298 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+#![allow(dead_code)]
+
+use inflector::cases;
+use proc_macro::TokenStream;
+use quote::quote;
+use sha3::{Digest, Keccak256};
+use syn::{
+	DeriveInput, GenericArgument, Ident, ItemImpl, Pat, Path, PathArguments,
+	PathSegment, Type, parse_macro_input, spanned::Spanned,
+};
+
+mod solidity_interface;
+mod to_log;
+
+fn fn_selector_str(input: &str) -> u32 {
+	let mut hasher = Keccak256::new();
+	hasher.update(input.as_bytes());
+	let result = hasher.finalize();
+
+	let mut selector_bytes = [0; 4];
+	selector_bytes.copy_from_slice(&result[0..4]);
+
+	u32::from_be_bytes(selector_bytes)
+}
+
+/// Returns solidity function selector (first 4 bytes of hash) by its
+/// textual representation
+///
+/// ```ignore
+/// use evm_coder_macros::fn_selector;
+///
+/// assert_eq!(fn_selector!(transfer(address, uint256)), 0xa9059cbb);
+/// ```
+#[proc_macro]
+pub fn fn_selector(input: TokenStream) -> TokenStream {
+	let input = input.to_string().replace(' ', "");
+	let selector = fn_selector_str(&input);
+
+	(quote! {
+		#selector
+	})
+	.into()
+}
+
+fn event_selector_str(input: &str) -> [u8; 32] {
+	let mut hasher = Keccak256::new();
+	hasher.update(input.as_bytes());
+	let result = hasher.finalize();
+
+	let mut selector_bytes = [0; 32];
+	selector_bytes.copy_from_slice(&result[0..32]);
+	selector_bytes
+}
+
+/// Returns solidity topic (hash) by its textual representation
+///
+/// ```ignore
+/// use evm_coder_macros::event_topic;
+///
+/// assert_eq!(
+///     format!("{:x}", event_topic!(Transfer(address, address, uint256))),
+///     "ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
+/// );
+/// ```
+#[proc_macro]
+pub fn event_topic(stream: TokenStream) -> TokenStream {
+	let input = stream.to_string().replace(' ', "");
+	let selector_bytes = event_selector_str(&input);
+
+	(quote! {
+		::primitive_types::H256([#(
+			#selector_bytes,
+		)*])
+	})
+	.into()
+}
+
+fn parse_path(ty: &Type) -> syn::Result<&Path> {
+	match &ty {
+		syn::Type::Path(pat) => {
+			if let Some(qself) = &pat.qself {
+				return Err(syn::Error::new(qself.ty.span(), "no receiver expected"));
+			}
+			Ok(&pat.path)
+		}
+		_ => Err(syn::Error::new(ty.span(), "expected ty to be path")),
+	}
+}
+
+fn parse_path_segment(path: &Path) -> syn::Result<&PathSegment> {
+	if path.segments.len() != 1 {
+		return Err(syn::Error::new(
+			path.span(),
+			"expected path to have only segment",
+		));
+	}
+	let last_segment = &path.segments.last().unwrap();
+	Ok(last_segment)
+}
+
+fn parse_ident_from_pat(pat: &Pat) -> syn::Result<&Ident> {
+	match pat {
+		Pat::Ident(i) => Ok(&i.ident),
+		_ => Err(syn::Error::new(pat.span(), "expected pat ident")),
+	}
+}
+
+fn parse_ident_from_segment(segment: &PathSegment, allow_generics: bool) -> syn::Result<&Ident> {
+	if segment.arguments != PathArguments::None && !allow_generics {
+		return Err(syn::Error::new(
+			segment.arguments.span(),
+			"unexpected generic type",
+		));
+	}
+	Ok(&segment.ident)
+}
+
+fn parse_ident_from_path(path: &Path, allow_generics: bool) -> syn::Result<&Ident> {
+	let segment = parse_path_segment(path)?;
+	parse_ident_from_segment(segment, allow_generics)
+}
+
+fn parse_ident_from_type(ty: &Type, allow_generics: bool) -> syn::Result<&Ident> {
+	let path = parse_path(ty)?;
+	parse_ident_from_path(path, allow_generics)
+}
+
+// Gets T out of Result<T>
+fn parse_result_ok(ty: &Type) -> syn::Result<&Type> {
+	let path = parse_path(ty)?;
+	let segment = parse_path_segment(path)?;
+
+	if segment.ident != "Result" {
+		return Err(syn::Error::new(
+			ty.span(),
+			"expected Result as return type (no renamed aliases allowed)",
+		));
+	}
+	let args = match &segment.arguments {
+		PathArguments::AngleBracketed(e) => e,
+		_ => {
+			return Err(syn::Error::new(
+				segment.arguments.span(),
+				"missing Result generics",
+			))
+		}
+	};
+
+	let args = &args.args;
+	let arg = args.first().unwrap();
+
+	let ty = match arg {
+		GenericArgument::Type(ty) => ty,
+		_ => {
+			return Err(syn::Error::new(
+				arg.span(),
+				"expected first generic to be type",
+			))
+		}
+	};
+
+	Ok(ty)
+}
+
+fn pascal_ident_to_call(ident: &Ident) -> Ident {
+	let name = format!("{}Call", ident);
+	Ident::new(&name, ident.span())
+}
+fn snake_ident_to_pascal(ident: &Ident) -> Ident {
+	let name = ident.to_string();
+	let name = cases::pascalcase::to_pascal_case(&name);
+	Ident::new(&name, ident.span())
+}
+fn snake_ident_to_screaming(ident: &Ident) -> Ident {
+	let name = ident.to_string();
+	let name = cases::screamingsnakecase::to_screaming_snake_case(&name);
+	Ident::new(&name, ident.span())
+}
+fn pascal_ident_to_snake_call(ident: &Ident) -> Ident {
+	let name = ident.to_string();
+	let name = cases::snakecase::to_snake_case(&name);
+	let name = format!("call_{}", name);
+	Ident::new(&name, ident.span())
+}
+
+/// Derives call enum implementing [`evm_coder::Callable`], [`evm_coder::Weighted`]
+/// and [`evm_coder::Call`] from impl block
+///
+/// ## Macro syntax
+///
+/// `#[solidity_interface(name, is, inline_is, events)]`
+/// - *name*: used in generated code, and for Call enum name
+/// - *is*: used to provide call inheritance, not found methods will be delegated to all contracts
+/// specified in is/inline_is
+/// - *inline_is*: same as is, but selectors for passed contracts will be used by derived ERC165
+/// implementation
+///
+/// `#[weight(value)]`
+/// Can be added to every method of impl block, used for deriving [`evm_coder::Weighted`], which
+/// is used by substrate bridge
+/// - *value*: expression, which evaluates to weight required to call this method.
+/// This expression can use call arguments to calculate non-constant execution time.
+/// This expression should evaluate faster than actual execution does, and may provide worser case
+/// than one is called
+///
+/// `#[solidity_interface(rename_selector)]`
+/// - *rename_selector*: by default, selector name will be generated by transforming method name
+/// from snake_case to camelCase. Use this option, if other naming convention is required.
+/// I.e: method `token_uri` will be automatically renamed to `tokenUri` in selector, but name
+/// required by ERC721 standard is `tokenURI`, thus we need to specify `rename_selector = "tokenURI"`
+/// explicitly
+///
+/// Also, any contract method may have doc comments, which will be automatically added to generated
+/// solidity interface definitions
+///
+/// ## Example
+///
+/// ```ignore
+/// struct SuperContract;
+/// struct InlineContract;
+/// struct Contract;
+///
+/// #[derive(ToLog)]
+/// enum ContractEvents {
+///     Event(#[indexed] uint32),
+/// }
+///
+/// #[solidity_interface(name = MyContract, is(SuperContract), inline_is(InlineContract))]
+/// impl Contract {
+///     /// Multiply two numbers
+///     #[weight(200 + a + b)]
+///     #[solidity_interface(rename_selector = "mul")]
+///     fn mul(&mut self, a: uint32, b: uint32) -> Result<uint32> {
+///         Ok(a.checked_mul(b).ok_or("overflow")?)
+///     }
+/// }
+/// ```
+#[proc_macro_attribute]
+pub fn solidity_interface(args: TokenStream, stream: TokenStream) -> TokenStream {
+	let args = parse_macro_input!(args as solidity_interface::InterfaceInfo);
+
+	let input: ItemImpl = match syn::parse(stream) {
+		Ok(t) => t,
+		Err(e) => return e.to_compile_error().into(),
+	};
+
+	let expanded = match solidity_interface::SolidityInterface::try_from(args, &input) {
+		Ok(v) => v.expand(),
+		Err(e) => e.to_compile_error(),
+	};
+
+	(quote! {
+		#input
+
+		#expanded
+	})
+	.into()
+}
+
+#[proc_macro_attribute]
+pub fn solidity(_args: TokenStream, stream: TokenStream) -> TokenStream {
+	stream
+}
+#[proc_macro_attribute]
+pub fn weight(_args: TokenStream, stream: TokenStream) -> TokenStream {
+	stream
+}
+
+/// ## Syntax
+///
+/// `#[indexed]`
+/// Marks this field as indexed, so it will appear in [`ethereum::Log`] topics instead of data
+#[proc_macro_derive(ToLog, attributes(indexed))]
+pub fn to_log(value: TokenStream) -> TokenStream {
+	let input = parse_macro_input!(value as DeriveInput);
+
+	match to_log::Events::try_from(&input) {
+		Ok(e) => e.expand(),
+		Err(e) => e.to_compile_error(),
+	}
+	.into()
+}
addedcrates/evm-coder/procedural/src/solidity_interface.rsdiffbeforeafterboth

no changes

addedcrates/evm-coder/procedural/src/to_log.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/evm-coder/procedural/src/to_log.rs
@@ -0,0 +1,238 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+use inflector::cases;
+use syn::{Data, DeriveInput, Field, Fields, Ident, Variant, spanned::Spanned};
+use std::fmt::Write;
+use quote::quote;
+
+use crate::{parse_ident_from_path, parse_ident_from_type, snake_ident_to_screaming};
+
+struct EventField {
+	name: Ident,
+	camel_name: String,
+	ty: Ident,
+	indexed: bool,
+}
+
+impl EventField {
+	fn try_from(field: &Field) -> syn::Result<Self> {
+		let name = field.ident.as_ref().unwrap();
+		let ty = parse_ident_from_type(&field.ty, false)?;
+		let mut indexed = false;
+		for attr in &field.attrs {
+			if let Ok(ident) = parse_ident_from_path(&attr.path, false) {
+				if ident == "indexed" {
+					indexed = true;
+				}
+			}
+		}
+		Ok(Self {
+			name: name.to_owned(),
+			camel_name: cases::camelcase::to_camel_case(&name.to_string()),
+			ty: ty.to_owned(),
+			indexed,
+		})
+	}
+	fn expand_solidity_argument(&self) -> proc_macro2::TokenStream {
+		let camel_name = &self.camel_name;
+		let ty = &self.ty;
+		let indexed = self.indexed;
+		quote! {
+			<SolidityEventArgument<#ty>>::new(#indexed, #camel_name)
+		}
+	}
+}
+
+struct Event {
+	name: Ident,
+	name_screaming: Ident,
+	fields: Vec<EventField>,
+	selector: [u8; 32],
+	selector_str: String,
+}
+
+impl Event {
+	fn try_from(variant: &Variant) -> syn::Result<Self> {
+		let name = &variant.ident;
+		let name_screaming = snake_ident_to_screaming(name);
+
+		let named = match &variant.fields {
+			Fields::Named(named) => named,
+			_ => {
+				return Err(syn::Error::new(
+					variant.fields.span(),
+					"expected named fields",
+				))
+			}
+		};
+		let mut fields = Vec::new();
+		for field in &named.named {
+			fields.push(EventField::try_from(field)?);
+		}
+		if fields.iter().filter(|f| f.indexed).count() > 3 {
+			return Err(syn::Error::new(
+				variant.fields.span(),
+				"events can have at most 4 indexed fields (1 indexed field is reserved for event signature)"
+			));
+		}
+		let mut selector_str = format!("{}(", name);
+		for (i, arg) in fields.iter().enumerate() {
+			if i != 0 {
+				write!(selector_str, ",").unwrap();
+			}
+			write!(selector_str, "{}", arg.ty).unwrap();
+		}
+		selector_str.push(')');
+		let selector = crate::event_selector_str(&selector_str);
+
+		Ok(Self {
+			name: name.to_owned(),
+			name_screaming,
+			fields,
+			selector,
+			selector_str,
+		})
+	}
+
+	fn expand_serializers(&self) -> proc_macro2::TokenStream {
+		let name = &self.name;
+		let name_screaming = &self.name_screaming;
+		let fields = self.fields.iter().map(|f| &f.name);
+
+		let indexed = self.fields.iter().filter(|f| f.indexed).map(|f| &f.name);
+		let plain = self.fields.iter().filter(|f| !f.indexed).map(|f| &f.name);
+
+		quote! {
+			Self::#name {#(
+				#fields,
+			)*} => {
+				topics.push(topic::from(Self::#name_screaming));
+				#(
+					topics.push(#indexed.to_topic());
+				)*
+				#(
+					#plain.abi_write(&mut writer);
+				)*
+			}
+		}
+	}
+
+	fn expand_consts(&self) -> proc_macro2::TokenStream {
+		let name_screaming = &self.name_screaming;
+		let selector_str = &self.selector_str;
+		let selector = &self.selector;
+
+		quote! {
+			#[doc = #selector_str]
+			const #name_screaming: [u8; 32] = [#(
+				#selector,
+			)*];
+		}
+	}
+
+	fn expand_solidity_function(&self) -> proc_macro2::TokenStream {
+		let name = self.name.to_string();
+		let args = self.fields.iter().map(EventField::expand_solidity_argument);
+		quote! {
+			SolidityEvent {
+				name: #name,
+				args: (
+					#(
+						#args,
+					)*
+				),
+			}
+		}
+	}
+}
+
+pub struct Events {
+	name: Ident,
+	events: Vec<Event>,
+}
+
+impl Events {
+	pub fn try_from(data: &DeriveInput) -> syn::Result<Self> {
+		let name = &data.ident;
+		let en = match &data.data {
+			Data::Enum(en) => en,
+			_ => return Err(syn::Error::new(data.span(), "expected enum")),
+		};
+		let mut events = Vec::new();
+		for variant in &en.variants {
+			events.push(Event::try_from(variant)?);
+		}
+		Ok(Self {
+			name: name.to_owned(),
+			events,
+		})
+	}
+	pub fn expand(&self) -> proc_macro2::TokenStream {
+		let name = &self.name;
+
+		let consts = self.events.iter().map(Event::expand_consts);
+		let serializers = self.events.iter().map(Event::expand_serializers);
+		let solidity_name = self.name.to_string();
+		let solidity_functions = self.events.iter().map(Event::expand_solidity_function);
+
+		quote! {
+			impl #name {
+				#(
+					#consts
+				)*
+
+				pub fn generate_solidity_interface(tc: &evm_coder::solidity::TypeCollector, is_impl: bool) {
+					use evm_coder::solidity::*;
+					use core::fmt::Write;
+					let interface = SolidityInterface {
+						docs: &[],
+						selector: [0; 4],
+						name: #solidity_name,
+						is: &[],
+						functions: (#(
+							#solidity_functions,
+						)*),
+					};
+					let mut out = string::new();
+					out.push_str("/// @dev inlined interface\n");
+					let _ = interface.format(is_impl, &mut out, tc);
+					tc.collect(out);
+				}
+			}
+
+			#[automatically_derived]
+			impl ::evm_coder::events::ToLog for #name {
+				fn to_log(&self, contract: address) -> ::ethereum::Log {
+					use ::evm_coder::events::ToTopic;
+					use ::evm_coder::abi::AbiWrite;
+					let mut writer = ::evm_coder::abi::AbiWriter::new();
+					let mut topics = Vec::new();
+					match self {
+						#(
+							#serializers,
+						)*
+					}
+					::ethereum::Log {
+						address: contract,
+						topics,
+						data: writer.finish(),
+					}
+				}
+			}
+		}
+	}
+}
modifiedcrates/evm-coder/src/solidity.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/solidity.rs
+++ b/crates/evm-coder/src/solidity.rs
@@ -427,9 +427,6 @@
 		for doc in self.docs {
 			writeln!(writer, "\t///{}", doc)?;
 		}
-		if !self.docs.is_empty() {
-			writeln!(writer, "\t///")?;
-		}
 		writeln!(writer, "\t/// @dev EVM selector for this function is: 0x{:0>8x},", self.selector)?;
 		writeln!(writer, "\t///  or in textual repr: {}", self.selector_str)?;
 		write!(writer, "\tfunction {}(", self.name)?;
@@ -491,6 +488,7 @@
 }
 
 pub struct SolidityInterface<F: SolidityFunctions> {
+	pub docs: &'static [&'static str],
 	pub selector: bytes4,
 	pub name: &'static str,
 	pub is: &'static [&'static str],
@@ -505,6 +503,9 @@
 		tc: &TypeCollector,
 	) -> fmt::Result {
 		const ZERO_BYTES: [u8; 4] = [0; 4];
+		for doc in self.docs {
+			writeln!(out, "///{}", doc)?;
+		}
 		if self.selector != ZERO_BYTES {
 			writeln!(
 				out,