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

difftreelog

source

crates/evm-coder-macros/src/solidity_interface.rs12.7 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	}76}7778#[derive(Default)]79struct IsList(Vec<Is>);80impl FromMeta for IsList {81	fn from_list(items: &[NestedMeta]) -> darling::Result<Self> {82		let mut out = Vec::new();83		for item in items {84			match item {85				NestedMeta::Meta(Meta::Path(path)) => out.push(Is::try_from(path)?),86				_ => return Err(syn::Error::new(item.span(), "expected path").into()),87			}88		}89		Ok(Self(out))90	}91}9293#[derive(FromMeta)]94pub struct InterfaceInfo {95	name: Ident,96	#[darling(default)]97	is: IsList,98	#[darling(default)]99	inline_is: IsList,100	#[darling(default)]101	events: IsList,102}103104#[derive(FromMeta)]105struct MethodInfo {106	#[darling(default)]107	rename_selector: Option<String>,108}109110struct MethodArg {111	name: Ident,112	ty: Ident,113}114impl MethodArg {115	fn try_from(value: &PatType) -> syn::Result<Self> {116		Ok(Self {117			name: parse_ident_from_pat(&value.pat)?.clone(),118			ty: parse_ident_from_type(&value.ty, false)?.clone(),119		})120	}121	fn is_value(&self) -> bool {122		self.ty == "value"123	}124	fn is_caller(&self) -> bool {125		self.ty == "caller"126	}127	fn is_special(&self) -> bool {128		self.is_value() || self.is_caller()129	}130	fn selector_ty(&self) -> &Ident {131		assert!(!self.is_special());132		&self.ty133	}134135	fn expand_call_def(&self) -> proc_macro2::TokenStream {136		assert!(!self.is_special());137		let name = &self.name;138		let ty = &self.ty;139140		quote! {141			#name: #ty142		}143	}144145	fn expand_parse(&self) -> proc_macro2::TokenStream {146		assert!(!self.is_special());147		let name = &self.name;148		quote! {149			#name: reader.abi_read()?150		}151	}152153	fn expand_call_arg(&self) -> proc_macro2::TokenStream {154		if self.is_value() {155			quote! {156				c.value.clone()157			}158		} else if self.is_caller() {159			quote! {160				c.caller.clone()161			}162		} else {163			let name = &self.name;164			quote! {165				#name166			}167		}168	}169170	fn expand_solidity_argument(&self) -> proc_macro2::TokenStream {171		let name = &self.name.to_string();172		let ty = &self.ty;173		quote! {174			<NamedArgument<#ty>>::new(#name)175		}176	}177}178179#[derive(PartialEq)]180enum Mutability {181	Mutable,182	View,183	Pure,184}185186struct Method {187	name: Ident,188	camel_name: String,189	pascal_name: Ident,190	screaming_name: Ident,191	selector_str: String,192	selector: u32,193	args: Vec<MethodArg>,194	has_normal_args: bool,195	mutability: Mutability,196	result: Type,197}198impl Method {199	fn try_from(value: &ImplItemMethod) -> syn::Result<Self> {200		let mut info = MethodInfo {201			rename_selector: None,202		};203		for attr in &value.attrs {204			let ident = parse_ident_from_path(&attr.path, false)?;205			if ident == "solidity" {206				let args = attr.parse_meta().unwrap();207				info = MethodInfo::from_meta(&args).unwrap();208			} else if ident == "doc" {209				// TODO: Add docs to evm interfaces210			}211		}212		let ident = &value.sig.ident;213		let ident_str = ident.to_string();214		if !cases::snakecase::is_snake_case(&ident_str) {215			return Err(syn::Error::new(ident.span(), "method name should be snake_cased\nif alternative solidity name needs to be set - use #[solidity] attribute"));216		}217218		let mut mutability = Mutability::Pure;219220		if let Some(FnArg::Receiver(receiver)) = value221			.sig222			.inputs223			.iter()224			.find(|arg| matches!(arg, FnArg::Receiver(_)))225		{226			if receiver.reference.is_none() {227				return Err(syn::Error::new(228					receiver.span(),229					"receiver should be by ref",230				));231			}232			if receiver.mutability.is_some() {233				mutability = Mutability::Mutable;234			} else {235				mutability = Mutability::View;236			}237		}238		let mut args = Vec::new();239		for typ in value240			.sig241			.inputs242			.iter()243			.filter(|arg| matches!(arg, FnArg::Typed(_)))244		{245			let typ = match typ {246				FnArg::Typed(typ) => typ,247				_ => unreachable!(),248			};249			args.push(MethodArg::try_from(typ)?);250		}251252		if mutability != Mutability::Mutable && args.iter().any(|arg| arg.is_value()) {253			return Err(syn::Error::new(254				args.iter().find(|arg| arg.is_value()).unwrap().ty.span(),255				"payable function should be mutable",256			));257		}258259		let result = match &value.sig.output {260			ReturnType::Type(_, ty) => ty,261			_ => 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)")),262		};263		let result = parse_result_ok(result)?;264265		let camel_name = info266			.rename_selector267			.unwrap_or_else(|| cases::camelcase::to_camel_case(&ident.to_string()));268		let mut selector_str = camel_name.clone();269		selector_str.push('(');270		let mut has_normal_args = false;271		for (i, arg) in args.iter().filter(|arg| !arg.is_special()).enumerate() {272			if i != 0 {273				selector_str.push(',');274			}275			write!(selector_str, "{}", arg.selector_ty()).unwrap();276			has_normal_args = true;277		}278		selector_str.push(')');279		let selector = fn_selector_str(&selector_str);280281		Ok(Self {282			name: ident.clone(),283			camel_name,284			pascal_name: snake_ident_to_pascal(ident),285			screaming_name: snake_ident_to_screaming(ident),286			selector_str,287			selector,288			args,289			has_normal_args,290			mutability,291			result: result.clone(),292		})293	}294	fn expand_call_def(&self) -> proc_macro2::TokenStream {295		let defs = self296			.args297			.iter()298			.filter(|a| !a.is_special())299			.map(|a| a.expand_call_def());300		let pascal_name = &self.pascal_name;301302		if self.has_normal_args {303			quote! {304				#pascal_name {305					#(306						#defs,307					)*308				}309			}310		} else {311			quote! {#pascal_name}312		}313	}314315	fn expand_const(&self) -> proc_macro2::TokenStream {316		let screaming_name = &self.screaming_name;317		let selector = self.selector;318		let selector_str = &self.selector_str;319		quote! {320			#[doc = #selector_str]321			const #screaming_name: u32 = #selector;322		}323	}324325	fn expand_interface_id(&self) -> proc_macro2::TokenStream {326		let screaming_name = &self.screaming_name;327		quote! {328			interface_id ^= Self::#screaming_name;329		}330	}331332	fn expand_parse(&self) -> proc_macro2::TokenStream {333		let pascal_name = &self.pascal_name;334		let screaming_name = &self.screaming_name;335		if self.has_normal_args {336			let parsers = self337				.args338				.iter()339				.filter(|a| !a.is_special())340				.map(|a| a.expand_parse());341			quote! {342				Self::#screaming_name => return Ok(Some(Self::#pascal_name {343					#(344						#parsers,345					)*346				}))347			}348		} else {349			quote! { Self::#screaming_name => return Ok(Some(Self::#pascal_name)) }350		}351	}352353	fn expand_variant_call(&self) -> proc_macro2::TokenStream {354		let pascal_name = &self.pascal_name;355		let name = &self.name;356357		let matcher = if self.has_normal_args {358			let names = self359				.args360				.iter()361				.filter(|a| !a.is_special())362				.map(|a| &a.name);363364			quote! {{365				#(366					#names,367				)*368			}}369		} else {370			quote! {}371		};372373		let receiver = match self.mutability {374			Mutability::Mutable | Mutability::View => quote! {self.},375			Mutability::Pure => quote! {Self::},376		};377		let args = self.args.iter().map(|a| a.expand_call_arg());378379		quote! {380			InternalCall::#pascal_name #matcher => {381				let result = #receiver #name(382					#(383						#args,384					)*385				)?;386				(&result).abi_write(&mut writer);387			}388		}389	}390391	fn expand_solidity_function(&self) -> proc_macro2::TokenStream {392		let camel_name = &self.camel_name;393		let mutability = match self.mutability {394			Mutability::Mutable => quote! {SolidityMutability::Mutable},395			Mutability::View => quote! { SolidityMutability::View },396			Mutability::Pure => quote! {SolidityMutability::Pure},397		};398		let result = &self.result;399400		let args = self.args.iter().map(MethodArg::expand_solidity_argument);401402		quote! {403			SolidityFunction {404				name: #camel_name,405				mutability: #mutability,406				args: (407					#(408						#args,409					)*410				),411				result: <UnnamedArgument<#result>>::default(),412			}413		}414	}415}416417pub struct SolidityInterface {418	generics: Generics,419	name: Box<syn::Type>,420	info: InterfaceInfo,421	methods: Vec<Method>,422}423impl SolidityInterface {424	pub fn try_from(info: InterfaceInfo, value: &ItemImpl) -> syn::Result<Self> {425		let mut methods = Vec::new();426427		for item in &value.items {428			if let ImplItem::Method(method) = item {429				methods.push(Method::try_from(method)?)430			}431		}432		Ok(Self {433			generics: value.generics.clone(),434			name: value.self_ty.clone(),435			info,436			methods,437		})438	}439	pub fn expand(self) -> proc_macro2::TokenStream {440		let name = self.name;441442		let solidity_name = self.info.name.to_string();443		let call_name = pascal_ident_to_call(&self.info.name);444		let generics = self.generics;445446		let call_sub = self447			.info448			.inline_is449			.0450			.iter()451			.chain(self.info.is.0.iter())452			.map(Is::expand_call_def);453		let call_parse = self454			.info455			.inline_is456			.0457			.iter()458			.chain(self.info.is.0.iter())459			.map(Is::expand_parse);460		let call_variants = self461			.info462			.inline_is463			.0464			.iter()465			.chain(self.info.is.0.iter())466			.map(Is::expand_variant_call);467468		let inline_interface_id = self.info.inline_is.0.iter().map(Is::expand_interface_id);469		let supports_interface = self.info.is.0.iter().map(Is::expand_supports_interface);470471		let calls = self.methods.iter().map(Method::expand_call_def);472		let consts = self.methods.iter().map(Method::expand_const);473		let interface_id = self.methods.iter().map(Method::expand_interface_id);474		let parsers = self.methods.iter().map(Method::expand_parse);475		let call_variants_this = self.methods.iter().map(Method::expand_variant_call);476		let solidity_functions = self.methods.iter().map(Method::expand_solidity_function);477478		// let methods = self.methods.iter().map(Method::solidity_def);479480		quote! {481			#[derive(Debug)]482			pub enum #call_name {483				#(484					#calls,485				)*486				#(487					#call_sub,488				)*489			}490			impl #call_name {491				#(492					#consts493				)*494				pub const fn interface_id() -> u32 {495					let mut interface_id = 0;496					#(#interface_id)*497					#(#inline_interface_id)*498					interface_id499				}500				pub fn supports_interface(interface_id: u32) -> bool {501					interface_id != 0xffffff && (502						interface_id == Self::interface_id()503						#(504							|| #supports_interface505						)*506					)507				}508				pub fn generate_solidity_interface() -> string {509					use evm_coder::solidity::*;510					use core::fmt::Write;511					let interface = SolidityInterface {512						name: #solidity_name,513						functions: (#(514							#solidity_functions,515						)*),516					};517					let mut out = string::new();518					let _ = interface.format(&mut out);519					out520				}521			}522			impl ::evm_coder::Call for #call_name {523				fn parse(method_id: u32, reader: &mut ::evm_coder::abi::AbiReader) -> ::evm_coder::execution::Result<Option<Self>> {524					use ::evm_coder::abi::AbiRead;525					match method_id {526						#(527							#parsers,528						)*529						_ => {},530					}531					#(532						#call_parse533					)else*534					return Ok(None);535				}536			}537			impl #generics ::evm_coder::Callable<#call_name> for #name {538				#[allow(unreachable_code)] // In case of no inner calls539				fn call(&mut self, c: Msg<#call_name>) -> Result<::evm_coder::abi::AbiWriter> {540					use ::evm_coder::abi::AbiWrite;541					type InternalCall = #call_name;542					match c.call {543						#(544							#call_variants,545						)*546						_ => {},547					}548					let mut writer = ::evm_coder::abi::AbiWriter::default();549					match c.call {550						#(551							#call_variants_this,552						)*553						_ => unreachable!()554					}555					Ok(writer)556				}557			}558		}559	}560}