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 210 }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 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)] 539 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}