git.delta.rocks / unique-network / refs/commits / 3d14ec560294

difftreelog

source

crates/evm-coder-macros/src/solidity_interface.rs19.3 KiBsourcehistory
1#![allow(dead_code)]23use quote::quote;4use darling::{FromMeta, ToTokens};5use inflector::cases;6use std::fmt::Write;7use syn::{8	Expr, FnArg, GenericArgument, Generics, Ident, ImplItem, ImplItemMethod, ItemImpl, Lit, Meta,9	MetaNameValue, NestedMeta, PatType, Path, PathArguments, ReturnType, Type, spanned::Spanned,10};1112use crate::{13	fn_selector_str, parse_ident_from_pat, parse_ident_from_path, parse_path, parse_path_segment,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(tc, 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(tc, 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}123124enum AbiType {125	// type126	Plain(Ident),127	// (type1,type2)128	Tuple(Vec<AbiType>),129	// type[]130	Vec(Box<AbiType>),131	// type[20]132	Array(Box<AbiType>, usize),133}134impl AbiType {135	fn try_from(value: &Type) -> syn::Result<Self> {136		let value = Self::try_maybe_special_from(value)?;137		if value.is_special() {138			return Err(syn::Error::new(value.span(), "unexpected special type"));139		}140		Ok(value)141	}142	fn try_maybe_special_from(value: &Type) -> syn::Result<Self> {143		match value {144			Type::Array(arr) => {145				let wrapped = AbiType::try_from(&arr.elem)?;146				match &arr.len {147					Expr::Lit(l) => match &l.lit {148						Lit::Int(i) => {149							let num = i.base10_parse::<usize>()?;150							Ok(AbiType::Array(Box::new(wrapped), num as usize))151						}152						_ => Err(syn::Error::new(arr.len.span(), "should be int literal")),153					},154					_ => Err(syn::Error::new(arr.len.span(), "should be literal")),155				}156			}157			Type::Path(_) => {158				let path = parse_path(value)?;159				let segment = parse_path_segment(path)?;160				if segment.ident == "Vec" {161					let args = match &segment.arguments {162						PathArguments::AngleBracketed(e) => e,163						_ => {164							return Err(syn::Error::new(165								segment.arguments.span(),166								"missing Vec generic",167							))168						}169					};170					let args = &args.args;171					if args.len() != 1 {172						return Err(syn::Error::new(173							args.span(),174							"expected only one generic for vec",175						));176					}177					let arg = args.first().unwrap();178179					let ty = match arg {180						GenericArgument::Type(ty) => ty,181						_ => {182							return Err(syn::Error::new(183								arg.span(),184								"expected first generic to be type",185							))186						}187					};188189					let wrapped = AbiType::try_from(ty)?;190					Ok(Self::Vec(Box::new(wrapped)))191				} else {192					if !segment.arguments.is_empty() {193						return Err(syn::Error::new(194							segment.arguments.span(),195							"unexpected generic arguments for non-vec type",196						));197					}198					Ok(Self::Plain(segment.ident.clone()))199				}200			}201			Type::Tuple(t) => {202				let mut out = Vec::with_capacity(t.elems.len());203				for el in t.elems.iter() {204					out.push(AbiType::try_from(el)?)205				}206				Ok(Self::Tuple(out))207			}208			_ => Err(syn::Error::new(209				value.span(),210				"unexpected type, only arrays, plain types and tuples are supported",211			)),212		}213	}214	fn is_value(&self) -> bool {215		match self {216			Self::Plain(v) if v == "value" => true,217			_ => false,218		}219	}220	fn is_caller(&self) -> bool {221		match self {222			Self::Plain(v) if v == "caller" => true,223			_ => false,224		}225	}226	fn is_special(&self) -> bool {227		self.is_caller() || self.is_value()228	}229	fn selector_ty_buf(&self, buf: &mut String) -> std::fmt::Result {230		match self {231			AbiType::Plain(t) => {232				write!(buf, "{}", t)233			}234			AbiType::Tuple(t) => {235				write!(buf, "(")?;236				for (i, t) in t.iter().enumerate() {237					if i != 0 {238						write!(buf, ",")?;239					}240					t.selector_ty_buf(buf)?;241				}242				write!(buf, ")")243			}244			AbiType::Vec(v) => {245				v.selector_ty_buf(buf)?;246				write!(buf, "[]")247			}248			AbiType::Array(v, len) => {249				v.selector_ty_buf(buf)?;250				write!(buf, "[{}]", len)251			}252		}253	}254	fn selector_ty(&self) -> String {255		let mut out = String::new();256		self.selector_ty_buf(&mut out).expect("no fmt error");257		out258	}259}260impl ToTokens for AbiType {261	fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {262		match self {263			AbiType::Plain(t) => tokens.extend(quote! {#t}),264			AbiType::Tuple(t) => {265				tokens.extend(quote! {(266					#(#t),*267				)});268			}269			AbiType::Vec(v) => tokens.extend(quote! {Vec<#v>}),270			AbiType::Array(v, l) => tokens.extend(quote! {[#v; #l]}),271		}272	}273}274275struct MethodArg {276	name: Ident,277	camel_name: String,278	ty: AbiType,279}280impl MethodArg {281	fn try_from(value: &PatType) -> syn::Result<Self> {282		let name = parse_ident_from_pat(&value.pat)?.clone();283		Ok(Self {284			camel_name: cases::camelcase::to_camel_case(&name.to_string()),285			name,286			ty: AbiType::try_maybe_special_from(&value.ty)?,287		})288	}289	fn is_value(&self) -> bool {290		self.ty.is_value()291	}292	fn is_caller(&self) -> bool {293		self.ty.is_caller()294	}295	fn is_special(&self) -> bool {296		self.ty.is_special()297	}298	fn selector_ty(&self) -> String {299		assert!(!self.is_special());300		self.ty.selector_ty()301	}302303	fn expand_call_def(&self) -> proc_macro2::TokenStream {304		assert!(!self.is_special());305		let name = &self.name;306		let ty = &self.ty;307308		quote! {309			#name: #ty310		}311	}312313	fn expand_parse(&self) -> proc_macro2::TokenStream {314		assert!(!self.is_special());315		let name = &self.name;316		quote! {317			#name: reader.abi_read()?318		}319	}320321	fn expand_call_arg(&self) -> proc_macro2::TokenStream {322		if self.is_value() {323			quote! {324				c.value.clone()325			}326		} else if self.is_caller() {327			quote! {328				c.caller.clone()329			}330		} else {331			let name = &self.name;332			quote! {333				#name334			}335		}336	}337338	fn expand_solidity_argument(&self) -> proc_macro2::TokenStream {339		let camel_name = &self.camel_name.to_string();340		let ty = &self.ty;341		quote! {342			<NamedArgument<#ty>>::new(#camel_name)343		}344	}345}346347#[derive(PartialEq)]348enum Mutability {349	Mutable,350	View,351	Pure,352}353354struct Method {355	name: Ident,356	camel_name: String,357	pascal_name: Ident,358	screaming_name: Ident,359	selector_str: String,360	selector: u32,361	args: Vec<MethodArg>,362	has_normal_args: bool,363	mutability: Mutability,364	result: Type,365	docs: Vec<String>,366}367impl Method {368	fn try_from(value: &ImplItemMethod) -> syn::Result<Self> {369		let mut info = MethodInfo {370			rename_selector: None,371		};372		let mut docs = Vec::new();373		for attr in &value.attrs {374			let ident = parse_ident_from_path(&attr.path, false)?;375			if ident == "solidity" {376				let args = attr.parse_meta().unwrap();377				info = MethodInfo::from_meta(&args).unwrap();378			} else if ident == "doc" {379				let args = attr.parse_meta().unwrap();380				let value = match args {381					Meta::NameValue(MetaNameValue {382						lit: Lit::Str(str), ..383					}) => str.value(),384					_ => unreachable!(),385				};386				docs.push(value);387			}388		}389		let ident = &value.sig.ident;390		let ident_str = ident.to_string();391		if !cases::snakecase::is_snake_case(&ident_str) {392			return Err(syn::Error::new(ident.span(), "method name should be snake_cased\nif alternative solidity name needs to be set - use #[solidity] attribute"));393		}394395		let mut mutability = Mutability::Pure;396397		if let Some(FnArg::Receiver(receiver)) = value398			.sig399			.inputs400			.iter()401			.find(|arg| matches!(arg, FnArg::Receiver(_)))402		{403			if receiver.reference.is_none() {404				return Err(syn::Error::new(405					receiver.span(),406					"receiver should be by ref",407				));408			}409			if receiver.mutability.is_some() {410				mutability = Mutability::Mutable;411			} else {412				mutability = Mutability::View;413			}414		}415		let mut args = Vec::new();416		for typ in value417			.sig418			.inputs419			.iter()420			.filter(|arg| matches!(arg, FnArg::Typed(_)))421		{422			let typ = match typ {423				FnArg::Typed(typ) => typ,424				_ => unreachable!(),425			};426			args.push(MethodArg::try_from(typ)?);427		}428429		if mutability != Mutability::Mutable && args.iter().any(|arg| arg.is_value()) {430			return Err(syn::Error::new(431				args.iter().find(|arg| arg.is_value()).unwrap().ty.span(),432				"payable function should be mutable",433			));434		}435436		let result = match &value.sig.output {437			ReturnType::Type(_, ty) => ty,438			_ => 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)")),439		};440		let result = parse_result_ok(result)?;441442		let camel_name = info443			.rename_selector444			.unwrap_or_else(|| cases::camelcase::to_camel_case(&ident.to_string()));445		let mut selector_str = camel_name.clone();446		selector_str.push('(');447		let mut has_normal_args = false;448		for (i, arg) in args.iter().filter(|arg| !arg.is_special()).enumerate() {449			if i != 0 {450				selector_str.push(',');451			}452			write!(selector_str, "{}", arg.selector_ty()).unwrap();453			has_normal_args = true;454		}455		selector_str.push(')');456		let selector = fn_selector_str(&selector_str);457458		Ok(Self {459			name: ident.clone(),460			camel_name,461			pascal_name: snake_ident_to_pascal(ident),462			screaming_name: snake_ident_to_screaming(ident),463			selector_str,464			selector,465			args,466			has_normal_args,467			mutability,468			result: result.clone(),469			docs,470		})471	}472	fn expand_call_def(&self) -> proc_macro2::TokenStream {473		let defs = self474			.args475			.iter()476			.filter(|a| !a.is_special())477			.map(|a| a.expand_call_def());478		let pascal_name = &self.pascal_name;479480		if self.has_normal_args {481			quote! {482				#pascal_name {483					#(484						#defs,485					)*486				}487			}488		} else {489			quote! {#pascal_name}490		}491	}492493	fn expand_const(&self) -> proc_macro2::TokenStream {494		let screaming_name = &self.screaming_name;495		let selector = self.selector;496		let selector_str = &self.selector_str;497		quote! {498			#[doc = #selector_str]499			const #screaming_name: u32 = #selector;500		}501	}502503	fn expand_interface_id(&self) -> proc_macro2::TokenStream {504		let screaming_name = &self.screaming_name;505		quote! {506			interface_id ^= Self::#screaming_name;507		}508	}509510	fn expand_parse(&self) -> proc_macro2::TokenStream {511		let pascal_name = &self.pascal_name;512		let screaming_name = &self.screaming_name;513		if self.has_normal_args {514			let parsers = self515				.args516				.iter()517				.filter(|a| !a.is_special())518				.map(|a| a.expand_parse());519			quote! {520				Self::#screaming_name => return Ok(Some(Self::#pascal_name {521					#(522						#parsers,523					)*524				}))525			}526		} else {527			quote! { Self::#screaming_name => return Ok(Some(Self::#pascal_name)) }528		}529	}530531	fn expand_variant_call(&self) -> proc_macro2::TokenStream {532		let pascal_name = &self.pascal_name;533		let name = &self.name;534535		let matcher = if self.has_normal_args {536			let names = self537				.args538				.iter()539				.filter(|a| !a.is_special())540				.map(|a| &a.name);541542			quote! {{543				#(544					#names,545				)*546			}}547		} else {548			quote! {}549		};550551		let receiver = match self.mutability {552			Mutability::Mutable | Mutability::View => quote! {self.},553			Mutability::Pure => quote! {Self::},554		};555		let args = self.args.iter().map(|a| a.expand_call_arg());556557		quote! {558			InternalCall::#pascal_name #matcher => {559				let result = #receiver #name(560					#(561						#args,562					)*563				)?;564				(&result).abi_write(&mut writer);565			}566		}567	}568569	fn expand_solidity_function(&self) -> proc_macro2::TokenStream {570		let camel_name = &self.camel_name;571		let mutability = match self.mutability {572			Mutability::Mutable => quote! {SolidityMutability::Mutable},573			Mutability::View => quote! { SolidityMutability::View },574			Mutability::Pure => quote! {SolidityMutability::Pure},575		};576		let result = &self.result;577578		let args = self579			.args580			.iter()581			.filter(|a| !a.is_special())582			.map(MethodArg::expand_solidity_argument);583		let docs = self.docs.iter();584		let selector = format!("{} {:0>8x}", self.selector_str, self.selector);585586		quote! {587			SolidityFunction {588				docs: &[#(#docs),*],589				selector: #selector,590				name: #camel_name,591				mutability: #mutability,592				args: (593					#(594						#args,595					)*596				),597				result: <UnnamedArgument<#result>>::default(),598			}599		}600	}601}602603pub struct SolidityInterface {604	generics: Generics,605	name: Box<syn::Type>,606	info: InterfaceInfo,607	methods: Vec<Method>,608}609impl SolidityInterface {610	pub fn try_from(info: InterfaceInfo, value: &ItemImpl) -> syn::Result<Self> {611		let mut methods = Vec::new();612613		for item in &value.items {614			if let ImplItem::Method(method) = item {615				methods.push(Method::try_from(method)?)616			}617		}618		Ok(Self {619			generics: value.generics.clone(),620			name: value.self_ty.clone(),621			info,622			methods,623		})624	}625	pub fn expand(self) -> proc_macro2::TokenStream {626		let name = self.name;627628		let solidity_name = self.info.name.to_string();629		let call_name = pascal_ident_to_call(&self.info.name);630		let generics = self.generics;631632		let call_sub = self633			.info634			.inline_is635			.0636			.iter()637			.chain(self.info.is.0.iter())638			.map(Is::expand_call_def);639		let call_parse = self640			.info641			.inline_is642			.0643			.iter()644			.chain(self.info.is.0.iter())645			.map(Is::expand_parse);646		let call_variants = self647			.info648			.inline_is649			.0650			.iter()651			.chain(self.info.is.0.iter())652			.map(Is::expand_variant_call);653654		let inline_interface_id = self.info.inline_is.0.iter().map(Is::expand_interface_id);655		let supports_interface = self.info.is.0.iter().map(Is::expand_supports_interface);656657		let calls = self.methods.iter().map(Method::expand_call_def);658		let consts = self.methods.iter().map(Method::expand_const);659		let interface_id = self.methods.iter().map(Method::expand_interface_id);660		let parsers = self.methods.iter().map(Method::expand_parse);661		let call_variants_this = self.methods.iter().map(Method::expand_variant_call);662		let solidity_functions = self.methods.iter().map(Method::expand_solidity_function);663664		// TODO: Inline inline_is665		let solidity_is = self666			.info667			.is668			.0669			.iter()670			.chain(self.info.inline_is.0.iter())671			.map(|is| is.name.to_string());672		let solidity_events_is = self.info.events.0.iter().map(|is| is.name.to_string());673		let solidity_generators = self674			.info675			.is676			.0677			.iter()678			.chain(self.info.inline_is.0.iter())679			.map(Is::expand_generator);680		let solidity_event_generators = self.info.events.0.iter().map(Is::expand_event_generator);681682		// let methods = self.methods.iter().map(Method::solidity_def);683684		quote! {685			#[derive(Debug)]686			pub enum #call_name {687				ERC165Call(::evm_coder::ERC165Call),688				#(689					#calls,690				)*691				#(692					#call_sub,693				)*694			}695			impl #call_name {696				#(697					#consts698				)*699				pub const fn interface_id() -> u32 {700					let mut interface_id = 0;701					#(#interface_id)*702					#(#inline_interface_id)*703					interface_id704				}705				pub fn supports_interface(interface_id: u32) -> bool {706					interface_id != 0xffffff && (707						interface_id == ::evm_coder::ERC165Call::INTERFACE_ID ||708						interface_id == Self::interface_id()709						#(710							|| #supports_interface711						)*712					)713				}714				pub fn generate_solidity_interface(tc: &evm_coder::solidity::TypeCollector, is_impl: bool) {715					use evm_coder::solidity::*;716					use core::fmt::Write;717					let interface = SolidityInterface {718						name: #solidity_name,719						selector: Self::interface_id(),720						is: &["Dummy", "ERC165", #(721							#solidity_is,722						)* #(723							#solidity_events_is,724						)* ],725						functions: (#(726							#solidity_functions,727						)*),728					};729					if is_impl {730						tc.collect("// 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());731					} else {732						tc.collect("// Common stubs holder\ninterface Dummy {\n}\ninterface ERC165 is Dummy {\n\tfunction supportsInterface(bytes4 interfaceID) external view returns (bool);\n}\n".into());733					}734					#(735						#solidity_generators736					)*737					#(738						#solidity_event_generators739					)*740741					let mut out = string::new();742					// In solidity interface usage (is) should be preceeded by interface definition743					// This comment helps to sort it in a set744					if #solidity_name.starts_with("Inline") {745						out.push_str("// Inline\n");746					}747					let _ = interface.format(is_impl, &mut out, tc);748					tc.collect(out);749				}750			}751			impl ::evm_coder::Call for #call_name {752				fn parse(method_id: u32, reader: &mut ::evm_coder::abi::AbiReader) -> ::evm_coder::execution::Result<Option<Self>> {753					use ::evm_coder::abi::AbiRead;754					match method_id {755						::evm_coder::ERC165Call::INTERFACE_ID => return Ok(::evm_coder::ERC165Call::parse(method_id, reader)?.map(Self::ERC165Call)),756						#(757							#parsers,758						)*759						_ => {},760					}761					#(762						#call_parse763					)else*764					return Ok(None);765				}766			}767			impl #generics ::evm_coder::Callable<#call_name> for #name {768				#[allow(unreachable_code)] // In case of no inner calls769				fn call(&mut self, c: Msg<#call_name>) -> Result<::evm_coder::abi::AbiWriter> {770					use ::evm_coder::abi::AbiWrite;771					type InternalCall = #call_name;772					match c.call {773						#(774							#call_variants,775						)*776						InternalCall::ERC165Call(::evm_coder::ERC165Call::SupportsInterface {interface_id}) => {777							let mut writer = ::evm_coder::abi::AbiWriter::default();778							writer.bool(&InternalCall::supports_interface(interface_id));779							return Ok(writer);780						}781						_ => {},782					}783					let mut writer = ::evm_coder::abi::AbiWriter::default();784					match c.call {785						#(786							#call_variants_this,787						)*788						_ => unreachable!()789					}790					Ok(writer)791				}792			}793		}794	}795}