git.delta.rocks / unique-network / refs/commits / 8057bddf39cb

difftreelog

source

crates/evm-coder-macros/src/solidity_interface.rs14.5 KiBsourcehistory
1#![allow(dead_code)]23use quote::quote;4use darling::FromMeta;5use inflector::cases;6use std::fmt::Write;7use syn::{8	FnArg, Generics, Ident, ImplItem, ImplItemMethod, ItemImpl, Meta, NestedMeta, PatType, Path,9	ReturnType, Type, spanned::Spanned,10};1112use crate::{13	fn_selector_str, parse_ident_from_pat, parse_ident_from_path, parse_ident_from_type,14	parse_result_ok, pascal_ident_to_call, pascal_ident_to_snake_call, snake_ident_to_pascal,15	snake_ident_to_screaming,16};1718struct Is {19	name: Ident,20	pascal_call_name: Ident,21	snake_call_name: Ident,22}23impl Is {24	fn try_from(path: &Path) -> syn::Result<Self> {25		let name = parse_ident_from_path(path, false)?.clone();26		Ok(Self {27			pascal_call_name: pascal_ident_to_call(&name),28			snake_call_name: pascal_ident_to_snake_call(&name),29			name,30		})31	}3233	fn expand_call_def(&self) -> proc_macro2::TokenStream {34		let name = &self.name;35		let pascal_call_name = &self.pascal_call_name;36		quote! {37			#name(#pascal_call_name)38		}39	}4041	fn expand_interface_id(&self) -> proc_macro2::TokenStream {42		let pascal_call_name = &self.pascal_call_name;43		quote! {44			interface_id ^= #pascal_call_name::interface_id();45		}46	}4748	fn expand_supports_interface(&self) -> proc_macro2::TokenStream {49		let pascal_call_name = &self.pascal_call_name;50		quote! {51			#pascal_call_name::supports_interface(interface_id)52		}53	}5455	fn expand_variant_call(&self) -> proc_macro2::TokenStream {56		let name = &self.name;57		let pascal_call_name = &self.pascal_call_name;58		quote! {59			InternalCall::#name(call) => return <Self as ::evm_coder::Callable<#pascal_call_name>>::call(self, Msg {60				call,61				caller: c.caller,62				value: c.value,63			})64		}65	}6667	fn expand_parse(&self) -> proc_macro2::TokenStream {68		let name = &self.name;69		let pascal_call_name = &self.pascal_call_name;70		quote! {71			if let Some(parsed_call) = #pascal_call_name::parse(method_id, reader)? {72				return Ok(Some(Self::#name(parsed_call)))73			}74		}75	}7677	fn expand_generator(&self) -> proc_macro2::TokenStream {78		let pascal_call_name = &self.pascal_call_name;79		quote! {80			#pascal_call_name::generate_solidity_interface(out_set, is_impl);81		}82	}8384	fn expand_event_generator(&self) -> proc_macro2::TokenStream {85		let name = &self.name;86		quote! {87			#name::generate_solidity_interface(out_set, is_impl);88		}89	}90}9192#[derive(Default)]93struct IsList(Vec<Is>);94impl FromMeta for IsList {95	fn from_list(items: &[NestedMeta]) -> darling::Result<Self> {96		let mut out = Vec::new();97		for item in items {98			match item {99				NestedMeta::Meta(Meta::Path(path)) => out.push(Is::try_from(path)?),100				_ => return Err(syn::Error::new(item.span(), "expected path").into()),101			}102		}103		Ok(Self(out))104	}105}106107#[derive(FromMeta)]108pub struct InterfaceInfo {109	name: Ident,110	#[darling(default)]111	is: IsList,112	#[darling(default)]113	inline_is: IsList,114	#[darling(default)]115	events: IsList,116}117118#[derive(FromMeta)]119struct MethodInfo {120	#[darling(default)]121	rename_selector: Option<String>,122}123124struct MethodArg {125	name: Ident,126	camel_name: String,127	ty: Ident,128}129impl MethodArg {130	fn try_from(value: &PatType) -> syn::Result<Self> {131		let name = parse_ident_from_pat(&value.pat)?.clone();132		Ok(Self {133			camel_name: cases::camelcase::to_camel_case(&name.to_string()),134			name,135			ty: parse_ident_from_type(&value.ty, false)?.clone(),136		})137	}138	fn is_value(&self) -> bool {139		self.ty == "value"140	}141	fn is_caller(&self) -> bool {142		self.ty == "caller"143	}144	fn is_special(&self) -> bool {145		self.is_value() || self.is_caller()146	}147	fn selector_ty(&self) -> &Ident {148		assert!(!self.is_special());149		&self.ty150	}151152	fn expand_call_def(&self) -> proc_macro2::TokenStream {153		assert!(!self.is_special());154		let name = &self.name;155		let ty = &self.ty;156157		quote! {158			#name: #ty159		}160	}161162	fn expand_parse(&self) -> proc_macro2::TokenStream {163		assert!(!self.is_special());164		let name = &self.name;165		quote! {166			#name: reader.abi_read()?167		}168	}169170	fn expand_call_arg(&self) -> proc_macro2::TokenStream {171		if self.is_value() {172			quote! {173				c.value.clone()174			}175		} else if self.is_caller() {176			quote! {177				c.caller.clone()178			}179		} else {180			let name = &self.name;181			quote! {182				#name183			}184		}185	}186187	fn expand_solidity_argument(&self) -> proc_macro2::TokenStream {188		let camel_name = &self.camel_name.to_string();189		let ty = &self.ty;190		quote! {191			<NamedArgument<#ty>>::new(#camel_name)192		}193	}194}195196#[derive(PartialEq)]197enum Mutability {198	Mutable,199	View,200	Pure,201}202203struct Method {204	name: Ident,205	camel_name: String,206	pascal_name: Ident,207	screaming_name: Ident,208	selector_str: String,209	selector: u32,210	args: Vec<MethodArg>,211	has_normal_args: bool,212	mutability: Mutability,213	result: Type,214}215impl Method {216	fn try_from(value: &ImplItemMethod) -> syn::Result<Self> {217		let mut info = MethodInfo {218			rename_selector: None,219		};220		for attr in &value.attrs {221			let ident = parse_ident_from_path(&attr.path, false)?;222			if ident == "solidity" {223				let args = attr.parse_meta().unwrap();224				info = MethodInfo::from_meta(&args).unwrap();225			} else if ident == "doc" {226				// TODO: Add docs to evm interfaces227			}228		}229		let ident = &value.sig.ident;230		let ident_str = ident.to_string();231		if !cases::snakecase::is_snake_case(&ident_str) {232			return Err(syn::Error::new(ident.span(), "method name should be snake_cased\nif alternative solidity name needs to be set - use #[solidity] attribute"));233		}234235		let mut mutability = Mutability::Pure;236237		if let Some(FnArg::Receiver(receiver)) = value238			.sig239			.inputs240			.iter()241			.find(|arg| matches!(arg, FnArg::Receiver(_)))242		{243			if receiver.reference.is_none() {244				return Err(syn::Error::new(245					receiver.span(),246					"receiver should be by ref",247				));248			}249			if receiver.mutability.is_some() {250				mutability = Mutability::Mutable;251			} else {252				mutability = Mutability::View;253			}254		}255		let mut args = Vec::new();256		for typ in value257			.sig258			.inputs259			.iter()260			.filter(|arg| matches!(arg, FnArg::Typed(_)))261		{262			let typ = match typ {263				FnArg::Typed(typ) => typ,264				_ => unreachable!(),265			};266			args.push(MethodArg::try_from(typ)?);267		}268269		if mutability != Mutability::Mutable && args.iter().any(|arg| arg.is_value()) {270			return Err(syn::Error::new(271				args.iter().find(|arg| arg.is_value()).unwrap().ty.span(),272				"payable function should be mutable",273			));274		}275276		let result = match &value.sig.output {277			ReturnType::Type(_, ty) => ty,278			_ => 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)")),279		};280		let result = parse_result_ok(result)?;281282		let camel_name = info283			.rename_selector284			.unwrap_or_else(|| cases::camelcase::to_camel_case(&ident.to_string()));285		let mut selector_str = camel_name.clone();286		selector_str.push('(');287		let mut has_normal_args = false;288		for (i, arg) in args.iter().filter(|arg| !arg.is_special()).enumerate() {289			if i != 0 {290				selector_str.push(',');291			}292			write!(selector_str, "{}", arg.selector_ty()).unwrap();293			has_normal_args = true;294		}295		selector_str.push(')');296		let selector = fn_selector_str(&selector_str);297298		Ok(Self {299			name: ident.clone(),300			camel_name,301			pascal_name: snake_ident_to_pascal(ident),302			screaming_name: snake_ident_to_screaming(ident),303			selector_str,304			selector,305			args,306			has_normal_args,307			mutability,308			result: result.clone(),309		})310	}311	fn expand_call_def(&self) -> proc_macro2::TokenStream {312		let defs = self313			.args314			.iter()315			.filter(|a| !a.is_special())316			.map(|a| a.expand_call_def());317		let pascal_name = &self.pascal_name;318319		if self.has_normal_args {320			quote! {321				#pascal_name {322					#(323						#defs,324					)*325				}326			}327		} else {328			quote! {#pascal_name}329		}330	}331332	fn expand_const(&self) -> proc_macro2::TokenStream {333		let screaming_name = &self.screaming_name;334		let selector = self.selector;335		let selector_str = &self.selector_str;336		quote! {337			#[doc = #selector_str]338			const #screaming_name: u32 = #selector;339		}340	}341342	fn expand_interface_id(&self) -> proc_macro2::TokenStream {343		let screaming_name = &self.screaming_name;344		quote! {345			interface_id ^= Self::#screaming_name;346		}347	}348349	fn expand_parse(&self) -> proc_macro2::TokenStream {350		let pascal_name = &self.pascal_name;351		let screaming_name = &self.screaming_name;352		if self.has_normal_args {353			let parsers = self354				.args355				.iter()356				.filter(|a| !a.is_special())357				.map(|a| a.expand_parse());358			quote! {359				Self::#screaming_name => return Ok(Some(Self::#pascal_name {360					#(361						#parsers,362					)*363				}))364			}365		} else {366			quote! { Self::#screaming_name => return Ok(Some(Self::#pascal_name)) }367		}368	}369370	fn expand_variant_call(&self) -> proc_macro2::TokenStream {371		let pascal_name = &self.pascal_name;372		let name = &self.name;373374		let matcher = if self.has_normal_args {375			let names = self376				.args377				.iter()378				.filter(|a| !a.is_special())379				.map(|a| &a.name);380381			quote! {{382				#(383					#names,384				)*385			}}386		} else {387			quote! {}388		};389390		let receiver = match self.mutability {391			Mutability::Mutable | Mutability::View => quote! {self.},392			Mutability::Pure => quote! {Self::},393		};394		let args = self.args.iter().map(|a| a.expand_call_arg());395396		quote! {397			InternalCall::#pascal_name #matcher => {398				let result = #receiver #name(399					#(400						#args,401					)*402				)?;403				(&result).abi_write(&mut writer);404			}405		}406	}407408	fn expand_solidity_function(&self) -> proc_macro2::TokenStream {409		let camel_name = &self.camel_name;410		let mutability = match self.mutability {411			Mutability::Mutable => quote! {SolidityMutability::Mutable},412			Mutability::View => quote! { SolidityMutability::View },413			Mutability::Pure => quote! {SolidityMutability::Pure},414		};415		let result = &self.result;416417		let args = self418			.args419			.iter()420			.filter(|a| !a.is_special())421			.map(MethodArg::expand_solidity_argument);422423		quote! {424			SolidityFunction {425				name: #camel_name,426				mutability: #mutability,427				args: (428					#(429						#args,430					)*431				),432				result: <UnnamedArgument<#result>>::default(),433			}434		}435	}436}437438pub struct SolidityInterface {439	generics: Generics,440	name: Box<syn::Type>,441	info: InterfaceInfo,442	methods: Vec<Method>,443}444impl SolidityInterface {445	pub fn try_from(info: InterfaceInfo, value: &ItemImpl) -> syn::Result<Self> {446		let mut methods = Vec::new();447448		for item in &value.items {449			if let ImplItem::Method(method) = item {450				methods.push(Method::try_from(method)?)451			}452		}453		Ok(Self {454			generics: value.generics.clone(),455			name: value.self_ty.clone(),456			info,457			methods,458		})459	}460	pub fn expand(self) -> proc_macro2::TokenStream {461		let name = self.name;462463		let solidity_name = self.info.name.to_string();464		let call_name = pascal_ident_to_call(&self.info.name);465		let generics = self.generics;466467		let call_sub = self468			.info469			.inline_is470			.0471			.iter()472			.chain(self.info.is.0.iter())473			.map(Is::expand_call_def);474		let call_parse = self475			.info476			.inline_is477			.0478			.iter()479			.chain(self.info.is.0.iter())480			.map(Is::expand_parse);481		let call_variants = self482			.info483			.inline_is484			.0485			.iter()486			.chain(self.info.is.0.iter())487			.map(Is::expand_variant_call);488489		let inline_interface_id = self.info.inline_is.0.iter().map(Is::expand_interface_id);490		let supports_interface = self.info.is.0.iter().map(Is::expand_supports_interface);491492		let calls = self.methods.iter().map(Method::expand_call_def);493		let consts = self.methods.iter().map(Method::expand_const);494		let interface_id = self.methods.iter().map(Method::expand_interface_id);495		let parsers = self.methods.iter().map(Method::expand_parse);496		let call_variants_this = self.methods.iter().map(Method::expand_variant_call);497		let solidity_functions = self.methods.iter().map(Method::expand_solidity_function);498499		// TODO: Inline inline_is500		let solidity_is = self501			.info502			.is503			.0504			.iter()505			.chain(self.info.inline_is.0.iter())506			.map(|is| is.name.to_string());507		let solidity_events_is = self.info.events.0.iter().map(|is| is.name.to_string());508		let solidity_generators = self509			.info510			.is511			.0512			.iter()513			.chain(self.info.inline_is.0.iter())514			.map(Is::expand_generator);515		let solidity_event_generators = self.info.events.0.iter().map(Is::expand_event_generator);516517		// let methods = self.methods.iter().map(Method::solidity_def);518519		quote! {520			#[derive(Debug)]521			pub enum #call_name {522				#(523					#calls,524				)*525				#(526					#call_sub,527				)*528			}529			impl #call_name {530				#(531					#consts532				)*533				pub const fn interface_id() -> u32 {534					let mut interface_id = 0;535					#(#interface_id)*536					#(#inline_interface_id)*537					interface_id538				}539				pub fn supports_interface(interface_id: u32) -> bool {540					interface_id != 0xffffff && (541						interface_id == Self::interface_id()542						#(543							|| #supports_interface544						)*545					)546				}547				pub fn generate_solidity_interface(out_set: &mut sp_std::collections::btree_set::BTreeSet<string>, is_impl: bool) {548					use evm_coder::solidity::*;549					use core::fmt::Write;550					let interface = SolidityInterface {551						name: #solidity_name,552						is: &["Dummy", #(553							#solidity_is,554						)* #(555							#solidity_events_is,556						)* ],557						functions: (#(558							#solidity_functions,559						)*),560					};561					if is_impl {562						out_set.insert("// Common stubs holder\ncontract Dummy {\n\tuint8 dummy;\n\tstring stub_error = \"this contract is implemented in native\";\n}\n".into());563					} else {564						out_set.insert("// Common stubs holder\ninterface Dummy {\n}\n".into());565					}566					#(567						#solidity_generators568					)*569					#(570						#solidity_event_generators571					)*572573					let mut out = string::new();574					// In solidity interface usage (is) should be preceeded by interface definition575					// This comment helps to sort it in a set576					if #solidity_name.starts_with("Inline") {577						out.push_str("// Inline\n");578					}579					let _ = interface.format(is_impl, &mut out);580					out_set.insert(out);581				}582			}583			impl ::evm_coder::Call for #call_name {584				fn parse(method_id: u32, reader: &mut ::evm_coder::abi::AbiReader) -> ::evm_coder::execution::Result<Option<Self>> {585					use ::evm_coder::abi::AbiRead;586					match method_id {587						#(588							#parsers,589						)*590						_ => {},591					}592					#(593						#call_parse594					)else*595					return Ok(None);596				}597			}598			impl #generics ::evm_coder::Callable<#call_name> for #name {599				#[allow(unreachable_code)] // In case of no inner calls600				fn call(&mut self, c: Msg<#call_name>) -> Result<::evm_coder::abi::AbiWriter> {601					use ::evm_coder::abi::AbiWrite;602					type InternalCall = #call_name;603					match c.call {604						#(605							#call_variants,606						)*607						_ => {},608					}609					let mut writer = ::evm_coder::abi::AbiWriter::default();610					match c.call {611						#(612							#call_variants_this,613						)*614						_ => unreachable!()615					}616					Ok(writer)617				}618			}619		}620	}621}