1use quote::quote;2use darling::FromMeta;3use inflector::cases;4use std::fmt::Write;5use syn::{6 FnArg, Ident, ItemTrait, Meta, NestedMeta, PatType, Path, ReturnType, TraitItem,7 TraitItemMethod, Visibility, spanned::Spanned,8};910use crate::{11 fn_selector_str, format_ty, parse_ident_from_pat, parse_ident_from_path, parse_ident_from_type,12 parse_result_ok, pascal_ident_to_call, pascal_ident_to_snake_call, snake_ident_to_pascal,13 snake_ident_to_screaming,14};1516struct Is {17 name: Ident,18 pascal_call_name: Ident,19 snake_call_name: Ident,20}21impl Is {22 fn try_from(path: &Path) -> syn::Result<Self> {23 let name = parse_ident_from_path(path)?.clone();24 Ok(Self {25 pascal_call_name: pascal_ident_to_call(&name),26 snake_call_name: pascal_ident_to_snake_call(&name),27 name,28 })29 }3031 fn expand_call_def(&self) -> proc_macro2::TokenStream {32 let name = &self.name;33 let pascal_call_name = &self.pascal_call_name;34 quote! {35 #name(#pascal_call_name)36 }37 }3839 fn expand_interface_id(&self) -> proc_macro2::TokenStream {40 let pascal_call_name = &self.pascal_call_name;41 quote! {42 interface_id ^= #pascal_call_name::interface_id();43 }44 }4546 fn expand_supports_interface(&self) -> proc_macro2::TokenStream {47 let pascal_call_name = &self.pascal_call_name;48 quote! {49 #pascal_call_name::supports_interface(interface_id)50 }51 }5253 fn expand_variant_call(&self) -> proc_macro2::TokenStream {54 let name = &self.name;55 let snake_call_name = &self.snake_call_name;56 quote! {57 InternalCall::#name(call) => return self.#snake_call_name(Msg {58 call,59 caller: c.caller,60 value: c.value,61 })62 }63 }6465 fn expand_call_inner(&self) -> proc_macro2::TokenStream {66 let snake_call_name = &self.snake_call_name;67 let pascal_call_name = &self.pascal_call_name;68 quote! {69 fn #snake_call_name(&mut self, c: Msg<#pascal_call_name>) -> ::core::result::Result<::evm_coder::abi::AbiWriter, Self::Error>;70 }71 }7273 fn expand_parse(&self) -> proc_macro2::TokenStream {74 let name = &self.name;75 let pascal_call_name = &self.pascal_call_name;76 quote! {77 if let Some(parsed_call) = #pascal_call_name::parse(method_id, reader)? {78 return Ok(Some(Self::#name(parsed_call)))79 }80 }81 }82}8384#[derive(Default)]85struct IsList(Vec<Is>);86impl FromMeta for IsList {87 fn from_list(items: &[NestedMeta]) -> darling::Result<Self> {88 let mut out = Vec::new();89 for item in items {90 match item {91 NestedMeta::Meta(Meta::Path(path)) => out.push(Is::try_from(path)?),92 _ => return Err(syn::Error::new(item.span(), "expected path").into()),93 }94 }95 Ok(Self(out))96 }97}9899#[derive(FromMeta)]100pub struct InterfaceInfo {101 #[darling(default)]102 is: IsList,103 #[darling(default)]104 inline_is: IsList,105 #[darling(default)]106 events: IsList,107}108109#[derive(FromMeta)]110struct MethodInfo {111 #[darling(default)]112 rename_selector: Option<String>,113}114115struct MethodArg {116 name: Ident,117 ty: Ident,118}119impl MethodArg {120 fn try_from(value: &PatType) -> syn::Result<Self> {121 Ok(Self {122 name: parse_ident_from_pat(&value.pat)?.clone(),123 ty: parse_ident_from_type(&value.ty)?.clone(),124 })125 }126 fn is_value(&self) -> bool {127 self.ty == "value"128 }129 fn is_caller(&self) -> bool {130 self.ty == "caller"131 }132 fn is_special(&self) -> bool {133 self.is_value() || self.is_caller()134 }135 fn selector_ty(&self) -> &Ident {136 assert!(!self.is_special());137 &self.ty138 }139140 fn expand_call_def(&self) -> proc_macro2::TokenStream {141 assert!(!self.is_special());142 let name = &self.name;143 let ty = &self.ty;144145 quote! {146 #name: #ty147 }148 }149150 fn expand_parse(&self) -> proc_macro2::TokenStream {151 assert!(!self.is_special());152 let name = &self.name;153 quote! {154 #name: reader.abi_read()?155 }156 }157158 fn expand_call_arg(&self) -> proc_macro2::TokenStream {159 if self.is_value() {160 quote! {161 c.value.clone()162 }163 } else if self.is_caller() {164 quote! {165 c.caller.clone()166 }167 } else {168 let name = &self.name;169 quote! {170 #name171 }172 }173 }174175 fn solidity_def(&self) -> String {176 assert!(!self.is_special());177 format!("{} {}", format_ty(&self.ty), self.name)178 }179}180181#[derive(PartialEq)]182enum Mutability {183 Mutable,184 View,185 Pure,186}187188struct Method {189 name: Ident,190 camel_name: String,191 pascal_name: Ident,192 screaming_name: Ident,193 selector_str: String,194 selector: u32,195 args: Vec<MethodArg>,196 has_normal_args: bool,197 mutability: Mutability,198 result: Ident,199}200impl Method {201 fn try_from(value: &TraitItemMethod) -> syn::Result<Self> {202 let mut info = MethodInfo {203 rename_selector: None,204 };205 for attr in &value.attrs {206 let ident = parse_ident_from_path(&attr.path)?;207 if ident == "solidity" {208 let args = attr.parse_meta().unwrap();209 info = MethodInfo::from_meta(&args).unwrap();210 } else if ident == "doc" {211 212 }213 }214 let ident = &value.sig.ident;215 let ident_str = ident.to_string();216 if !cases::snakecase::is_snake_case(&ident_str) {217 return Err(syn::Error::new(ident.span(), "method name should be snake_cased\nif alternative solidity name needs to be set - use #[solidity] attribute"));218 }219220 let mut mutability = Mutability::Pure;221222 if let Some(FnArg::Receiver(receiver)) = value223 .sig224 .inputs225 .iter()226 .find(|arg| matches!(arg, FnArg::Receiver(_)))227 {228 if receiver.reference.is_none() {229 return Err(syn::Error::new(230 receiver.span(),231 "receiver should be by ref",232 ));233 }234 if receiver.mutability.is_some() {235 mutability = Mutability::Mutable;236 } else {237 mutability = Mutability::View;238 }239 }240 let mut args = Vec::new();241 for typ in value242 .sig243 .inputs244 .iter()245 .filter(|arg| matches!(arg, FnArg::Typed(_)))246 {247 let typ = match typ {248 FnArg::Typed(typ) => typ,249 _ => unreachable!(),250 };251 args.push(MethodArg::try_from(typ)?);252 }253254 if mutability != Mutability::Mutable && args.iter().any(|arg| arg.is_value()) {255 return Err(syn::Error::new(256 args.iter().find(|arg| arg.is_value()).unwrap().ty.span(),257 "payable function should be mutable",258 ));259 }260261 let result = match &value.sig.output {262 ReturnType::Type(_, ty) => ty,263 _ => 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)")),264 };265 let result = parse_result_ok(&result)?;266267 let camel_name = info268 .rename_selector269 .unwrap_or_else(|| cases::camelcase::to_camel_case(&ident.to_string()));270 let mut selector_str = camel_name.clone();271 selector_str.push('(');272 let mut has_normal_args = false;273 for (i, arg) in args.iter().filter(|arg| !arg.is_special()).enumerate() {274 if i != 0 {275 selector_str.push(',');276 }277 write!(selector_str, "{}", arg.selector_ty()).unwrap();278 has_normal_args = true;279 }280 selector_str.push(')');281 let selector = fn_selector_str(&selector_str);282283 Ok(Self {284 name: ident.clone(),285 camel_name,286 pascal_name: snake_ident_to_pascal(&ident),287 screaming_name: snake_ident_to_screaming(&ident),288 selector_str,289 selector,290 args,291 has_normal_args,292 mutability,293 result: result.clone(),294 })295 }296 fn expand_call_def(&self) -> proc_macro2::TokenStream {297 let defs = self298 .args299 .iter()300 .filter(|a| !a.is_special())301 .map(|a| a.expand_call_def());302 let pascal_name = &self.pascal_name;303304 if self.has_normal_args {305 quote! {306 #pascal_name {307 #(308 #defs,309 )*310 }311 }312 } else {313 quote! {#pascal_name}314 }315 }316317 fn expand_const(&self) -> proc_macro2::TokenStream {318 let screaming_name = &self.screaming_name;319 let selector = self.selector;320 let selector_str = &self.selector_str;321 quote! {322 #[doc = #selector_str]323 const #screaming_name: u32 = #selector;324 }325 }326327 fn expand_interface_id(&self) -> proc_macro2::TokenStream {328 let screaming_name = &self.screaming_name;329 quote! {330 interface_id ^= Self::#screaming_name;331 }332 }333334 fn expand_parse(&self) -> proc_macro2::TokenStream {335 let pascal_name = &self.pascal_name;336 let screaming_name = &self.screaming_name;337 if self.has_normal_args {338 let parsers = self339 .args340 .iter()341 .filter(|a| !a.is_special())342 .map(|a| a.expand_parse());343 quote! {344 Self::#screaming_name => return Ok(Some(Self::#pascal_name {345 #(346 #parsers,347 )*348 }))349 }350 } else {351 quote! { Self::#screaming_name => return Ok(Some(Self::#pascal_name)) }352 }353 }354355 fn expand_variant_call(&self) -> proc_macro2::TokenStream {356 let pascal_name = &self.pascal_name;357 let name = &self.name;358359 let matcher = if self.has_normal_args {360 let names = self361 .args362 .iter()363 .filter(|a| !a.is_special())364 .map(|a| &a.name);365366 quote! {{367 #(368 #names,369 )*370 }}371 } else {372 quote! {}373 };374375 let receiver = match self.mutability {376 Mutability::Mutable | Mutability::View => quote! {self.},377 Mutability::Pure => quote! {Self::},378 };379 let args = self.args.iter().map(|a| a.expand_call_arg());380381 quote! {382 InternalCall::#pascal_name #matcher => {383 let result = #receiver #name(384 #(385 #args,386 )*387 )?;388 (&result).abi_write(&mut writer);389 }390 }391 }392393 fn solidity_def(&self) -> String {394 let mut out = format!("function {}(", self.camel_name);395 for (i, arg) in self.args.iter().filter(|a| !a.is_special()).enumerate() {396 if i != 0 {397 out.push_str(", ");398 }399 out.push_str(&arg.solidity_def());400 }401 out.push(')');402 match self.mutability {403 Mutability::Mutable => {}404 Mutability::View => write!(out, " view").unwrap(),405 Mutability::Pure => write!(out, " pure").unwrap(),406 }407 if self.result != "void" {408 write!(out, " returns ({})", format_ty(&self.result)).unwrap();409 }410 out.push(';');411 out412 }413}414415pub struct SolidityInterface {416 vis: Visibility,417 name: Ident,418 info: InterfaceInfo,419 methods: Vec<Method>,420 items: Vec<TraitItem>,421}422impl SolidityInterface {423 pub fn try_from(info: InterfaceInfo, value: &ItemTrait) -> syn::Result<Self> {424 let mut found_error = false;425 let mut methods = Vec::new();426427 for item in &value.items {428 match item {429 TraitItem::Type(ty) => {430 if ty.ident == "Error" {431 found_error = true;432 }433 }434 TraitItem::Method(method) => methods.push(Method::try_from(&method)?),435 _ => {}436 }437 }438 if !found_error {439 return Err(syn::Error::new(440 value.span(),441 "expected associated type called Error, which should implement From<&str>",442 ));443 }444 Ok(Self {445 vis: value.vis.clone(),446 name: value.ident.clone(),447 info,448 methods,449 items: value.items.clone(),450 })451 }452 pub fn expand(self) -> proc_macro2::TokenStream {453 let vis = self.vis;454 let name = self.name;455 let items = self.items;456457 let call_name = pascal_ident_to_call(&name);458459 let call_sub = self460 .info461 .inline_is462 .0463 .iter()464 .chain(self.info.is.0.iter())465 .map(Is::expand_call_def);466 let call_inner = self467 .info468 .inline_is469 .0470 .iter()471 .chain(self.info.is.0.iter())472 .map(Is::expand_call_inner);473 let call_parse = self474 .info475 .inline_is476 .0477 .iter()478 .chain(self.info.is.0.iter())479 .map(Is::expand_parse);480 let call_variants = self481 .info482 .inline_is483 .0484 .iter()485 .chain(self.info.is.0.iter())486 .map(Is::expand_variant_call);487488 let inline_interface_id = self.info.inline_is.0.iter().map(Is::expand_interface_id);489 let supports_interface = self.info.is.0.iter().map(Is::expand_supports_interface);490491 let calls = self.methods.iter().map(Method::expand_call_def);492 let consts = self.methods.iter().map(Method::expand_const);493 let interface_id = self.methods.iter().map(Method::expand_interface_id);494 let parsers = self.methods.iter().map(Method::expand_parse);495 let call_variants_this = self.methods.iter().map(Method::expand_variant_call);496497 498499 quote! {500 #[derive(Debug)]501 #vis enum #call_name {502 #(503 #calls,504 )*505 #(506 #call_sub,507 )*508 }509 impl #call_name {510 #(511 #consts512 )*513 pub fn parse(method_id: u32, reader: &mut ::evm_coder::abi::AbiReader) -> ::evm_coder::abi::Result<Option<Self>> {514 use ::evm_coder::abi::AbiRead;515 match method_id {516 #(517 #parsers,518 )*519 _ => {},520 }521 #(522 #call_parse523 )else*524 return Ok(None);525 }526 pub const fn interface_id() -> u32 {527 let mut interface_id = 0;528 #(#interface_id)*529 #(#inline_interface_id)*530 interface_id531 }532 pub fn supports_interface(interface_id: u32) -> bool {533 interface_id != 0xffffff && (534 interface_id == Self::interface_id()535 #(536 || #supports_interface537 )*538 )539 }540 }541 #vis trait #name {542 #(543 #items544 )*545 #(546 #call_inner547 )*548 fn call(&mut self, c: Msg<#call_name>) -> ::core::result::Result<::evm_coder::abi::AbiWriter, Self::Error> {549 use ::evm_coder::abi::AbiWrite;550 type InternalCall = #call_name;551 match c.call {552 #(553 #call_variants,554 )*555 _ => {},556 }557 let mut writer = ::evm_coder::abi::AbiWriter::default();558 match c.call {559 #(560 #call_variants_this,561 )*562 _ => unreachable!()563 }564 Ok(writer)565 }566 }567 }568 }569}