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

difftreelog

source

crates/evm-coder-macros/src/solidity_interface.rs11.5 KiBsourcehistory
1#![allow(dead_code)]23use quote::quote;4use darling::FromMeta;5use inflector::cases;6use std::fmt::Write;7use syn::{FnArg, Generics, Ident, ImplItem, ImplItemMethod, ItemImpl, Meta, NestedMeta, PatType, Path, ReturnType, Type, spanned::Spanned};89use crate::{10	fn_selector_str, parse_ident_from_pat, parse_ident_from_path, parse_ident_from_type,11	parse_result_ok, pascal_ident_to_call, pascal_ident_to_snake_call, snake_ident_to_pascal,12	snake_ident_to_screaming,13};1415struct Is {16	name: Ident,17	pascal_call_name: Ident,18	snake_call_name: Ident,19}20impl Is {21	fn try_from(path: &Path) -> syn::Result<Self> {22		let name = parse_ident_from_path(path, false)?.clone();23		Ok(Self {24			pascal_call_name: pascal_ident_to_call(&name),25			snake_call_name: pascal_ident_to_snake_call(&name),26			name,27		})28	}2930	fn expand_call_def(&self) -> proc_macro2::TokenStream {31		let name = &self.name;32		let pascal_call_name = &self.pascal_call_name;33		quote! {34			#name(#pascal_call_name)35		}36	}3738	fn expand_interface_id(&self) -> proc_macro2::TokenStream {39		let pascal_call_name = &self.pascal_call_name;40		quote! {41			interface_id ^= #pascal_call_name::interface_id();42		}43	}4445	fn expand_supports_interface(&self) -> proc_macro2::TokenStream {46		let pascal_call_name = &self.pascal_call_name;47		quote! {48			#pascal_call_name::supports_interface(interface_id)49		}50	}5152	fn expand_variant_call(&self) -> proc_macro2::TokenStream {53		let name = &self.name;54		let pascal_call_name = &self.pascal_call_name;55		quote! {56			InternalCall::#name(call) => return <Self as ::evm_coder::Callable<#pascal_call_name>>::call(self, Msg {57				call,58				caller: c.caller,59				value: c.value,60			})61		}62	}6364	fn expand_parse(&self) -> proc_macro2::TokenStream {65		let name = &self.name;66		let pascal_call_name = &self.pascal_call_name;67		quote! {68			if let Some(parsed_call) = #pascal_call_name::parse(method_id, reader)? {69				return Ok(Some(Self::#name(parsed_call)))70			}71		}72	}73}7475#[derive(Default)]76struct IsList(Vec<Is>);77impl FromMeta for IsList {78	fn from_list(items: &[NestedMeta]) -> darling::Result<Self> {79		let mut out = Vec::new();80		for item in items {81			match item {82				NestedMeta::Meta(Meta::Path(path)) => out.push(Is::try_from(path)?),83				_ => return Err(syn::Error::new(item.span(), "expected path").into()),84			}85		}86		Ok(Self(out))87	}88}8990#[derive(FromMeta)]91pub struct InterfaceInfo {92	name: Ident,93	#[darling(default)]94	is: IsList,95	#[darling(default)]96	inline_is: IsList,97	#[darling(default)]98	events: IsList,99}100101#[derive(FromMeta)]102struct MethodInfo {103	#[darling(default)]104	rename_selector: Option<String>,105}106107struct MethodArg {108	name: Ident,109	ty: Ident,110}111impl MethodArg {112	fn try_from(value: &PatType) -> syn::Result<Self> {113		Ok(Self {114			name: parse_ident_from_pat(&value.pat)?.clone(),115			ty: parse_ident_from_type(&value.ty, false)?.clone(),116		})117	}118	fn is_value(&self) -> bool {119		self.ty == "value"120	}121	fn is_caller(&self) -> bool {122		self.ty == "caller"123	}124	fn is_special(&self) -> bool {125		self.is_value() || self.is_caller()126	}127	fn selector_ty(&self) -> &Ident {128		assert!(!self.is_special());129		&self.ty130	}131132	fn expand_call_def(&self) -> proc_macro2::TokenStream {133		assert!(!self.is_special());134		let name = &self.name;135		let ty = &self.ty;136137		quote! {138			#name: #ty139		}140	}141142	fn expand_parse(&self) -> proc_macro2::TokenStream {143		assert!(!self.is_special());144		let name = &self.name;145		quote! {146			#name: reader.abi_read()?147		}148	}149150	fn expand_call_arg(&self) -> proc_macro2::TokenStream {151		if self.is_value() {152			quote! {153				c.value.clone()154			}155		} else if self.is_caller() {156			quote! {157				c.caller.clone()158			}159		} else {160			let name = &self.name;161			quote! {162				#name163			}164		}165	}166}167168#[derive(PartialEq)]169enum Mutability {170	Mutable,171	View,172	Pure,173}174175struct Method {176	name: Ident,177	camel_name: String,178	pascal_name: Ident,179	screaming_name: Ident,180	selector_str: String,181	selector: u32,182	args: Vec<MethodArg>,183	has_normal_args: bool,184	mutability: Mutability,185	result: Type,186}187impl Method {188	fn try_from(value: &ImplItemMethod) -> syn::Result<Self> {189		let mut info = MethodInfo {190			rename_selector: None,191		};192		for attr in &value.attrs {193			let ident = parse_ident_from_path(&attr.path, false)?;194			if ident == "solidity" {195				let args = attr.parse_meta().unwrap();196				info = MethodInfo::from_meta(&args).unwrap();197			} else if ident == "doc" {198				// TODO: Add docs to evm interfaces199			}200		}201		let ident = &value.sig.ident;202		let ident_str = ident.to_string();203		if !cases::snakecase::is_snake_case(&ident_str) {204			return Err(syn::Error::new(ident.span(), "method name should be snake_cased\nif alternative solidity name needs to be set - use #[solidity] attribute"));205		}206207		let mut mutability = Mutability::Pure;208209		if let Some(FnArg::Receiver(receiver)) = value210			.sig211			.inputs212			.iter()213			.find(|arg| matches!(arg, FnArg::Receiver(_)))214		{215			if receiver.reference.is_none() {216				return Err(syn::Error::new(217					receiver.span(),218					"receiver should be by ref",219				));220			}221			if receiver.mutability.is_some() {222				mutability = Mutability::Mutable;223			} else {224				mutability = Mutability::View;225			}226		}227		let mut args = Vec::new();228		for typ in value229			.sig230			.inputs231			.iter()232			.filter(|arg| matches!(arg, FnArg::Typed(_)))233		{234			let typ = match typ {235				FnArg::Typed(typ) => typ,236				_ => unreachable!(),237			};238			args.push(MethodArg::try_from(typ)?);239		}240241		if mutability != Mutability::Mutable && args.iter().any(|arg| arg.is_value()) {242			return Err(syn::Error::new(243				args.iter().find(|arg| arg.is_value()).unwrap().ty.span(),244				"payable function should be mutable",245			));246		}247248		let result = match &value.sig.output {249			ReturnType::Type(_, ty) => ty,250			_ => 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)")),251		};252		let result = parse_result_ok(result)?;253254		let camel_name = info255			.rename_selector256			.unwrap_or_else(|| cases::camelcase::to_camel_case(&ident.to_string()));257		let mut selector_str = camel_name.clone();258		selector_str.push('(');259		let mut has_normal_args = false;260		for (i, arg) in args.iter().filter(|arg| !arg.is_special()).enumerate() {261			if i != 0 {262				selector_str.push(',');263			}264			write!(selector_str, "{}", arg.selector_ty()).unwrap();265			has_normal_args = true;266		}267		selector_str.push(')');268		let selector = fn_selector_str(&selector_str);269270		Ok(Self {271			name: ident.clone(),272			camel_name,273			pascal_name: snake_ident_to_pascal(ident),274			screaming_name: snake_ident_to_screaming(ident),275			selector_str,276			selector,277			args,278			has_normal_args,279			mutability,280			result: result.clone(),281		})282	}283	fn expand_call_def(&self) -> proc_macro2::TokenStream {284		let defs = self285			.args286			.iter()287			.filter(|a| !a.is_special())288			.map(|a| a.expand_call_def());289		let pascal_name = &self.pascal_name;290291		if self.has_normal_args {292			quote! {293				#pascal_name {294					#(295						#defs,296					)*297				}298			}299		} else {300			quote! {#pascal_name}301		}302	}303304	fn expand_const(&self) -> proc_macro2::TokenStream {305		let screaming_name = &self.screaming_name;306		let selector = self.selector;307		let selector_str = &self.selector_str;308		quote! {309			#[doc = #selector_str]310			const #screaming_name: u32 = #selector;311		}312	}313314	fn expand_interface_id(&self) -> proc_macro2::TokenStream {315		let screaming_name = &self.screaming_name;316		quote! {317			interface_id ^= Self::#screaming_name;318		}319	}320321	fn expand_parse(&self) -> proc_macro2::TokenStream {322		let pascal_name = &self.pascal_name;323		let screaming_name = &self.screaming_name;324		if self.has_normal_args {325			let parsers = self326				.args327				.iter()328				.filter(|a| !a.is_special())329				.map(|a| a.expand_parse());330			quote! {331				Self::#screaming_name => return Ok(Some(Self::#pascal_name {332					#(333						#parsers,334					)*335				}))336			}337		} else {338			quote! { Self::#screaming_name => return Ok(Some(Self::#pascal_name)) }339		}340	}341342	fn expand_variant_call(&self) -> proc_macro2::TokenStream {343		let pascal_name = &self.pascal_name;344		let name = &self.name;345346		let matcher = if self.has_normal_args {347			let names = self348				.args349				.iter()350				.filter(|a| !a.is_special())351				.map(|a| &a.name);352353			quote! {{354				#(355					#names,356				)*357			}}358		} else {359			quote! {}360		};361362		let receiver = match self.mutability {363			Mutability::Mutable | Mutability::View => quote! {self.},364			Mutability::Pure => quote! {Self::},365		};366		let args = self.args.iter().map(|a| a.expand_call_arg());367368		quote! {369			InternalCall::#pascal_name #matcher => {370				let result = #receiver #name(371					#(372						#args,373					)*374				)?;375				(&result).abi_write(&mut writer);376			}377		}378	}379}380381pub struct SolidityInterface {382    generics: Generics,383	name: Box<syn::Type>,384	info: InterfaceInfo,385	methods: Vec<Method>,386}387impl SolidityInterface {388	pub fn try_from(info: InterfaceInfo, value: &ItemImpl) -> syn::Result<Self> {389		let mut methods = Vec::new();390391		for item in &value.items {392			if let ImplItem::Method(method) = item {393				methods.push(Method::try_from(method)?)394			}395		}396		Ok(Self {397            generics: value.generics.clone(),398			name: value.self_ty.clone(),399			info,400			methods,401		})402	}403	pub fn expand(self) -> proc_macro2::TokenStream {404		let name = self.name;405406		let call_name = pascal_ident_to_call(&self.info.name);407        let generics = self.generics;408409		let call_sub = self410			.info411			.inline_is412			.0413			.iter()414			.chain(self.info.is.0.iter())415			.map(Is::expand_call_def);416		let call_parse = self417			.info418			.inline_is419			.0420			.iter()421			.chain(self.info.is.0.iter())422			.map(Is::expand_parse);423		let call_variants = self424			.info425			.inline_is426			.0427			.iter()428			.chain(self.info.is.0.iter())429			.map(Is::expand_variant_call);430431		let inline_interface_id = self.info.inline_is.0.iter().map(Is::expand_interface_id);432		let supports_interface = self.info.is.0.iter().map(Is::expand_supports_interface);433434		let calls = self.methods.iter().map(Method::expand_call_def);435		let consts = self.methods.iter().map(Method::expand_const);436		let interface_id = self.methods.iter().map(Method::expand_interface_id);437		let parsers = self.methods.iter().map(Method::expand_parse);438		let call_variants_this = self.methods.iter().map(Method::expand_variant_call);439440		// let methods = self.methods.iter().map(Method::solidity_def);441442		quote! {443			#[derive(Debug)]444			pub enum #call_name {445				#(446					#calls,447				)*448				#(449					#call_sub,450				)*451			}452			impl #call_name {453				#(454					#consts455				)*456				pub const fn interface_id() -> u32 {457					let mut interface_id = 0;458					#(#interface_id)*459					#(#inline_interface_id)*460					interface_id461				}462				pub fn supports_interface(interface_id: u32) -> bool {463					interface_id != 0xffffff && (464						interface_id == Self::interface_id()465						#(466							|| #supports_interface467						)*468					)469				}470			}471			impl ::evm_coder::Call for #call_name {472				fn parse(method_id: u32, reader: &mut ::evm_coder::abi::AbiReader) -> ::evm_coder::execution::Result<Option<Self>> {473					use ::evm_coder::abi::AbiRead;474					match method_id {475						#(476							#parsers,477						)*478						_ => {},479					}480					#(481						#call_parse482					)else*483					return Ok(None);484				}485			}486			impl #generics ::evm_coder::Callable<#call_name> for #name {487				#[allow(unreachable_code)] // In case of no inner calls488				fn call(&mut self, c: Msg<#call_name>) -> Result<::evm_coder::abi::AbiWriter> {489					use ::evm_coder::abi::AbiWrite;490					type InternalCall = #call_name;491					match c.call {492						#(493							#call_variants,494						)*495						_ => {},496					}497					let mut writer = ::evm_coder::abi::AbiWriter::default();498					match c.call {499						#(500							#call_variants_this,501						)*502						_ => unreachable!()503					}504					Ok(writer)505				}506			}507		}508	}509}