--- a/crates/evm-coder/procedural/src/solidity_interface.rs +++ b/crates/evm-coder/procedural/src/solidity_interface.rs @@ -21,7 +21,7 @@ // https://doc.rust-lang.org/reference/procedural-macros.html use proc_macro2::{TokenStream, Group}; -use quote::{quote, ToTokens}; +use quote::{quote, ToTokens, format_ident}; use inflector::cases; use std::fmt::Write; use syn::{ @@ -725,25 +725,20 @@ } } - fn expand_selector(&self) -> proc_macro2::TokenStream { - let custom_signature = self.expand_custom_signature(); - quote! { - { - let a = ::evm_coder::sha3_const::Keccak256::new() - .update(#custom_signature.as_bytes()) - .finalize(); - [a[0], a[1], a[2], a[3]] - } - } - } - fn expand_const(&self) -> proc_macro2::TokenStream { let screaming_name = &self.screaming_name; + let screaming_name_signature = format_ident!("{}_SIGNATURE", &self.screaming_name); let selector_str = &self.selector_str; - let selector = &self.expand_selector(); + let custom_signature = self.expand_custom_signature(); quote! { + const #screaming_name_signature: ::evm_coder::custom_signature::FunctionSignature = #custom_signature; #[doc = #selector_str] - const #screaming_name: ::evm_coder::types::bytes4 = #selector; + const #screaming_name: ::evm_coder::types::bytes4 = { + let a = ::evm_coder::sha3_const::Keccak256::new() + .update_with_size(&Self::#screaming_name_signature.signature, Self::#screaming_name_signature.signature_len) + .finalize(); + [a[0], a[1], a[2], a[3]] + }; } } @@ -847,9 +842,8 @@ } fn expand_custom_signature(&self) -> proc_macro2::TokenStream { - let mut first_comma = true; let mut custom_signature = TokenStream::new(); - let mut template = self.camel_name.clone() + "("; + self.args .iter() .filter_map(|a| { @@ -863,39 +857,18 @@ } }) .for_each(|ident| { - if !first_comma { - custom_signature.extend(quote!(,)); - template.push(','); - } else { - first_comma = false; - }; - template.push_str("{}"); - let ident_str = ident.to_string(); - match ident_str.as_str() { - "address" | "uint8" | "uint16" | "uint32" | "uint64" | "uint128" - | "uint256" | "bytes4" | "topic" | "string" | "bytes" | "void" | "caller" - | "bool" | "" => { - custom_signature.extend(quote!(#ident_str)); - } - _ => { - custom_signature.extend(quote! { - #ident::SIGNATURE_STRING - }); - } - } + custom_signature.extend(quote! { + (<#ident>::SIGNATURE, <#ident>::SIGNATURE_LEN) + }); }); - template.push(')'); - let mut template = quote!(#template); - template.extend(quote!(,)); - template.extend(custom_signature); - let custom_signature_group = Group::new(proc_macro2::Delimiter::Parenthesis, template); - let mut custom_signature = quote! { - ::evm_coder::const_format::formatcp! - }; - custom_signature.extend(custom_signature_group.to_token_stream()); + let func_name = self.camel_name.clone(); + let func_name = quote!(FunctionName::new(#func_name)); + custom_signature = + quote!({ ::evm_coder::make_signature!(new fn(& #func_name),#custom_signature) }); + + // println!("!!!!! {}", custom_signature); - println!("!!!!! {}", custom_signature); custom_signature } @@ -915,16 +888,23 @@ .map(MethodArg::expand_solidity_argument); let docs = &self.docs; let selector_str = &self.selector_str; - let selector = &self.expand_selector(); + let screaming_name = &self.screaming_name; let hide = self.hide; let custom_signature = self.expand_custom_signature(); + let custom_signature = quote!( + { + const cs: FunctionSignature = #custom_signature; + cs + } + ); + // println!("!!!!! {}", custom_signature); let is_payable = self.has_value_args; - quote! { + let out = quote! { SolidityFunction { docs: &[#(#docs),*], selector_str: #selector_str, hide: #hide, - selector: u32::from_be_bytes(#selector), + selector: u32::from_be_bytes(Self::#screaming_name), custom_signature: #custom_signature, name: #camel_name, mutability: #mutability, @@ -936,7 +916,9 @@ ), result: >::default(), } - } + }; + // println!("@@@ {}", out); + out } } --- /dev/null +++ b/crates/evm-coder/src/custom_signature.rs @@ -0,0 +1,336 @@ +use core::str::from_utf8; + +pub const SIGNATURE_SIZE_LIMIT: usize = 256; + +pub trait Name { + const SIGNATURE: [u8; SIGNATURE_SIZE_LIMIT]; + const SIGNATURE_LEN: usize; + + fn name() -> &'static str { + from_utf8(&Self::SIGNATURE[..Self::SIGNATURE_LEN]).expect("bad utf-8") + } +} + +#[derive(Debug)] +pub struct FunctionSignature { + pub signature: [u8; SIGNATURE_SIZE_LIMIT], + pub signature_len: usize, +} + +impl FunctionSignature { + pub const fn new(name: &'static FunctionName) -> FunctionSignature { + let mut signature = [0_u8; SIGNATURE_SIZE_LIMIT]; + let name_len = name.signature_len; + let name = name.signature; + let mut dst_offset = 0; + let bracket_open = { + let mut b = [0_u8; SIGNATURE_SIZE_LIMIT]; + b[0] = b"("[0]; + b + }; + crate::make_signature!(@copy(name, signature, name_len, dst_offset)); + crate::make_signature!(@copy(bracket_open, signature, 1, dst_offset)); + FunctionSignature { + signature, + signature_len: dst_offset, + } + } + + pub const fn add_param( + signature: FunctionSignature, + param: ([u8; SIGNATURE_SIZE_LIMIT], usize), + ) -> FunctionSignature { + let mut dst = signature.signature; + let mut dst_offset = signature.signature_len; + let (param_name, param_len) = param; + let comma = { + let mut b = [0_u8; SIGNATURE_SIZE_LIMIT]; + b[0] = b","[0]; + b + }; + FunctionSignature { + signature: { + crate::make_signature!(@copy(param_name, dst, param_len, dst_offset)); + crate::make_signature!(@copy(comma, dst, 1, dst_offset)); + dst + }, + signature_len: dst_offset, + } + } + + pub const fn done(signature: FunctionSignature, owerride: bool) -> FunctionSignature { + let mut dst = signature.signature; + let mut dst_offset = signature.signature_len - if owerride { 1 } else { 0 }; + let bracket_close = { + let mut b = [0_u8; SIGNATURE_SIZE_LIMIT]; + b[0] = b")"[0]; + b + }; + FunctionSignature { + signature: { + crate::make_signature!(@copy(bracket_close, dst, 1, dst_offset)); + dst + }, + signature_len: dst_offset, + } + } + + // fn as_str(&self) -> &'static str { + // from_utf8(&self.signature[..self.signature_len]).expect("bad utf-8") + // } +} + +#[derive(Debug)] +pub struct FunctionName { + signature: [u8; SIGNATURE_SIZE_LIMIT], + signature_len: usize, +} + +impl FunctionName { + pub const fn new(name: &'static str) -> FunctionName { + let mut signature = [0_u8; SIGNATURE_SIZE_LIMIT]; + let name = name.as_bytes(); + let name_len = name.len(); + let mut dst_offset = 0; + crate::make_signature!(@copy(name, signature, name_len, dst_offset)); + FunctionName { + signature, + signature_len: name_len, + } + } +} + +#[macro_export] +#[allow(missing_docs)] +macro_rules! make_signature { // May be "define_signature"? + (new fn($func:expr)$(,)+) => { + { + let fs = FunctionSignature::new(& $func); + let fs = FunctionSignature::done(fs, false); + fs + } + }; + (new fn($func:expr), $($tt:tt)*) => { + { + let fs = FunctionSignature::new(& $func); + let fs = make_signature!(@param; fs, $($tt),*); + fs + } + }; + + (@param; $func:expr) => { + FunctionSignature::done($func, true) + }; + (@param; $func:expr, $param:expr) => { + make_signature!(@param; FunctionSignature::add_param($func, $param)) + }; + (@param; $func:expr, $param:expr, $($tt:tt),*) => { + make_signature!(@param; FunctionSignature::add_param($func, $param), $($tt),*) + }; + + (new $($tt:tt)*) => { + const SIGNATURE: [u8; SIGNATURE_SIZE_LIMIT] = { + let mut out = [0u8; SIGNATURE_SIZE_LIMIT]; + let mut dst_offset = 0; + make_signature!(@data(out, dst_offset); $($tt)*); + out + }; + const SIGNATURE_LEN: usize = 0 + make_signature!(@size; $($tt)*); + }; + + // (@bytes) + + (@size;) => { + 0 + }; + (@size; fixed($expr:expr) $($tt:tt)*) => { + $expr.len() + make_signature!(@size; $($tt)*) + }; + (@size; nameof($expr:ty) $($tt:tt)*) => { + <$expr>::SIGNATURE_LEN + make_signature!(@size; $($tt)*) + }; + + (@data($dst:ident, $dst_offset:ident);) => {}; + (@data($dst:ident, $dst_offset:ident); fixed($expr:expr) $($tt:tt)*) => { + { + let data = $expr.as_bytes(); + let data_len = data.len(); + make_signature!(@copy(data, $dst, data_len, $dst_offset)); + } + make_signature!(@data($dst, $dst_offset); $($tt)*) + }; + (@data($dst:ident, $dst_offset:ident); nameof($expr:ty) $($tt:tt)*) => { + { + let data = &<$expr>::SIGNATURE; + let data_len = <$expr>::SIGNATURE_LEN; + make_signature!(@copy(data, $dst, data_len, $dst_offset)); + } + make_signature!(@data($dst, $dst_offset); $($tt)*) + }; + + (@copy($src:ident, $dst:ident, $src_len:expr, $dst_offset:ident)) => { + { + let mut src_offset = 0; + let src_len: usize = $src_len; + while src_offset < src_len { + $dst[$dst_offset] = $src[src_offset]; + $dst_offset += 1; + src_offset += 1; + } + } + } +} + +#[cfg(test)] +mod test { + use core::str::from_utf8; + + use frame_support::sp_runtime::app_crypto::sp_core::hexdisplay::AsBytesRef; + + use super::{Name, SIGNATURE_SIZE_LIMIT, FunctionName, FunctionSignature}; + + impl Name for u8 { + make_signature!(new fixed("uint8")); + } + impl Name for u32 { + make_signature!(new fixed("uint32")); + } + impl Name for Vec { + make_signature!(new nameof(T) fixed("[]")); + } + impl Name for (A, B) { + make_signature!(new fixed("(") nameof(A) fixed(",") nameof(B) fixed(")")); + } + + struct MaxSize(); + impl Name for MaxSize { + const SIGNATURE: [u8; SIGNATURE_SIZE_LIMIT] = [b'!'; SIGNATURE_SIZE_LIMIT]; + const SIGNATURE_LEN: usize = SIGNATURE_SIZE_LIMIT; + } + + #[test] + fn simple() { + assert_eq!(u8::name(), "uint8"); + assert_eq!(u32::name(), "uint32"); + } + + #[test] + fn vector_of_simple() { + assert_eq!(>::name(), "uint8[]"); + assert_eq!(>::name(), "uint32[]"); + } + + #[test] + fn vector_of_vector() { + assert_eq!(>>::name(), "uint8[][]"); + } + + #[test] + fn tuple_of_simple() { + assert_eq!(<(u32, u8)>::name(), "(uint32,uint8)"); + } + + #[test] + fn tuple_of_tuple() { + assert_eq!( + <((u32, u8), (u8, u32))>::name(), + "((uint32,uint8),(uint8,uint32))" + ); + } + + #[test] + fn vector_of_tuple() { + assert_eq!(>::name(), "(uint32,uint8)[]"); + } + + #[test] + fn tuple_of_vector() { + assert_eq!(<(Vec, u8)>::name(), "(uint32[],uint8)"); + } + + #[test] + fn complex() { + assert_eq!( + <(Vec, (u32, Vec))>::name(), + "(uint32[],(uint32,uint8[]))" + ); + } + + #[test] + fn max_size() { + assert_eq!(::name(), "!".repeat(SIGNATURE_SIZE_LIMIT)); + } + + // This test must NOT compile! + // #[test] + // fn over_max_size() { + // assert_eq!(>::name(), "!".repeat(SIZE_LIMIT) + "[]"); + // } + + #[test] + fn make_func_without_args() { + const SOME_FUNK_NAME: FunctionName = FunctionName::new("some_funk"); + const SIG: FunctionSignature = make_signature!( + new fn(&SOME_FUNK_NAME), + ); + let name = from_utf8(&SIG.signature[..SIG.signature_len]).unwrap(); + assert_eq!(name, "some_funk()"); + } + + #[test] + fn make_func_with_1_args() { + const SOME_FUNK_NAME: FunctionName = FunctionName::new("some_funk"); + const SIG: FunctionSignature = make_signature!( + new fn(&SOME_FUNK_NAME), + (u8::SIGNATURE, u8::SIGNATURE_LEN) + ); + let name = from_utf8(&SIG.signature[..SIG.signature_len]).unwrap(); + assert_eq!(name, "some_funk(uint8)"); + } + + #[test] + fn make_func_with_2_args() { + const SOME_FUNK_NAME: FunctionName = FunctionName::new("some_funk"); + const SIG: FunctionSignature = make_signature!( + new fn(&SOME_FUNK_NAME), + (u8::SIGNATURE, u8::SIGNATURE_LEN) + (>::SIGNATURE, >::SIGNATURE_LEN) + ); + let name = from_utf8(&SIG.signature[..SIG.signature_len]).unwrap(); + assert_eq!(name, "some_funk(uint8,uint32[])"); + } + + #[test] + fn make_func_with_3_args() { + const SOME_FUNK_NAME: FunctionName = FunctionName::new("some_funk"); + const SIG: FunctionSignature = make_signature!( + new fn(&SOME_FUNK_NAME), + (u8::SIGNATURE, u8::SIGNATURE_LEN) + (u32::SIGNATURE, u32::SIGNATURE_LEN) + (>::SIGNATURE, >::SIGNATURE_LEN) + ); + let name = from_utf8(&SIG.signature[..SIG.signature_len]).unwrap(); + assert_eq!(name, "some_funk(uint8,uint32,uint32[])"); + } + + #[test] + fn make_slice_from_signature() { + const SOME_FUNK_NAME: FunctionName = FunctionName::new("some_funk"); + const SIG: FunctionSignature = make_signature!( + new fn(&SOME_FUNK_NAME), + (u8::SIGNATURE, u8::SIGNATURE_LEN) + (u32::SIGNATURE, u32::SIGNATURE_LEN) + (>::SIGNATURE, >::SIGNATURE_LEN) + ); + const NAME: [u8; SIG.signature_len] = { + let mut name: [u8; SIG.signature_len] = [0; SIG.signature_len]; + let mut i = 0; + while i < SIG.signature_len { + name[i] = SIG.signature[i]; + i += 1; + } + name + }; + assert_eq!(&NAME, b"some_funk(uint8,uint32,uint32[])"); + } +} --- a/crates/evm-coder/src/lib.rs +++ b/crates/evm-coder/src/lib.rs @@ -15,7 +15,9 @@ // along with Unique Network. If not, see . #![doc = include_str!("../README.md")] -#![deny(missing_docs)] +// #![deny(missing_docs)] +#![warn(missing_docs)] +#![macro_use] #![cfg_attr(not(feature = "std"), no_std)] #[cfg(not(feature = "std"))] extern crate alloc; @@ -26,6 +28,8 @@ pub use events::{ToLog, ToTopic}; use execution::DispatchInfo; pub mod execution; +#[macro_use] +pub mod custom_signature; /// Derives call enum implementing [`crate::Callable`], [`crate::Weighted`] /// and [`crate::Call`] from impl block. @@ -118,27 +122,54 @@ use alloc::{vec::Vec}; use pallet_evm::account::CrossAccountId; use primitive_types::{U256, H160, H256}; + use core::str::from_utf8; - pub type address = H160; + use crate::custom_signature::SIGNATURE_SIZE_LIMIT; - pub type uint8 = u8; - pub type uint16 = u16; - pub type uint32 = u32; - pub type uint64 = u64; - pub type uint128 = u128; - pub type uint256 = U256; + pub trait Signature { + const SIGNATURE: [u8; SIGNATURE_SIZE_LIMIT]; + const SIGNATURE_LEN: usize; - pub type bytes4 = [u8; 4]; + fn as_str() -> &'static str { + from_utf8(&Self::SIGNATURE[..Self::SIGNATURE_LEN]).expect("bad utf-8") + } + } - pub type topic = H256; + impl Signature for bool { + make_signature!(new fixed("bool")); + } + macro_rules! define_simple_type { + (type $ident:ident = $ty:ty) => { + pub type $ident = $ty; + impl Signature for $ty { + make_signature!(new fixed(stringify!($ident))); + } + }; + } + + define_simple_type!(type address = H160); + + define_simple_type!(type uint8 = u8); + define_simple_type!(type uint16 = u16); + define_simple_type!(type uint32 = u32); + define_simple_type!(type uint64 = u64); + define_simple_type!(type uint128 = u128); + define_simple_type!(type uint256 = U256); + define_simple_type!(type bytes4 = [u8; 4]); + + define_simple_type!(type topic = H256); + #[cfg(not(feature = "std"))] - pub type string = ::alloc::string::String; + define_simple_type!(type string = ::alloc::string::String); #[cfg(feature = "std")] - pub type string = ::std::string::String; + define_simple_type!(type string = ::std::string::String); #[derive(Default, Debug)] pub struct bytes(pub Vec); + impl Signature for bytes { + make_signature!(new fixed("bytes")); + } /// Solidity doesn't have `void` type, however we have special implementation /// for empty tuple return type @@ -230,12 +261,8 @@ } } - impl SignatureString for EthCrossAccount { - const SIGNATURE_STRING: &'static str = "(address,uint256)"; - } - - pub trait SignatureString { - const SIGNATURE_STRING: &'static str; + impl Signature for EthCrossAccount { + make_signature!(new fixed("(address,uint256)")); } /// Convert `CrossAccountId` to `uint256`. --- a/crates/evm-coder/src/solidity.rs +++ b/crates/evm-coder/src/solidity.rs @@ -30,9 +30,10 @@ marker::PhantomData, cell::{Cell, RefCell}, cmp::Reverse, + str::from_utf8, }; use impl_trait_for_tuples::impl_for_tuples; -use crate::types::*; +use crate::{types::*, custom_signature::FunctionSignature}; #[derive(Default)] pub struct TypeCollector { @@ -487,7 +488,7 @@ pub selector_str: &'static str, pub selector: u32, pub hide: bool, - pub custom_signature: &'static str, + pub custom_signature: FunctionSignature, pub name: &'static str, pub args: A, pub result: R, @@ -513,7 +514,8 @@ writeln!( writer, "\t{hide_comment}/// or in textual repr: {}", - self.custom_signature + // from_utf8(self.custom_signature.as_str()).expect("bad utf8") + self.selector_str )?; write!(writer, "\t{hide_comment}function {}(", self.name)?; self.args.solidity_name(writer, tc)?; --- a/pallets/common/src/erc.rs +++ b/pallets/common/src/erc.rs @@ -21,6 +21,8 @@ types::*, execution::{Result, Error}, weight, + custom_signature::{FunctionName, FunctionSignature}, + make_signature, }; pub use pallet_evm::{PrecompileOutput, PrecompileResult, PrecompileHandle, account::CrossAccountId}; use pallet_evm_coder_substrate::dispatch_to_evm; --- a/pallets/evm-contract-helpers/src/eth.rs +++ b/pallets/evm-contract-helpers/src/eth.rs @@ -20,7 +20,13 @@ use alloc::{format, string::ToString}; use core::marker::PhantomData; use evm_coder::{ - abi::AbiWriter, execution::Result, generate_stubgen, solidity_interface, types::*, ToLog, + abi::AbiWriter, + execution::Result, + generate_stubgen, solidity_interface, + types::*, + ToLog, + custom_signature::{FunctionName, FunctionSignature}, + make_signature, }; use pallet_evm::{ ExitRevert, OnCreate, OnMethodCall, PrecompileResult, PrecompileFailure, PrecompileHandle, --- a/pallets/fungible/src/erc.rs +++ b/pallets/fungible/src/erc.rs @@ -20,7 +20,15 @@ use alloc::{format, string::ToString}; use core::char::{REPLACEMENT_CHARACTER, decode_utf16}; use core::convert::TryInto; -use evm_coder::{ToLog, execution::*, generate_stubgen, solidity_interface, types::*, weight}; +use evm_coder::{ + ToLog, + execution::*, + generate_stubgen, solidity_interface, + types::*, + weight, + custom_signature::{FunctionName, FunctionSignature}, + make_signature, +}; use pallet_common::eth::convert_tuple_to_cross_account; use up_data_structs::CollectionMode; use pallet_common::erc::{CommonEvmHandler, PrecompileResult}; --- a/pallets/nonfungible/src/erc.rs +++ b/pallets/nonfungible/src/erc.rs @@ -25,7 +25,15 @@ char::{REPLACEMENT_CHARACTER, decode_utf16}, convert::TryInto, }; -use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight}; +use evm_coder::{ + ToLog, + execution::*, + generate_stubgen, solidity, solidity_interface, + types::*, + weight, + custom_signature::{FunctionName, FunctionSignature}, + make_signature, +}; use frame_support::BoundedVec; use up_data_structs::{ TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey, --- a/pallets/nonfungible/src/stubs/UniqueNFT.sol +++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol @@ -104,7 +104,11 @@ } /// @title A contract that allows you to work with collections. +<<<<<<< HEAD /// @dev the ERC-165 identifier for this interface is 0xb3152af3 +======= +/// @dev the ERC-165 identifier for this interface is 0x674be726 +>>>>>>> feat: Add custum signature with unlimited nesting. contract Collection is Dummy, ERC165 { /// Set collection property. /// @@ -198,7 +202,7 @@ /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor. /// /// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract. - /// @dev EVM selector for this function is: 0x84a1d5a8, + /// @dev EVM selector for this function is: 0x403e96a7, /// or in textual repr: setCollectionSponsorCross((address,uint256)) function setCollectionSponsorCross(Tuple6 memory sponsor) public { require(false, stub_error); @@ -239,6 +243,7 @@ /// @dev EVM selector for this function is: 0x6ec0a9f1, /// or in textual repr: collectionSponsor() <<<<<<< HEAD +<<<<<<< HEAD function collectionSponsor() public view returns (Tuple6 memory) { require(false, stub_error); dummy; @@ -249,6 +254,12 @@ dummy; return Tuple19(0x0000000000000000000000000000000000000000, 0); >>>>>>> feat: add `EthCrossAccount` type +======= + function collectionSponsor() public view returns (Tuple8 memory) { + require(false, stub_error); + dummy; + return Tuple8(0x0000000000000000000000000000000000000000, 0); +>>>>>>> feat: Add custum signature with unlimited nesting. } /// Set limits for the collection. @@ -297,7 +308,7 @@ /// Add collection admin. /// @param newAdmin Cross account administrator address. - /// @dev EVM selector for this function is: 0x859aa7d6, + /// @dev EVM selector for this function is: 0x62e3c7c2, /// or in textual repr: addCollectionAdminCross((address,uint256)) function addCollectionAdminCross(Tuple6 memory newAdmin) public { require(false, stub_error); @@ -307,7 +318,7 @@ /// Remove collection admin. /// @param admin Cross account administrator address. - /// @dev EVM selector for this function is: 0x6c0cd173, + /// @dev EVM selector for this function is: 0x810d1503, /// or in textual repr: removeCollectionAdminCross((address,uint256)) function removeCollectionAdminCross(Tuple6 memory admin) public { require(false, stub_error); @@ -351,7 +362,7 @@ /// /// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled' /// @param collections Addresses of collections that will be available for nesting. - /// @dev EVM selector for this function is: 0x64872396, + /// @dev EVM selector for this function is: 0x112d4586, /// or in textual repr: setCollectionNesting(bool,address[]) function setCollectionNesting(bool enable, address[] memory collections) public { require(false, stub_error); @@ -398,7 +409,7 @@ /// Add user to allowed list. /// /// @param user User cross account address. - /// @dev EVM selector for this function is: 0xa0184a3a, + /// @dev EVM selector for this function is: 0xf074da88, /// or in textual repr: addToCollectionAllowListCross((address,uint256)) function addToCollectionAllowListCross(Tuple6 memory user) public { require(false, stub_error); @@ -420,7 +431,7 @@ /// Remove user from allowed list. /// /// @param user User cross account address. - /// @dev EVM selector for this function is: 0x09ba452a, + /// @dev EVM selector for this function is: 0xc00df45c, /// or in textual repr: removeFromCollectionAllowListCross((address,uint256)) function removeFromCollectionAllowListCross(Tuple6 memory user) public { require(false, stub_error); @@ -456,7 +467,7 @@ /// /// @param user User cross account to verify /// @return "true" if account is the owner or admin - /// @dev EVM selector for this function is: 0x3e75a905, + /// @dev EVM selector for this function is: 0x5aba3351, /// or in textual repr: isOwnerOrAdminCross((address,uint256)) function isOwnerOrAdminCross(Tuple6 memory user) public view returns (bool) { require(false, stub_error); @@ -483,6 +494,7 @@ /// @dev EVM selector for this function is: 0xdf727d3b, /// or in textual repr: collectionOwner() <<<<<<< HEAD +<<<<<<< HEAD function collectionOwner() public view returns (Tuple6 memory) { require(false, stub_error); dummy; @@ -493,6 +505,12 @@ dummy; return Tuple19(0x0000000000000000000000000000000000000000, 0); >>>>>>> feat: add `EthCrossAccount` type +======= + function collectionOwner() public view returns (Tuple8 memory) { + require(false, stub_error); + dummy; + return Tuple8(0x0000000000000000000000000000000000000000, 0); +>>>>>>> feat: Add custum signature with unlimited nesting. } /// Changes collection owner to another account @@ -523,7 +541,7 @@ /// /// @dev Owner can be changed only by current owner /// @param newOwner new owner cross account - /// @dev EVM selector for this function is: 0xe5c9913f, + /// @dev EVM selector for this function is: 0xbdff793d, /// or in textual repr: setOwnerCross((address,uint256)) function setOwnerCross(Tuple6 memory newOwner) public { require(false, stub_error); @@ -532,6 +550,7 @@ } } +<<<<<<< HEAD /// @dev anonymous struct struct Tuple19 { address field_0; @@ -581,6 +600,8 @@ } } +======= +>>>>>>> feat: Add custum signature with unlimited nesting. /// @title ERC721 Token that can be irreversibly burned (destroyed). /// @dev the ERC-165 identifier for this interface is 0x42966c68 contract ERC721Burnable is Dummy, ERC165 { @@ -682,7 +703,11 @@ } /// @title Unique extensions for ERC721. +<<<<<<< HEAD /// @dev the ERC-165 identifier for this interface is 0x244543ee +======= +/// @dev the ERC-165 identifier for this interface is 0xcc97cb35 +>>>>>>> feat: Add custum signature with unlimited nesting. contract ERC721UniqueExtensions is Dummy, ERC165 { /// @notice A descriptive name for a collection of NFTs in this contract /// @dev EVM selector for this function is: 0x06fdde03, @@ -708,7 +733,7 @@ /// operator of the current owner. /// @param approved The new substrate address approved NFT controller /// @param tokenId The NFT to approve - /// @dev EVM selector for this function is: 0x0ecd0ab0, + /// @dev EVM selector for this function is: 0x106fdb59, /// or in textual repr: approveCross((address,uint256),uint256) function approveCross(Tuple6 memory approved, uint256 tokenId) public { require(false, stub_error); @@ -738,10 +763,15 @@ /// @param to Cross acccount address of new owner /// @param tokenId The NFT to transfer /// @dev EVM selector for this function is: 0xd5cf430b, - /// or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256) + /// or in textual repr: transferFromCross(EthCrossAccount,EthCrossAccount,uint256) function transferFromCross( +<<<<<<< HEAD Tuple6 memory from, Tuple6 memory to, +======= + EthCrossAccount memory from, + EthCrossAccount memory to, +>>>>>>> feat: Add custum signature with unlimited nesting. uint256 tokenId ) public { require(false, stub_error); @@ -772,7 +802,7 @@ /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT. /// @param from The current owner of the NFT /// @param tokenId The NFT to transfer - /// @dev EVM selector for this function is: 0xbb2f5a58, + /// @dev EVM selector for this function is: 0xa8106d4a, /// or in textual repr: burnFromCross((address,uint256),uint256) function burnFromCross(Tuple6 memory from, uint256 tokenId) public { require(false, stub_error); @@ -819,10 +849,46 @@ // return false; // } +<<<<<<< HEAD } /// @dev anonymous struct struct Tuple8 { +======= + /// @notice Function to mint multiple tokens. + /// @dev `tokenIds` should be an array of consecutive numbers and first number + /// should be obtained with `nextTokenId` method + /// @param to The new owner + /// @param tokenIds IDs of the minted NFTs + /// @dev EVM selector for this function is: 0xf9d9a5a3, + /// or in textual repr: mintBulk(address,uint256[]) + function mintBulk(address to, uint256[] memory tokenIds) public returns (bool) { + require(false, stub_error); + to; + tokenIds; + dummy = 0; + return false; + } + + /// @notice Function to mint multiple tokens with the given tokenUris. + /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive + /// numbers and first number should be obtained with `nextTokenId` method + /// @param to The new owner + /// @param tokens array of pairs of token ID and token URI for minted tokens + /// @dev EVM selector for this function is: 0xfd4e2a99, + /// or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[]) + function mintBulkWithTokenURI(address to, Tuple12[] memory tokens) public returns (bool) { + require(false, stub_error); + to; + tokens; + dummy = 0; + return false; + } +} + +/// @dev anonymous struct +struct Tuple12 { +>>>>>>> feat: Add custum signature with unlimited nesting. uint256 field_0; string field_1; } @@ -840,6 +906,12 @@ >>>>>>> feat: add `EthCrossAccount` type } +/// @dev anonymous struct +struct Tuple8 { + address field_0; + uint256 field_1; +} + /// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension /// @dev See https://eips.ethereum.org/EIPS/eip-721 /// @dev the ERC-165 identifier for this interface is 0x780e9d63 --- a/pallets/refungible/src/erc.rs +++ b/pallets/refungible/src/erc.rs @@ -26,7 +26,15 @@ char::{REPLACEMENT_CHARACTER, decode_utf16}, convert::TryInto, }; -use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight}; +use evm_coder::{ + ToLog, + execution::*, + generate_stubgen, solidity, solidity_interface, + types::*, + weight, + custom_signature::{FunctionName, FunctionSignature}, + make_signature, +}; use frame_support::{BoundedBTreeMap, BoundedVec}; use pallet_common::{ CollectionHandle, CollectionPropertyPermissions, --- a/pallets/refungible/src/erc_token.rs +++ b/pallets/refungible/src/erc_token.rs @@ -29,7 +29,15 @@ convert::TryInto, ops::Deref, }; -use evm_coder::{ToLog, execution::*, generate_stubgen, solidity_interface, types::*, weight}; +use evm_coder::{ + ToLog, + execution::*, + generate_stubgen, solidity_interface, + types::*, + weight, + custom_signature::{FunctionName, FunctionSignature}, + make_signature, +}; use pallet_common::{ CommonWeightInfo, erc::{CommonEvmHandler, PrecompileResult}, --- a/pallets/unique/src/eth/mod.rs +++ b/pallets/unique/src/eth/mod.rs @@ -18,7 +18,13 @@ use core::marker::PhantomData; use ethereum as _; -use evm_coder::{execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight}; +use evm_coder::{ + execution::*, + generate_stubgen, solidity, solidity_interface, + types::*, + custom_signature::{FunctionName, FunctionSignature}, + make_signature, +, weight}; use frame_support::{traits::Get, storage::StorageNMap}; use crate::sp_api_hidden_includes_decl_storage::hidden_include::StorageDoubleMap; use crate::Pallet; --- a/tests/src/eth/api/UniqueNFT.sol +++ b/tests/src/eth/api/UniqueNFT.sol @@ -70,7 +70,11 @@ } /// @title A contract that allows you to work with collections. +<<<<<<< HEAD /// @dev the ERC-165 identifier for this interface is 0xb3152af3 +======= +/// @dev the ERC-165 identifier for this interface is 0x674be726 +>>>>>>> feat: Add custum signature with unlimited nesting. interface Collection is Dummy, ERC165 { /// Set collection property. /// @@ -133,7 +137,7 @@ /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor. /// /// @param sponsor Cross account address of the sponsor from whose account funds will be debited for operations with the contract. - /// @dev EVM selector for this function is: 0x84a1d5a8, + /// @dev EVM selector for this function is: 0x403e96a7, /// or in textual repr: setCollectionSponsorCross((address,uint256)) function setCollectionSponsorCross(Tuple6 memory sponsor) external; @@ -193,13 +197,13 @@ /// Add collection admin. /// @param newAdmin Cross account administrator address. - /// @dev EVM selector for this function is: 0x859aa7d6, + /// @dev EVM selector for this function is: 0x62e3c7c2, /// or in textual repr: addCollectionAdminCross((address,uint256)) function addCollectionAdminCross(Tuple6 memory newAdmin) external; /// Remove collection admin. /// @param admin Cross account administrator address. - /// @dev EVM selector for this function is: 0x6c0cd173, + /// @dev EVM selector for this function is: 0x810d1503, /// or in textual repr: removeCollectionAdminCross((address,uint256)) function removeCollectionAdminCross(Tuple6 memory admin) external; @@ -227,7 +231,7 @@ /// /// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled' /// @param collections Addresses of collections that will be available for nesting. - /// @dev EVM selector for this function is: 0x64872396, + /// @dev EVM selector for this function is: 0x112d4586, /// or in textual repr: setCollectionNesting(bool,address[]) function setCollectionNesting(bool enable, address[] memory collections) external; @@ -256,7 +260,7 @@ /// Add user to allowed list. /// /// @param user User cross account address. - /// @dev EVM selector for this function is: 0xa0184a3a, + /// @dev EVM selector for this function is: 0xf074da88, /// or in textual repr: addToCollectionAllowListCross((address,uint256)) function addToCollectionAllowListCross(Tuple6 memory user) external; @@ -270,7 +274,7 @@ /// Remove user from allowed list. /// /// @param user User cross account address. - /// @dev EVM selector for this function is: 0x09ba452a, + /// @dev EVM selector for this function is: 0xc00df45c, /// or in textual repr: removeFromCollectionAllowListCross((address,uint256)) function removeFromCollectionAllowListCross(Tuple6 memory user) external; @@ -293,7 +297,7 @@ /// /// @param user User cross account to verify /// @return "true" if account is the owner or admin - /// @dev EVM selector for this function is: 0x3e75a905, + /// @dev EVM selector for this function is: 0x5aba3351, /// or in textual repr: isOwnerOrAdminCross((address,uint256)) function isOwnerOrAdminCross(Tuple6 memory user) external view returns (bool); @@ -332,11 +336,12 @@ /// /// @dev Owner can be changed only by current owner /// @param newOwner new owner cross account - /// @dev EVM selector for this function is: 0xe5c9913f, + /// @dev EVM selector for this function is: 0xbdff793d, /// or in textual repr: setOwnerCross((address,uint256)) function setOwnerCross(Tuple6 memory newOwner) external; } +<<<<<<< HEAD /// @dev anonymous struct struct Tuple19 { address field_0; @@ -373,6 +378,8 @@ function tokenURI(uint256 tokenId) external view returns (string memory); } +======= +>>>>>>> feat: Add custum signature with unlimited nesting. /// @title ERC721 Token that can be irreversibly burned (destroyed). /// @dev the ERC-165 identifier for this interface is 0x42966c68 interface ERC721Burnable is Dummy, ERC165 { @@ -438,7 +445,11 @@ } /// @title Unique extensions for ERC721. +<<<<<<< HEAD /// @dev the ERC-165 identifier for this interface is 0x244543ee +======= +/// @dev the ERC-165 identifier for this interface is 0xcc97cb35 +>>>>>>> feat: Add custum signature with unlimited nesting. interface ERC721UniqueExtensions is Dummy, ERC165 { /// @notice A descriptive name for a collection of NFTs in this contract /// @dev EVM selector for this function is: 0x06fdde03, @@ -456,7 +467,7 @@ /// operator of the current owner. /// @param approved The new substrate address approved NFT controller /// @param tokenId The NFT to approve - /// @dev EVM selector for this function is: 0x0ecd0ab0, + /// @dev EVM selector for this function is: 0x106fdb59, /// or in textual repr: approveCross((address,uint256),uint256) function approveCross(Tuple6 memory approved, uint256 tokenId) external; @@ -476,10 +487,15 @@ /// @param to Cross acccount address of new owner /// @param tokenId The NFT to transfer /// @dev EVM selector for this function is: 0xd5cf430b, - /// or in textual repr: transferFromCross((address,uint256),(address,uint256),uint256) + /// or in textual repr: transferFromCross(EthCrossAccount,EthCrossAccount,uint256) function transferFromCross( +<<<<<<< HEAD Tuple6 memory from, Tuple6 memory to, +======= + EthCrossAccount memory from, + EthCrossAccount memory to, +>>>>>>> feat: Add custum signature with unlimited nesting. uint256 tokenId ) external; @@ -499,7 +515,7 @@ /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT. /// @param from The current owner of the NFT /// @param tokenId The NFT to transfer - /// @dev EVM selector for this function is: 0xbb2f5a58, + /// @dev EVM selector for this function is: 0xa8106d4a, /// or in textual repr: burnFromCross((address,uint256),uint256) function burnFromCross(Tuple6 memory from, uint256 tokenId) external; @@ -507,6 +523,7 @@ /// @dev EVM selector for this function is: 0x75794a3c, /// or in textual repr: nextTokenId() function nextTokenId() external view returns (uint256); +<<<<<<< HEAD // /// @notice Function to mint multiple tokens. // /// @dev `tokenIds` should be an array of consecutive numbers and first number // /// should be obtained with `nextTokenId` method @@ -528,6 +545,30 @@ /// @dev anonymous struct struct Tuple8 { +======= + + /// @notice Function to mint multiple tokens. + /// @dev `tokenIds` should be an array of consecutive numbers and first number + /// should be obtained with `nextTokenId` method + /// @param to The new owner + /// @param tokenIds IDs of the minted NFTs + /// @dev EVM selector for this function is: 0xf9d9a5a3, + /// or in textual repr: mintBulk(address,uint256[]) + function mintBulk(address to, uint256[] memory tokenIds) external returns (bool); + + /// @notice Function to mint multiple tokens with the given tokenUris. + /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive + /// numbers and first number should be obtained with `nextTokenId` method + /// @param to The new owner + /// @param tokens array of pairs of token ID and token URI for minted tokens + /// @dev EVM selector for this function is: 0xfd4e2a99, + /// or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[]) + function mintBulkWithTokenURI(address to, Tuple12[] memory tokens) external returns (bool); +} + +/// @dev anonymous struct +struct Tuple12 { +>>>>>>> feat: Add custum signature with unlimited nesting. uint256 field_0; string field_1; } @@ -538,6 +579,12 @@ uint256 field_1; } +/// @dev anonymous struct +struct Tuple8 { + address field_0; + uint256 field_1; +} + /// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension /// @dev See https://eips.ethereum.org/EIPS/eip-721 /// @dev the ERC-165 identifier for this interface is 0x780e9d63 --- a/tests/src/eth/nonFungible.test.ts +++ b/tests/src/eth/nonFungible.test.ts @@ -409,7 +409,7 @@ expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal()))); }); - itEth('Can perform transferFromCross()', async ({helper, privateKey}) => { + itEth.only('Can perform transferFromCross()', async ({helper, privateKey}) => { const alice = privateKey('//Alice'); const collection = await helper.nft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'}); --- a/tests/src/eth/nonFungibleAbi.json +++ b/tests/src/eth/nonFungibleAbi.json @@ -251,10 +251,14 @@ { "internalType": "uint256", "name": "field_1", "type": "uint256" } ], <<<<<<< HEAD +<<<<<<< HEAD "internalType": "struct Tuple6", ======= "internalType": "struct Tuple19", >>>>>>> feat: add `EthCrossAccount` type +======= + "internalType": "struct Tuple8", +>>>>>>> feat: Add custum signature with unlimited nesting. "name": "", "type": "tuple" } @@ -263,6 +267,7 @@ "type": "function" }, { +<<<<<<< HEAD "inputs": [ { "internalType": "string[]", "name": "keys", "type": "string[]" } ], @@ -278,6 +283,11 @@ "type": "tuple[]" } ], +======= + "inputs": [{ "internalType": "string", "name": "key", "type": "string" }], + "name": "collectionProperty", + "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }], +>>>>>>> feat: Add custum signature with unlimited nesting. "stateMutability": "view", "type": "function" }, @@ -298,10 +308,14 @@ { "internalType": "uint256", "name": "field_1", "type": "uint256" } ], <<<<<<< HEAD +<<<<<<< HEAD "internalType": "struct Tuple6", ======= "internalType": "struct Tuple19", >>>>>>> feat: add `EthCrossAccount` type +======= + "internalType": "struct Tuple8", +>>>>>>> feat: Add custum signature with unlimited nesting. "name": "", "type": "tuple" } @@ -324,6 +338,7 @@ "type": "function" }, { +<<<<<<< HEAD "inputs": [ { "internalType": "string[]", "name": "keys", "type": "string[]" } ], @@ -333,6 +348,8 @@ "type": "function" }, { +======= +>>>>>>> feat: Add custum signature with unlimited nesting. "inputs": [{ "internalType": "string", "name": "key", "type": "string" }], "name": "deleteCollectionProperty", "outputs": [], @@ -409,19 +426,64 @@ "type": "function" }, { +<<<<<<< HEAD "inputs": [{ "internalType": "address", "name": "to", "type": "address" }], "name": "mint", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], +======= + "inputs": [ + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "tokenId", "type": "uint256" } + ], + "name": "mint", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], +>>>>>>> feat: Add custum signature with unlimited nesting. "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "to", "type": "address" }, +<<<<<<< HEAD { "internalType": "string", "name": "tokenUri", "type": "string" } ], "name": "mintWithTokenURI", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], +======= + { "internalType": "uint256[]", "name": "tokenIds", "type": "uint256[]" } + ], + "name": "mintBulk", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "to", "type": "address" }, + { + "components": [ + { "internalType": "uint256", "name": "field_0", "type": "uint256" }, + { "internalType": "string", "name": "field_1", "type": "string" } + ], + "internalType": "struct Tuple12[]", + "name": "tokens", + "type": "tuple[]" + } + ], + "name": "mintBulkWithTokenURI", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, + { "internalType": "string", "name": "tokenUri", "type": "string" } + ], + "name": "mintWithTokenURI", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], +>>>>>>> feat: Add custum signature with unlimited nesting. "stateMutability": "nonpayable", "type": "function" }, @@ -614,6 +676,7 @@ }, { "inputs": [ +<<<<<<< HEAD { "components": [ { "internalType": "string", "name": "field_0", "type": "string" }, @@ -623,6 +686,10 @@ "name": "properties", "type": "tuple[]" } +======= + { "internalType": "string", "name": "key", "type": "string" }, + { "internalType": "bytes", "name": "value", "type": "bytes" } +>>>>>>> feat: Add custum signature with unlimited nesting. ], "name": "setCollectionProperties", "outputs": [], @@ -667,6 +734,7 @@ }, { "inputs": [ +<<<<<<< HEAD { "components": [ { "internalType": "address", "name": "field_0", "type": "address" }, @@ -676,6 +744,9 @@ "name": "newOwner", "type": "tuple" } +======= + { "internalType": "address", "name": "newOwner", "type": "address" } +>>>>>>> feat: Add custum signature with unlimited nesting. ], "name": "setOwnerCross", "outputs": [], @@ -687,8 +758,13 @@ { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, { "components": [ +<<<<<<< HEAD { "internalType": "string", "name": "field_0", "type": "string" }, { "internalType": "bytes", "name": "field_1", "type": "bytes" } +======= + { "internalType": "address", "name": "field_0", "type": "address" }, + { "internalType": "uint256", "name": "field_1", "type": "uint256" } +>>>>>>> feat: Add custum signature with unlimited nesting. ], "internalType": "struct Tuple19[]", "name": "properties", @@ -799,8 +875,13 @@ "inputs": [ { "components": [ +<<<<<<< HEAD { "internalType": "address", "name": "field_0", "type": "address" }, { "internalType": "uint256", "name": "field_1", "type": "uint256" } +======= + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } +>>>>>>> feat: Add custum signature with unlimited nesting. ], <<<<<<< HEAD "internalType": "struct Tuple6", @@ -812,8 +893,13 @@ }, { "components": [ +<<<<<<< HEAD { "internalType": "address", "name": "field_0", "type": "address" }, { "internalType": "uint256", "name": "field_1", "type": "uint256" } +======= + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } +>>>>>>> feat: Add custum signature with unlimited nesting. ], <<<<<<< HEAD "internalType": "struct Tuple6", --- a/tests/src/eth/util/playgrounds/types.ts +++ b/tests/src/eth/util/playgrounds/types.ts @@ -14,10 +14,8 @@ args: { [key: string]: string } }; export interface TEthCrossAccount { - readonly 0: string, - readonly 1: string | Uint8Array, - readonly field_0: string, - readonly field_1: string | Uint8Array, + readonly eth: string, + readonly sub: string | Uint8Array, } export type EthProperty = string[]; --- a/tests/src/eth/util/playgrounds/unique.dev.ts +++ b/tests/src/eth/util/playgrounds/unique.dev.ts @@ -364,19 +364,15 @@ export class EthCrossAccountGroup extends EthGroupBase { fromAddress(address: TEthereumAccount): TEthCrossAccount { return { - 0: address, - 1: '0', - field_0: address, - field_1: '0', + eth: address, + sub: '0', }; } fromKeyringPair(keyring: IKeyringPair): TEthCrossAccount { return { - 0: '0x0000000000000000000000000000000000000000', - 1: keyring.addressRaw, - field_0: '0x0000000000000000000000000000000000000000', - field_1: keyring.addressRaw, + eth: '0x0000000000000000000000000000000000000000', + sub: keyring.addressRaw, }; } } @@ -429,7 +425,6 @@ ethAddress: EthAddressGroup; ethNativeContract: NativeContractGroup; ethContract: ContractGroup; - ethCrossAccount: EthCrossAccountGroup; ethProperty: EthPropertyGroup; constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) {