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 227 }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 500 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 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 575 576 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)] 600 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}