--- a/crates/evm-coder/procedural/src/solidity_interface.rs +++ b/crates/evm-coder/procedural/src/solidity_interface.rs @@ -42,6 +42,7 @@ pascal_call_name: Ident, snake_call_name: Ident, via: Option<(Type, Ident)>, + condition: Option, } impl Is { fn expand_call_def(&self, gen_ref: &proc_macro2::TokenStream) -> proc_macro2::TokenStream { @@ -64,8 +65,13 @@ generics: &proc_macro2::TokenStream, ) -> proc_macro2::TokenStream { let pascal_call_name = &self.pascal_call_name; + let condition = self.condition.as_ref().map(|condition| { + quote! { + (#condition) && + } + }); quote! { - <#pascal_call_name #generics>::supports_interface(interface_id) + #condition <#pascal_call_name #generics>::supports_interface(this, interface_id) } } @@ -93,8 +99,13 @@ .as_ref() .map(|(_, i)| quote! {.#i()}) .unwrap_or_default(); + let condition = self.condition.as_ref().map(|condition| { + quote! { + if ({let this = &self; (#condition)}) + } + }); quote! { - #call_name::#name(call) => return <#via_typ as ::evm_coder::Callable<#pascal_call_name #generics>>::call(self #via_map, Msg { + #call_name::#name(call) #condition => return <#via_typ as ::evm_coder::Callable<#pascal_call_name #generics>>::call(self #via_map, Msg { call, caller: c.caller, value: c.value, @@ -138,17 +149,50 @@ } let name = input.parse::()?; let lookahead = input.lookahead1(); - let via = if lookahead.peek(syn::token::Paren) { + + let mut condition: Option = None; + let mut via: Option<(Type, Ident)> = None; + + if lookahead.peek(syn::token::Paren) { let contents; parenthesized!(contents in input); - let method = contents.parse::()?; - contents.parse::()?; - let ty = contents.parse::()?; - Some((ty, method)) - } else if lookahead.peek(Token![,]) { - None - } else if input.is_empty() { - None + let input = contents; + + while !input.is_empty() { + let lookahead = input.lookahead1(); + if lookahead.peek(Token![if]) { + input.parse::()?; + let contents; + parenthesized!(contents in input); + let contents = contents.parse::()?; + + if condition.replace(contents).is_some() { + return Err(syn::Error::new(input.span(), "condition is already set")); + } + } else if lookahead.peek(kw::via) { + input.parse::()?; + let contents; + parenthesized!(contents in input); + + let method = contents.parse::()?; + contents.parse::()?; + let ty = contents.parse::()?; + + if via.replace((ty, method)).is_some() { + return Err(syn::Error::new(input.span(), "via is already set")); + } + } else { + return Err(lookahead.error()); + } + + if input.peek(Token![,]) { + input.parse::()?; + } else if !input.is_empty() { + return Err(syn::Error::new(input.span(), "expected end")); + } + } + } else if lookahead.peek(Token![,]) || input.is_empty() { + // Pass } else { return Err(lookahead.error()); }; @@ -157,6 +201,7 @@ snake_call_name: pascal_ident_to_snake_call(&name), name, via, + condition, }); if input.peek(Token![,]) { input.parse::()?; @@ -495,6 +540,7 @@ syn::custom_keyword!(weight); syn::custom_keyword!(via); + syn::custom_keyword!(returns); syn::custom_keyword!(name); syn::custom_keyword!(is); syn::custom_keyword!(inline_is); @@ -996,16 +1042,6 @@ #(#inline_interface_id)* u32::to_be_bytes(interface_id) } - /// Is this contract implements specified ERC165 selector - pub fn supports_interface(interface_id: ::evm_coder::types::bytes4) -> bool { - interface_id != u32::to_be_bytes(0xffffff) && ( - interface_id == ::evm_coder::ERC165Call::INTERFACE_ID || - interface_id == Self::interface_id() - #( - || #supports_interface - )* - ) - } /// Generate solidity definitions for methods described in this interface pub fn generate_solidity_interface(tc: &evm_coder::solidity::TypeCollector, is_impl: bool) { use evm_coder::solidity::*; @@ -1024,7 +1060,7 @@ )*), }; - let mut out = string::new(); + let mut out = ::evm_coder::types::string::new(); if #solidity_name.starts_with("Inline") { out.push_str("/// @dev inlined interface\n"); } @@ -1062,6 +1098,20 @@ return Ok(None); } } + impl #generics #call_name #gen_ref + #gen_where + { + /// Is this contract implements specified ERC165 selector + pub fn supports_interface(this: &#name, interface_id: ::evm_coder::types::bytes4) -> bool { + interface_id != u32::to_be_bytes(0xffffff) && ( + interface_id == ::evm_coder::ERC165Call::INTERFACE_ID || + interface_id == Self::interface_id() + #( + || #supports_interface + )* + ) + } + } impl #generics ::evm_coder::Weighted for #call_name #gen_ref #gen_where { @@ -1091,7 +1141,7 @@ )* #call_name::ERC165Call(::evm_coder::ERC165Call::SupportsInterface {interface_id}, _) => { let mut writer = ::evm_coder::abi::AbiWriter::default(); - writer.bool(&<#call_name #gen_ref>::supports_interface(interface_id)); + writer.bool(&<#call_name #gen_ref>::supports_interface(self, interface_id)); return Ok(writer.into()); } _ => {}, @@ -1101,7 +1151,7 @@ #( #call_variants_this, )* - _ => unreachable!() + _ => Err(::evm_coder::execution::Error::from("method is not available").into()), } } } --- a/crates/evm-coder/src/abi.rs +++ b/crates/evm-coder/src/abi.rs @@ -313,7 +313,7 @@ /// Finish writer, concatenating all internal buffers pub fn finish(mut self) -> Vec { for (static_offset, part) in self.dynamic_part { - let part_offset = self.static_part.len() - self.had_call.then(|| 4).unwrap_or(0); + let part_offset = self.static_part.len() - if self.had_call { 4 } else { 0 }; let encoded_dynamic_offset = usize::to_be_bytes(part_offset); self.static_part[static_offset + ABI_ALIGNMENT - encoded_dynamic_offset.len() --- a/crates/evm-coder/src/lib.rs +++ b/crates/evm-coder/src/lib.rs @@ -74,10 +74,10 @@ /// #[solidity_interface(name = MyContract, is(SuperContract), inline_is(InlineContract))] /// impl Contract { /// /// Multiply two numbers -/// /// @param a First number -/// /// @param b Second number -/// /// @return uint32 Product of two passed numbers -/// /// @dev This function returns error in case of overflow +/// /// @param a First number +/// /// @param b Second number +/// /// @return uint32 Product of two passed numbers +/// /// @dev This function returns error in case of overflow /// #[weight(200 + a + b)] /// #[solidity_interface(rename_selector = "mul")] /// fn mul(&mut self, a: uint32, b: uint32) -> Result { --- /dev/null +++ b/crates/evm-coder/tests/conditional_is.rs @@ -0,0 +1,44 @@ +use evm_coder::{types::*, solidity_interface, execution::Result, Call}; + +pub struct Contract(bool); + +#[solidity_interface(name = A)] +impl Contract { + fn method_a() -> Result { + Ok(()) + } +} + +#[solidity_interface(name = B)] +impl Contract { + fn method_b() -> Result { + Ok(()) + } +} + +#[solidity_interface(name = Contract, is( + A(if(this.0)), + B(if(!this.0)), +))] +impl Contract {} + +#[test] +fn conditional_erc165() { + assert!(ContractCall::supports_interface( + &Contract(true), + ACall::METHOD_A + )); + assert!(!ContractCall::supports_interface( + &Contract(false), + ACall::METHOD_A + )); + + assert!(ContractCall::supports_interface( + &Contract(false), + BCall::METHOD_B + )); + assert!(!ContractCall::supports_interface( + &Contract(true), + BCall::METHOD_B + )); +} --- a/crates/evm-coder/tests/generics.rs +++ b/crates/evm-coder/tests/generics.rs @@ -17,7 +17,7 @@ use std::marker::PhantomData; use evm_coder::{execution::Result, generate_stubgen, solidity_interface, types::*}; -struct Generic(PhantomData); +pub struct Generic(PhantomData); #[solidity_interface(name = GenericIs)] impl Generic { --- a/crates/evm-coder/tests/random.rs +++ b/crates/evm-coder/tests/random.rs @@ -18,7 +18,7 @@ use evm_coder::{ToLog, execution::Result, solidity_interface, types::*, solidity, weight}; -struct Impls; +pub struct Impls; #[solidity_interface(name = OurInterface)] impl Impls { --- a/crates/evm-coder/tests/solidity_generation.rs +++ b/crates/evm-coder/tests/solidity_generation.rs @@ -16,7 +16,7 @@ use evm_coder::{execution::Result, generate_stubgen, solidity_interface, types::*}; -struct ERC20; +pub struct ERC20; #[solidity_interface(name = ERC20)] impl ERC20 { --- a/pallets/common/src/erc.rs +++ b/pallets/common/src/erc.rs @@ -406,9 +406,9 @@ true => { let mut bv = OwnerRestrictedSet::new(); for i in collections { - bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or(Error::Revert( - "Can't convert address into collection id".into(), - ))?) + bv.try_insert(crate::eth::map_eth_to_id(&i).ok_or_else(|| { + Error::Revert("Can't convert address into collection id".into()) + })?) .map_err(|_| "too many collections")?; } let mut nesting = permissions.nesting().clone(); --- a/pallets/fungible/src/erc.rs +++ b/pallets/fungible/src/erc.rs @@ -199,7 +199,7 @@ ERC20, ERC20Mintable, ERC20UniqueExtensions, - Collection(common_mut, CollectionHandle), + Collection(via(common_mut returns CollectionHandle)), ) )] impl FungibleHandle where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {} --- a/pallets/nonfungible/src/erc.rs +++ b/pallets/nonfungible/src/erc.rs @@ -736,7 +736,7 @@ ERC721UniqueExtensions, ERC721Mintable, ERC721Burnable, - Collection(common_mut, CollectionHandle), + Collection(via(common_mut returns CollectionHandle)), TokenProperties, ) )] --- a/pallets/refungible/src/erc.rs +++ b/pallets/refungible/src/erc.rs @@ -785,7 +785,7 @@ ERC721UniqueExtensions, ERC721Mintable, ERC721Burnable, - Collection(common_mut, CollectionHandle), + Collection(via(common_mut returns CollectionHandle)), TokenProperties, ) )]