--- a/.maintain/scripts/generate_abi.sh +++ b/.maintain/scripts/generate_abi.sh @@ -4,6 +4,7 @@ dir=$PWD tmp=$(mktemp -d) +echo "Tmp file: $tmp/input.sol" cd $tmp cp $dir/$INPUT input.sol solcjs --abi -p input.sol --- a/Cargo.lock +++ b/Cargo.lock @@ -1058,16 +1058,6 @@ ] [[package]] -name = "concat-idents" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fe0e1d9f7de897d18e590a7496b5facbe87813f746cf4b8db596ba77e07e832" -dependencies = [ - "quote", - "syn", -] - -[[package]] name = "concurrent-queue" version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2347,9 +2337,8 @@ [[package]] name = "evm-coder" -version = "0.1.4" +version = "0.1.5" dependencies = [ - "concat-idents", "ethereum", "evm-coder-procedural", "evm-core", @@ -2367,7 +2356,7 @@ [[package]] name = "evm-coder-procedural" -version = "0.2.1" +version = "0.2.2" dependencies = [ "Inflector", "hex", --- a/crates/evm-coder/CHANGELOG.md +++ b/crates/evm-coder/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. +## [v0.1.5] - 2022-11-30 + +### Added +- Derive macro to support structures and enums. ## [v0.1.4] - 2022-11-02 --- a/crates/evm-coder/Cargo.toml +++ b/crates/evm-coder/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evm-coder" -version = "0.1.4" +version = "0.1.5" license = "GPLv3" edition = "2021" @@ -26,7 +26,6 @@ hex = "0.4.3" hex-literal = "0.3.4" similar-asserts = "1.4.2" -concat-idents = "1.1.3" trybuild = "1.0" [features] --- a/crates/evm-coder/procedural/Cargo.toml +++ b/crates/evm-coder/procedural/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evm-coder-procedural" -version = "0.2.1" +version = "0.2.2" license = "GPLv3" edition = "2021" --- /dev/null +++ b/crates/evm-coder/procedural/src/abi_derive/derive_enum.rs @@ -0,0 +1,211 @@ +use quote::quote; + +pub fn impl_solidity_option<'a>( + name: &proc_macro2::Ident, + enum_options: impl Iterator, +) -> proc_macro2::TokenStream { + let enum_options = enum_options.map(|opt| { + let s = name.to_string() + "." + opt.to_string().as_str(); + let as_string = proc_macro2::Literal::string(s.as_str()); + quote!(#name::#opt => #as_string,) + }); + quote!( + #[cfg(feature = "stubgen")] + impl ::evm_coder::solidity::SolidityEnum for #name { + fn solidity_option(&self) -> &str { + match self { + #(#enum_options)* + } + } + } + ) +} + +pub fn impl_enum_from_u8<'a>( + name: &proc_macro2::Ident, + enum_options: impl Iterator, +) -> proc_macro2::TokenStream { + let error_str = format!("Value not convertible into enum \"{name}\""); + let error_str = proc_macro2::Literal::string(&error_str); + let enum_options = enum_options.enumerate().map(|(i, opt)| { + let n = proc_macro2::Literal::u8_suffixed(i as u8); + quote! {#n => Ok(#name::#opt),} + }); + + quote!( + impl TryFrom for #name { + type Error = &'static str; + + fn try_from(value: u8) -> ::core::result::Result { + const err: &'static str = #error_str; + match value { + #(#enum_options)* + _ => Err(err) + } + } + } + ) +} + +pub fn impl_enum_abi_type(name: &syn::Ident) -> proc_macro2::TokenStream { + quote! { + impl ::evm_coder::abi::AbiType for #name { + const SIGNATURE: ::evm_coder::custom_signature::SignatureUnit = ::SIGNATURE; + + fn is_dynamic() -> bool { + ::is_dynamic() + } + fn size() -> usize { + ::size() + } + } + } +} + +pub fn impl_enum_abi_read(name: &syn::Ident) -> proc_macro2::TokenStream { + quote!( + impl ::evm_coder::abi::AbiRead for #name { + fn abi_read(reader: &mut ::evm_coder::abi::AbiReader) -> ::evm_coder::execution::Result { + Ok( + ::abi_read(reader)? + .try_into()? + ) + } + } + ) +} + +pub fn impl_enum_abi_write(name: &syn::Ident) -> proc_macro2::TokenStream { + quote!( + impl ::evm_coder::abi::AbiWrite for #name { + fn abi_write(&self, writer: &mut ::evm_coder::abi::AbiWriter) { + ::evm_coder::abi::AbiWrite::abi_write(&(*self as u8), writer); + } + } + ) +} + +pub fn impl_enum_solidity_type_name(name: &syn::Ident) -> proc_macro2::TokenStream { + quote!( + #[cfg(feature = "stubgen")] + impl ::evm_coder::solidity::SolidityTypeName for #name { + fn solidity_name( + writer: &mut impl ::core::fmt::Write, + tc: &::evm_coder::solidity::TypeCollector, + ) -> ::core::fmt::Result { + write!(writer, "{}", tc.collect_struct::()) + } + + fn is_simple() -> bool { + true + } + + fn solidity_default( + writer: &mut impl ::core::fmt::Write, + tc: &::evm_coder::solidity::TypeCollector, + ) -> ::core::fmt::Result { + write!(writer, "{}", <#name as ::evm_coder::solidity::SolidityEnum>::solidity_option(&<#name>::default())) + } + } + ) +} + +pub fn impl_enum_solidity_struct_collect<'a>( + name: &syn::Ident, + enum_options: impl Iterator, + option_count: usize, + enum_options_docs: impl Iterator>>, + docs: &[proc_macro2::TokenStream], +) -> proc_macro2::TokenStream { + let string_name = name.to_string(); + let enum_options = enum_options + .zip(enum_options_docs) + .enumerate() + .map(|(i, (opt, doc))| { + let opt = proc_macro2::Literal::string(opt.to_string().as_str()); + let doc = doc.expect("Doc parsing error"); + let comma = if i != option_count - 1 { "," } else { "" }; + quote! { + #(#doc)* + writeln!(str, "\t{}{}", #opt, #comma).expect("Enum format option"); + } + }); + + quote!( + #[cfg(feature = "stubgen")] + impl ::evm_coder::solidity::StructCollect for #name { + fn name() -> String { + #string_name.into() + } + + fn declaration() -> String { + use std::fmt::Write; + + let mut str = String::new(); + #(#docs)* + writeln!(str, "enum {} {{", ::name()).unwrap(); + #(#enum_options)* + writeln!(str, "}}").unwrap(); + str + } + } + ) +} + +pub fn check_and_count_options(de: &syn::DataEnum) -> syn::Result { + let mut count = 0; + for v in de.variants.iter() { + if !v.fields.is_empty() { + return Err(syn::Error::new( + v.ident.span(), + "Enumeration parameters should not have fields", + )); + } else if v.discriminant.is_some() { + return Err(syn::Error::new( + v.ident.span(), + "Enumeration options should not have an explicit specified value", + )); + } else { + count += 1; + } + } + + Ok(count) +} + +pub fn check_repr_u8(name: &syn::Ident, attrs: &[syn::Attribute]) -> syn::Result<()> { + let mut has_repr = false; + for attr in attrs.iter() { + if attr.path.is_ident("repr") { + has_repr = true; + let meta = attr.parse_meta()?; + check_meta_u8(&meta)?; + } + } + + if !has_repr { + return Err(syn::Error::new(name.span(), "Enum is not \"repr(u8)\"")); + } + + Ok(()) +} + +fn check_meta_u8(meta: &syn::Meta) -> Result<(), syn::Error> { + if let syn::Meta::List(p) = meta { + for nm in p.nested.iter() { + if let syn::NestedMeta::Meta(syn::Meta::Path(p)) = nm { + if !p.is_ident("u8") { + return Err(syn::Error::new( + p.segments + .first() + .expect("repr segments are empty") + .ident + .span(), + "Enum is not \"repr(u8)\"", + )); + } + } + } + } + Ok(()) +} --- /dev/null +++ b/crates/evm-coder/procedural/src/abi_derive/derive_struct.rs @@ -0,0 +1,260 @@ +use super::extract_docs; +use quote::quote; + +pub fn tuple_type<'a>( + field_types: impl Iterator + Clone, +) -> proc_macro2::TokenStream { + let field_types = field_types.map(|ty| quote!(#ty,)); + quote! {(#(#field_types)*)} +} + +pub fn tuple_ref_type<'a>( + field_types: impl Iterator + Clone, +) -> proc_macro2::TokenStream { + let field_types = field_types.map(|ty| quote!(&#ty,)); + quote! {(#(#field_types)*)} +} + +pub fn tuple_data_as_ref( + is_named_fields: bool, + field_names: impl Iterator + Clone, +) -> proc_macro2::TokenStream { + let field_names = field_names.enumerate().map(|(i, field)| { + if is_named_fields { + quote!(&self.#field,) + } else { + let field = proc_macro2::Literal::usize_unsuffixed(i); + quote!(&self.#field,) + } + }); + quote! {(#(#field_names)*)} +} + +pub fn tuple_names( + is_named_fields: bool, + field_names: impl Iterator + Clone, +) -> proc_macro2::TokenStream { + let field_names = field_names.enumerate().map(|(i, field)| { + if is_named_fields { + quote!(#field,) + } else { + let field = proc_macro2::Ident::new( + format!("field{}", i).as_str(), + proc_macro2::Span::call_site(), + ); + quote!(#field,) + } + }); + quote! {(#(#field_names)*)} +} + +pub fn struct_from_tuple( + name: &syn::Ident, + is_named_fields: bool, + field_names: impl Iterator + Clone, +) -> proc_macro2::TokenStream { + let field_names = field_names.enumerate().map(|(i, field)| { + if is_named_fields { + quote!(#field,) + } else { + let field = proc_macro2::Ident::new( + format!("field{}", i).as_str(), + proc_macro2::Span::call_site(), + ); + quote!(#field,) + } + }); + + if is_named_fields { + quote! {#name {#(#field_names)*}} + } else { + quote! {#name (#(#field_names)*)} + } +} + +pub fn map_field_to_name(field: (usize, &syn::Field)) -> syn::Ident { + match field.1.ident.as_ref() { + Some(name) => name.clone(), + None => { + let mut name = "field".to_string(); + name.push_str(field.0.to_string().as_str()); + syn::Ident::new(name.as_str(), proc_macro2::Span::call_site()) + } + } +} + +pub fn map_field_to_type(field: &syn::Field) -> &syn::Type { + &field.ty +} + +pub fn map_field_to_doc(field: &syn::Field) -> syn::Result> { + extract_docs(&field.attrs, true) +} + +pub fn impl_can_be_placed_in_vec(ident: &syn::Ident) -> proc_macro2::TokenStream { + quote! { + impl ::evm_coder::sealed::CanBePlacedInVec for #ident {} + } +} + +pub fn impl_struct_abi_type( + name: &syn::Ident, + tuple_type: proc_macro2::TokenStream, +) -> proc_macro2::TokenStream { + quote! { + impl ::evm_coder::abi::AbiType for #name { + const SIGNATURE: ::evm_coder::custom_signature::SignatureUnit = <#tuple_type as ::evm_coder::abi::AbiType>::SIGNATURE; + fn is_dynamic() -> bool { + <#tuple_type as ::evm_coder::abi::AbiType>::is_dynamic() + } + fn size() -> usize { + <#tuple_type as ::evm_coder::abi::AbiType>::size() + } + } + } +} + +pub fn impl_struct_abi_read( + name: &syn::Ident, + tuple_type: proc_macro2::TokenStream, + tuple_names: proc_macro2::TokenStream, + struct_from_tuple: proc_macro2::TokenStream, +) -> proc_macro2::TokenStream { + quote!( + impl ::evm_coder::abi::AbiRead for #name { + fn abi_read(reader: &mut ::evm_coder::abi::AbiReader) -> ::evm_coder::execution::Result { + let #tuple_names = <#tuple_type as ::evm_coder::abi::AbiRead>::abi_read(reader)?; + Ok(#struct_from_tuple) + } + } + ) +} + +pub fn impl_struct_abi_write( + name: &syn::Ident, + _is_named_fields: bool, + tuple_type: proc_macro2::TokenStream, + tuple_data: proc_macro2::TokenStream, +) -> proc_macro2::TokenStream { + quote!( + impl ::evm_coder::abi::AbiWrite for #name { + fn abi_write(&self, writer: &mut ::evm_coder::abi::AbiWriter) { + <#tuple_type as ::evm_coder::abi::AbiWrite>::abi_write(&#tuple_data, writer) + } + } + ) +} + +pub fn impl_struct_solidity_type<'a>( + name: &syn::Ident, + field_types: impl Iterator + Clone, + params_count: usize, +) -> proc_macro2::TokenStream { + let len = proc_macro2::Literal::usize_suffixed(params_count); + quote! { + #[cfg(feature = "stubgen")] + impl ::evm_coder::solidity::SolidityType for #name { + fn names(tc: &::evm_coder::solidity::TypeCollector) -> Vec { + let mut collected = + Vec::with_capacity(::len()); + #({ + let mut out = String::new(); + <#field_types as ::evm_coder::solidity::SolidityTypeName>::solidity_name(&mut out, tc) + .expect("no fmt error"); + collected.push(out); + })* + collected + } + + fn len() -> usize { + #len + } + } + } +} + +pub fn impl_struct_solidity_type_name<'a>( + name: &syn::Ident, + field_types: impl Iterator + Clone, + params_count: usize, +) -> proc_macro2::TokenStream { + let arg_dafaults = field_types.enumerate().map(|(i, ty)| { + let mut defult_value = quote!(<#ty as ::evm_coder::solidity::SolidityTypeName + >::solidity_default(writer, tc)?;); + let last_item = params_count - 1; + if i != last_item { + defult_value.extend(quote! {write!(writer, ",")?;}) + } + defult_value + }); + + quote! { + #[cfg(feature = "stubgen")] + impl ::evm_coder::solidity::SolidityTypeName for #name { + fn solidity_name( + writer: &mut impl ::core::fmt::Write, + tc: &::evm_coder::solidity::TypeCollector, + ) -> ::core::fmt::Result { + write!(writer, "{}", tc.collect_struct::()) + } + + fn is_simple() -> bool { + false + } + + fn solidity_default( + writer: &mut impl ::core::fmt::Write, + tc: &::evm_coder::solidity::TypeCollector, + ) -> ::core::fmt::Result { + write!(writer, "{}(", tc.collect_struct::())?; + + #(#arg_dafaults)* + + write!(writer, ")") + } + } + } +} + +pub fn impl_struct_solidity_struct_collect<'a>( + name: &syn::Ident, + field_names: impl Iterator + Clone, + field_types: impl Iterator + Clone, + field_docs: impl Iterator>> + Clone, + docs: &[proc_macro2::TokenStream], +) -> syn::Result { + let string_name = name.to_string(); + let name_type = field_names + .into_iter() + .zip(field_types) + .zip(field_docs) + .map(|((name, ty), doc)| { + let field_docs = doc.expect("Doc parse error"); + let name = format!("{}", name); + quote!( + #(#field_docs)* + write!(str, "\t{} ", <#ty as ::evm_coder::solidity::StructCollect>::name()).unwrap(); + writeln!(str, "{};", #name).unwrap(); + ) + }); + + Ok(quote! { + #[cfg(feature = "stubgen")] + impl ::evm_coder::solidity::StructCollect for #name { + fn name() -> String { + #string_name.into() + } + + fn declaration() -> String { + use std::fmt::Write; + + let mut str = String::new(); + #(#docs)* + writeln!(str, "struct {} {{", Self::name()).unwrap(); + #(#name_type)* + writeln!(str, "}}").unwrap(); + str + } + } + }) +} --- /dev/null +++ b/crates/evm-coder/procedural/src/abi_derive/mod.rs @@ -0,0 +1,145 @@ +mod derive_enum; +mod derive_struct; + +use quote::quote; +use derive_struct::*; +use derive_enum::*; + +pub(crate) fn impl_abi_macro(ast: &syn::DeriveInput) -> syn::Result { + let name = &ast.ident; + match &ast.data { + syn::Data::Struct(ds) => expand_struct(ds, ast), + syn::Data::Enum(de) => expand_enum(de, ast), + syn::Data::Union(_) => Err(syn::Error::new(name.span(), "Unions not supported")), + } +} + +fn expand_struct( + ds: &syn::DataStruct, + ast: &syn::DeriveInput, +) -> syn::Result { + let name = &ast.ident; + let docs = extract_docs(&ast.attrs, false)?; + let (is_named_fields, field_names, field_types, field_docs, params_count) = match ds.fields { + syn::Fields::Named(ref fields) => Ok(( + true, + fields.named.iter().enumerate().map(map_field_to_name), + fields.named.iter().map(map_field_to_type), + fields.named.iter().map(map_field_to_doc), + fields.named.len(), + )), + syn::Fields::Unnamed(ref fields) => Ok(( + false, + fields.unnamed.iter().enumerate().map(map_field_to_name), + fields.unnamed.iter().map(map_field_to_type), + fields.unnamed.iter().map(map_field_to_doc), + fields.unnamed.len(), + )), + syn::Fields::Unit => Err(syn::Error::new(name.span(), "Unit structs not supported")), + }?; + + if params_count == 0 { + return Err(syn::Error::new(name.span(), "Empty structs not supported")); + }; + + let tuple_type = tuple_type(field_types.clone()); + let tuple_ref_type = tuple_ref_type(field_types.clone()); + let tuple_data = tuple_data_as_ref(is_named_fields, field_names.clone()); + let tuple_names = tuple_names(is_named_fields, field_names.clone()); + let struct_from_tuple = struct_from_tuple(name, is_named_fields, field_names.clone()); + + let can_be_plcaed_in_vec = impl_can_be_placed_in_vec(name); + let abi_type = impl_struct_abi_type(name, tuple_type.clone()); + let abi_read = impl_struct_abi_read(name, tuple_type, tuple_names, struct_from_tuple); + let abi_write = impl_struct_abi_write(name, is_named_fields, tuple_ref_type, tuple_data); + let solidity_type = impl_struct_solidity_type(name, field_types.clone(), params_count); + let solidity_type_name = + impl_struct_solidity_type_name(name, field_types.clone(), params_count); + let solidity_struct_collect = + impl_struct_solidity_struct_collect(name, field_names, field_types, field_docs, &docs)?; + + Ok(quote! { + #can_be_plcaed_in_vec + #abi_type + #abi_read + #abi_write + #solidity_type + #solidity_type_name + #solidity_struct_collect + }) +} + +fn expand_enum( + de: &syn::DataEnum, + ast: &syn::DeriveInput, +) -> syn::Result { + let name = &ast.ident; + check_repr_u8(name, &ast.attrs)?; + let docs = extract_docs(&ast.attrs, false)?; + let option_count = check_and_count_options(de)?; + let enum_options = de.variants.iter().map(|v| &v.ident); + let enum_options_docs = de.variants.iter().map(|v| extract_docs(&v.attrs, true)); + + let from = impl_enum_from_u8(name, enum_options.clone()); + let solidity_option = impl_solidity_option(name, enum_options.clone()); + let can_be_plcaed_in_vec = impl_can_be_placed_in_vec(name); + let abi_type = impl_enum_abi_type(name); + let abi_read = impl_enum_abi_read(name); + let abi_write = impl_enum_abi_write(name); + let solidity_type_name = impl_enum_solidity_type_name(name); + let solidity_struct_collect = impl_enum_solidity_struct_collect( + name, + enum_options, + option_count, + enum_options_docs, + &docs, + ); + + Ok(quote! { + #from + #solidity_option + #can_be_plcaed_in_vec + #abi_type + #abi_read + #abi_write + #solidity_type_name + #solidity_struct_collect + }) +} + +fn extract_docs( + attrs: &[syn::Attribute], + is_field_doc: bool, +) -> syn::Result> { + attrs + .iter() + .filter_map(|attr| { + if let Some(ps) = attr.path.segments.first() { + if ps.ident == "doc" { + let meta = match attr.parse_meta() { + Ok(meta) => meta, + Err(e) => return Some(Err(e)), + }; + match meta { + syn::Meta::NameValue(mnv) => match &mnv.lit { + syn::Lit::Str(ls) => return Some(Ok(ls.value())), + _ => unreachable!(), + }, + _ => unreachable!(), + } + } + } + None + }) + .enumerate() + .map(|(i, doc)| { + let doc = doc?; + let doc = doc.trim(); + let dev = if i == 0 { " @dev" } else { "" }; + let tab = if is_field_doc { "\t" } else { "" }; + Ok(quote! { + writeln!(str, "{}///{} {}", #tab, #dev, #doc).unwrap(); + }) + }) + .collect() +} --- a/crates/evm-coder/procedural/src/lib.rs +++ b/crates/evm-coder/procedural/src/lib.rs @@ -25,6 +25,7 @@ parse_macro_input, spanned::Spanned, }; +mod abi_derive; mod solidity_interface; mod to_log; @@ -242,3 +243,13 @@ } .into() } + +#[proc_macro_derive(AbiCoder)] +pub fn abi_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let ast = syn::parse(input).unwrap(); + let ts = match abi_derive::impl_abi_macro(&ast) { + Ok(e) => e, + Err(e) => e.to_compile_error(), + }; + ts.into() +} --- a/crates/evm-coder/procedural/src/solidity_interface.rs +++ b/crates/evm-coder/procedural/src/solidity_interface.rs @@ -404,7 +404,11 @@ let name = &self.name; let ty = &self.ty; quote! { - #name: <#ty>::abi_read(reader)? + #name: { + let value = <#ty as ::evm_coder::abi::AbiRead>::abi_read(reader)?; + if !is_dynamic {reader.bytes_read(<#ty as ::evm_coder::abi::AbiType>::size())}; + value + } } } @@ -630,17 +634,18 @@ let pascal_name = &self.pascal_name; let screaming_name = &self.screaming_name; if self.has_normal_args { - let parsers = self - .args - .iter() - .filter(|a| !a.is_special()) - .map(|a| a.expand_parse()); + let args_iter = self.args.iter().filter(|a| !a.is_special()); + let arg_type = args_iter.clone().map(|a| &a.ty); + let parsers = args_iter.map(|a| a.expand_parse()); quote! { - Self::#screaming_name => return Ok(Some(Self::#pascal_name { - #( - #parsers, - )* - })) + Self::#screaming_name => { + let is_dynamic = false #(|| <#arg_type as ::evm_coder::abi::AbiType>::is_dynamic())*; + return Ok(Some(Self::#pascal_name { + #( + #parsers, + )* + })) + } } } else { quote! { Self::#screaming_name => return Ok(Some(Self::#pascal_name)) } --- a/crates/evm-coder/src/abi/impls.rs +++ b/crates/evm-coder/src/abi/impls.rs @@ -1,8 +1,8 @@ use crate::{ + custom_signature::SignatureUnit, execution::{Result, ResultWithPostInfo, WithPostDispatchInfo}, + make_signature, sealed, types::*, - make_signature, - custom_signature::SignatureUnit, }; use super::{traits::*, ABI_ALIGNMENT, AbiReader, AbiWriter}; use primitive_types::{U256, H160}; @@ -10,12 +10,12 @@ #[cfg(not(feature = "std"))] use alloc::vec::Vec; -macro_rules! impl_abi_readable { - ($ty:ty, $method:ident, $dynamic:literal) => { +macro_rules! impl_abi_type { + ($ty:ty, $name:ident, $dynamic:literal) => { impl sealed::CanBePlacedInVec for $ty {} impl AbiType for $ty { - const SIGNATURE: SignatureUnit = make_signature!(new fixed(stringify!($ty))); + const SIGNATURE: SignatureUnit = make_signature!(new fixed(stringify!($name))); fn is_dynamic() -> bool { $dynamic @@ -25,7 +25,11 @@ ABI_ALIGNMENT } } + }; +} +macro_rules! impl_abi_readable { + ($ty:ty, $method:ident) => { impl AbiRead for $ty { fn abi_read(reader: &mut AbiReader) -> Result<$ty> { reader.$method() @@ -34,79 +38,93 @@ }; } -impl_abi_readable!(uint32, uint32, false); -impl_abi_readable!(uint64, uint64, false); -impl_abi_readable!(uint128, uint128, false); -impl_abi_readable!(uint256, uint256, false); -impl_abi_readable!(bytes4, bytes4, false); -impl_abi_readable!(address, address, false); -impl_abi_readable!(string, string, true); +macro_rules! impl_abi_writeable { + ($ty:ty, $method:ident) => { + impl AbiWrite for $ty { + fn abi_write(&self, writer: &mut AbiWriter) { + writer.$method(&self) + } + } + }; +} -impl sealed::CanBePlacedInVec for bool {} +macro_rules! impl_abi { + ($ty:ty, $method:ident, $dynamic:literal) => { + impl_abi_type!($ty, $method, $dynamic); + impl_abi_readable!($ty, $method); + impl_abi_writeable!($ty, $method); + }; +} -impl AbiType for bool { - const SIGNATURE: SignatureUnit = make_signature!(new fixed("bool")); +impl_abi!(bool, bool, false); +impl_abi!(u8, uint8, false); +impl_abi!(u32, uint32, false); +impl_abi!(u64, uint64, false); +impl_abi!(u128, uint128, false); +impl_abi!(U256, uint256, false); +impl_abi!(H160, address, false); +impl_abi!(string, string, true); - fn is_dynamic() -> bool { - false - } - fn size() -> usize { - ABI_ALIGNMENT +impl_abi_writeable!(&str, string); + +impl_abi_type!(bytes, bytes, true); + +impl AbiRead for bytes { + fn abi_read(reader: &mut AbiReader) -> Result { + Ok(bytes(reader.bytes()?)) } } -impl AbiRead for bool { - fn abi_read(reader: &mut AbiReader) -> Result { - reader.bool() + +impl AbiWrite for bytes { + fn abi_write(&self, writer: &mut AbiWriter) { + writer.bytes(self.0.as_slice()) } } - -impl AbiType for uint8 { - const SIGNATURE: SignatureUnit = make_signature!(new fixed("uint8")); - fn is_dynamic() -> bool { - false - } - fn size() -> usize { - ABI_ALIGNMENT +impl_abi_type!(bytes4, bytes4, false); +impl AbiRead for bytes4 { + fn abi_read(reader: &mut AbiReader) -> Result { + reader.bytes4() } } -impl AbiRead for uint8 { - fn abi_read(reader: &mut AbiReader) -> Result { - reader.uint8() + +impl AbiWrite for &T { + fn abi_write(&self, writer: &mut AbiWriter) { + T::abi_write(self, writer); } } -impl AbiType for bytes { - const SIGNATURE: SignatureUnit = make_signature!(new fixed("bytes")); +impl AbiType for &T { + const SIGNATURE: SignatureUnit = T::SIGNATURE; fn is_dynamic() -> bool { - true + T::is_dynamic() } + fn size() -> usize { - ABI_ALIGNMENT - } -} -impl AbiRead for bytes { - fn abi_read(reader: &mut AbiReader) -> Result { - Ok(bytes(reader.bytes()?)) + T::size() } } -impl AbiRead for Vec { - fn abi_read(reader: &mut AbiReader) -> Result> { +impl AbiRead for Vec { + fn abi_read(reader: &mut AbiReader) -> Result> { let mut sub = reader.subresult(None)?; let size = sub.uint32()? as usize; sub.subresult_offset = sub.offset; + let is_dynamic = ::is_dynamic(); let mut out = Vec::with_capacity(size); for _ in 0..size { - out.push(::abi_read(&mut sub)?); + out.push(::abi_read(&mut sub)?); + if !is_dynamic { + sub.bytes_read(::size()); + }; } Ok(out) } } -impl AbiType for Vec { - const SIGNATURE: SignatureUnit = make_signature!(new nameof(R::SIGNATURE) fixed("[]")); +impl AbiType for Vec { + const SIGNATURE: SignatureUnit = make_signature!(new nameof(T::SIGNATURE) fixed("[]")); fn is_dynamic() -> bool { true @@ -114,42 +132,6 @@ fn size() -> usize { ABI_ALIGNMENT - } -} - -impl sealed::CanBePlacedInVec for EthCrossAccount {} - -impl AbiType for EthCrossAccount { - const SIGNATURE: SignatureUnit = make_signature!(new fixed("(address,uint256)")); - - fn is_dynamic() -> bool { - address::is_dynamic() || uint256::is_dynamic() - } - - fn size() -> usize { -
::size() + ::size() - } -} - -impl AbiRead for EthCrossAccount { - fn abi_read(reader: &mut AbiReader) -> Result { - let size = if !EthCrossAccount::is_dynamic() { - Some(::size()) - } else { - None - }; - let mut subresult = reader.subresult(size)?; - let eth =
::abi_read(&mut subresult)?; - let sub = ::abi_read(&mut subresult)?; - - Ok(EthCrossAccount { eth, sub }) - } -} - -impl AbiWrite for EthCrossAccount { - fn abi_write(&self, writer: &mut AbiWriter) { - self.eth.abi_write(writer); - self.sub.abi_write(writer); } } @@ -188,36 +170,6 @@ } } -macro_rules! impl_abi_writeable { - ($ty:ty, $method:ident) => { - impl AbiWrite for $ty { - fn abi_write(&self, writer: &mut AbiWriter) { - writer.$method(&self) - } - } - }; -} - -impl_abi_writeable!(u8, uint8); -impl_abi_writeable!(u32, uint32); -impl_abi_writeable!(u128, uint128); -impl_abi_writeable!(U256, uint256); -impl_abi_writeable!(H160, address); -impl_abi_writeable!(bool, bool); -impl_abi_writeable!(&str, string); - -impl AbiWrite for string { - fn abi_write(&self, writer: &mut AbiWriter) { - writer.string(self) - } -} - -impl AbiWrite for bytes { - fn abi_write(&self, writer: &mut AbiWriter) { - writer.bytes(self.0.as_slice()) - } -} - impl AbiWrite for Vec { fn abi_write(&self, writer: &mut AbiWriter) { let is_dynamic = T::is_dynamic(); @@ -295,14 +247,19 @@ impl<$($ident),+> AbiRead for ($($ident,)+) where - $($ident: AbiRead,)+ - ($($ident,)+): AbiType, + Self: AbiType, + $($ident: AbiRead + AbiType,)+ { fn abi_read(reader: &mut AbiReader) -> Result<($($ident,)+)> { - let size = if !<($($ident,)+)>::is_dynamic() { Some(<($($ident,)+)>::size()) } else { None }; + let is_dynamic = ::is_dynamic(); + let size = if !is_dynamic { Some(::size()) } else { None }; let mut subresult = reader.subresult(size)?; Ok(( - $(<$ident>::abi_read(&mut subresult)?,)+ + $({ + let value = <$ident>::abi_read(&mut subresult)?; + if !is_dynamic {subresult.bytes_read(<$ident as AbiType>::size())}; + value + },)+ )) } } @@ -310,11 +267,11 @@ #[allow(non_snake_case)] impl<$($ident),+> AbiWrite for ($($ident,)+) where - $($ident: AbiWrite,)+ + $($ident: AbiWrite + AbiType,)+ { fn abi_write(&self, writer: &mut AbiWriter) { let ($($ident,)+) = self; - if writer.is_dynamic { + if ::is_dynamic() { let mut sub = AbiWriter::new(); $($ident.abi_write(&mut sub);)+ writer.write_subresult(sub); --- a/crates/evm-coder/src/abi/mod.rs +++ b/crates/evm-coder/src/abi/mod.rs @@ -75,19 +75,19 @@ buf: &[u8], offset: usize, pad_start: usize, - pad_size: usize, + pad_end: usize, block_start: usize, - block_size: usize, + block_end: usize, ) -> Result<[u8; S]> { if buf.len() - offset < ABI_ALIGNMENT { return Err(Error::Error(ExitError::OutOfOffset)); } let mut block = [0; S]; - let is_pad_zeroed = buf[pad_start..pad_size].iter().all(|&v| v == 0); + let is_pad_zeroed = buf[pad_start..pad_end].iter().all(|&v| v == 0); if !is_pad_zeroed { return Err(Error::Error(ExitError::InvalidRange)); } - block.copy_from_slice(&buf[block_start..block_size]); + block.copy_from_slice(&buf[block_start..block_end]); Ok(block) } @@ -186,11 +186,10 @@ /// Slice recursive buffer, advance one word for buffer offset /// If `size` is [`None`] then [`Self::offset`] and [`Self::subresult_offset`] evals from [`Self::buf`]. - fn subresult(&mut self, size: Option) -> Result> { + pub fn subresult(&mut self, size: Option) -> Result> { let subresult_offset = self.subresult_offset; let offset = if let Some(size) = size { self.offset += size; - self.subresult_offset += size; 0 } else { self.uint32()? as usize @@ -208,6 +207,11 @@ }) } + /// Notify about readed data portion. + pub fn bytes_read(&mut self, size: usize) { + self.subresult_offset += size; + } + /// Is this parser reached end of buffer? pub fn is_finished(&self) -> bool { self.buf.len() == self.offset @@ -277,6 +281,11 @@ self.write_padleft(&u32::to_be_bytes(*value)) } + /// Write [`u64`] to end of buffer + pub fn uint64(&mut self, value: &u64) { + self.write_padleft(&u64::to_be_bytes(*value)) + } + /// Write [`u128`] to end of buffer pub fn uint128(&mut self, value: &u128) { self.write_padleft(&u128::to_be_bytes(*value)) --- a/crates/evm-coder/src/abi/test.rs +++ b/crates/evm-coder/src/abi/test.rs @@ -6,36 +6,25 @@ use super::{AbiReader, AbiWriter}; use hex_literal::hex; use primitive_types::{H160, U256}; -use concat_idents::concat_idents; -macro_rules! test_impl { - ($name:ident, $type:ty, $function_identifier:expr, $decoded_data:expr, $encoded_data:expr) => { - concat_idents!(test_name = encode_decode_, $name { - #[test] - fn test_name() { - let function_identifier: u32 = $function_identifier; - let decoded_data = $decoded_data; - let encoded_data = $encoded_data; - - let (call, mut decoder) = AbiReader::new_call(encoded_data).unwrap(); - assert_eq!(call, u32::to_be_bytes(function_identifier)); - let data = <$type>::abi_read(&mut decoder).unwrap(); - assert_eq!(data, decoded_data); +fn test_impl(function_identifier: u32, decoded_data: T, encoded_data: &[u8]) +where + T: AbiWrite + AbiRead + std::cmp::PartialEq + std::fmt::Debug, +{ + let (call, mut decoder) = AbiReader::new_call(encoded_data).unwrap(); + assert_eq!(call, u32::to_be_bytes(function_identifier)); + let data = ::abi_read(&mut decoder).unwrap(); + assert_eq!(data, decoded_data); - let mut writer = AbiWriter::new_call(function_identifier); - decoded_data.abi_write(&mut writer); - let ed = writer.finish(); - similar_asserts::assert_eq!(encoded_data, ed.as_slice()); - } - }); - }; + let mut writer = AbiWriter::new_call(function_identifier); + decoded_data.abi_write(&mut writer); + let ed = writer.finish(); + similar_asserts::assert_eq!(encoded_data, ed.as_slice()); } macro_rules! test_impl_uint { ($type:ident) => { - test_impl!( - $type, - $type, + test_impl::<$type>( 0xdeadbeef, 255 as $type, &hex!( @@ -43,101 +32,148 @@ deadbeef 00000000000000000000000000000000000000000000000000000000000000ff " - ) + ), ); }; } -test_impl_uint!(uint8); -test_impl_uint!(uint32); -test_impl_uint!(uint128); +#[test] +fn encode_decode_uint8() { + test_impl_uint!(uint8); +} -test_impl!( - uint256, - uint256, - 0xdeadbeef, - U256([255, 0, 0, 0]), - &hex!( - " - deadbeef - 00000000000000000000000000000000000000000000000000000000000000ff - " - ) -); +#[test] +fn encode_decode_uint32() { + test_impl_uint!(uint32); +} -test_impl!( - vec_tuple_address_uint256, - Vec<(address, uint256)>, - 0x1ACF2D55, - vec![ - ( - H160(hex!("2D2FF76104B7BACB2E8F6731D5BFC184EBECDDBC")), - U256([10, 0, 0, 0]), - ), - ( - H160(hex!("AB8E3D9134955566483B11E6825C9223B6737B10")), - U256([20, 0, 0, 0]), - ), - ( - H160(hex!("8C582BDF2953046705FC56F189385255EFC1BE18")), - U256([30, 0, 0, 0]), - ), - ], - &hex!( - " - 1ACF2D55 - 0000000000000000000000000000000000000000000000000000000000000020 // offset of (address, uint256)[] - 0000000000000000000000000000000000000000000000000000000000000003 // length of (address, uint256)[] +#[test] +fn encode_decode_uint128() { + test_impl_uint!(uint128); +} - 0000000000000000000000002D2FF76104B7BACB2E8F6731D5BFC184EBECDDBC // address - 000000000000000000000000000000000000000000000000000000000000000A // uint256 +#[test] +fn encode_decode_uint256() { + test_impl::( + 0xdeadbeef, + U256([255, 0, 0, 0]), + &hex!( + " + deadbeef + 00000000000000000000000000000000000000000000000000000000000000ff + " + ), + ); +} - 000000000000000000000000AB8E3D9134955566483B11E6825C9223B6737B10 // address - 0000000000000000000000000000000000000000000000000000000000000014 // uint256 +#[test] +fn encode_decode_string() { + test_impl::( + 0xdeadbeef, + "some string".to_string(), + &hex!( + " + deadbeef + 0000000000000000000000000000000000000000000000000000000000000020 + 000000000000000000000000000000000000000000000000000000000000000b + 736f6d6520737472696e67000000000000000000000000000000000000000000 + " + ), + ); +} - 0000000000000000000000008C582BDF2953046705FC56F189385255EFC1BE18 // address - 000000000000000000000000000000000000000000000000000000000000001E // uint256 - " - ) -); +#[test] +fn encode_decode_tuple_string() { + test_impl::<(String,)>( + 0xdeadbeef, + ("some string".to_string(),), + &hex!( + " + deadbeef + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000020 + 000000000000000000000000000000000000000000000000000000000000000b + 736f6d6520737472696e67000000000000000000000000000000000000000000 + " + ), + ); +} -test_impl!( - vec_tuple_uint256_string, - Vec<(uint256, string)>, - 0xdeadbeef, - vec![ - (1.into(), "Test URI 0".to_string()), - (11.into(), "Test URI 1".to_string()), - (12.into(), "Test URI 2".to_string()), - ], - &hex!( - " - deadbeef - 0000000000000000000000000000000000000000000000000000000000000020 // offset of (uint256, string)[] - 0000000000000000000000000000000000000000000000000000000000000003 // length of (uint256, string)[] +#[test] +fn encode_decode_vec_tuple_address_uint256() { + test_impl::>( + 0x1ACF2D55, + vec![ + ( + H160(hex!("2D2FF76104B7BACB2E8F6731D5BFC184EBECDDBC")), + U256([10, 0, 0, 0]), + ), + ( + H160(hex!("AB8E3D9134955566483B11E6825C9223B6737B10")), + U256([20, 0, 0, 0]), + ), + ( + H160(hex!("8C582BDF2953046705FC56F189385255EFC1BE18")), + U256([30, 0, 0, 0]), + ), + ], + &hex!( + " + 1ACF2D55 + 0000000000000000000000000000000000000000000000000000000000000020 // offset of (address, uint256)[] + 0000000000000000000000000000000000000000000000000000000000000003 // length of (address, uint256)[] - 0000000000000000000000000000000000000000000000000000000000000060 // offset of first elem - 00000000000000000000000000000000000000000000000000000000000000e0 // offset of second elem - 0000000000000000000000000000000000000000000000000000000000000160 // offset of third elem + 0000000000000000000000002D2FF76104B7BACB2E8F6731D5BFC184EBECDDBC // address + 000000000000000000000000000000000000000000000000000000000000000A // uint256 - 0000000000000000000000000000000000000000000000000000000000000001 // first token id? #60 - 0000000000000000000000000000000000000000000000000000000000000040 // offset of string - 000000000000000000000000000000000000000000000000000000000000000a // size of string - 5465737420555249203000000000000000000000000000000000000000000000 // string + 000000000000000000000000AB8E3D9134955566483B11E6825C9223B6737B10 // address + 0000000000000000000000000000000000000000000000000000000000000014 // uint256 - 000000000000000000000000000000000000000000000000000000000000000b // second token id? Why ==11? #e0 - 0000000000000000000000000000000000000000000000000000000000000040 // offset of string - 000000000000000000000000000000000000000000000000000000000000000a // size of string - 5465737420555249203100000000000000000000000000000000000000000000 // string + 0000000000000000000000008C582BDF2953046705FC56F189385255EFC1BE18 // address + 000000000000000000000000000000000000000000000000000000000000001E // uint256 + " + ) + ); +} - 000000000000000000000000000000000000000000000000000000000000000c // third token id? Why ==12? #160 - 0000000000000000000000000000000000000000000000000000000000000040 // offset of string - 000000000000000000000000000000000000000000000000000000000000000a // size of string - 5465737420555249203200000000000000000000000000000000000000000000 // string - " - ) -); +#[test] +fn encode_decode_vec_tuple_uint256_string() { + test_impl::>( + 0xdeadbeef, + vec![ + (1.into(), "Test URI 0".to_string()), + (11.into(), "Test URI 1".to_string()), + (12.into(), "Test URI 2".to_string()), + ], + &hex!( + " + deadbeef + 0000000000000000000000000000000000000000000000000000000000000020 // offset of (uint256, string)[] + 0000000000000000000000000000000000000000000000000000000000000003 // length of (uint256, string)[] + 0000000000000000000000000000000000000000000000000000000000000060 // offset of first elem + 00000000000000000000000000000000000000000000000000000000000000e0 // offset of second elem + 0000000000000000000000000000000000000000000000000000000000000160 // offset of third elem + + 0000000000000000000000000000000000000000000000000000000000000001 // first token id? #60 + 0000000000000000000000000000000000000000000000000000000000000040 // offset of string + 000000000000000000000000000000000000000000000000000000000000000a // size of string + 5465737420555249203000000000000000000000000000000000000000000000 // string + + 000000000000000000000000000000000000000000000000000000000000000b // second token id? Why ==11? #e0 + 0000000000000000000000000000000000000000000000000000000000000040 // offset of string + 000000000000000000000000000000000000000000000000000000000000000a // size of string + 5465737420555249203100000000000000000000000000000000000000000000 // string + + 000000000000000000000000000000000000000000000000000000000000000c // third token id? Why ==12? #160 + 0000000000000000000000000000000000000000000000000000000000000040 // offset of string + 000000000000000000000000000000000000000000000000000000000000000a // size of string + 5465737420555249203200000000000000000000000000000000000000000000 // string + " + ) + ); +} + #[test] fn dynamic_after_static() { let mut encoder = AbiWriter::new(); @@ -235,63 +271,270 @@ similar_asserts::assert_eq!(encoded_data, ed.as_slice()); } -test_impl!( - vec_tuple_string_bytes, - Vec<(string, bytes)>, - 0xdeadbeef, - vec![ - ( - "Test URI 0".to_string(), - bytes(vec![ - 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, - 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, - 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, - 0x11, 0x11, 0x11, 0x11, 0x11, 0x11 - ]) +#[test] +fn encode_decode_vec_tuple_string_bytes() { + test_impl::>( + 0xdeadbeef, + vec![ + ( + "Test URI 0".to_string(), + bytes(vec![ + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + ]), + ), + ( + "Test URI 1".to_string(), + bytes(vec![ + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + ]), + ), + ("Test URI 2".to_string(), bytes(vec![0x33, 0x33])), + ], + &hex!( + " + deadbeef + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000003 + + 0000000000000000000000000000000000000000000000000000000000000060 + 0000000000000000000000000000000000000000000000000000000000000140 + 0000000000000000000000000000000000000000000000000000000000000220 + + 0000000000000000000000000000000000000000000000000000000000000040 + 0000000000000000000000000000000000000000000000000000000000000080 + 000000000000000000000000000000000000000000000000000000000000000a + 5465737420555249203000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000030 + 1111111111111111111111111111111111111111111111111111111111111111 + 1111111111111111111111111111111100000000000000000000000000000000 + + 0000000000000000000000000000000000000000000000000000000000000040 + 0000000000000000000000000000000000000000000000000000000000000080 + 000000000000000000000000000000000000000000000000000000000000000a + 5465737420555249203100000000000000000000000000000000000000000000 + 000000000000000000000000000000000000000000000000000000000000002f + 2222222222222222222222222222222222222222222222222222222222222222 + 2222222222222222222222222222220000000000000000000000000000000000 + + 0000000000000000000000000000000000000000000000000000000000000040 + 0000000000000000000000000000000000000000000000000000000000000080 + 000000000000000000000000000000000000000000000000000000000000000a + 5465737420555249203200000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000002 + 3333000000000000000000000000000000000000000000000000000000000000 + " + ), + ); +} + +#[test] +// #[ignore = "reason"] +fn encode_decode_tuple0_tuple1_uint8_tuple1_string_bytes_tuple1_uint8_bytes() { + let int = 0xff; + let by = bytes(vec![0x11, 0x22, 0x33]); + let string = "some string".to_string(); + + test_impl::<((u8,), (String, bytes), (u8, bytes))>( + 0xdeadbeef, + ((int,), (string.clone(), by.clone()), (int, by)), + &hex!( + " + deadbeef + 0000000000000000000000000000000000000000000000000000000000000020 + 00000000000000000000000000000000000000000000000000000000000000ff + 0000000000000000000000000000000000000000000000000000000000000060 + 0000000000000000000000000000000000000000000000000000000000000120 + 0000000000000000000000000000000000000000000000000000000000000040 + 0000000000000000000000000000000000000000000000000000000000000080 + 000000000000000000000000000000000000000000000000000000000000000b + 736f6d6520737472696e67000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000003 + 1122330000000000000000000000000000000000000000000000000000000000 + 00000000000000000000000000000000000000000000000000000000000000ff + 0000000000000000000000000000000000000000000000000000000000000040 + 0000000000000000000000000000000000000000000000000000000000000003 + 1122330000000000000000000000000000000000000000000000000000000000 + " + ), + ); +} + +#[test] +fn encode_decode_tuple0_tuple1_uint8_tuple1_uint8_uint8_tuple1_uint8_uint8() { + test_impl::<((u8,), (u8, u8), (u8, u8))>( + 0xdeadbeef, + ((43,), (44, 45), (46, 47)), + &hex!( + " + deadbeef + 000000000000000000000000000000000000000000000000000000000000002b + 000000000000000000000000000000000000000000000000000000000000002c + 000000000000000000000000000000000000000000000000000000000000002d + 000000000000000000000000000000000000000000000000000000000000002e + 000000000000000000000000000000000000000000000000000000000000002f + " + ), + ); +} + +#[test] +fn encode_decode_tuple0_tuple1_uint8_tuple1_uint8() { + test_impl::<((u8,), (u8,))>( + 0xdeadbeef, + ((43,), (44,)), + &hex!( + " + deadbeef + 000000000000000000000000000000000000000000000000000000000000002b + 000000000000000000000000000000000000000000000000000000000000002c + " + ), + ); +} + +#[test] +fn encode_decode_tuple0_tuple1_uint8_uint8() { + test_impl::<((u8, u8),)>( + 0xdeadbeef, + ((43, 44),), + &hex!( + " + deadbeef + 000000000000000000000000000000000000000000000000000000000000002b + 000000000000000000000000000000000000000000000000000000000000002c + " + ), + ); +} + +#[test] +fn encode_decode_tuple_uint8_uint8() { + test_impl::<(u8, u8)>( + 0xdeadbeef, + (43, 44), + &hex!( + " + deadbeef + 000000000000000000000000000000000000000000000000000000000000002b + 000000000000000000000000000000000000000000000000000000000000002c + " + ), + ); +} + +#[test] +fn encode_decode_tuple0_tuple1_uint8_uint8_tuple1_uint8_uint8_and_uint8() { + test_impl::<((u8, u8), (u8, u8), u8)>( + 0xdeadbeef, + ((10, 11), (12, 13), 14), + &hex!( + " + deadbeef + 000000000000000000000000000000000000000000000000000000000000000a + 000000000000000000000000000000000000000000000000000000000000000b + 000000000000000000000000000000000000000000000000000000000000000c + 000000000000000000000000000000000000000000000000000000000000000d + 000000000000000000000000000000000000000000000000000000000000000e + " ), - ( - "Test URI 1".to_string(), - bytes(vec![ - 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, - 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, - 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, - 0x22, 0x22, 0x22, 0x22, 0x22 - ]) + ); +} + +#[test] +fn encode_decode_tuple0_tuple1_string() { + test_impl::<((String,),)>( + 0xdeadbeef, + (("some string".to_string(),),), + &hex!( + " + deadbeef + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000020 + 000000000000000000000000000000000000000000000000000000000000000b + 736f6d6520737472696e67000000000000000000000000000000000000000000 + " ), - ("Test URI 2".to_string(), bytes(vec![0x33, 0x33])), - ], - &hex!( - " - deadbeef - 0000000000000000000000000000000000000000000000000000000000000020 - 0000000000000000000000000000000000000000000000000000000000000003 - - 0000000000000000000000000000000000000000000000000000000000000060 - 0000000000000000000000000000000000000000000000000000000000000140 - 0000000000000000000000000000000000000000000000000000000000000220 + ); +} - 0000000000000000000000000000000000000000000000000000000000000040 - 0000000000000000000000000000000000000000000000000000000000000080 - 000000000000000000000000000000000000000000000000000000000000000a - 5465737420555249203000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000030 - 1111111111111111111111111111111111111111111111111111111111111111 - 1111111111111111111111111111111100000000000000000000000000000000 +#[test] +fn encode_decode_tuple0_tuple1_uint8_string() { + test_impl::<((u8, String),)>( + 0xdeadbeef, + ((0xff, "some string".to_string()),), + &hex!( + " + deadbeef + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000020 + 00000000000000000000000000000000000000000000000000000000000000ff + 0000000000000000000000000000000000000000000000000000000000000040 + 000000000000000000000000000000000000000000000000000000000000000b + 736f6d6520737472696e67000000000000000000000000000000000000000000 + " + ), + ); +} - 0000000000000000000000000000000000000000000000000000000000000040 - 0000000000000000000000000000000000000000000000000000000000000080 - 000000000000000000000000000000000000000000000000000000000000000a - 5465737420555249203100000000000000000000000000000000000000000000 - 000000000000000000000000000000000000000000000000000000000000002f - 2222222222222222222222222222222222222222222222222222222222222222 - 2222222222222222222222222222220000000000000000000000000000000000 +#[test] +fn encode_decode_tuple0_tuple1_string_bytes() { + test_impl::<((String, bytes),)>( + 0xdeadbeef, + (("some string".to_string(), bytes(vec![1, 2, 3])),), + &hex!( + " + deadbeef + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000040 + 0000000000000000000000000000000000000000000000000000000000000080 + 000000000000000000000000000000000000000000000000000000000000000b + 736f6d6520737472696e67000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000003 + 0102030000000000000000000000000000000000000000000000000000000000 + " + ), + ); +} - 0000000000000000000000000000000000000000000000000000000000000040 - 0000000000000000000000000000000000000000000000000000000000000080 +#[test] +fn encode_decode_tuple0_tuple1_uint8_tuple1_string() { + test_impl::<((u8,), (String,))>( + 0xdeadbeef, + ((0xff,), ("some string".to_string(),)), + &hex!( + " + deadbeef + 0000000000000000000000000000000000000000000000000000000000000020 + 00000000000000000000000000000000000000000000000000000000000000ff + 0000000000000000000000000000000000000000000000000000000000000040 + 0000000000000000000000000000000000000000000000000000000000000020 + 000000000000000000000000000000000000000000000000000000000000000b + 736f6d6520737472696e67000000000000000000000000000000000000000000 + " + ), + ); +} + +#[test] +fn parse_multiple_params() { + let encoded_data = hex!( + " + deadbeef 000000000000000000000000000000000000000000000000000000000000000a - 5465737420555249203200000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000002 - 3333000000000000000000000000000000000000000000000000000000000000 + 000000000000000000000000000000000000000000000000000000000000000b " - ) -); + ); + let (_, mut decoder) = AbiReader::new_call(&encoded_data).unwrap(); + let p1 = ::abi_read(&mut decoder).unwrap(); + let p2 = ::abi_read(&mut decoder).unwrap(); + assert_eq!(p1, 0x0a); + assert_eq!(p2, 0x0b); +} --- a/crates/evm-coder/src/abi/traits.rs +++ b/crates/evm-coder/src/abi/traits.rs @@ -22,12 +22,6 @@ fn size() -> usize; } -/// Sealed traits. -pub mod sealed { - /// Not all types can be placed in vec, i.e `Vec` is restricted, `bytes` should be used instead - pub trait CanBePlacedInVec {} -} - /// [`AbiReader`] implements reading of many types. pub trait AbiRead { /// Read item from current position, advanding decoder @@ -47,11 +41,5 @@ let mut writer = AbiWriter::new(); self.abi_write(&mut writer); Ok(writer.into()) - } -} - -impl AbiWrite for &T { - fn abi_write(&self, writer: &mut AbiWriter) { - T::abi_write(self, writer); } } --- a/crates/evm-coder/src/lib.rs +++ b/crates/evm-coder/src/lib.rs @@ -93,6 +93,7 @@ pub use evm_coder_procedural::solidity; /// See [`solidity_interface`] pub use evm_coder_procedural::weight; +pub use evm_coder_procedural::AbiCoder; pub use sha3_const; /// Derives [`ToLog`] for enum @@ -111,6 +112,15 @@ #[cfg(feature = "stubgen")] pub mod solidity; +/// Sealed traits. +pub mod sealed { + /// Not every type should be directly placed in vec. + /// Vec encoding is not memory efficient, as every item will be padded + /// to 32 bytes. + /// Instead you should use specialized types (`bytes` in case of `Vec`) + pub trait CanBePlacedInVec {} +} + /// Solidity type definitions (aliases from solidity name to rust type) /// To be used in [`solidity_interface`] definitions, to make sure there is no /// type conflict between Rust code and generated definitions @@ -119,7 +129,6 @@ #[cfg(not(feature = "std"))] use alloc::{vec::Vec}; - use pallet_evm::account::CrossAccountId; use primitive_types::{U256, H160, H256}; pub type address = H160; @@ -137,7 +146,7 @@ #[cfg(feature = "std")] pub type string = ::std::string::String; - #[derive(Default, Debug, PartialEq)] + #[derive(Default, Debug, PartialEq, Eq, Clone)] pub struct bytes(pub Vec); /// Solidity doesn't have `void` type, however we have special implementation @@ -185,73 +194,7 @@ #[must_use] pub fn is_empty(&self) -> bool { self.len() == 0 - } - } - - #[derive(Debug, Default)] - pub struct EthCrossAccount { - pub(crate) eth: address, - pub(crate) sub: uint256, - } - - impl EthCrossAccount { - pub fn from_sub_cross_account(cross_account_id: &T::CrossAccountId) -> Self - where - T: pallet_evm::Config, - T::AccountId: AsRef<[u8; 32]>, - { - if cross_account_id.is_canonical_substrate() { - Self { - eth: Default::default(), - sub: convert_cross_account_to_uint256::(cross_account_id), - } - } else { - Self { - eth: *cross_account_id.as_eth(), - sub: Default::default(), - } - } } - - pub fn into_sub_cross_account(&self) -> crate::execution::Result - where - T: pallet_evm::Config, - T::AccountId: From<[u8; 32]>, - { - if self.eth == Default::default() && self.sub == Default::default() { - Err("All fields of cross account is zeroed".into()) - } else if self.eth == Default::default() { - Ok(convert_uint256_to_cross_account::(self.sub)) - } else if self.sub == Default::default() { - Ok(T::CrossAccountId::from_eth(self.eth)) - } else { - Err("All fields of cross account is non zeroed".into()) - } - } - } - - /// Convert `CrossAccountId` to `uint256`. - pub fn convert_cross_account_to_uint256( - from: &T::CrossAccountId, - ) -> uint256 - where - T::AccountId: AsRef<[u8; 32]>, - { - let slice = from.as_sub().as_ref(); - uint256::from_big_endian(slice) - } - - /// Convert `uint256` to `CrossAccountId`. - pub fn convert_uint256_to_cross_account( - from: uint256, - ) -> T::CrossAccountId - where - T::AccountId: From<[u8; 32]>, - { - let mut new_admin_arr = [0_u8; 32]; - from.to_big_endian(&mut new_admin_arr); - let account_id = T::AccountId::from(new_admin_arr); - T::CrossAccountId::from_sub(account_id) } #[derive(Debug, Default)] --- a/crates/evm-coder/src/solidity.rs +++ /dev/null @@ -1,702 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// This file is part of Unique Network. - -// Unique Network is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Unique Network is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Unique Network. If not, see . - -//! Implementation detail of [`crate::solidity_interface`] macro code-generation. -//! You should not rely on any public item from this module, as it is only intended to be used -//! by procedural macro, API and output format may be changed at any time. -//! -//! Purpose of this module is to receive solidity contract definition in module-specified -//! format, and then output string, representing interface of this contract in solidity language - -#[cfg(not(feature = "std"))] -use alloc::{string::String, vec::Vec, collections::BTreeMap, format}; -#[cfg(feature = "std")] -use std::collections::BTreeMap; -use core::{ - fmt::{self, Write}, - marker::PhantomData, - cell::{Cell, RefCell}, - cmp::Reverse, -}; -use impl_trait_for_tuples::impl_for_tuples; -use crate::{types::*, custom_signature::SignatureUnit}; - -#[derive(Default)] -pub struct TypeCollector { - /// Code => id - /// id ordering is required to perform topo-sort on the resulting data - structs: RefCell>, - anonymous: RefCell, usize>>, - id: Cell, -} -impl TypeCollector { - pub fn new() -> Self { - Self::default() - } - pub fn collect(&self, item: string) { - let id = self.next_id(); - self.structs.borrow_mut().insert(item, id); - } - pub fn next_id(&self) -> usize { - let v = self.id.get(); - self.id.set(v + 1); - v - } - pub fn collect_tuple(&self) -> String { - let names = T::names(self); - if let Some(id) = self.anonymous.borrow().get(&names).cloned() { - return format!("Tuple{}", id); - } - let id = self.next_id(); - let mut str = String::new(); - writeln!(str, "/// @dev anonymous struct").unwrap(); - writeln!(str, "struct Tuple{} {{", id).unwrap(); - for (i, name) in names.iter().enumerate() { - writeln!(str, "\t{} field_{};", name, i).unwrap(); - } - writeln!(str, "}}").unwrap(); - self.collect(str); - self.anonymous.borrow_mut().insert(names, id); - format!("Tuple{}", id) - } - pub fn collect_struct(&self) -> String { - self.collect(::declaration()); - ::name() - } - pub fn finish(self) -> Vec { - let mut data = self.structs.into_inner().into_iter().collect::>(); - data.sort_by_key(|(_, id)| Reverse(*id)); - data.into_iter().map(|(code, _)| code).collect() - } -} - -pub trait StructCollect: 'static { - /// Structure name. - fn name() -> String; - /// Structure declaration. - fn declaration() -> String; -} - -pub trait SolidityTypeName: 'static { - fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result; - /// "simple" types are stored inline, no `memory` modifier should be used in solidity - fn is_simple() -> bool; - fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result; - /// Specialization - fn is_void() -> bool { - false - } -} -macro_rules! solidity_type_name { - ($($ty:ty => $name:literal $simple:literal = $default:literal),* $(,)?) => { - $( - impl SolidityTypeName for $ty { - fn solidity_name(writer: &mut impl core::fmt::Write, _tc: &TypeCollector) -> core::fmt::Result { - write!(writer, $name) - } - fn is_simple() -> bool { - $simple - } - fn solidity_default(writer: &mut impl core::fmt::Write, _tc: &TypeCollector) -> core::fmt::Result { - write!(writer, $default) - } - } - )* - }; -} - -solidity_type_name! { - uint8 => "uint8" true = "0", - uint32 => "uint32" true = "0", - uint64 => "uint64" true = "0", - uint128 => "uint128" true = "0", - uint256 => "uint256" true = "0", - bytes4 => "bytes4" true = "bytes4(0)", - address => "address" true = "0x0000000000000000000000000000000000000000", - string => "string" false = "\"\"", - bytes => "bytes" false = "hex\"\"", - bool => "bool" true = "false", -} -impl SolidityTypeName for void { - fn solidity_name(_writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result { - Ok(()) - } - fn is_simple() -> bool { - true - } - fn solidity_default(_writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result { - Ok(()) - } - fn is_void() -> bool { - true - } -} - -mod sealed { - /// Not every type should be directly placed in vec. - /// Vec encoding is not memory efficient, as every item will be padded - /// to 32 bytes. - /// Instead you should use specialized types (`bytes` in case of `Vec`) - pub trait CanBePlacedInVec {} -} - -impl sealed::CanBePlacedInVec for uint256 {} -impl sealed::CanBePlacedInVec for string {} -impl sealed::CanBePlacedInVec for address {} -impl sealed::CanBePlacedInVec for EthCrossAccount {} -impl sealed::CanBePlacedInVec for Property {} - -impl SolidityTypeName for Vec { - fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { - T::solidity_name(writer, tc)?; - write!(writer, "[]") - } - fn is_simple() -> bool { - false - } - fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { - write!(writer, "new ")?; - T::solidity_name(writer, tc)?; - write!(writer, "[](0)") - } -} - -impl SolidityTupleType for EthCrossAccount { - fn names(tc: &TypeCollector) -> Vec { - let mut collected = Vec::with_capacity(Self::len()); - { - let mut out = string::new(); - address::solidity_name(&mut out, tc).expect("no fmt error"); - collected.push(out); - } - { - let mut out = string::new(); - uint256::solidity_name(&mut out, tc).expect("no fmt error"); - collected.push(out); - } - collected - } - - fn len() -> usize { - 2 - } -} - -impl SolidityTypeName for EthCrossAccount { - fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { - write!(writer, "{}", tc.collect_struct::()) - } - - fn is_simple() -> bool { - false - } - - fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { - write!(writer, "{}(", tc.collect_struct::())?; - address::solidity_default(writer, tc)?; - write!(writer, ",")?; - uint256::solidity_default(writer, tc)?; - write!(writer, ")") - } -} - -impl StructCollect for EthCrossAccount { - fn name() -> String { - "EthCrossAccount".into() - } - - fn declaration() -> String { - let mut str = String::new(); - writeln!(str, "/// @dev Cross account struct").unwrap(); - writeln!(str, "struct {} {{", Self::name()).unwrap(); - writeln!(str, "\taddress eth;").unwrap(); - writeln!(str, "\tuint256 sub;").unwrap(); - writeln!(str, "}}").unwrap(); - str - } -} - -impl StructCollect for Property { - fn name() -> String { - "Property".into() - } - - fn declaration() -> String { - let mut str = String::new(); - writeln!(str, "/// @dev Property struct").unwrap(); - writeln!(str, "struct {} {{", Self::name()).unwrap(); - writeln!(str, "\tstring key;").unwrap(); - writeln!(str, "\tbytes value;").unwrap(); - writeln!(str, "}}").unwrap(); - str - } -} - -impl SolidityTypeName for Property { - fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { - write!(writer, "{}", tc.collect_struct::()) - } - - fn is_simple() -> bool { - false - } - - fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { - write!(writer, "{}(", tc.collect_struct::())?; - address::solidity_default(writer, tc)?; - write!(writer, ",")?; - uint256::solidity_default(writer, tc)?; - write!(writer, ")") - } -} - -impl SolidityTupleType for Property { - fn names(tc: &TypeCollector) -> Vec { - let mut collected = Vec::with_capacity(Self::len()); - { - let mut out = string::new(); - string::solidity_name(&mut out, tc).expect("no fmt error"); - collected.push(out); - } - { - let mut out = string::new(); - bytes::solidity_name(&mut out, tc).expect("no fmt error"); - collected.push(out); - } - collected - } - - fn len() -> usize { - 2 - } -} - -pub trait SolidityTupleType { - fn names(tc: &TypeCollector) -> Vec; - fn len() -> usize; -} - -macro_rules! count { - () => (0usize); - ( $x:tt $($xs:tt)* ) => (1usize + count!($($xs)*)); -} - -macro_rules! impl_tuples { - ($($ident:ident)+) => { - impl<$($ident),+> sealed::CanBePlacedInVec for ($($ident,)+) {} - impl<$($ident: SolidityTypeName + 'static),+> SolidityTupleType for ($($ident,)+) { - fn names(tc: &TypeCollector) -> Vec { - let mut collected = Vec::with_capacity(Self::len()); - $({ - let mut out = string::new(); - $ident::solidity_name(&mut out, tc).expect("no fmt error"); - collected.push(out); - })*; - collected - } - - fn len() -> usize { - count!($($ident)*) - } - } - impl<$($ident: SolidityTypeName + 'static),+> SolidityTypeName for ($($ident,)+) { - fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { - write!(writer, "{}", tc.collect_tuple::()) - } - fn is_simple() -> bool { - false - } - #[allow(unused_assignments)] - fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { - write!(writer, "{}(", tc.collect_tuple::())?; - let mut first = true; - $( - if !first { - write!(writer, ",")?; - } else { - first = false; - } - <$ident>::solidity_default(writer, tc)?; - )* - write!(writer, ")") - } - } - }; -} - -impl_tuples! {A} -impl_tuples! {A B} -impl_tuples! {A B C} -impl_tuples! {A B C D} -impl_tuples! {A B C D E} -impl_tuples! {A B C D E F} -impl_tuples! {A B C D E F G} -impl_tuples! {A B C D E F G H} -impl_tuples! {A B C D E F G H I} -impl_tuples! {A B C D E F G H I J} - -pub trait SolidityArguments { - fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result; - fn solidity_get(&self, prefix: &str, writer: &mut impl fmt::Write) -> fmt::Result; - fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result; - fn is_empty(&self) -> bool { - self.len() == 0 - } - fn len(&self) -> usize; -} - -#[derive(Default)] -pub struct UnnamedArgument(PhantomData<*const T>); - -impl SolidityArguments for UnnamedArgument { - fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { - if !T::is_void() { - T::solidity_name(writer, tc)?; - if !T::is_simple() { - write!(writer, " memory")?; - } - Ok(()) - } else { - Ok(()) - } - } - fn solidity_get(&self, _prefix: &str, _writer: &mut impl fmt::Write) -> fmt::Result { - Ok(()) - } - fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { - T::solidity_default(writer, tc) - } - fn len(&self) -> usize { - if T::is_void() { - 0 - } else { - 1 - } - } -} - -pub struct NamedArgument(&'static str, PhantomData<*const T>); - -impl NamedArgument { - pub fn new(name: &'static str) -> Self { - Self(name, Default::default()) - } -} - -impl SolidityArguments for NamedArgument { - fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { - if !T::is_void() { - T::solidity_name(writer, tc)?; - if !T::is_simple() { - write!(writer, " memory")?; - } - write!(writer, " {}", self.0) - } else { - Ok(()) - } - } - fn solidity_get(&self, prefix: &str, writer: &mut impl fmt::Write) -> fmt::Result { - writeln!(writer, "\t{prefix}\t{};", self.0) - } - fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { - T::solidity_default(writer, tc) - } - fn len(&self) -> usize { - if T::is_void() { - 0 - } else { - 1 - } - } -} - -pub struct SolidityEventArgument(pub bool, &'static str, PhantomData<*const T>); - -impl SolidityEventArgument { - pub fn new(indexed: bool, name: &'static str) -> Self { - Self(indexed, name, Default::default()) - } -} - -impl SolidityArguments for SolidityEventArgument { - fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { - if !T::is_void() { - T::solidity_name(writer, tc)?; - if self.0 { - write!(writer, " indexed")?; - } - write!(writer, " {}", self.1) - } else { - Ok(()) - } - } - fn solidity_get(&self, prefix: &str, writer: &mut impl fmt::Write) -> fmt::Result { - writeln!(writer, "\t{prefix}\t{};", self.1) - } - fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { - T::solidity_default(writer, tc) - } - fn len(&self) -> usize { - if T::is_void() { - 0 - } else { - 1 - } - } -} - -impl SolidityArguments for () { - fn solidity_name(&self, _writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result { - Ok(()) - } - fn solidity_get(&self, _prefix: &str, _writer: &mut impl fmt::Write) -> fmt::Result { - Ok(()) - } - fn solidity_default(&self, _writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result { - Ok(()) - } - fn len(&self) -> usize { - 0 - } -} - -#[impl_for_tuples(1, 12)] -impl SolidityArguments for Tuple { - for_tuples!( where #( Tuple: SolidityArguments ),* ); - - fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { - let mut first = true; - for_tuples!( #( - if !Tuple.is_empty() { - if !first { - write!(writer, ", ")?; - } - first = false; - Tuple.solidity_name(writer, tc)?; - } - )* ); - Ok(()) - } - fn solidity_get(&self, prefix: &str, writer: &mut impl fmt::Write) -> fmt::Result { - for_tuples!( #( - Tuple.solidity_get(prefix, writer)?; - )* ); - Ok(()) - } - fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { - if self.is_empty() { - Ok(()) - } else if self.len() == 1 { - for_tuples!( #( - Tuple.solidity_default(writer, tc)?; - )* ); - Ok(()) - } else { - write!(writer, "(")?; - let mut first = true; - for_tuples!( #( - if !Tuple.is_empty() { - if !first { - write!(writer, ", ")?; - } - first = false; - Tuple.solidity_default(writer, tc)?; - } - )* ); - write!(writer, ")")?; - Ok(()) - } - } - fn len(&self) -> usize { - for_tuples!( #( Tuple.len() )+* ) - } -} - -pub trait SolidityFunctions { - fn solidity_name( - &self, - is_impl: bool, - writer: &mut impl fmt::Write, - tc: &TypeCollector, - ) -> fmt::Result; -} - -pub enum SolidityMutability { - Pure, - View, - Mutable, -} -pub struct SolidityFunction { - pub docs: &'static [&'static str], - pub selector: u32, - pub hide: bool, - pub custom_signature: SignatureUnit, - pub name: &'static str, - pub args: A, - pub result: R, - pub mutability: SolidityMutability, - pub is_payable: bool, -} -impl SolidityFunctions for SolidityFunction { - fn solidity_name( - &self, - is_impl: bool, - writer: &mut impl fmt::Write, - tc: &TypeCollector, - ) -> fmt::Result { - let hide_comment = self.hide.then(|| "// ").unwrap_or(""); - for doc in self.docs { - writeln!(writer, "\t{hide_comment}///{}", doc)?; - } - writeln!( - writer, - "\t{hide_comment}/// @dev EVM selector for this function is: 0x{:0>8x},", - self.selector - )?; - writeln!( - writer, - "\t{hide_comment}/// or in textual repr: {}", - self.custom_signature.as_str().expect("bad utf-8") - )?; - write!(writer, "\t{hide_comment}function {}(", self.name)?; - self.args.solidity_name(writer, tc)?; - write!(writer, ")")?; - if is_impl { - write!(writer, " public")?; - } else { - write!(writer, " external")?; - } - match &self.mutability { - SolidityMutability::Pure => write!(writer, " pure")?, - SolidityMutability::View => write!(writer, " view")?, - SolidityMutability::Mutable => {} - } - if self.is_payable { - write!(writer, " payable")?; - } - if !self.result.is_empty() { - write!(writer, " returns (")?; - self.result.solidity_name(writer, tc)?; - write!(writer, ")")?; - } - if is_impl { - writeln!(writer, " {{")?; - writeln!(writer, "\t{hide_comment}\trequire(false, stub_error);")?; - self.args.solidity_get(hide_comment, writer)?; - match &self.mutability { - SolidityMutability::Pure => {} - SolidityMutability::View => writeln!(writer, "\t{hide_comment}\tdummy;")?, - SolidityMutability::Mutable => writeln!(writer, "\t{hide_comment}\tdummy = 0;")?, - } - if !self.result.is_empty() { - write!(writer, "\t{hide_comment}\treturn ")?; - self.result.solidity_default(writer, tc)?; - writeln!(writer, ";")?; - } - writeln!(writer, "\t{hide_comment}}}")?; - } else { - writeln!(writer, ";")?; - } - if self.hide { - writeln!(writer, "// FORMATTING: FORCE NEWLINE")?; - } - Ok(()) - } -} - -#[impl_for_tuples(0, 48)] -impl SolidityFunctions for Tuple { - for_tuples!( where #( Tuple: SolidityFunctions ),* ); - - fn solidity_name( - &self, - is_impl: bool, - writer: &mut impl fmt::Write, - tc: &TypeCollector, - ) -> fmt::Result { - let mut first = false; - for_tuples!( #( - Tuple.solidity_name(is_impl, writer, tc)?; - )* ); - Ok(()) - } -} - -pub struct SolidityInterface { - pub docs: &'static [&'static str], - pub selector: bytes4, - pub name: &'static str, - pub is: &'static [&'static str], - pub functions: F, -} - -impl SolidityInterface { - pub fn format( - &self, - is_impl: bool, - out: &mut impl fmt::Write, - tc: &TypeCollector, - ) -> fmt::Result { - const ZERO_BYTES: [u8; 4] = [0; 4]; - for doc in self.docs { - writeln!(out, "///{}", doc)?; - } - if self.selector != ZERO_BYTES { - writeln!( - out, - "/// @dev the ERC-165 identifier for this interface is 0x{:0>8x}", - u32::from_be_bytes(self.selector) - )?; - } - if is_impl { - write!(out, "contract ")?; - } else { - write!(out, "interface ")?; - } - write!(out, "{}", self.name)?; - if !self.is.is_empty() { - write!(out, " is")?; - for (i, n) in self.is.iter().enumerate() { - if i != 0 { - write!(out, ",")?; - } - write!(out, " {}", n)?; - } - } - writeln!(out, " {{")?; - self.functions.solidity_name(is_impl, out, tc)?; - writeln!(out, "}}")?; - Ok(()) - } -} - -pub struct SolidityEvent { - pub name: &'static str, - pub args: A, -} - -impl SolidityFunctions for SolidityEvent { - fn solidity_name( - &self, - _is_impl: bool, - writer: &mut impl fmt::Write, - tc: &TypeCollector, - ) -> fmt::Result { - write!(writer, "\tevent {}(", self.name)?; - self.args.solidity_name(writer, tc)?; - writeln!(writer, ");") - } -} --- /dev/null +++ b/crates/evm-coder/src/solidity/impls.rs @@ -0,0 +1,190 @@ +use super::{TypeCollector, SolidityTypeName, SolidityType, StructCollect}; +use crate::{sealed, types::*}; +use core::fmt; +use primitive_types::{U256, H160}; + +macro_rules! solidity_type_name { + ($($ty:ty => $name:literal $simple:literal = $default:literal),* $(,)?) => { + $( + impl SolidityTypeName for $ty { + fn solidity_name(writer: &mut impl core::fmt::Write, _tc: &TypeCollector) -> core::fmt::Result { + write!(writer, $name) + } + fn is_simple() -> bool { + $simple + } + fn solidity_default(writer: &mut impl core::fmt::Write, _tc: &TypeCollector) -> core::fmt::Result { + write!(writer, $default) + } + } + + impl StructCollect for $ty { + fn name() -> String { + $name.to_string() + } + + fn declaration() -> String { + String::default() + } + } + )* + }; +} + +solidity_type_name! { + u8 => "uint8" true = "0", + u32 => "uint32" true = "0", + u64 => "uint64" true = "0", + u128 => "uint128" true = "0", + U256 => "uint256" true = "0", + bytes4 => "bytes4" true = "bytes4(0)", + H160 => "address" true = "0x0000000000000000000000000000000000000000", + string => "string" false = "\"\"", + bytes => "bytes" false = "hex\"\"", + bool => "bool" true = "false", +} + +impl SolidityTypeName for void { + fn solidity_name(_writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result { + Ok(()) + } + fn is_simple() -> bool { + true + } + fn solidity_default(_writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result { + Ok(()) + } + fn is_void() -> bool { + true + } +} + +impl SolidityTypeName for Vec { + fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { + T::solidity_name(writer, tc)?; + write!(writer, "[]") + } + fn is_simple() -> bool { + false + } + fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { + write!(writer, "new ")?; + T::solidity_name(writer, tc)?; + write!(writer, "[](0)") + } +} + +macro_rules! count { + () => (0usize); + ( $x:tt $($xs:tt)* ) => (1usize + count!($($xs)*)); +} + +macro_rules! impl_tuples { + ($($ident:ident)+) => { + impl<$($ident: SolidityTypeName + 'static),+> SolidityType for ($($ident,)+) { + fn names(tc: &TypeCollector) -> Vec { + let mut collected = Vec::with_capacity(Self::len()); + $({ + let mut out = string::new(); + $ident::solidity_name(&mut out, tc).expect("no fmt error"); + collected.push(out); + })*; + collected + } + + fn len() -> usize { + count!($($ident)*) + } + } + impl<$($ident: SolidityTypeName + 'static),+> SolidityTypeName for ($($ident,)+) { + fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { + write!(writer, "{}", tc.collect_tuple::()) + } + fn is_simple() -> bool { + false + } + #[allow(unused_assignments)] + fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { + write!(writer, "{}(", tc.collect_tuple::())?; + let mut first = true; + $( + if !first { + write!(writer, ",")?; + } else { + first = false; + } + <$ident>::solidity_default(writer, tc)?; + )* + write!(writer, ")") + } + } + }; +} + +impl_tuples! {A} +impl_tuples! {A B} +impl_tuples! {A B C} +impl_tuples! {A B C D} +impl_tuples! {A B C D E} +impl_tuples! {A B C D E F} +impl_tuples! {A B C D E F G} +impl_tuples! {A B C D E F G H} +impl_tuples! {A B C D E F G H I} +impl_tuples! {A B C D E F G H I J} + +impl StructCollect for Property { + fn name() -> String { + "Property".into() + } + + fn declaration() -> String { + use std::fmt::Write; + + let mut str = String::new(); + writeln!(str, "/// @dev Property struct").unwrap(); + writeln!(str, "struct {} {{", Self::name()).unwrap(); + writeln!(str, "\tstring key;").unwrap(); + writeln!(str, "\tbytes value;").unwrap(); + writeln!(str, "}}").unwrap(); + str + } +} + +impl SolidityTypeName for Property { + fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { + write!(writer, "{}", tc.collect_struct::()) + } + + fn is_simple() -> bool { + false + } + + fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { + write!(writer, "{}(", tc.collect_struct::())?; + address::solidity_default(writer, tc)?; + write!(writer, ",")?; + uint256::solidity_default(writer, tc)?; + write!(writer, ")") + } +} + +impl SolidityType for Property { + fn names(tc: &TypeCollector) -> Vec { + let mut collected = Vec::with_capacity(Self::len()); + { + let mut out = string::new(); + string::solidity_name(&mut out, tc).expect("no fmt error"); + collected.push(out); + } + { + let mut out = string::new(); + bytes::solidity_name(&mut out, tc).expect("no fmt error"); + collected.push(out); + } + collected + } + + fn len() -> usize { + 2 + } +} --- /dev/null +++ b/crates/evm-coder/src/solidity/mod.rs @@ -0,0 +1,421 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +//! Implementation detail of [`crate::solidity_interface`] macro code-generation. +//! You should not rely on any public item from this module, as it is only intended to be used +//! by procedural macro, API and output format may be changed at any time. +//! +//! Purpose of this module is to receive solidity contract definition in module-specified +//! format, and then output string, representing interface of this contract in solidity language + +mod traits; +pub use traits::*; +mod impls; + +#[cfg(not(feature = "std"))] +use alloc::{string::String, vec::Vec, collections::BTreeMap, format}; +#[cfg(feature = "std")] +use std::collections::BTreeMap; +use core::{ + fmt::{self, Write}, + marker::PhantomData, + cell::{Cell, RefCell}, + cmp::Reverse, +}; +use impl_trait_for_tuples::impl_for_tuples; +use crate::{types::*, custom_signature::SignatureUnit}; + +#[derive(Default)] +pub struct TypeCollector { + /// Code => id + /// id ordering is required to perform topo-sort on the resulting data + structs: RefCell>, + anonymous: RefCell, usize>>, + id: Cell, +} +impl TypeCollector { + pub fn new() -> Self { + Self::default() + } + pub fn collect(&self, item: string) { + let id = self.next_id(); + self.structs.borrow_mut().insert(item, id); + } + pub fn next_id(&self) -> usize { + let v = self.id.get(); + self.id.set(v + 1); + v + } + pub fn collect_tuple(&self) -> String { + let names = T::names(self); + if let Some(id) = self.anonymous.borrow().get(&names).cloned() { + return format!("Tuple{}", id); + } + let id = self.next_id(); + let mut str = String::new(); + writeln!(str, "/// @dev anonymous struct").unwrap(); + writeln!(str, "struct Tuple{} {{", id).unwrap(); + for (i, name) in names.iter().enumerate() { + writeln!(str, "\t{} field_{};", name, i).unwrap(); + } + writeln!(str, "}}").unwrap(); + self.collect(str); + self.anonymous.borrow_mut().insert(names, id); + format!("Tuple{}", id) + } + pub fn collect_struct(&self) -> String { + self.collect(::declaration()); + ::name() + } + pub fn finish(self) -> Vec { + let mut data = self.structs.into_inner().into_iter().collect::>(); + data.sort_by_key(|(_, id)| Reverse(*id)); + data.into_iter().map(|(code, _)| code).collect() + } +} +#[derive(Default)] +pub struct UnnamedArgument(PhantomData<*const T>); + +impl SolidityArguments for UnnamedArgument { + fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { + if !T::is_void() { + T::solidity_name(writer, tc)?; + if !T::is_simple() { + write!(writer, " memory")?; + } + Ok(()) + } else { + Ok(()) + } + } + fn solidity_get(&self, _prefix: &str, _writer: &mut impl fmt::Write) -> fmt::Result { + Ok(()) + } + fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { + T::solidity_default(writer, tc) + } + fn len(&self) -> usize { + if T::is_void() { + 0 + } else { + 1 + } + } +} + +pub struct NamedArgument(&'static str, PhantomData<*const T>); + +impl NamedArgument { + pub fn new(name: &'static str) -> Self { + Self(name, Default::default()) + } +} + +impl SolidityArguments for NamedArgument { + fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { + if !T::is_void() { + T::solidity_name(writer, tc)?; + if !T::is_simple() { + write!(writer, " memory")?; + } + write!(writer, " {}", self.0) + } else { + Ok(()) + } + } + fn solidity_get(&self, prefix: &str, writer: &mut impl fmt::Write) -> fmt::Result { + writeln!(writer, "\t{prefix}\t{};", self.0) + } + fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { + T::solidity_default(writer, tc) + } + fn len(&self) -> usize { + if T::is_void() { + 0 + } else { + 1 + } + } +} + +pub struct SolidityEventArgument(pub bool, &'static str, PhantomData<*const T>); + +impl SolidityEventArgument { + pub fn new(indexed: bool, name: &'static str) -> Self { + Self(indexed, name, Default::default()) + } +} + +impl SolidityArguments for SolidityEventArgument { + fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { + if !T::is_void() { + T::solidity_name(writer, tc)?; + if self.0 { + write!(writer, " indexed")?; + } + write!(writer, " {}", self.1) + } else { + Ok(()) + } + } + fn solidity_get(&self, prefix: &str, writer: &mut impl fmt::Write) -> fmt::Result { + writeln!(writer, "\t{prefix}\t{};", self.1) + } + fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { + T::solidity_default(writer, tc) + } + fn len(&self) -> usize { + if T::is_void() { + 0 + } else { + 1 + } + } +} + +impl SolidityArguments for () { + fn solidity_name(&self, _writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result { + Ok(()) + } + fn solidity_get(&self, _prefix: &str, _writer: &mut impl fmt::Write) -> fmt::Result { + Ok(()) + } + fn solidity_default(&self, _writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result { + Ok(()) + } + fn len(&self) -> usize { + 0 + } +} + +#[impl_for_tuples(1, 12)] +impl SolidityArguments for Tuple { + for_tuples!( where #( Tuple: SolidityArguments ),* ); + + fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { + let mut first = true; + for_tuples!( #( + if !Tuple.is_empty() { + if !first { + write!(writer, ", ")?; + } + first = false; + Tuple.solidity_name(writer, tc)?; + } + )* ); + Ok(()) + } + fn solidity_get(&self, prefix: &str, writer: &mut impl fmt::Write) -> fmt::Result { + for_tuples!( #( + Tuple.solidity_get(prefix, writer)?; + )* ); + Ok(()) + } + fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result { + if self.is_empty() { + Ok(()) + } else if self.len() == 1 { + for_tuples!( #( + Tuple.solidity_default(writer, tc)?; + )* ); + Ok(()) + } else { + write!(writer, "(")?; + let mut first = true; + for_tuples!( #( + if !Tuple.is_empty() { + if !first { + write!(writer, ", ")?; + } + first = false; + Tuple.solidity_default(writer, tc)?; + } + )* ); + write!(writer, ")")?; + Ok(()) + } + } + fn len(&self) -> usize { + for_tuples!( #( Tuple.len() )+* ) + } +} + +pub enum SolidityMutability { + Pure, + View, + Mutable, +} +pub struct SolidityFunction { + pub docs: &'static [&'static str], + pub selector: u32, + pub hide: bool, + pub custom_signature: SignatureUnit, + pub name: &'static str, + pub args: A, + pub result: R, + pub mutability: SolidityMutability, + pub is_payable: bool, +} +impl SolidityFunctions for SolidityFunction { + fn solidity_name( + &self, + is_impl: bool, + writer: &mut impl fmt::Write, + tc: &TypeCollector, + ) -> fmt::Result { + let hide_comment = self.hide.then_some("// ").unwrap_or(""); + for doc in self.docs { + writeln!(writer, "\t{hide_comment}///{}", doc)?; + } + writeln!( + writer, + "\t{hide_comment}/// @dev EVM selector for this function is: 0x{:0>8x},", + self.selector + )?; + writeln!( + writer, + "\t{hide_comment}/// or in textual repr: {}", + self.custom_signature.as_str().expect("bad utf-8") + )?; + write!(writer, "\t{hide_comment}function {}(", self.name)?; + self.args.solidity_name(writer, tc)?; + write!(writer, ")")?; + if is_impl { + write!(writer, " public")?; + } else { + write!(writer, " external")?; + } + match &self.mutability { + SolidityMutability::Pure => write!(writer, " pure")?, + SolidityMutability::View => write!(writer, " view")?, + SolidityMutability::Mutable => {} + } + if self.is_payable { + write!(writer, " payable")?; + } + if !self.result.is_empty() { + write!(writer, " returns (")?; + self.result.solidity_name(writer, tc)?; + write!(writer, ")")?; + } + if is_impl { + writeln!(writer, " {{")?; + writeln!(writer, "\t{hide_comment}\trequire(false, stub_error);")?; + self.args.solidity_get(hide_comment, writer)?; + match &self.mutability { + SolidityMutability::Pure => {} + SolidityMutability::View => writeln!(writer, "\t{hide_comment}\tdummy;")?, + SolidityMutability::Mutable => writeln!(writer, "\t{hide_comment}\tdummy = 0;")?, + } + if !self.result.is_empty() { + write!(writer, "\t{hide_comment}\treturn ")?; + self.result.solidity_default(writer, tc)?; + writeln!(writer, ";")?; + } + writeln!(writer, "\t{hide_comment}}}")?; + } else { + writeln!(writer, ";")?; + } + if self.hide { + writeln!(writer, "// FORMATTING: FORCE NEWLINE")?; + } + Ok(()) + } +} + +#[impl_for_tuples(0, 48)] +impl SolidityFunctions for Tuple { + for_tuples!( where #( Tuple: SolidityFunctions ),* ); + + fn solidity_name( + &self, + is_impl: bool, + writer: &mut impl fmt::Write, + tc: &TypeCollector, + ) -> fmt::Result { + let mut first = false; + for_tuples!( #( + Tuple.solidity_name(is_impl, writer, tc)?; + )* ); + Ok(()) + } +} + +pub struct SolidityInterface { + pub docs: &'static [&'static str], + pub selector: bytes4, + pub name: &'static str, + pub is: &'static [&'static str], + pub functions: F, +} + +impl SolidityInterface { + pub fn format( + &self, + is_impl: bool, + out: &mut impl fmt::Write, + tc: &TypeCollector, + ) -> fmt::Result { + const ZERO_BYTES: [u8; 4] = [0; 4]; + for doc in self.docs { + writeln!(out, "///{}", doc)?; + } + if self.selector != ZERO_BYTES { + writeln!( + out, + "/// @dev the ERC-165 identifier for this interface is 0x{:0>8x}", + u32::from_be_bytes(self.selector) + )?; + } + if is_impl { + write!(out, "contract ")?; + } else { + write!(out, "interface ")?; + } + write!(out, "{}", self.name)?; + if !self.is.is_empty() { + write!(out, " is")?; + for (i, n) in self.is.iter().enumerate() { + if i != 0 { + write!(out, ",")?; + } + write!(out, " {}", n)?; + } + } + writeln!(out, " {{")?; + self.functions.solidity_name(is_impl, out, tc)?; + writeln!(out, "}}")?; + Ok(()) + } +} + +pub struct SolidityEvent { + pub name: &'static str, + pub args: A, +} + +impl SolidityFunctions for SolidityEvent { + fn solidity_name( + &self, + _is_impl: bool, + writer: &mut impl fmt::Write, + tc: &TypeCollector, + ) -> fmt::Result { + write!(writer, "\tevent {}(", self.name)?; + self.args.solidity_name(writer, tc)?; + writeln!(writer, ");") + } +} --- /dev/null +++ b/crates/evm-coder/src/solidity/traits.rs @@ -0,0 +1,48 @@ +use super::TypeCollector; +use core::fmt; + +pub trait StructCollect: 'static { + /// Structure name. + fn name() -> String; + /// Structure declaration. + fn declaration() -> String; +} + +pub trait SolidityEnum: 'static { + fn solidity_option(&self) -> &str; +} + +pub trait SolidityTypeName: 'static { + fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result; + /// "simple" types are stored inline, no `memory` modifier should be used in solidity + fn is_simple() -> bool; + fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result; + /// Specialization + fn is_void() -> bool { + false + } +} + +pub trait SolidityType { + fn names(tc: &TypeCollector) -> Vec; + fn len() -> usize; +} + +pub trait SolidityArguments { + fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result; + fn solidity_get(&self, prefix: &str, writer: &mut impl fmt::Write) -> fmt::Result; + fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result; + fn is_empty(&self) -> bool { + self.len() == 0 + } + fn len(&self) -> usize; +} + +pub trait SolidityFunctions { + fn solidity_name( + &self, + is_impl: bool, + writer: &mut impl fmt::Write, + tc: &TypeCollector, + ) -> fmt::Result; +} --- /dev/null +++ b/crates/evm-coder/tests/abi_derive_generation.rs @@ -0,0 +1,841 @@ +mod test_struct { + use evm_coder_procedural::AbiCoder; + use evm_coder::types::bytes; + + #[test] + fn empty_struct() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/build_failed/abi_derive_struct_generation.rs"); + } + + #[derive(AbiCoder, PartialEq, Debug)] + struct TypeStruct1SimpleParam { + _a: u8, + } + + #[derive(AbiCoder, PartialEq, Debug)] + struct TypeStruct1DynamicParam { + _a: String, + } + + #[derive(AbiCoder, PartialEq, Debug)] + struct TypeStruct2SimpleParam { + _a: u8, + _b: u32, + } + + #[derive(AbiCoder, PartialEq, Debug)] + struct TypeStruct2DynamicParam { + _a: String, + _b: bytes, + } + + #[derive(AbiCoder, PartialEq, Debug)] + struct TypeStruct2MixedParam { + _a: u8, + _b: bytes, + } + + #[derive(AbiCoder, PartialEq, Debug)] + struct TypeStruct1DerivedSimpleParam { + _a: TypeStruct1SimpleParam, + } + + #[derive(AbiCoder, PartialEq, Debug)] + struct TypeStruct2DerivedSimpleParam { + _a: TypeStruct1SimpleParam, + _b: TypeStruct2SimpleParam, + } + + #[derive(AbiCoder, PartialEq, Debug)] + struct TypeStruct1DerivedDynamicParam { + _a: TypeStruct1DynamicParam, + } + + #[derive(AbiCoder, PartialEq, Debug)] + struct TypeStruct2DerivedDynamicParam { + _a: TypeStruct1DynamicParam, + _b: TypeStruct2DynamicParam, + } + + /// Some docs + /// At multi + /// line + #[derive(AbiCoder, PartialEq, Debug)] + struct TypeStruct3DerivedMixedParam { + /// Docs for A + /// multi + /// line + _a: TypeStruct1SimpleParam, + /// Docs for B + _b: TypeStruct2DynamicParam, + /// Docs for C + _c: TypeStruct2MixedParam, + } + + #[test] + #[cfg(feature = "stubgen")] + fn struct_collect_type_struct3_derived_mixed_param() { + assert_eq!( + ::name(), + "TypeStruct3DerivedMixedParam" + ); + similar_asserts::assert_eq!( + ::declaration(), + r#"/// @dev Some docs +/// At multi +/// line +struct TypeStruct3DerivedMixedParam { + /// @dev Docs for A + /// multi + /// line + TypeStruct1SimpleParam _a; + /// @dev Docs for B + TypeStruct2DynamicParam _b; + /// @dev Docs for C + TypeStruct2MixedParam _c; +} +"# + ); + } + + #[test] + fn impl_abi_type_signature() { + assert_eq!( + ::SIGNATURE + .as_str() + .unwrap(), + "(uint8)" + ); + assert_eq!( + ::SIGNATURE + .as_str() + .unwrap(), + "(string)" + ); + assert_eq!( + ::SIGNATURE + .as_str() + .unwrap(), + "(uint8,uint32)" + ); + assert_eq!( + ::SIGNATURE + .as_str() + .unwrap(), + "(string,bytes)" + ); + assert_eq!( + ::SIGNATURE + .as_str() + .unwrap(), + "(uint8,bytes)" + ); + assert_eq!( + ::SIGNATURE + .as_str() + .unwrap(), + "((uint8))" + ); + assert_eq!( + ::SIGNATURE + .as_str() + .unwrap(), + "((uint8),(uint8,uint32))" + ); + assert_eq!( + ::SIGNATURE + .as_str() + .unwrap(), + "((string))" + ); + assert_eq!( + ::SIGNATURE + .as_str() + .unwrap(), + "((string),(string,bytes))" + ); + assert_eq!( + ::SIGNATURE + .as_str() + .unwrap(), + "((uint8),(string,bytes),(uint8,bytes))" + ); + } + + #[test] + fn impl_abi_type_is_dynamic() { + assert_eq!( + ::is_dynamic(), + false + ); + assert_eq!( + ::is_dynamic(), + true + ); + assert_eq!( + ::is_dynamic(), + false + ); + assert_eq!( + ::is_dynamic(), + true + ); + assert_eq!( + ::is_dynamic(), + true + ); + assert_eq!( + ::is_dynamic(), + false + ); + assert_eq!( + ::is_dynamic(), + false + ); + assert_eq!( + ::is_dynamic(), + true + ); + assert_eq!( + ::is_dynamic(), + true + ); + assert_eq!( + ::is_dynamic(), + true + ); + } + + #[test] + fn impl_abi_type_size() { + const ABI_ALIGNMENT: usize = 32; + assert_eq!( + ::size(), + ABI_ALIGNMENT + ); + assert_eq!( + ::size(), + ABI_ALIGNMENT + ); + assert_eq!( + ::size(), + ABI_ALIGNMENT * 2 + ); + assert_eq!( + ::size(), + ABI_ALIGNMENT * 2 + ); + assert_eq!( + ::size(), + ABI_ALIGNMENT * 2 + ); + assert_eq!( + ::size(), + ABI_ALIGNMENT + ); + assert_eq!( + ::size(), + ABI_ALIGNMENT * 3 + ); + assert_eq!( + ::size(), + ABI_ALIGNMENT + ); + assert_eq!( + ::size(), + ABI_ALIGNMENT * 3 + ); + assert_eq!( + ::size(), + ABI_ALIGNMENT * 5 + ); + } + + #[derive(AbiCoder, PartialEq, Debug)] + struct TupleStruct1SimpleParam(u8); + + #[derive(AbiCoder, PartialEq, Debug)] + struct TupleStruct1DynamicParam(String); + + #[derive(AbiCoder, PartialEq, Debug)] + struct TupleStruct2SimpleParam(u8, u32); + + #[derive(AbiCoder, PartialEq, Debug)] + struct TupleStruct2DynamicParam(String, bytes); + + #[derive(AbiCoder, PartialEq, Debug)] + struct TupleStruct2MixedParam(u8, bytes); + + #[derive(AbiCoder, PartialEq, Debug)] + struct TupleStruct1DerivedSimpleParam(TupleStruct1SimpleParam); + + #[derive(AbiCoder, PartialEq, Debug)] + struct TupleStruct2DerivedSimpleParam(TupleStruct1SimpleParam, TupleStruct2SimpleParam); + + #[derive(AbiCoder, PartialEq, Debug)] + struct TupleStruct1DerivedDynamicParam(TupleStruct1DynamicParam); + + #[derive(AbiCoder, PartialEq, Debug)] + struct TupleStruct2DerivedDynamicParam(TupleStruct1DynamicParam, TupleStruct2DynamicParam); + + /// Some docs + /// At multi + /// line + #[derive(AbiCoder, PartialEq, Debug)] + struct TupleStruct3DerivedMixedParam( + /// Docs for A + /// multi + /// line + TupleStruct1SimpleParam, + TupleStruct2DynamicParam, + /// Docs for C + TupleStruct2MixedParam, + ); + + #[test] + #[cfg(feature = "stubgen")] + fn struct_collect_tuple_struct3_derived_mixed_param() { + assert_eq!( + ::name(), + "TupleStruct3DerivedMixedParam" + ); + similar_asserts::assert_eq!( + ::declaration(), + r#"/// @dev Some docs +/// At multi +/// line +struct TupleStruct3DerivedMixedParam { + /// @dev Docs for A + /// multi + /// line + TupleStruct1SimpleParam field0; + TupleStruct2DynamicParam field1; + /// @dev Docs for C + TupleStruct2MixedParam field2; +} +"# + ); + } + + #[test] + fn impl_abi_type_signature_same_for_structs() { + assert_eq!( + ::SIGNATURE + .as_str() + .unwrap(), + ::SIGNATURE + .as_str() + .unwrap() + ); + assert_eq!( + ::SIGNATURE + .as_str() + .unwrap(), + ::SIGNATURE + .as_str() + .unwrap() + ); + assert_eq!( + ::SIGNATURE + .as_str() + .unwrap(), + ::SIGNATURE + .as_str() + .unwrap() + ); + assert_eq!( + ::SIGNATURE + .as_str() + .unwrap(), + ::SIGNATURE + .as_str() + .unwrap() + ); + assert_eq!( + ::SIGNATURE + .as_str() + .unwrap(), + ::SIGNATURE + .as_str() + .unwrap(), + ); + assert_eq!( + ::SIGNATURE + .as_str() + .unwrap(), + ::SIGNATURE + .as_str() + .unwrap(), + ); + assert_eq!( + ::SIGNATURE + .as_str() + .unwrap(), + ::SIGNATURE + .as_str() + .unwrap(), + ); + assert_eq!( + ::SIGNATURE + .as_str() + .unwrap(), + ::SIGNATURE + .as_str() + .unwrap(), + ); + assert_eq!( + ::SIGNATURE + .as_str() + .unwrap(), + ::SIGNATURE + .as_str() + .unwrap(), + ); + assert_eq!( + ::SIGNATURE + .as_str() + .unwrap(), + ::SIGNATURE + .as_str() + .unwrap(), + ); + } + + #[test] + fn impl_abi_type_is_dynamic_same_for_structs() { + assert_eq!( + ::is_dynamic(), + ::is_dynamic() + ); + assert_eq!( + ::is_dynamic(), + ::is_dynamic() + ); + assert_eq!( + ::is_dynamic(), + ::is_dynamic() + ); + assert_eq!( + ::is_dynamic(), + ::is_dynamic() + ); + assert_eq!( + ::is_dynamic(), + ::is_dynamic() + ); + assert_eq!( + ::is_dynamic(), + ::is_dynamic() + ); + assert_eq!( + ::is_dynamic(), + ::is_dynamic() + ); + assert_eq!( + ::is_dynamic(), + ::is_dynamic() + ); + assert_eq!( + ::is_dynamic(), + ::is_dynamic() + ); + assert_eq!( + ::is_dynamic(), + ::is_dynamic() + ); + } + + #[test] + fn impl_abi_type_size_same_for_structs() { + assert_eq!( + ::size(), + ::size() + ); + assert_eq!( + ::size(), + ::size() + ); + assert_eq!( + ::size(), + ::size() + ); + assert_eq!( + ::size(), + ::size() + ); + assert_eq!( + ::size(), + ::size() + ); + assert_eq!( + ::size(), + ::size() + ); + assert_eq!( + ::size(), + ::size() + ); + assert_eq!( + ::size(), + ::size() + ); + assert_eq!( + ::size(), + ::size() + ); + assert_eq!( + ::size(), + ::size() + ); + } + + const FUNCTION_IDENTIFIER: u32 = 0xdeadbeef; + + fn test_impl( + tuple_data: Tuple, + tuple_struct_data: TupleStruct, + type_struct_data: TypeStruct, + ) where + TypeStruct: evm_coder::abi::AbiWrite + + evm_coder::abi::AbiRead + + std::cmp::PartialEq + + std::fmt::Debug, + TupleStruct: evm_coder::abi::AbiWrite + + evm_coder::abi::AbiRead + + std::cmp::PartialEq + + std::fmt::Debug, + Tuple: evm_coder::abi::AbiWrite + + evm_coder::abi::AbiRead + + std::cmp::PartialEq + + std::fmt::Debug, + { + let encoded_type_struct = test_abi_write_impl(&type_struct_data); + let encoded_tuple_struct = test_abi_write_impl(&tuple_struct_data); + let encoded_tuple = test_abi_write_impl(&tuple_data); + + similar_asserts::assert_eq!(encoded_tuple, encoded_type_struct); + similar_asserts::assert_eq!(encoded_tuple, encoded_tuple_struct); + + { + let (_, mut decoder) = evm_coder::abi::AbiReader::new_call(&encoded_tuple).unwrap(); + let restored_struct_data = ::abi_read(&mut decoder).unwrap(); + assert_eq!(restored_struct_data, type_struct_data); + } + { + let (_, mut decoder) = evm_coder::abi::AbiReader::new_call(&encoded_tuple).unwrap(); + let restored_struct_data = ::abi_read(&mut decoder).unwrap(); + assert_eq!(restored_struct_data, tuple_struct_data); + } + + { + let (_, mut decoder) = + evm_coder::abi::AbiReader::new_call(&encoded_type_struct).unwrap(); + let restored_tuple_data = ::abi_read(&mut decoder).unwrap(); + assert_eq!(restored_tuple_data, tuple_data); + } + { + let (_, mut decoder) = + evm_coder::abi::AbiReader::new_call(&encoded_tuple_struct).unwrap(); + let restored_tuple_data = ::abi_read(&mut decoder).unwrap(); + assert_eq!(restored_tuple_data, tuple_data); + } + } + + fn test_abi_write_impl(data: &A) -> Vec + where + A: evm_coder::abi::AbiWrite + + evm_coder::abi::AbiRead + + std::cmp::PartialEq + + std::fmt::Debug, + { + let mut writer = evm_coder::abi::AbiWriter::new_call(FUNCTION_IDENTIFIER); + data.abi_write(&mut writer); + let encoded_tuple = writer.finish(); + encoded_tuple + } + + #[test] + fn codec_struct_1_simple() { + let _a = 0xff; + test_impl::<(u8,), TupleStruct1SimpleParam, TypeStruct1SimpleParam>( + (_a,), + TupleStruct1SimpleParam(_a), + TypeStruct1SimpleParam { _a }, + ); + } + + #[test] + fn codec_struct_1_dynamic() { + let _a: String = "some string".into(); + test_impl::<(String,), TupleStruct1DynamicParam, TypeStruct1DynamicParam>( + (_a.clone(),), + TupleStruct1DynamicParam(_a.clone()), + TypeStruct1DynamicParam { _a }, + ); + } + + #[test] + fn codec_struct_1_derived_simple() { + let _a: u8 = 0xff; + test_impl::<((u8,),), TupleStruct1DerivedSimpleParam, TypeStruct1DerivedSimpleParam>( + ((_a,),), + TupleStruct1DerivedSimpleParam(TupleStruct1SimpleParam(_a)), + TypeStruct1DerivedSimpleParam { + _a: TypeStruct1SimpleParam { _a }, + }, + ); + } + + #[test] + fn codec_struct_1_derived_dynamic() { + let _a: String = "some string".into(); + test_impl::<((String,),), TupleStruct1DerivedDynamicParam, TypeStruct1DerivedDynamicParam>( + ((_a.clone(),),), + TupleStruct1DerivedDynamicParam(TupleStruct1DynamicParam(_a.clone())), + TypeStruct1DerivedDynamicParam { + _a: TypeStruct1DynamicParam { _a }, + }, + ); + } + + #[test] + fn codec_struct_2_simple() { + let _a = 0xff; + let _b = 0xbeefbaba; + test_impl::<(u8, u32), TupleStruct2SimpleParam, TypeStruct2SimpleParam>( + (_a, _b), + TupleStruct2SimpleParam(_a, _b), + TypeStruct2SimpleParam { _a, _b }, + ); + } + + #[test] + fn codec_struct_2_dynamic() { + let _a: String = "some string".into(); + let _b: bytes = bytes(vec![0x11, 0x22, 0x33]); + test_impl::<(String, bytes), TupleStruct2DynamicParam, TypeStruct2DynamicParam>( + (_a.clone(), _b.clone()), + TupleStruct2DynamicParam(_a.clone(), _b.clone()), + TypeStruct2DynamicParam { _a, _b }, + ); + } + + #[test] + fn codec_struct_2_mixed() { + let _a: u8 = 0xff; + let _b: bytes = bytes(vec![0x11, 0x22, 0x33]); + test_impl::<(u8, bytes), TupleStruct2MixedParam, TypeStruct2MixedParam>( + (_a.clone(), _b.clone()), + TupleStruct2MixedParam(_a.clone(), _b.clone()), + TypeStruct2MixedParam { _a, _b }, + ); + } + + #[test] + fn codec_struct_2_derived_simple() { + let _a = 0xff; + let _b = 0xbeefbaba; + test_impl::< + ((u8,), (u8, u32)), + TupleStruct2DerivedSimpleParam, + TypeStruct2DerivedSimpleParam, + >( + ((_a,), (_a, _b)), + TupleStruct2DerivedSimpleParam( + TupleStruct1SimpleParam(_a), + TupleStruct2SimpleParam(_a, _b), + ), + TypeStruct2DerivedSimpleParam { + _a: TypeStruct1SimpleParam { _a }, + _b: TypeStruct2SimpleParam { _a, _b }, + }, + ); + } + + #[test] + fn codec_struct_2_derived_dynamic() { + let _a = "some string".to_string(); + let _b = bytes(vec![0x11, 0x22, 0x33]); + test_impl::< + ((String,), (String, bytes)), + TupleStruct2DerivedDynamicParam, + TypeStruct2DerivedDynamicParam, + >( + ((_a.clone(),), (_a.clone(), _b.clone())), + TupleStruct2DerivedDynamicParam( + TupleStruct1DynamicParam(_a.clone()), + TupleStruct2DynamicParam(_a.clone(), _b.clone()), + ), + TypeStruct2DerivedDynamicParam { + _a: TypeStruct1DynamicParam { _a: _a.clone() }, + _b: TypeStruct2DynamicParam { _a, _b }, + }, + ); + } + + #[test] + fn codec_struct_3_derived_mixed() { + let int = 0xff; + let by = bytes(vec![0x11, 0x22, 0x33]); + let string = "some string".to_string(); + test_impl::< + ((u8,), (String, bytes), (u8, bytes)), + TupleStruct3DerivedMixedParam, + TypeStruct3DerivedMixedParam, + >( + ((int,), (string.clone(), by.clone()), (int, by.clone())), + TupleStruct3DerivedMixedParam( + TupleStruct1SimpleParam(int), + TupleStruct2DynamicParam(string.clone(), by.clone()), + TupleStruct2MixedParam(int, by.clone()), + ), + TypeStruct3DerivedMixedParam { + _a: TypeStruct1SimpleParam { _a: int }, + _b: TypeStruct2DynamicParam { + _a: string.clone(), + _b: by.clone(), + }, + _c: TypeStruct2MixedParam { _a: int, _b: by }, + }, + ); + } + + #[derive(AbiCoder, PartialEq, Debug)] + struct TypeStruct2SimpleStruct1Simple { + _a: TypeStruct2SimpleParam, + _b: TypeStruct2SimpleParam, + _c: u8, + } + #[derive(AbiCoder, PartialEq, Debug)] + struct TupleStruct2SimpleStruct1Simple(TupleStruct2SimpleParam, TupleStruct2SimpleParam, u8); + + #[test] + fn codec_struct_2_struct_simple_1_simple() { + let _a = 0xff; + let _b = 0xbeefbaba; + test_impl::< + ((u8, u32), (u8, u32), u8), + TupleStruct2SimpleStruct1Simple, + TypeStruct2SimpleStruct1Simple, + >( + ((_a, _b), (_a, _b), _a), + TupleStruct2SimpleStruct1Simple( + TupleStruct2SimpleParam(_a, _b), + TupleStruct2SimpleParam(_a, _b), + _a, + ), + TypeStruct2SimpleStruct1Simple { + _a: TypeStruct2SimpleParam { _a, _b }, + _b: TypeStruct2SimpleParam { _a, _b }, + _c: _a, + }, + ); + } +} + +mod test_enum { + use evm_coder::AbiCoder; + + /// Some docs + /// At multi + /// line + #[derive(AbiCoder, Debug, PartialEq, Default)] + #[repr(u8)] + enum Color { + /// Docs for Red + /// multi + /// line + Red, + Green, + /// Docs for Blue + #[default] + Blue, + } + + #[test] + fn empty() {} + + #[test] + fn bad_enums() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/build_failed/abi_derive_enum_generation.rs"); + } + + #[test] + fn impl_abi_type_signature_same_for_structs() { + assert_eq!( + ::SIGNATURE + .as_str() + .unwrap(), + ::SIGNATURE.as_str().unwrap() + ); + } + + #[test] + fn impl_abi_type_is_dynamic_same_for_structs() { + assert_eq!( + ::is_dynamic(), + ::is_dynamic() + ); + } + + #[test] + fn impl_abi_type_size_same_for_structs() { + assert_eq!( + ::size(), + ::size() + ); + } + + #[test] + fn test_coder() { + const FUNCTION_IDENTIFIER: u32 = 0xdeadbeef; + + let encoded_enum = { + let mut writer = evm_coder::abi::AbiWriter::new_call(FUNCTION_IDENTIFIER); + ::abi_write(&Color::Green, &mut writer); + writer.finish() + }; + + let encoded_u8 = { + let mut writer = evm_coder::abi::AbiWriter::new_call(FUNCTION_IDENTIFIER); + ::abi_write(&(Color::Green as u8), &mut writer); + writer.finish() + }; + + similar_asserts::assert_eq!(encoded_enum, encoded_u8); + + { + let (_, mut decoder) = evm_coder::abi::AbiReader::new_call(&encoded_enum).unwrap(); + let restored_enum_data = + ::abi_read(&mut decoder).unwrap(); + assert_eq!(restored_enum_data, Color::Green); + } + } + + #[test] + #[cfg(feature = "stubgen")] + fn struct_collect_enum() { + assert_eq!( + ::name(), + "Color" + ); + similar_asserts::assert_eq!( + ::declaration(), + r#"/// @dev Some docs +/// At multi +/// line +enum Color { + /// @dev Docs for Red + /// multi + /// line + Red, + Green, + /// @dev Docs for Blue + Blue +} +"# + ); + } +} --- /dev/null +++ b/crates/evm-coder/tests/build_failed/abi_derive_enum_generation.rs @@ -0,0 +1,36 @@ +use evm_coder_procedural::AbiCoder; + +#[derive(AbiCoder)] +enum NonRepr { + A, + B, + C, +} + +#[derive(AbiCoder)] +#[repr(u32)] +enum NonReprU8 { + A, + B, + C, +} + +#[derive(AbiCoder)] +#[repr(u8)] +enum RustEnum { + A(u128), + B, + C, +} + +#[derive(AbiCoder)] +#[repr(u8)] +enum WithExplicit { + A = 128, + B, + C, +} + +fn main() { + assert!(false); +} --- /dev/null +++ b/crates/evm-coder/tests/build_failed/abi_derive_enum_generation.stderr @@ -0,0 +1,23 @@ +error: Enum is not "repr(u8)" + --> tests/build_failed/abi_derive_enum_generation.rs:4:6 + | +4 | enum NonRepr { + | ^^^^^^^ + +error: Enum is not "repr(u8)" + --> tests/build_failed/abi_derive_enum_generation.rs:11:8 + | +11 | #[repr(u32)] + | ^^^ + +error: Enumeration parameters should not have fields + --> tests/build_failed/abi_derive_enum_generation.rs:21:2 + | +21 | A(u128), + | ^ + +error: Enumeration options should not have an explicit specified value + --> tests/build_failed/abi_derive_enum_generation.rs:29:2 + | +29 | A = 128, + | ^ --- /dev/null +++ b/crates/evm-coder/tests/build_failed/abi_derive_struct_generation.rs @@ -0,0 +1,11 @@ +use evm_coder_procedural::AbiCoder; + +#[derive(AbiCoder, PartialEq, Debug)] +struct EmptyStruct {} + +#[derive(AbiCoder, PartialEq, Debug)] +struct EmptyTupleStruct(); + +fn main() { + assert!(false); +} --- /dev/null +++ b/crates/evm-coder/tests/build_failed/abi_derive_struct_generation.stderr @@ -0,0 +1,11 @@ +error: Empty structs not supported + --> tests/build_failed/abi_derive_struct_generation.rs:4:8 + | +4 | struct EmptyStruct {} + | ^^^^^^^^^^^ + +error: Empty structs not supported + --> tests/build_failed/abi_derive_struct_generation.rs:7:8 + | +7 | struct EmptyTupleStruct(); + | ^^^^^^^^^^^^^^^^ --- a/pallets/common/src/erc.rs +++ b/pallets/common/src/erc.rs @@ -16,6 +16,7 @@ //! This module contains the implementation of pallet methods for evm. +pub use pallet_evm::{PrecompileOutput, PrecompileResult, PrecompileHandle, account::CrossAccountId}; use evm_coder::{ abi::AbiType, solidity_interface, solidity, ToLog, @@ -24,7 +25,6 @@ execution::{Result, Error}, weight, }; -pub use pallet_evm::{PrecompileOutput, PrecompileResult, PrecompileHandle, account::CrossAccountId}; use pallet_evm_coder_substrate::dispatch_to_evm; use sp_std::vec::Vec; use up_data_structs::{ @@ -35,7 +35,8 @@ use crate::{ Pallet, CollectionHandle, Config, CollectionProperties, SelfWeightOf, - eth::convert_cross_account_to_uint256, weights::WeightInfo, + eth::{EthCrossAccount, convert_cross_account_to_uint256}, + weights::WeightInfo, }; /// Events for ethereum collection helper. --- a/pallets/common/src/eth.rs +++ b/pallets/common/src/eth.rs @@ -16,7 +16,10 @@ //! The module contains a number of functions for converting and checking ethereum identifiers. -use evm_coder::types::{uint256, address}; +use evm_coder::{ + AbiCoder, + types::{uint256, address}, +}; pub use pallet_evm::{Config, account::CrossAccountId}; use sp_core::H160; use up_data_structs::CollectionId; @@ -109,3 +112,46 @@ Err("All fields of cross account is non zeroed".into()) } } + +/// Cross account struct +#[derive(Debug, Default, AbiCoder)] +pub struct EthCrossAccount { + pub(crate) eth: address, + pub(crate) sub: uint256, +} + +impl EthCrossAccount { + pub fn from_sub_cross_account(cross_account_id: &T::CrossAccountId) -> Self + where + T: pallet_evm::Config, + T::AccountId: AsRef<[u8; 32]>, + { + if cross_account_id.is_canonical_substrate() { + Self { + eth: Default::default(), + sub: convert_cross_account_to_uint256::(cross_account_id), + } + } else { + Self { + eth: *cross_account_id.as_eth(), + sub: Default::default(), + } + } + } + + pub fn into_sub_cross_account(&self) -> evm_coder::execution::Result + where + T: pallet_evm::Config, + T::AccountId: From<[u8; 32]>, + { + if self.eth == Default::default() && self.sub == Default::default() { + Err("All fields of cross account is zeroed".into()) + } else if self.eth == Default::default() { + Ok(convert_uint256_to_cross_account::(self.sub)) + } else if self.sub == Default::default() { + Ok(T::CrossAccountId::from_eth(self.eth)) + } else { + Err("All fields of cross account is non zeroed".into()) + } + } +} --- a/pallets/common/src/lib.rs +++ b/pallets/common/src/lib.rs @@ -376,6 +376,7 @@ type TreasuryAccountId: Get; /// Address under which the CollectionHelper contract would be available. + #[pallet::constant] type ContractAddress: Get; /// Mapper for token addresses to Ethereum addresses. --- a/pallets/evm-contract-helpers/src/lib.rs +++ b/pallets/evm-contract-helpers/src/lib.rs @@ -47,6 +47,7 @@ type RuntimeEvent: IsType<::RuntimeEvent> + From>; /// Address, under which magic contract will be available + #[pallet::constant] type ContractAddress: Get; /// In case of enabled sponsoring, but no sponsoring rate limit set, --- a/pallets/fungible/src/erc.rs +++ b/pallets/fungible/src/erc.rs @@ -24,12 +24,16 @@ weight, }; use up_data_structs::CollectionMode; -use pallet_common::erc::{CommonEvmHandler, PrecompileResult}; +use pallet_common::{ + CollectionHandle, + erc::{CommonEvmHandler, PrecompileResult, CollectionCall}, + eth::EthCrossAccount, +}; use sp_std::vec::Vec; use pallet_evm::{account::CrossAccountId, PrecompileHandle}; use pallet_evm_coder_substrate::{call, dispatch_to_evm}; use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _}; -use pallet_common::{CollectionHandle, erc::CollectionCall}; +use sp_core::Get; use crate::{ Allowance, Balance, Config, FungibleHandle, Pallet, SelfWeightOf, TotalSupply, @@ -132,6 +136,11 @@ Ok(>::get((self.id, owner, spender)).into()) } + + /// @notice Returns collection helper contract address + fn collection_helper_address(&self) -> Result
{ + Ok(T::ContractAddress::get()) + } } #[solidity_interface(name = ERC20Mintable)] --- a/pallets/fungible/src/stubs/UniqueFungible.sol +++ b/pallets/fungible/src/stubs/UniqueFungible.sol @@ -547,7 +547,7 @@ event Approval(address indexed owner, address indexed spender, uint256 value); } -/// @dev the ERC-165 identifier for this interface is 0x942e8b22 +/// @dev the ERC-165 identifier for this interface is 0x8cb847c4 contract ERC20 is Dummy, ERC165, ERC20Events { /// @dev EVM selector for this function is: 0x06fdde03, /// or in textual repr: name() @@ -634,6 +634,15 @@ dummy; return 0; } + + /// @notice Returns collection helper contract address + /// @dev EVM selector for this function is: 0x1896cce6, + /// or in textual repr: collectionHelperAddress() + function collectionHelperAddress() public view returns (address) { + require(false, stub_error); + dummy; + return 0x0000000000000000000000000000000000000000; + } } contract UniqueFungible is Dummy, ERC165, ERC20, ERC20Mintable, ERC20UniqueExtensions, Collection {} --- a/pallets/nonfungible/src/erc.rs +++ b/pallets/nonfungible/src/erc.rs @@ -36,12 +36,14 @@ use pallet_evm_coder_substrate::dispatch_to_evm; use sp_std::vec::Vec; use pallet_common::{ + CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations, erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key}, - CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations, + eth::EthCrossAccount, }; use pallet_evm::{account::CrossAccountId, PrecompileHandle}; use pallet_evm_coder_substrate::call; use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _}; +use sp_core::Get; use crate::{ AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted, @@ -489,6 +491,11 @@ // TODO: Not implemetable Err("not implemented".into()) } + + /// @notice Returns collection helper contract address + fn collection_helper_address(&self) -> Result
{ + Ok(T::ContractAddress::get()) + } } /// @title ERC721 Token that can be irreversibly burned (destroyed). --- a/pallets/nonfungible/src/stubs/UniqueNFT.sol +++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol @@ -920,7 +920,7 @@ /// @title ERC-721 Non-Fungible Token Standard /// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md -/// @dev the ERC-165 identifier for this interface is 0x80ac58cd +/// @dev the ERC-165 identifier for this interface is 0x983a942b contract ERC721 is Dummy, ERC165, ERC721Events { /// @notice Count all NFTs assigned to an owner /// @dev NFTs assigned to the zero address are considered invalid, and this @@ -1050,6 +1050,15 @@ dummy; return 0x0000000000000000000000000000000000000000; } + + /// @notice Returns collection helper contract address + /// @dev EVM selector for this function is: 0x1896cce6, + /// or in textual repr: collectionHelperAddress() + function collectionHelperAddress() public view returns (address) { + require(false, stub_error); + dummy; + return 0x0000000000000000000000000000000000000000; + } } contract UniqueNFT is --- a/pallets/proxy-rmrk-core/src/lib.rs +++ b/pallets/proxy-rmrk-core/src/lib.rs @@ -178,7 +178,7 @@ use RmrkProperty::*; -/// Maximum number of levels of depth in the token nesting tree. +/// A maximum number of levels of depth in the token nesting tree. pub const NESTING_BUDGET: u32 = 5; type PendingTarget = (CollectionId, TokenId); --- a/pallets/refungible/src/erc.rs +++ b/pallets/refungible/src/erc.rs @@ -31,14 +31,14 @@ }; use frame_support::{BoundedBTreeMap, BoundedVec}; use pallet_common::{ - CollectionHandle, CollectionPropertyPermissions, + CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations, erc::{CommonEvmHandler, CollectionCall, static_property::key}, - CommonCollectionOperations, + eth::EthCrossAccount, }; use pallet_evm::{account::CrossAccountId, PrecompileHandle}; use pallet_evm_coder_substrate::{call, dispatch_to_evm}; use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _}; -use sp_core::H160; +use sp_core::{H160, Get}; use sp_std::{collections::btree_map::BTreeMap, vec::Vec, vec}; use up_data_structs::{ CollectionId, CollectionPropertiesVec, mapping::TokenAddressMapping, Property, PropertyKey, @@ -482,6 +482,11 @@ // TODO: Not implemetable Err("not implemented".into()) } + + /// @notice Returns collection helper contract address + fn collection_helper_address(&self) -> Result
{ + Ok(T::ContractAddress::get()) + } } /// Returns amount of pieces of `token` that `owner` have --- a/pallets/refungible/src/stubs/UniqueRefungible.sol +++ b/pallets/refungible/src/stubs/UniqueRefungible.sol @@ -919,7 +919,7 @@ /// @title ERC-721 Non-Fungible Token Standard /// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md -/// @dev the ERC-165 identifier for this interface is 0x58800161 +/// @dev the ERC-165 identifier for this interface is 0x4016cd87 contract ERC721 is Dummy, ERC165, ERC721Events { /// @notice Count all RFTs assigned to an owner /// @dev RFTs assigned to the zero address are considered invalid, and this @@ -1047,6 +1047,15 @@ dummy; return 0x0000000000000000000000000000000000000000; } + + /// @notice Returns collection helper contract address + /// @dev EVM selector for this function is: 0x1896cce6, + /// or in textual repr: collectionHelperAddress() + function collectionHelperAddress() public view returns (address) { + require(false, stub_error); + dummy; + return 0x0000000000000000000000000000000000000000; + } } contract UniqueRefungible is --- a/pallets/unique/src/eth/mod.rs +++ b/pallets/unique/src/eth/mod.rs @@ -28,6 +28,7 @@ CollectionById, dispatch::CollectionDispatch, erc::{CollectionHelpersEvents, static_property::key}, + eth::{map_eth_to_id, collection_id_to_address}, Pallet as PalletCommon, }; use pallet_evm::{account::CrossAccountId, OnMethodCall, PrecompileHandle, PrecompileResult}; @@ -35,7 +36,7 @@ use sp_std::vec; use up_data_structs::{ CollectionDescription, CollectionMode, CollectionName, CollectionTokenPrefix, - CreateCollectionData, + CreateCollectionData, CollectionId, }; use crate::{weights::WeightInfo, Config, SelfWeightOf}; @@ -361,6 +362,25 @@ .expect("Collection creation price should be convertible to u128"); Ok(price.into()) } + + /// Returns address of a collection. + /// @param collectionId - CollectionId of the collection + /// @return eth mirror address of the collection + fn collection_address(&self, collection_id: uint32) -> Result
{ + Ok(collection_id_to_address(collection_id.into())) + } + + /// Returns collectionId of a collection. + /// @param collectionAddress - Eth address of the collection + /// @return collectionId of the collection + fn collection_id(&self, collection_address: address) -> Result { + map_eth_to_id(&collection_address) + .map(|id| id.0) + .ok_or(Error::Revert(format!( + "failed to convert address {} into collectionId.", + collection_address + ))) + } } /// Implements [`OnMethodCall`], which delegates call to [`EvmCollectionHelpers`] --- a/pallets/unique/src/eth/stubs/CollectionHelpers.sol +++ b/pallets/unique/src/eth/stubs/CollectionHelpers.sol @@ -24,7 +24,7 @@ } /// @title Contract, which allows users to operate with collections -/// @dev the ERC-165 identifier for this interface is 0x7dea03b1 +/// @dev the ERC-165 identifier for this interface is 0xe65011aa contract CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents { /// Create an NFT collection /// @param name Name of the collection @@ -130,4 +130,28 @@ dummy; return 0; } + + /// Returns address of a collection. + /// @param collectionId - CollectionId of the collection + /// @return eth mirror address of the collection + /// @dev EVM selector for this function is: 0x2e716683, + /// or in textual repr: collectionAddress(uint32) + function collectionAddress(uint32 collectionId) public view returns (address) { + require(false, stub_error); + collectionId; + dummy; + return 0x0000000000000000000000000000000000000000; + } + + /// Returns collectionId of a collection. + /// @param collectionAddress - Eth address of the collection + /// @return collectionId of the collection + /// @dev EVM selector for this function is: 0xb5cb7498, + /// or in textual repr: collectionId(address) + function collectionId(address collectionAddress) public view returns (uint32) { + require(false, stub_error); + collectionAddress; + dummy; + return 0; + } } --- a/pallets/unique/src/lib.rs +++ b/pallets/unique/src/lib.rs @@ -86,6 +86,8 @@ use sp_std::{vec, vec::Vec}; use up_data_structs::{ MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH, + MAX_PROPERTIES_PER_ITEM, MAX_PROPERTY_KEY_LENGTH, MAX_PROPERTY_VALUE_LENGTH, + MAX_COLLECTION_PROPERTIES_SIZE, COLLECTION_ADMINS_LIMIT, MAX_TOKEN_PROPERTIES_SIZE, CreateItemData, CollectionLimits, CollectionPermissions, CollectionId, CollectionMode, TokenId, SponsorshipState, CreateCollectionData, CreateItemExData, budget, Property, PropertyKey, PropertyKeyPermission, @@ -102,7 +104,7 @@ pub mod weights; use weights::WeightInfo; -/// Maximum number of levels of depth in the token nesting tree. +/// A maximum number of levels of depth in the token nesting tree. pub const NESTING_BUDGET: u32 = 5; decl_error! { @@ -277,6 +279,46 @@ { type Error = Error; + #[doc = "A maximum number of levels of depth in the token nesting tree."] + const NESTING_BUDGET: u32 = NESTING_BUDGET; + + #[doc = "Maximal length of a collection name."] + const MAX_COLLECTION_NAME_LENGTH: u32 = MAX_COLLECTION_NAME_LENGTH; + + #[doc = "Maximal length of a collection description."] + const MAX_COLLECTION_DESCRIPTION_LENGTH: u32 = MAX_COLLECTION_DESCRIPTION_LENGTH; + + #[doc = "Maximal length of a token prefix."] + const MAX_TOKEN_PREFIX_LENGTH: u32 = MAX_TOKEN_PREFIX_LENGTH; + + #[doc = "Maximum admins per collection."] + const COLLECTION_ADMINS_LIMIT: u32 = COLLECTION_ADMINS_LIMIT; + + #[doc = "Maximal length of a property key."] + const MAX_PROPERTY_KEY_LENGTH: u32 = MAX_PROPERTY_KEY_LENGTH; + + #[doc = "Maximal length of a property value."] + const MAX_PROPERTY_VALUE_LENGTH: u32 = MAX_PROPERTY_VALUE_LENGTH; + + #[doc = "A maximum number of token properties."] + const MAX_PROPERTIES_PER_ITEM: u32 = MAX_PROPERTIES_PER_ITEM; + + #[doc = "Maximum size for all collection properties."] + const MAX_COLLECTION_PROPERTIES_SIZE: u32 = MAX_COLLECTION_PROPERTIES_SIZE; + + #[doc = "Maximum size of all token properties."] + const MAX_TOKEN_PROPERTIES_SIZE: u32 = MAX_TOKEN_PROPERTIES_SIZE; + + #[doc = "Default NFT collection limit."] + const NFT_DEFAULT_COLLECTION_LIMITS: CollectionLimits = CollectionLimits::with_default_limits(CollectionMode::NFT); + + #[doc = "Default RFT collection limit."] + const RFT_DEFAULT_COLLECTION_LIMITS: CollectionLimits = CollectionLimits::with_default_limits(CollectionMode::ReFungible); + + #[doc = "Default FT collection limit."] + const FT_DEFAULT_COLLECTION_LIMITS: CollectionLimits = CollectionLimits::with_default_limits(CollectionMode::Fungible(0)); + + pub fn deposit_event() = default; fn on_initialize(_now: T::BlockNumber) -> Weight { --- a/primitives/data-structs/CHANGELOG.md +++ b/primitives/data-structs/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. + ## [v0.2.2] 2022-08-16 ### Other changes @@ -28,12 +29,19 @@ multiple users into `RefungibleMultipleItems` call. ## [v0.2.0] - 2022-08-01 + ### Deprecated + - `CreateReFungibleData::const_data` ## [v0.1.2] - 2022-07-25 + ### Added + - Type aliases `CollectionName`, `CollectionDescription`, `CollectionTokenPrefix` + ## [v0.1.1] - 2022-07-22 + ### Added -- Аields with properties to `CreateReFungibleData` and `CreateRefungibleExData`. \ No newline at end of file + +- Fields with properties to `CreateReFungibleData` and `CreateRefungibleExData`. --- a/primitives/data-structs/src/lib.rs +++ b/primitives/data-structs/src/lib.rs @@ -120,22 +120,22 @@ // TODO: not used. Delete? pub const COLLECTION_FIELD_LIMIT: u32 = CONST_ON_CHAIN_SCHEMA_LIMIT; -/// Maximum length for collection name. +/// Maximal length of a collection name. pub const MAX_COLLECTION_NAME_LENGTH: u32 = 64; -/// Maximum length for collection description. +/// Maximal length of a collection description. pub const MAX_COLLECTION_DESCRIPTION_LENGTH: u32 = 256; -/// Maximal token prefix length. +/// Maximal length of a token prefix. pub const MAX_TOKEN_PREFIX_LENGTH: u32 = 16; -/// Maximal lenght of property key. +/// Maximal length of a property key. pub const MAX_PROPERTY_KEY_LENGTH: u32 = 256; -/// Maximal lenght of property value. +/// Maximal length of a property value. pub const MAX_PROPERTY_VALUE_LENGTH: u32 = 32768; -/// Maximum properties that can be assigned to token. +/// A maximum number of token properties. pub const MAX_PROPERTIES_PER_ITEM: u32 = 64; /// Maximal lenght of extended property value. @@ -144,7 +144,7 @@ /// Maximum size for all collection properties. pub const MAX_COLLECTION_PROPERTIES_SIZE: u32 = 40960; -/// Maximum size for all token properties. +/// Maximum size of all token properties. pub const MAX_TOKEN_PROPERTIES_SIZE: u32 = 32768; /// How much items can be created per single @@ -609,6 +609,24 @@ } impl CollectionLimits { + pub fn with_default_limits(collection_type: CollectionMode) -> Self { + CollectionLimits { + account_token_ownership_limit: Some(ACCOUNT_TOKEN_OWNERSHIP_LIMIT), + sponsored_data_size: Some(CUSTOM_DATA_LIMIT), + sponsored_data_rate_limit: Some(SponsoringRateLimit::SponsoringDisabled), + token_limit: Some(COLLECTION_TOKEN_LIMIT), + sponsor_transfer_timeout: match collection_type { + CollectionMode::NFT => Some(NFT_SPONSOR_TRANSFER_TIMEOUT), + CollectionMode::ReFungible => Some(REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT), + CollectionMode::Fungible(_) => Some(FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT), + }, + sponsor_approve_timeout: Some(SPONSOR_APPROVE_TIMEOUT), + owner_can_transfer: Some(false), + owner_can_destroy: Some(true), + transfers_enabled: Some(true), + } + } + /// Get effective value for [`account_token_ownership_limit`](self.account_token_ownership_limit). pub fn account_token_ownership_limit(&self) -> u32 { self.account_token_ownership_limit --- a/tests/package.json +++ b/tests/package.json @@ -102,6 +102,7 @@ "testXcmTransferStatemine": "mocha --timeout 9999999 -r ts-node/register ./**/xcm/xcmTransferStatemine.test.ts statemineId=1000 uniqueId=5000", "testXcmTransferMoonbeam": "mocha --timeout 9999999 -r ts-node/register ./**/xcm/xcmTransferMoonbeam.test.ts", "benchMintingFee": "ts-node src/benchmarks/mintFee/benchmark.ts", + "testApiConsts": "mocha --timeout 9999999 -r ts-node/register ./**/apiConsts.test.ts", "load": "mocha --timeout 9999999 -r ts-node/register './**/*.load.ts'", "loadTransfer": "ts-node src/transfer.nload.ts", "polkadot-types-fetch-metadata": "curl -H 'Content-Type: application/json' -d '{\"id\":\"1\", \"jsonrpc\":\"2.0\", \"method\": \"state_getMetadata\", \"params\":[]}' http://localhost:9933 > src/interfaces/metadata.json", --- /dev/null +++ b/tests/src/apiConsts.test.ts @@ -0,0 +1,120 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +import {ApiPromise} from '@polkadot/api'; +import {ApiBase} from '@polkadot/api/base'; +import {usingPlaygrounds, itSub, expect} from './util'; + + +const MAX_COLLECTION_DESCRIPTION_LENGTH = 256n; +const MAX_COLLECTION_NAME_LENGTH = 64n; +const COLLECTION_ADMINS_LIMIT = 5n; +const MAX_COLLECTION_PROPERTIES_SIZE = 40960n; +const MAX_TOKEN_PREFIX_LENGTH = 16n; +const MAX_PROPERTY_KEY_LENGTH = 256n; +const MAX_PROPERTY_VALUE_LENGTH = 32768n; +const MAX_PROPERTIES_PER_ITEM = 64n; +const MAX_TOKEN_PROPERTIES_SIZE = 32768n; +const NESTING_BUDGET = 5n; + +const DEFAULT_COLLETCTION_LIMIT = { + accountTokenOwnershipLimit: '1,000,000', + sponsoredDataSize: '2,048', + sponsoredDataRateLimit: 'SponsoringDisabled', + tokenLimit: '4,294,967,295', + sponsorTransferTimeout: '5', + sponsorApproveTimeout: '5', + ownerCanTransfer: false, + ownerCanDestroy: true, + transfersEnabled: true, +}; + +const EVM_COLLECTION_HELPERS_ADDRESS = '0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f'; +const HELPERS_CONTRACT_ADDRESS = '0x842899ECF380553E8a4de75bF534cdf6fBF64049'; + +describe('integration test: API UNIQUE consts', () => { + let api: ApiPromise; + + before(async () => { + await usingPlaygrounds(async (helper) => { + api = await helper.getApi(); + }); + }); + + itSub('DEFAULT_NFT_COLLECTION_LIMITS', () => { + expect(api.consts.unique.nftDefaultCollectionLimits.toHuman()).to.deep.equal(DEFAULT_COLLETCTION_LIMIT); + }); + + itSub('DEFAULT_RFT_COLLECTION_LIMITS', () => { + expect(api.consts.unique.rftDefaultCollectionLimits.toHuman()).to.deep.equal(DEFAULT_COLLETCTION_LIMIT); + }); + + itSub('DEFAULT_FT_COLLECTION_LIMITS', () => { + expect(api.consts.unique.ftDefaultCollectionLimits.toHuman()).to.deep.equal(DEFAULT_COLLETCTION_LIMIT); + }); + + itSub('MAX_COLLECTION_NAME_LENGTH', () => { + checkConst(api.consts.unique.maxCollectionNameLength, MAX_COLLECTION_NAME_LENGTH); + }); + + itSub('MAX_COLLECTION_DESCRIPTION_LENGTH', () => { + checkConst(api.consts.unique.maxCollectionDescriptionLength, MAX_COLLECTION_DESCRIPTION_LENGTH); + }); + + itSub('MAX_COLLECTION_PROPERTIES_SIZE', () => { + checkConst(api.consts.unique.maxCollectionPropertiesSize, MAX_COLLECTION_PROPERTIES_SIZE); + }); + + itSub('MAX_TOKEN_PREFIX_LENGTH', () => { + checkConst(api.consts.unique.maxTokenPrefixLength, MAX_TOKEN_PREFIX_LENGTH); + }); + + itSub('MAX_PROPERTY_KEY_LENGTH', () => { + checkConst(api.consts.unique.maxPropertyKeyLength, MAX_PROPERTY_KEY_LENGTH); + }); + + itSub('MAX_PROPERTY_VALUE_LENGTH', () => { + checkConst(api.consts.unique.maxPropertyValueLength, MAX_PROPERTY_VALUE_LENGTH); + }); + + itSub('MAX_PROPERTIES_PER_ITEM', () => { + checkConst(api.consts.unique.maxPropertiesPerItem, MAX_PROPERTIES_PER_ITEM); + }); + + itSub('NESTING_BUDGET', () => { + checkConst(api.consts.unique.nestingBudget, NESTING_BUDGET); + }); + + itSub('MAX_TOKEN_PROPERTIES_SIZE', () => { + checkConst(api.consts.unique.maxTokenPropertiesSize, MAX_TOKEN_PROPERTIES_SIZE); + }); + + itSub('COLLECTION_ADMINS_LIMIT', () => { + checkConst(api.consts.unique.collectionAdminsLimit, COLLECTION_ADMINS_LIMIT); + }); + + itSub('HELPERS_CONTRACT_ADDRESS', () => { + expect(api.consts.evmContractHelpers.contractAddress.toString().toLowerCase()).to.be.equal(HELPERS_CONTRACT_ADDRESS.toLowerCase()); + }); + + itSub('EVM_COLLECTION_HELPERS_ADDRESS', () => { + expect(api.consts.common.contractAddress.toString().toLowerCase()).to.be.equal(EVM_COLLECTION_HELPERS_ADDRESS.toLowerCase()); + }); +}); + +function checkConst(constValue: any, expectedValue: T) { + expect(constValue.toBigInt()).equal(expectedValue); +} \ No newline at end of file --- a/tests/src/eth/abi/collectionHelpers.json +++ b/tests/src/eth/abi/collectionHelpers.json @@ -32,6 +32,15 @@ "type": "event" }, { + "inputs": [ + { "internalType": "uint32", "name": "collectionId", "type": "uint32" } + ], + "name": "collectionAddress", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { "inputs": [], "name": "collectionCreationFee", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], @@ -40,6 +49,19 @@ }, { "inputs": [ + { + "internalType": "address", + "name": "collectionAddress", + "type": "address" + } + ], + "name": "collectionId", + "outputs": [{ "internalType": "uint32", "name": "", "type": "uint32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ { "internalType": "string", "name": "name", "type": "string" }, { "internalType": "uint8", "name": "decimals", "type": "uint8" }, { "internalType": "string", "name": "description", "type": "string" }, --- a/tests/src/eth/abi/fungible.json +++ b/tests/src/eth/abi/fungible.json @@ -201,6 +201,13 @@ }, { "inputs": [], + "name": "collectionHelperAddress", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], "name": "collectionOwner", "outputs": [ { --- a/tests/src/eth/abi/nonFungible.json +++ b/tests/src/eth/abi/nonFungible.json @@ -231,6 +231,13 @@ }, { "inputs": [], + "name": "collectionHelperAddress", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], "name": "collectionOwner", "outputs": [ { --- a/tests/src/eth/abi/reFungible.json +++ b/tests/src/eth/abi/reFungible.json @@ -213,6 +213,13 @@ }, { "inputs": [], + "name": "collectionHelperAddress", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], "name": "collectionOwner", "outputs": [ { --- a/tests/src/eth/api/CollectionHelpers.sol +++ b/tests/src/eth/api/CollectionHelpers.sol @@ -19,7 +19,7 @@ } /// @title Contract, which allows users to operate with collections -/// @dev the ERC-165 identifier for this interface is 0x7dea03b1 +/// @dev the ERC-165 identifier for this interface is 0xe65011aa interface CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents { /// Create an NFT collection /// @param name Name of the collection @@ -78,4 +78,18 @@ /// @dev EVM selector for this function is: 0xd23a7ab1, /// or in textual repr: collectionCreationFee() function collectionCreationFee() external view returns (uint256); + + /// Returns address of a collection. + /// @param collectionId - CollectionId of the collection + /// @return eth mirror address of the collection + /// @dev EVM selector for this function is: 0x2e716683, + /// or in textual repr: collectionAddress(uint32) + function collectionAddress(uint32 collectionId) external view returns (address); + + /// Returns collectionId of a collection. + /// @param collectionAddress - Eth address of the collection + /// @return collectionId of the collection + /// @dev EVM selector for this function is: 0xb5cb7498, + /// or in textual repr: collectionId(address) + function collectionId(address collectionAddress) external view returns (uint32); } --- a/tests/src/eth/api/UniqueFungible.sol +++ b/tests/src/eth/api/UniqueFungible.sol @@ -354,7 +354,7 @@ event Approval(address indexed owner, address indexed spender, uint256 value); } -/// @dev the ERC-165 identifier for this interface is 0x942e8b22 +/// @dev the ERC-165 identifier for this interface is 0x8cb847c4 interface ERC20 is Dummy, ERC165, ERC20Events { /// @dev EVM selector for this function is: 0x06fdde03, /// or in textual repr: name() @@ -395,6 +395,11 @@ /// @dev EVM selector for this function is: 0xdd62ed3e, /// or in textual repr: allowance(address,address) function allowance(address owner, address spender) external view returns (uint256); + + /// @notice Returns collection helper contract address + /// @dev EVM selector for this function is: 0x1896cce6, + /// or in textual repr: collectionHelperAddress() + function collectionHelperAddress() external view returns (address); } interface UniqueFungible is Dummy, ERC165, ERC20, ERC20Mintable, ERC20UniqueExtensions, Collection {} --- a/tests/src/eth/api/UniqueNFT.sol +++ b/tests/src/eth/api/UniqueNFT.sol @@ -605,7 +605,7 @@ /// @title ERC-721 Non-Fungible Token Standard /// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md -/// @dev the ERC-165 identifier for this interface is 0x80ac58cd +/// @dev the ERC-165 identifier for this interface is 0x983a942b interface ERC721 is Dummy, ERC165, ERC721Events { /// @notice Count all NFTs assigned to an owner /// @dev NFTs assigned to the zero address are considered invalid, and this @@ -685,6 +685,11 @@ /// @dev EVM selector for this function is: 0xe985e9c5, /// or in textual repr: isApprovedForAll(address,address) function isApprovedForAll(address owner, address operator) external view returns (address); + + /// @notice Returns collection helper contract address + /// @dev EVM selector for this function is: 0x1896cce6, + /// or in textual repr: collectionHelperAddress() + function collectionHelperAddress() external view returns (address); } interface UniqueNFT is --- a/tests/src/eth/api/UniqueRefungible.sol +++ b/tests/src/eth/api/UniqueRefungible.sol @@ -604,7 +604,7 @@ /// @title ERC-721 Non-Fungible Token Standard /// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md -/// @dev the ERC-165 identifier for this interface is 0x58800161 +/// @dev the ERC-165 identifier for this interface is 0x4016cd87 interface ERC721 is Dummy, ERC165, ERC721Events { /// @notice Count all RFTs assigned to an owner /// @dev RFTs assigned to the zero address are considered invalid, and this @@ -682,6 +682,11 @@ /// @dev EVM selector for this function is: 0xe985e9c5, /// or in textual repr: isApprovedForAll(address,address) function isApprovedForAll(address owner, address operator) external view returns (address); + + /// @notice Returns collection helper contract address + /// @dev EVM selector for this function is: 0x1896cce6, + /// or in textual repr: collectionHelperAddress() + function collectionHelperAddress() external view returns (address); } interface UniqueRefungible is --- /dev/null +++ b/tests/src/eth/collectionHelperAddress.test.ts @@ -0,0 +1,72 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +import {itEth, usingEthPlaygrounds, expect} from './util'; +import {IKeyringPair} from '@polkadot/types/types'; +import {Pallets} from '../util'; + +const EVM_COLLECTION_HELPERS_ADDRESS = '0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f'; + +describe('[eth]CollectionHelperAddress test: ERC20/ERC721 ', () => { + let donor: IKeyringPair; + + before(async function() { + await usingEthPlaygrounds(async (helper, privateKey) => { + donor = await privateKey({filename: __filename}); + }); + }); + + itEth('NFT', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + + const {collectionAddress: nftCollectionAddress} = await helper.eth.createNFTCollection(owner, 'Sponsor', 'absolutely anything', 'ROC'); + const nftCollection = helper.ethNativeContract.collection(nftCollectionAddress, 'nft', owner); + + expect((await nftCollection.methods.collectionHelperAddress().call()) + .toString().toLowerCase()).to.be.equal(EVM_COLLECTION_HELPERS_ADDRESS); + }); + + itEth.ifWithPallets('RFT ', [Pallets.ReFungible], async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + + const {collectionAddress: rftCollectionAddress} = await helper.eth.createRFTCollection(owner, 'Sponsor', 'absolutely anything', 'ROC'); + + const rftCollection = helper.ethNativeContract.collection(rftCollectionAddress, 'rft', owner); + expect((await rftCollection.methods.collectionHelperAddress().call()) + .toString().toLowerCase()).to.be.equal(EVM_COLLECTION_HELPERS_ADDRESS); + }); + + itEth('FT', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + + const {collectionAddress} = await helper.eth.createFungibleCollection(owner, 'Sponsor', 18, 'absolutely anything', 'ROC'); + const collection = helper.ethNativeContract.collection(collectionAddress, 'ft', owner); + + expect((await collection.methods.collectionHelperAddress().call()) + .toString().toLowerCase()).to.be.equal(EVM_COLLECTION_HELPERS_ADDRESS); + }); + + itEth('[collectionHelpers] convert collectionId into address', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const collectionId = 7; + const collectionAddress = helper.ethAddress.fromCollectionId(collectionId); + const helperContract = helper.ethNativeContract.collectionHelpers(owner); + + expect(await helperContract.methods.collectionAddress(collectionId).call()).to.be.equal(collectionAddress); + expect(parseInt(await helperContract.methods.collectionId(collectionAddress).call())).to.be.equal(collectionId); + }); + +}); --- a/tests/src/eth/util/playgrounds/unique.dev.ts +++ b/tests/src/eth/util/playgrounds/unique.dev.ts @@ -103,12 +103,12 @@ contractHelpers(caller: string): Contract { const web3 = this.helper.getWeb3(); - return new web3.eth.Contract(contractHelpersAbi as any, '0x842899ECF380553E8a4de75bF534cdf6fBF64049', {from: caller, gas: this.helper.eth.DEFAULT_GAS}); + return new web3.eth.Contract(contractHelpersAbi as any, this.helper.getApi().consts.evmContractHelpers.contractAddress.toString(), {from: caller, gas: this.helper.eth.DEFAULT_GAS}); } collectionHelpers(caller: string) { const web3 = this.helper.getWeb3(); - return new web3.eth.Contract(collectionHelpersAbi as any, '0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f', {from: caller, gas: this.helper.eth.DEFAULT_GAS}); + return new web3.eth.Contract(collectionHelpersAbi as any, this.helper.getApi().consts.common.contractAddress.toString(), {from: caller, gas: this.helper.eth.DEFAULT_GAS}); } collection(address: string, mode: TCollectionMode, caller?: string, mergeDeprecated = false): Contract { --- a/tests/src/interfaces/augment-api-consts.ts +++ b/tests/src/interfaces/augment-api-consts.ts @@ -8,8 +8,8 @@ import type { ApiTypes, AugmentedConst } from '@polkadot/api-base/types'; import type { Option, u128, u16, u32, u64, u8 } from '@polkadot/types-codec'; import type { Codec } from '@polkadot/types-codec/types'; -import type { Perbill, Permill, Weight } from '@polkadot/types/interfaces/runtime'; -import type { FrameSupportPalletId, FrameSystemLimitsBlockLength, FrameSystemLimitsBlockWeights, SpVersionRuntimeVersion, SpWeightsRuntimeDbWeight, XcmV1MultiLocation } from '@polkadot/types/lookup'; +import type { H160, Perbill, Permill, Weight } from '@polkadot/types/interfaces/runtime'; +import type { FrameSupportPalletId, FrameSystemLimitsBlockLength, FrameSystemLimitsBlockWeights, SpVersionRuntimeVersion, SpWeightsRuntimeDbWeight, UpDataStructsCollectionLimits, XcmV1MultiLocation } from '@polkadot/types/lookup'; export type __AugmentedConst = AugmentedConst; @@ -70,6 +70,10 @@ **/ collectionCreationPrice: u128 & AugmentedConst; /** + * Address under which the CollectionHelper contract would be available. + **/ + contractAddress: H160 & AugmentedConst; + /** * Generic const **/ [key: string]: Codec; @@ -82,6 +86,16 @@ **/ [key: string]: Codec; }; + evmContractHelpers: { + /** + * Address, under which magic contract will be available + **/ + contractAddress: H160 & AugmentedConst; + /** + * Generic const + **/ + [key: string]: Codec; + }; inflation: { /** * Number of blocks that pass between treasury balance updates due to inflation @@ -231,6 +245,64 @@ **/ [key: string]: Codec; }; + unique: { + /** + * Maximum admins per collection. + **/ + collectionAdminsLimit: u32 & AugmentedConst; + /** + * Default FT collection limit. + **/ + ftDefaultCollectionLimits: UpDataStructsCollectionLimits & AugmentedConst; + /** + * Maximal length of a collection description. + **/ + maxCollectionDescriptionLength: u32 & AugmentedConst; + /** + * Maximal length of a collection name. + **/ + maxCollectionNameLength: u32 & AugmentedConst; + /** + * Maximum size for all collection properties. + **/ + maxCollectionPropertiesSize: u32 & AugmentedConst; + /** + * A maximum number of token properties. + **/ + maxPropertiesPerItem: u32 & AugmentedConst; + /** + * Maximal length of a property key. + **/ + maxPropertyKeyLength: u32 & AugmentedConst; + /** + * Maximal length of a property value. + **/ + maxPropertyValueLength: u32 & AugmentedConst; + /** + * Maximal length of a token prefix. + **/ + maxTokenPrefixLength: u32 & AugmentedConst; + /** + * Maximum size of all token properties. + **/ + maxTokenPropertiesSize: u32 & AugmentedConst; + /** + * A maximum number of levels of depth in the token nesting tree. + **/ + nestingBudget: u32 & AugmentedConst; + /** + * Default NFT collection limit. + **/ + nftDefaultCollectionLimits: UpDataStructsCollectionLimits & AugmentedConst; + /** + * Default RFT collection limit. + **/ + rftDefaultCollectionLimits: UpDataStructsCollectionLimits & AugmentedConst; + /** + * Generic const + **/ + [key: string]: Codec; + }; vesting: { /** * The minimum amount transferred to call `vested_transfer`.