difftreelog
Merge branch 'develop' into tests/eth-helpers
in: master
59 files changed
.maintain/scripts/generate_abi.shdiffbeforeafterboth--- 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
Cargo.lockdiffbeforeafterboth--- 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",
crates/evm-coder/CHANGELOG.mddiffbeforeafterboth--- 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.
<!-- bureaucrate goes here -->
+## [v0.1.5] - 2022-11-30
+
+### Added
+- Derive macro to support structures and enums.
## [v0.1.4] - 2022-11-02
crates/evm-coder/Cargo.tomldiffbeforeafterboth--- 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]
crates/evm-coder/procedural/Cargo.tomldiffbeforeafterboth--- 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"
crates/evm-coder/procedural/src/abi_derive/derive_enum.rsdiffbeforeafterboth--- /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<Item = &'a syn::Ident>,
+) -> 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<Item = &'a syn::Ident>,
+) -> 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<u8> for #name {
+ type Error = &'static str;
+
+ fn try_from(value: u8) -> ::core::result::Result<Self, Self::Error> {
+ 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 = <u8 as ::evm_coder::abi::AbiType>::SIGNATURE;
+
+ fn is_dynamic() -> bool {
+ <u8 as ::evm_coder::abi::AbiType>::is_dynamic()
+ }
+ fn size() -> usize {
+ <u8 as ::evm_coder::abi::AbiType>::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<Self> {
+ Ok(
+ <u8 as ::evm_coder::abi::AbiRead>::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::<Self>())
+ }
+
+ 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<Item = &'a syn::Ident>,
+ option_count: usize,
+ enum_options_docs: impl Iterator<Item = syn::Result<Vec<proc_macro2::TokenStream>>>,
+ 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 {} {{", <Self as ::evm_coder::solidity::StructCollect>::name()).unwrap();
+ #(#enum_options)*
+ writeln!(str, "}}").unwrap();
+ str
+ }
+ }
+ )
+}
+
+pub fn check_and_count_options(de: &syn::DataEnum) -> syn::Result<usize> {
+ 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(())
+}
crates/evm-coder/procedural/src/abi_derive/derive_struct.rsdiffbeforeafterboth--- /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<Item = &'a syn::Type> + 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<Item = &'a syn::Type> + 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<Item = syn::Ident> + 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<Item = syn::Ident> + 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<Item = syn::Ident> + 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<Vec<proc_macro2::TokenStream>> {
+ 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<Self> {
+ 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<Item = &'a syn::Type> + 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<String> {
+ let mut collected =
+ Vec::with_capacity(<Self as ::evm_coder::solidity::SolidityType>::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<Item = &'a syn::Type> + 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::<Self>())
+ }
+
+ 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::<Self>())?;
+
+ #(#arg_dafaults)*
+
+ write!(writer, ")")
+ }
+ }
+ }
+}
+
+pub fn impl_struct_solidity_struct_collect<'a>(
+ name: &syn::Ident,
+ field_names: impl Iterator<Item = proc_macro2::Ident> + Clone,
+ field_types: impl Iterator<Item = &'a syn::Type> + Clone,
+ field_docs: impl Iterator<Item = syn::Result<Vec<proc_macro2::TokenStream>>> + Clone,
+ docs: &[proc_macro2::TokenStream],
+) -> syn::Result<proc_macro2::TokenStream> {
+ 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
+ }
+ }
+ })
+}
crates/evm-coder/procedural/src/abi_derive/mod.rsdiffbeforeafterboth--- /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<proc_macro2::TokenStream> {
+ 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<proc_macro2::TokenStream> {
+ 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<proc_macro2::TokenStream> {
+ 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<Vec<proc_macro2::TokenStream>> {
+ 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()
+}
crates/evm-coder/procedural/src/lib.rsdiffbeforeafterboth--- 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()
+}
crates/evm-coder/procedural/src/solidity_interface.rsdiffbeforeafterboth--- 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)) }
crates/evm-coder/src/abi/impls.rsdiffbeforeafterboth--- 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<bytes> {
+ Ok(bytes(reader.bytes()?))
}
}
-impl AbiRead for bool {
- fn abi_read(reader: &mut AbiReader) -> Result<bool> {
- 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<bytes4> {
+ reader.bytes4()
}
}
-impl AbiRead for uint8 {
- fn abi_read(reader: &mut AbiReader) -> Result<uint8> {
- reader.uint8()
+
+impl<T: AbiWrite> 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<T: AbiType> 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<bytes> {
- Ok(bytes(reader.bytes()?))
+ T::size()
}
}
-impl<R: AbiRead + sealed::CanBePlacedInVec> AbiRead for Vec<R> {
- fn abi_read(reader: &mut AbiReader) -> Result<Vec<R>> {
+impl<T: AbiType + AbiRead + sealed::CanBePlacedInVec> AbiRead for Vec<T> {
+ fn abi_read(reader: &mut AbiReader) -> Result<Vec<T>> {
let mut sub = reader.subresult(None)?;
let size = sub.uint32()? as usize;
sub.subresult_offset = sub.offset;
+ let is_dynamic = <T as AbiType>::is_dynamic();
let mut out = Vec::with_capacity(size);
for _ in 0..size {
- out.push(<R>::abi_read(&mut sub)?);
+ out.push(<T as AbiRead>::abi_read(&mut sub)?);
+ if !is_dynamic {
+ sub.bytes_read(<T as AbiType>::size());
+ };
}
Ok(out)
}
}
-impl<R: AbiType> AbiType for Vec<R> {
- const SIGNATURE: SignatureUnit = make_signature!(new nameof(R::SIGNATURE) fixed("[]"));
+impl<T: AbiType> AbiType for Vec<T> {
+ 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 {
- <address as AbiType>::size() + <uint256 as AbiType>::size()
- }
-}
-
-impl AbiRead for EthCrossAccount {
- fn abi_read(reader: &mut AbiReader) -> Result<EthCrossAccount> {
- let size = if !EthCrossAccount::is_dynamic() {
- Some(<EthCrossAccount as AbiType>::size())
- } else {
- None
- };
- let mut subresult = reader.subresult(size)?;
- let eth = <address>::abi_read(&mut subresult)?;
- let sub = <uint256>::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<T: AbiWrite + AbiType> AbiWrite for Vec<T> {
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 = <Self>::is_dynamic();
+ let size = if !is_dynamic { Some(<Self>::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 <Self as AbiType>::is_dynamic() {
let mut sub = AbiWriter::new();
$($ident.abi_write(&mut sub);)+
writer.write_subresult(sub);
crates/evm-coder/src/abi/mod.rsdiffbeforeafterboth--- 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<usize>) -> Result<AbiReader<'i>> {
+ pub fn subresult(&mut self, size: Option<usize>) -> Result<AbiReader<'i>> {
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))
crates/evm-coder/src/abi/test.rsdiffbeforeafterboth--- 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<T>(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 = <T>::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::<uint256>(
+ 0xdeadbeef,
+ U256([255, 0, 0, 0]),
+ &hex!(
+ "
+ deadbeef
+ 00000000000000000000000000000000000000000000000000000000000000ff
+ "
+ ),
+ );
+}
- 000000000000000000000000AB8E3D9134955566483B11E6825C9223B6737B10 // address
- 0000000000000000000000000000000000000000000000000000000000000014 // uint256
+#[test]
+fn encode_decode_string() {
+ test_impl::<String>(
+ 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::<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)[]
- 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::<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)[]
+ 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::<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 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 = <u8>::abi_read(&mut decoder).unwrap();
+ let p2 = <u8>::abi_read(&mut decoder).unwrap();
+ assert_eq!(p1, 0x0a);
+ assert_eq!(p2, 0x0b);
+}
crates/evm-coder/src/abi/traits.rsdiffbeforeafterboth--- 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<u8>` 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<T: AbiWrite> AbiWrite for &T {
- fn abi_write(&self, writer: &mut AbiWriter) {
- T::abi_write(self, writer);
}
}
crates/evm-coder/src/lib.rsdiffbeforeafterboth--- 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<u8>`)
+ 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<u8>);
/// 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<T>(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::<T>(cross_account_id),
- }
- } else {
- Self {
- eth: *cross_account_id.as_eth(),
- sub: Default::default(),
- }
- }
}
-
- pub fn into_sub_cross_account<T>(&self) -> crate::execution::Result<T::CrossAccountId>
- 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::<T>(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<T: pallet_evm::Config>(
- 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<T: pallet_evm::Config>(
- 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)]
crates/evm-coder/src/solidity.rsdiffbeforeafterboth--- 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 <http://www.gnu.org/licenses/>.
-
-//! 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<BTreeMap<string, usize>>,
- anonymous: RefCell<BTreeMap<Vec<string>, usize>>,
- id: Cell<usize>,
-}
-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<T: SolidityTupleType>(&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<T: StructCollect>(&self) -> String {
- self.collect(<T as StructCollect>::declaration());
- <T as StructCollect>::name()
- }
- pub fn finish(self) -> Vec<string> {
- let mut data = self.structs.into_inner().into_iter().collect::<Vec<_>>();
- 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<u8>`)
- 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<T: SolidityTypeName + sealed::CanBePlacedInVec> SolidityTypeName for Vec<T> {
- 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<string> {
- 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::<Self>())
- }
-
- fn is_simple() -> bool {
- false
- }
-
- fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
- write!(writer, "{}(", tc.collect_struct::<Self>())?;
- 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::<Self>())
- }
-
- fn is_simple() -> bool {
- false
- }
-
- fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
- write!(writer, "{}(", tc.collect_struct::<Self>())?;
- address::solidity_default(writer, tc)?;
- write!(writer, ",")?;
- uint256::solidity_default(writer, tc)?;
- write!(writer, ")")
- }
-}
-
-impl SolidityTupleType for Property {
- fn names(tc: &TypeCollector) -> Vec<string> {
- 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<String>;
- 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<string> {
- 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::<Self>())
- }
- 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::<Self>())?;
- 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<T>(PhantomData<*const T>);
-
-impl<T: SolidityTypeName> SolidityArguments for UnnamedArgument<T> {
- 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<T>(&'static str, PhantomData<*const T>);
-
-impl<T> NamedArgument<T> {
- pub fn new(name: &'static str) -> Self {
- Self(name, Default::default())
- }
-}
-
-impl<T: SolidityTypeName> SolidityArguments for NamedArgument<T> {
- 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<T>(pub bool, &'static str, PhantomData<*const T>);
-
-impl<T> SolidityEventArgument<T> {
- pub fn new(indexed: bool, name: &'static str) -> Self {
- Self(indexed, name, Default::default())
- }
-}
-
-impl<T: SolidityTypeName> SolidityArguments for SolidityEventArgument<T> {
- 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<A, R> {
- 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<A: SolidityArguments, R: SolidityArguments> SolidityFunctions for SolidityFunction<A, R> {
- 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<F: SolidityFunctions> {
- pub docs: &'static [&'static str],
- pub selector: bytes4,
- pub name: &'static str,
- pub is: &'static [&'static str],
- pub functions: F,
-}
-
-impl<F: SolidityFunctions> SolidityInterface<F> {
- 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<A> {
- pub name: &'static str,
- pub args: A,
-}
-
-impl<A: SolidityArguments> SolidityFunctions for SolidityEvent<A> {
- 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, ");")
- }
-}
crates/evm-coder/src/solidity/impls.rsdiffbeforeafterboth--- /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<T: SolidityTypeName + sealed::CanBePlacedInVec> SolidityTypeName for Vec<T> {
+ 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<string> {
+ 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::<Self>())
+ }
+ 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::<Self>())?;
+ 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::<Self>())
+ }
+
+ fn is_simple() -> bool {
+ false
+ }
+
+ fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
+ write!(writer, "{}(", tc.collect_struct::<Self>())?;
+ address::solidity_default(writer, tc)?;
+ write!(writer, ",")?;
+ uint256::solidity_default(writer, tc)?;
+ write!(writer, ")")
+ }
+}
+
+impl SolidityType for Property {
+ fn names(tc: &TypeCollector) -> Vec<string> {
+ 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
+ }
+}
crates/evm-coder/src/solidity/mod.rsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+//! 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<BTreeMap<string, usize>>,
+ anonymous: RefCell<BTreeMap<Vec<string>, usize>>,
+ id: Cell<usize>,
+}
+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<T: SolidityType>(&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<T: StructCollect>(&self) -> String {
+ self.collect(<T as StructCollect>::declaration());
+ <T as StructCollect>::name()
+ }
+ pub fn finish(self) -> Vec<string> {
+ let mut data = self.structs.into_inner().into_iter().collect::<Vec<_>>();
+ data.sort_by_key(|(_, id)| Reverse(*id));
+ data.into_iter().map(|(code, _)| code).collect()
+ }
+}
+#[derive(Default)]
+pub struct UnnamedArgument<T>(PhantomData<*const T>);
+
+impl<T: SolidityTypeName> SolidityArguments for UnnamedArgument<T> {
+ 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<T>(&'static str, PhantomData<*const T>);
+
+impl<T> NamedArgument<T> {
+ pub fn new(name: &'static str) -> Self {
+ Self(name, Default::default())
+ }
+}
+
+impl<T: SolidityTypeName> SolidityArguments for NamedArgument<T> {
+ 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<T>(pub bool, &'static str, PhantomData<*const T>);
+
+impl<T> SolidityEventArgument<T> {
+ pub fn new(indexed: bool, name: &'static str) -> Self {
+ Self(indexed, name, Default::default())
+ }
+}
+
+impl<T: SolidityTypeName> SolidityArguments for SolidityEventArgument<T> {
+ 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<A, R> {
+ 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<A: SolidityArguments, R: SolidityArguments> SolidityFunctions for SolidityFunction<A, R> {
+ 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<F: SolidityFunctions> {
+ pub docs: &'static [&'static str],
+ pub selector: bytes4,
+ pub name: &'static str,
+ pub is: &'static [&'static str],
+ pub functions: F,
+}
+
+impl<F: SolidityFunctions> SolidityInterface<F> {
+ 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<A> {
+ pub name: &'static str,
+ pub args: A,
+}
+
+impl<A: SolidityArguments> SolidityFunctions for SolidityEvent<A> {
+ 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, ");")
+ }
+}
crates/evm-coder/src/solidity/traits.rsdiffbeforeafterboth--- /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<String>;
+ 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;
+}
crates/evm-coder/tests/abi_derive_generation.rsdiffbeforeafterboth--- /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!(
+ <TypeStruct3DerivedMixedParam as ::evm_coder::solidity::StructCollect>::name(),
+ "TypeStruct3DerivedMixedParam"
+ );
+ similar_asserts::assert_eq!(
+ <TypeStruct3DerivedMixedParam as ::evm_coder::solidity::StructCollect>::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!(
+ <TypeStruct1SimpleParam as evm_coder::abi::AbiType>::SIGNATURE
+ .as_str()
+ .unwrap(),
+ "(uint8)"
+ );
+ assert_eq!(
+ <TypeStruct1DynamicParam as evm_coder::abi::AbiType>::SIGNATURE
+ .as_str()
+ .unwrap(),
+ "(string)"
+ );
+ assert_eq!(
+ <TypeStruct2SimpleParam as evm_coder::abi::AbiType>::SIGNATURE
+ .as_str()
+ .unwrap(),
+ "(uint8,uint32)"
+ );
+ assert_eq!(
+ <TypeStruct2DynamicParam as evm_coder::abi::AbiType>::SIGNATURE
+ .as_str()
+ .unwrap(),
+ "(string,bytes)"
+ );
+ assert_eq!(
+ <TypeStruct2MixedParam as evm_coder::abi::AbiType>::SIGNATURE
+ .as_str()
+ .unwrap(),
+ "(uint8,bytes)"
+ );
+ assert_eq!(
+ <TypeStruct1DerivedSimpleParam as evm_coder::abi::AbiType>::SIGNATURE
+ .as_str()
+ .unwrap(),
+ "((uint8))"
+ );
+ assert_eq!(
+ <TypeStruct2DerivedSimpleParam as evm_coder::abi::AbiType>::SIGNATURE
+ .as_str()
+ .unwrap(),
+ "((uint8),(uint8,uint32))"
+ );
+ assert_eq!(
+ <TypeStruct1DerivedDynamicParam as evm_coder::abi::AbiType>::SIGNATURE
+ .as_str()
+ .unwrap(),
+ "((string))"
+ );
+ assert_eq!(
+ <TypeStruct2DerivedDynamicParam as evm_coder::abi::AbiType>::SIGNATURE
+ .as_str()
+ .unwrap(),
+ "((string),(string,bytes))"
+ );
+ assert_eq!(
+ <TypeStruct3DerivedMixedParam as evm_coder::abi::AbiType>::SIGNATURE
+ .as_str()
+ .unwrap(),
+ "((uint8),(string,bytes),(uint8,bytes))"
+ );
+ }
+
+ #[test]
+ fn impl_abi_type_is_dynamic() {
+ assert_eq!(
+ <TypeStruct1SimpleParam as evm_coder::abi::AbiType>::is_dynamic(),
+ false
+ );
+ assert_eq!(
+ <TypeStruct1DynamicParam as evm_coder::abi::AbiType>::is_dynamic(),
+ true
+ );
+ assert_eq!(
+ <TypeStruct2SimpleParam as evm_coder::abi::AbiType>::is_dynamic(),
+ false
+ );
+ assert_eq!(
+ <TypeStruct2DynamicParam as evm_coder::abi::AbiType>::is_dynamic(),
+ true
+ );
+ assert_eq!(
+ <TypeStruct2MixedParam as evm_coder::abi::AbiType>::is_dynamic(),
+ true
+ );
+ assert_eq!(
+ <TypeStruct1DerivedSimpleParam as evm_coder::abi::AbiType>::is_dynamic(),
+ false
+ );
+ assert_eq!(
+ <TypeStruct2DerivedSimpleParam as evm_coder::abi::AbiType>::is_dynamic(),
+ false
+ );
+ assert_eq!(
+ <TypeStruct1DerivedDynamicParam as evm_coder::abi::AbiType>::is_dynamic(),
+ true
+ );
+ assert_eq!(
+ <TypeStruct2DerivedDynamicParam as evm_coder::abi::AbiType>::is_dynamic(),
+ true
+ );
+ assert_eq!(
+ <TypeStruct3DerivedMixedParam as evm_coder::abi::AbiType>::is_dynamic(),
+ true
+ );
+ }
+
+ #[test]
+ fn impl_abi_type_size() {
+ const ABI_ALIGNMENT: usize = 32;
+ assert_eq!(
+ <TypeStruct1SimpleParam as evm_coder::abi::AbiType>::size(),
+ ABI_ALIGNMENT
+ );
+ assert_eq!(
+ <TypeStruct1DynamicParam as evm_coder::abi::AbiType>::size(),
+ ABI_ALIGNMENT
+ );
+ assert_eq!(
+ <TypeStruct2SimpleParam as evm_coder::abi::AbiType>::size(),
+ ABI_ALIGNMENT * 2
+ );
+ assert_eq!(
+ <TypeStruct2DynamicParam as evm_coder::abi::AbiType>::size(),
+ ABI_ALIGNMENT * 2
+ );
+ assert_eq!(
+ <TypeStruct2MixedParam as evm_coder::abi::AbiType>::size(),
+ ABI_ALIGNMENT * 2
+ );
+ assert_eq!(
+ <TypeStruct1DerivedSimpleParam as evm_coder::abi::AbiType>::size(),
+ ABI_ALIGNMENT
+ );
+ assert_eq!(
+ <TypeStruct2DerivedSimpleParam as evm_coder::abi::AbiType>::size(),
+ ABI_ALIGNMENT * 3
+ );
+ assert_eq!(
+ <TypeStruct1DerivedDynamicParam as evm_coder::abi::AbiType>::size(),
+ ABI_ALIGNMENT
+ );
+ assert_eq!(
+ <TypeStruct2DerivedDynamicParam as evm_coder::abi::AbiType>::size(),
+ ABI_ALIGNMENT * 3
+ );
+ assert_eq!(
+ <TypeStruct3DerivedMixedParam as evm_coder::abi::AbiType>::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!(
+ <TupleStruct3DerivedMixedParam as ::evm_coder::solidity::StructCollect>::name(),
+ "TupleStruct3DerivedMixedParam"
+ );
+ similar_asserts::assert_eq!(
+ <TupleStruct3DerivedMixedParam as ::evm_coder::solidity::StructCollect>::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!(
+ <TypeStruct1SimpleParam as evm_coder::abi::AbiType>::SIGNATURE
+ .as_str()
+ .unwrap(),
+ <TupleStruct1SimpleParam as evm_coder::abi::AbiType>::SIGNATURE
+ .as_str()
+ .unwrap()
+ );
+ assert_eq!(
+ <TypeStruct1DynamicParam as evm_coder::abi::AbiType>::SIGNATURE
+ .as_str()
+ .unwrap(),
+ <TupleStruct1DynamicParam as evm_coder::abi::AbiType>::SIGNATURE
+ .as_str()
+ .unwrap()
+ );
+ assert_eq!(
+ <TypeStruct2SimpleParam as evm_coder::abi::AbiType>::SIGNATURE
+ .as_str()
+ .unwrap(),
+ <TupleStruct2SimpleParam as evm_coder::abi::AbiType>::SIGNATURE
+ .as_str()
+ .unwrap()
+ );
+ assert_eq!(
+ <TypeStruct2DynamicParam as evm_coder::abi::AbiType>::SIGNATURE
+ .as_str()
+ .unwrap(),
+ <TupleStruct2DynamicParam as evm_coder::abi::AbiType>::SIGNATURE
+ .as_str()
+ .unwrap()
+ );
+ assert_eq!(
+ <TypeStruct2MixedParam as evm_coder::abi::AbiType>::SIGNATURE
+ .as_str()
+ .unwrap(),
+ <TupleStruct2MixedParam as evm_coder::abi::AbiType>::SIGNATURE
+ .as_str()
+ .unwrap(),
+ );
+ assert_eq!(
+ <TypeStruct1DerivedSimpleParam as evm_coder::abi::AbiType>::SIGNATURE
+ .as_str()
+ .unwrap(),
+ <TupleStruct1DerivedSimpleParam as evm_coder::abi::AbiType>::SIGNATURE
+ .as_str()
+ .unwrap(),
+ );
+ assert_eq!(
+ <TypeStruct2DerivedSimpleParam as evm_coder::abi::AbiType>::SIGNATURE
+ .as_str()
+ .unwrap(),
+ <TupleStruct2DerivedSimpleParam as evm_coder::abi::AbiType>::SIGNATURE
+ .as_str()
+ .unwrap(),
+ );
+ assert_eq!(
+ <TypeStruct1DerivedDynamicParam as evm_coder::abi::AbiType>::SIGNATURE
+ .as_str()
+ .unwrap(),
+ <TupleStruct1DerivedDynamicParam as evm_coder::abi::AbiType>::SIGNATURE
+ .as_str()
+ .unwrap(),
+ );
+ assert_eq!(
+ <TypeStruct2DerivedDynamicParam as evm_coder::abi::AbiType>::SIGNATURE
+ .as_str()
+ .unwrap(),
+ <TupleStruct2DerivedDynamicParam as evm_coder::abi::AbiType>::SIGNATURE
+ .as_str()
+ .unwrap(),
+ );
+ assert_eq!(
+ <TypeStruct3DerivedMixedParam as evm_coder::abi::AbiType>::SIGNATURE
+ .as_str()
+ .unwrap(),
+ <TupleStruct3DerivedMixedParam as evm_coder::abi::AbiType>::SIGNATURE
+ .as_str()
+ .unwrap(),
+ );
+ }
+
+ #[test]
+ fn impl_abi_type_is_dynamic_same_for_structs() {
+ assert_eq!(
+ <TypeStruct1SimpleParam as evm_coder::abi::AbiType>::is_dynamic(),
+ <TupleStruct1SimpleParam as evm_coder::abi::AbiType>::is_dynamic()
+ );
+ assert_eq!(
+ <TypeStruct1DynamicParam as evm_coder::abi::AbiType>::is_dynamic(),
+ <TupleStruct1DynamicParam as evm_coder::abi::AbiType>::is_dynamic()
+ );
+ assert_eq!(
+ <TypeStruct2SimpleParam as evm_coder::abi::AbiType>::is_dynamic(),
+ <TupleStruct2SimpleParam as evm_coder::abi::AbiType>::is_dynamic()
+ );
+ assert_eq!(
+ <TypeStruct2DynamicParam as evm_coder::abi::AbiType>::is_dynamic(),
+ <TupleStruct2DynamicParam as evm_coder::abi::AbiType>::is_dynamic()
+ );
+ assert_eq!(
+ <TypeStruct2MixedParam as evm_coder::abi::AbiType>::is_dynamic(),
+ <TupleStruct2MixedParam as evm_coder::abi::AbiType>::is_dynamic()
+ );
+ assert_eq!(
+ <TypeStruct1DerivedSimpleParam as evm_coder::abi::AbiType>::is_dynamic(),
+ <TupleStruct1DerivedSimpleParam as evm_coder::abi::AbiType>::is_dynamic()
+ );
+ assert_eq!(
+ <TypeStruct2DerivedSimpleParam as evm_coder::abi::AbiType>::is_dynamic(),
+ <TupleStruct2DerivedSimpleParam as evm_coder::abi::AbiType>::is_dynamic()
+ );
+ assert_eq!(
+ <TypeStruct1DerivedDynamicParam as evm_coder::abi::AbiType>::is_dynamic(),
+ <TupleStruct1DerivedDynamicParam as evm_coder::abi::AbiType>::is_dynamic()
+ );
+ assert_eq!(
+ <TypeStruct2DerivedDynamicParam as evm_coder::abi::AbiType>::is_dynamic(),
+ <TupleStruct2DerivedDynamicParam as evm_coder::abi::AbiType>::is_dynamic()
+ );
+ assert_eq!(
+ <TypeStruct3DerivedMixedParam as evm_coder::abi::AbiType>::is_dynamic(),
+ <TupleStruct3DerivedMixedParam as evm_coder::abi::AbiType>::is_dynamic()
+ );
+ }
+
+ #[test]
+ fn impl_abi_type_size_same_for_structs() {
+ assert_eq!(
+ <TypeStruct1SimpleParam as evm_coder::abi::AbiType>::size(),
+ <TupleStruct1SimpleParam as evm_coder::abi::AbiType>::size()
+ );
+ assert_eq!(
+ <TypeStruct1DynamicParam as evm_coder::abi::AbiType>::size(),
+ <TupleStruct1DynamicParam as evm_coder::abi::AbiType>::size()
+ );
+ assert_eq!(
+ <TypeStruct2SimpleParam as evm_coder::abi::AbiType>::size(),
+ <TupleStruct2SimpleParam as evm_coder::abi::AbiType>::size()
+ );
+ assert_eq!(
+ <TypeStruct2DynamicParam as evm_coder::abi::AbiType>::size(),
+ <TupleStruct2DynamicParam as evm_coder::abi::AbiType>::size()
+ );
+ assert_eq!(
+ <TypeStruct2MixedParam as evm_coder::abi::AbiType>::size(),
+ <TupleStruct2MixedParam as evm_coder::abi::AbiType>::size()
+ );
+ assert_eq!(
+ <TypeStruct1DerivedSimpleParam as evm_coder::abi::AbiType>::size(),
+ <TupleStruct1DerivedSimpleParam as evm_coder::abi::AbiType>::size()
+ );
+ assert_eq!(
+ <TypeStruct2DerivedSimpleParam as evm_coder::abi::AbiType>::size(),
+ <TupleStruct2DerivedSimpleParam as evm_coder::abi::AbiType>::size()
+ );
+ assert_eq!(
+ <TypeStruct1DerivedDynamicParam as evm_coder::abi::AbiType>::size(),
+ <TupleStruct1DerivedDynamicParam as evm_coder::abi::AbiType>::size()
+ );
+ assert_eq!(
+ <TypeStruct2DerivedDynamicParam as evm_coder::abi::AbiType>::size(),
+ <TupleStruct2DerivedDynamicParam as evm_coder::abi::AbiType>::size()
+ );
+ assert_eq!(
+ <TypeStruct3DerivedMixedParam as evm_coder::abi::AbiType>::size(),
+ <TupleStruct3DerivedMixedParam as evm_coder::abi::AbiType>::size()
+ );
+ }
+
+ const FUNCTION_IDENTIFIER: u32 = 0xdeadbeef;
+
+ fn test_impl<Tuple, TupleStruct, TypeStruct>(
+ 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 = <TypeStruct>::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 = <TupleStruct>::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 = <Tuple>::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 = <Tuple>::abi_read(&mut decoder).unwrap();
+ assert_eq!(restored_tuple_data, tuple_data);
+ }
+ }
+
+ fn test_abi_write_impl<A>(data: &A) -> Vec<u8>
+ 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!(
+ <Color as evm_coder::abi::AbiType>::SIGNATURE
+ .as_str()
+ .unwrap(),
+ <u8 as evm_coder::abi::AbiType>::SIGNATURE.as_str().unwrap()
+ );
+ }
+
+ #[test]
+ fn impl_abi_type_is_dynamic_same_for_structs() {
+ assert_eq!(
+ <Color as evm_coder::abi::AbiType>::is_dynamic(),
+ <u8 as evm_coder::abi::AbiType>::is_dynamic()
+ );
+ }
+
+ #[test]
+ fn impl_abi_type_size_same_for_structs() {
+ assert_eq!(
+ <Color as evm_coder::abi::AbiType>::size(),
+ <u8 as evm_coder::abi::AbiType>::size()
+ );
+ }
+
+ #[test]
+ fn test_coder() {
+ const FUNCTION_IDENTIFIER: u32 = 0xdeadbeef;
+
+ let encoded_enum = {
+ let mut writer = evm_coder::abi::AbiWriter::new_call(FUNCTION_IDENTIFIER);
+ <Color as evm_coder::abi::AbiWrite>::abi_write(&Color::Green, &mut writer);
+ writer.finish()
+ };
+
+ let encoded_u8 = {
+ let mut writer = evm_coder::abi::AbiWriter::new_call(FUNCTION_IDENTIFIER);
+ <u8 as evm_coder::abi::AbiWrite>::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 =
+ <Color as evm_coder::abi::AbiRead>::abi_read(&mut decoder).unwrap();
+ assert_eq!(restored_enum_data, Color::Green);
+ }
+ }
+
+ #[test]
+ #[cfg(feature = "stubgen")]
+ fn struct_collect_enum() {
+ assert_eq!(
+ <Color as ::evm_coder::solidity::StructCollect>::name(),
+ "Color"
+ );
+ similar_asserts::assert_eq!(
+ <Color as ::evm_coder::solidity::StructCollect>::declaration(),
+ r#"/// @dev Some docs
+/// At multi
+/// line
+enum Color {
+ /// @dev Docs for Red
+ /// multi
+ /// line
+ Red,
+ Green,
+ /// @dev Docs for Blue
+ Blue
+}
+"#
+ );
+ }
+}
crates/evm-coder/tests/build_failed/abi_derive_enum_generation.rsdiffbeforeafterboth--- /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);
+}
crates/evm-coder/tests/build_failed/abi_derive_enum_generation.stderrdiffbeforeafterboth--- /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,
+ | ^
crates/evm-coder/tests/build_failed/abi_derive_struct_generation.rsdiffbeforeafterboth--- /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);
+}
crates/evm-coder/tests/build_failed/abi_derive_struct_generation.stderrdiffbeforeafterboth--- /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();
+ | ^^^^^^^^^^^^^^^^
pallets/common/src/erc.rsdiffbeforeafterboth--- 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.
pallets/common/src/eth.rsdiffbeforeafterboth--- 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<T>(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::<T>(cross_account_id),
+ }
+ } else {
+ Self {
+ eth: *cross_account_id.as_eth(),
+ sub: Default::default(),
+ }
+ }
+ }
+
+ pub fn into_sub_cross_account<T>(&self) -> evm_coder::execution::Result<T::CrossAccountId>
+ 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::<T>(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())
+ }
+ }
+}
pallets/common/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Common pallet18//!19//! The Common pallet provides functionality for handling collections.20//!21//! ## Overview22//!23//! The Common pallet provides an interface for common collection operations for different collection types24//! (see [CommonCollectionOperations]), as well as a generic dispatcher for these, see [dispatch] module.25//! It also provides this functionality to EVM, see [erc] and [eth] modules.26//!27//! The Common pallet provides functions for:28//!29//! - Setting and approving collection sponsor.30//! - Get\set\delete allow list.31//! - Get\set\delete collection properties.32//! - Get\set\delete collection property permissions.33//! - Get\set\delete token property permissions.34//! - Get\set\delete collection administrators.35//! - Checking access permissions.36//!37//! ### Terminology38//! **Collection sponsor** - For the collection, you can set a sponsor, at whose expense it will39//! be possible to mint tokens.40//!41//! **Allow list** - List of users who have the right to minting tokens.42//!43//! **Collection properties** - Collection properties are simply key-value stores where various44//! metadata can be placed.45//!46//! **Permissions on token properties** - For each property in the token can be set permission47//! to change, see [`PropertyPermission`].48//!49//! **Collection administrator** - For a collection, you can set administrators who have the right50//! to most actions on the collection.5152#![warn(missing_docs)]53#![cfg_attr(not(feature = "std"), no_std)]54extern crate alloc;5556use core::ops::{Deref, DerefMut};57use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};58use sp_std::vec::Vec;59use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};60use evm_coder::ToLog;61use frame_support::{62 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},63 ensure,64 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},65 dispatch::Pays,66 transactional,67};68use pallet_evm::GasWeightMapping;69use up_data_structs::{70 COLLECTION_NUMBER_LIMIT,71 Collection,72 RpcCollection,73 CollectionFlags,74 RpcCollectionFlags,75 CollectionId,76 CreateItemData,77 MAX_TOKEN_PREFIX_LENGTH,78 COLLECTION_ADMINS_LIMIT,79 TokenId,80 TokenChild,81 CollectionStats,82 MAX_TOKEN_OWNERSHIP,83 CollectionMode,84 NFT_SPONSOR_TRANSFER_TIMEOUT,85 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,86 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,87 MAX_SPONSOR_TIMEOUT,88 CUSTOM_DATA_LIMIT,89 CollectionLimits,90 CreateCollectionData,91 SponsorshipState,92 CreateItemExData,93 SponsoringRateLimit,94 budget::Budget,95 PhantomType,96 Property,97 Properties,98 PropertiesPermissionMap,99 PropertyKey,100 PropertyValue,101 PropertyPermission,102 PropertiesError,103 PropertyKeyPermission,104 TokenData,105 TrySetProperty,106 PropertyScope,107 // RMRK108 RmrkCollectionInfo,109 RmrkInstanceInfo,110 RmrkResourceInfo,111 RmrkPropertyInfo,112 RmrkBaseInfo,113 RmrkPartType,114 RmrkBoundedTheme,115 RmrkNftChild,116 CollectionPermissions,117};118119pub use pallet::*;120use sp_core::H160;121use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};122#[cfg(feature = "runtime-benchmarks")]123pub mod benchmarking;124pub mod dispatch;125pub mod erc;126pub mod eth;127pub mod weights;128129/// Weight info.130pub type SelfWeightOf<T> = <T as Config>::WeightInfo;131132/// Collection handle contains information about collection data and id.133/// Also provides functionality to count consumed gas.134///135/// CollectionHandle is used as a generic wrapper for collections of all types.136/// It allows to perform common operations and queries on any collection type,137/// both completely general for all, as well as their respective implementations of [`CommonCollectionOperations`].138#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]139pub struct CollectionHandle<T: Config> {140 /// Collection id141 pub id: CollectionId,142 collection: Collection<T::AccountId>,143 /// Substrate recorder for counting consumed gas144 pub recorder: SubstrateRecorder<T>,145}146147impl<T: Config> WithRecorder<T> for CollectionHandle<T> {148 fn recorder(&self) -> &SubstrateRecorder<T> {149 &self.recorder150 }151 fn into_recorder(self) -> SubstrateRecorder<T> {152 self.recorder153 }154}155156impl<T: Config> CollectionHandle<T> {157 /// Same as [CollectionHandle::new] but with an explicit gas limit.158 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {159 <CollectionById<T>>::get(id).map(|collection| Self {160 id,161 collection,162 recorder: SubstrateRecorder::new(gas_limit),163 })164 }165166 /// Same as [CollectionHandle::new] but with an existed [`SubstrateRecorder`].167 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {168 <CollectionById<T>>::get(id).map(|collection| Self {169 id,170 collection,171 recorder,172 })173 }174175 /// Retrives collection data from storage and creates collection handle with default parameters.176 /// If collection not found return `None`177 pub fn new(id: CollectionId) -> Option<Self> {178 Self::new_with_gas_limit(id, u64::MAX)179 }180181 /// Same as [`CollectionHandle::new`] but if collection not found [CollectionNotFound](Error::CollectionNotFound) returned.182 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {183 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)184 }185186 /// Consume gas for reading.187 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {188 self.recorder189 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(190 <T as frame_system::Config>::DbWeight::get()191 .read192 .saturating_mul(reads),193 )))194 }195196 /// Consume gas for writing.197 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {198 self.recorder199 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(200 <T as frame_system::Config>::DbWeight::get()201 .write202 .saturating_mul(writes),203 )))204 }205206 /// Consume gas for reading and writing.207 pub fn consume_store_reads_and_writes(208 &self,209 reads: u64,210 writes: u64,211 ) -> evm_coder::execution::Result<()> {212 let weight = <T as frame_system::Config>::DbWeight::get();213 let reads = weight.read.saturating_mul(reads);214 let writes = weight.read.saturating_mul(writes);215 self.recorder216 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(217 reads.saturating_add(writes),218 )))219 }220221 /// Save collection to storage.222 pub fn save(&self) -> DispatchResult {223 <CollectionById<T>>::insert(self.id, &self.collection);224 Ok(())225 }226227 /// Set collection sponsor.228 ///229 /// Unique collections allows sponsoring for certain actions.230 /// This method allows you to set the sponsor of the collection.231 /// In order for sponsorship to become active, it must be confirmed through [`Self::confirm_sponsorship`].232 pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {233 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);234 Ok(())235 }236237 /// Confirm sponsorship238 ///239 /// In order for the sponsorship to become active, the user set as the sponsor must confirm their participation.240 /// Before confirming sponsorship, the user must be specified as the sponsor of the collection via [`Self::set_sponsor`].241 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {242 if self.collection.sponsorship.pending_sponsor() != Some(sender) {243 return Ok(false);244 }245246 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());247 Ok(true)248 }249250 /// Remove collection sponsor.251 pub fn remove_sponsor(&mut self) -> DispatchResult {252 self.collection.sponsorship = SponsorshipState::Disabled;253 Ok(())254 }255256 /// Checks that the collection was created with, and must be operated upon through **Unique API**.257 /// Now check only the `external` flag and if it's **true**, then return [`Error::CollectionIsExternal`] error.258 pub fn check_is_internal(&self) -> DispatchResult {259 if self.flags.external {260 return Err(<Error<T>>::CollectionIsExternal)?;261 }262263 Ok(())264 }265266 /// Checks that the collection was created with, and must be operated upon through an **assimilated API**.267 /// Now check only the `external` flag and if it's **false**, then return [`Error::CollectionIsInternal`] error.268 pub fn check_is_external(&self) -> DispatchResult {269 if !self.flags.external {270 return Err(<Error<T>>::CollectionIsInternal)?;271 }272273 Ok(())274 }275}276277impl<T: Config> Deref for CollectionHandle<T> {278 type Target = Collection<T::AccountId>;279280 fn deref(&self) -> &Self::Target {281 &self.collection282 }283}284285impl<T: Config> DerefMut for CollectionHandle<T> {286 fn deref_mut(&mut self) -> &mut Self::Target {287 &mut self.collection288 }289}290291impl<T: Config> CollectionHandle<T> {292 /// Checks if the `user` is the owner of the collection.293 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {294 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);295 Ok(())296 }297298 /// Returns **true** if the `user` is the owner or administrator of the collection.299 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {300 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))301 }302303 /// Checks if the `user` is the owner or administrator of the collection.304 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {305 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);306 Ok(())307 }308309 /// Return **true** if `user` was not allowed to have tokens, and he can ignore such restrictions.310 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {311 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)312 }313314 /// Return **true** if `user` does not have enough token parts, and he can ignore such restrictions.315 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {316 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)317 }318319 /// Checks if the user is in the allow list. If not [Error::AddressNotInAllowlist] returns.320 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {321 ensure!(322 <Allowlist<T>>::get((self.id, user)),323 <Error<T>>::AddressNotInAllowlist324 );325 Ok(())326 }327328 /// Changes collection owner to another account329 /// #### Store read/writes330 /// 1 writes331 fn set_owner_internal(332 &mut self,333 caller: T::CrossAccountId,334 new_owner: T::CrossAccountId,335 ) -> DispatchResult {336 self.check_is_owner(&caller)?;337 self.collection.owner = new_owner.as_sub().clone();338 self.save()339 }340}341342#[frame_support::pallet]343pub mod pallet {344 use super::*;345 use dispatch::CollectionDispatch;346 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};347 use frame_system::pallet_prelude::*;348 use frame_support::traits::Currency;349 use up_data_structs::{TokenId, mapping::TokenAddressMapping};350 use scale_info::TypeInfo;351 use weights::WeightInfo;352353 #[pallet::config]354 pub trait Config:355 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo356 {357 /// Weight information for functions of this pallet.358 type WeightInfo: WeightInfo;359360 /// Events compatible with [`frame_system::Config::Event`].361 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;362363 /// Handler of accounts and payment.364 type Currency: Currency<Self::AccountId>;365366 /// Set price to create a collection.367 #[pallet::constant]368 type CollectionCreationPrice: Get<369 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,370 >;371372 /// Dispatcher of operations on collections.373 type CollectionDispatch: CollectionDispatch<Self>;374375 /// Account which holds the chain's treasury.376 type TreasuryAccountId: Get<Self::AccountId>;377378 /// Address under which the CollectionHelper contract would be available.379 type ContractAddress: Get<H160>;380381 /// Mapper for token addresses to Ethereum addresses.382 type EvmTokenAddressMapping: TokenAddressMapping<H160>;383384 /// Mapper for token addresses to [`CrossAccountId`].385 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;386 }387388 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);389390 #[pallet::pallet]391 #[pallet::storage_version(STORAGE_VERSION)]392 #[pallet::generate_store(pub(super) trait Store)]393 pub struct Pallet<T>(_);394395 #[pallet::extra_constants]396 impl<T: Config> Pallet<T> {397 /// Maximum admins per collection.398 pub fn collection_admins_limit() -> u32 {399 COLLECTION_ADMINS_LIMIT400 }401 }402403 #[pallet::event]404 #[pallet::generate_deposit(pub fn deposit_event)]405 pub enum Event<T: Config> {406 /// New collection was created407 CollectionCreated(408 /// Globally unique identifier of newly created collection.409 CollectionId,410 /// [`CollectionMode`] converted into _u8_.411 u8,412 /// Collection owner.413 T::AccountId,414 ),415416 /// New collection was destroyed417 CollectionDestroyed(418 /// Globally unique identifier of collection.419 CollectionId,420 ),421422 /// New item was created.423 ItemCreated(424 /// Id of the collection where item was created.425 CollectionId,426 /// Id of an item. Unique within the collection.427 TokenId,428 /// Owner of newly created item429 T::CrossAccountId,430 /// Always 1 for NFT431 u128,432 ),433434 /// Collection item was burned.435 ItemDestroyed(436 /// Id of the collection where item was destroyed.437 CollectionId,438 /// Identifier of burned NFT.439 TokenId,440 /// Which user has destroyed its tokens.441 T::CrossAccountId,442 /// Amount of token pieces destroed. Always 1 for NFT.443 u128,444 ),445446 /// Item was transferred447 Transfer(448 /// Id of collection to which item is belong.449 CollectionId,450 /// Id of an item.451 TokenId,452 /// Original owner of item.453 T::CrossAccountId,454 /// New owner of item.455 T::CrossAccountId,456 /// Amount of token pieces transfered. Always 1 for NFT.457 u128,458 ),459460 /// Amount pieces of token owned by `sender` was approved for `spender`.461 Approved(462 /// Id of collection to which item is belong.463 CollectionId,464 /// Id of an item.465 TokenId,466 /// Original owner of item.467 T::CrossAccountId,468 /// Id for which the approval was granted.469 T::CrossAccountId,470 /// Amount of token pieces transfered. Always 1 for NFT.471 u128,472 ),473474 /// The colletion property has been added or edited.475 CollectionPropertySet(476 /// Id of collection to which property has been set.477 CollectionId,478 /// The property that was set.479 PropertyKey,480 ),481482 /// The property has been deleted.483 CollectionPropertyDeleted(484 /// Id of collection to which property has been deleted.485 CollectionId,486 /// The property that was deleted.487 PropertyKey,488 ),489490 /// The token property has been added or edited.491 TokenPropertySet(492 /// Identifier of the collection whose token has the property set.493 CollectionId,494 /// The token for which the property was set.495 TokenId,496 /// The property that was set.497 PropertyKey,498 ),499500 /// The token property has been deleted.501 TokenPropertyDeleted(502 /// Identifier of the collection whose token has the property deleted.503 CollectionId,504 /// The token for which the property was deleted.505 TokenId,506 /// The property that was deleted.507 PropertyKey,508 ),509510 /// The token property permission of a collection has been set.511 PropertyPermissionSet(512 /// ID of collection to which property permission has been set.513 CollectionId,514 /// The property permission that was set.515 PropertyKey,516 ),517 }518519 #[pallet::error]520 pub enum Error<T> {521 /// This collection does not exist.522 CollectionNotFound,523 /// Sender parameter and item owner must be equal.524 MustBeTokenOwner,525 /// No permission to perform action526 NoPermission,527 /// Destroying only empty collections is allowed528 CantDestroyNotEmptyCollection,529 /// Collection is not in mint mode.530 PublicMintingNotAllowed,531 /// Address is not in allow list.532 AddressNotInAllowlist,533534 /// Collection name can not be longer than 63 char.535 CollectionNameLimitExceeded,536 /// Collection description can not be longer than 255 char.537 CollectionDescriptionLimitExceeded,538 /// Token prefix can not be longer than 15 char.539 CollectionTokenPrefixLimitExceeded,540 /// Total collections bound exceeded.541 TotalCollectionsLimitExceeded,542 /// Exceeded max admin count543 CollectionAdminCountExceeded,544 /// Collection limit bounds per collection exceeded545 CollectionLimitBoundsExceeded,546 /// Tried to enable permissions which are only permitted to be disabled547 OwnerPermissionsCantBeReverted,548 /// Collection settings not allowing items transferring549 TransferNotAllowed,550 /// Account token limit exceeded per collection551 AccountTokenLimitExceeded,552 /// Collection token limit exceeded553 CollectionTokenLimitExceeded,554 /// Metadata flag frozen555 MetadataFlagFrozen,556557 /// Item does not exist558 TokenNotFound,559 /// Item is balance not enough560 TokenValueTooLow,561 /// Requested value is more than the approved562 ApprovedValueTooLow,563 /// Tried to approve more than owned564 CantApproveMoreThanOwned,565566 /// Can't transfer tokens to ethereum zero address567 AddressIsZero,568569 /// The operation is not supported570 UnsupportedOperation,571572 /// Insufficient funds to perform an action573 NotSufficientFounds,574575 /// User does not satisfy the nesting rule576 UserIsNotAllowedToNest,577 /// Only tokens from specific collections may nest tokens under this one578 SourceCollectionIsNotAllowedToNest,579580 /// Tried to store more data than allowed in collection field581 CollectionFieldSizeExceeded,582583 /// Tried to store more property data than allowed584 NoSpaceForProperty,585586 /// Tried to store more property keys than allowed587 PropertyLimitReached,588589 /// Property key is too long590 PropertyKeyIsTooLong,591592 /// Only ASCII letters, digits, and symbols `_`, `-`, and `.` are allowed593 InvalidCharacterInPropertyKey,594595 /// Empty property keys are forbidden596 EmptyPropertyKey,597598 /// Tried to access an external collection with an internal API599 CollectionIsExternal,600601 /// Tried to access an internal collection with an external API602 CollectionIsInternal,603 }604605 /// Storage of the count of created collections. Essentially contains the last collection ID.606 #[pallet::storage]607 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;608609 /// Storage of the count of deleted collections.610 #[pallet::storage]611 pub type DestroyedCollectionCount<T> =612 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;613614 /// Storage of collection info.615 #[pallet::storage]616 pub type CollectionById<T> = StorageMap<617 Hasher = Blake2_128Concat,618 Key = CollectionId,619 Value = Collection<<T as frame_system::Config>::AccountId>,620 QueryKind = OptionQuery,621 >;622623 /// Storage of collection properties.624 #[pallet::storage]625 #[pallet::getter(fn collection_properties)]626 pub type CollectionProperties<T> = StorageMap<627 Hasher = Blake2_128Concat,628 Key = CollectionId,629 Value = Properties,630 QueryKind = ValueQuery,631 OnEmpty = up_data_structs::CollectionProperties,632 >;633634 /// Storage of token property permissions of a collection.635 #[pallet::storage]636 #[pallet::getter(fn property_permissions)]637 pub type CollectionPropertyPermissions<T> = StorageMap<638 Hasher = Blake2_128Concat,639 Key = CollectionId,640 Value = PropertiesPermissionMap,641 QueryKind = ValueQuery,642 >;643644 /// Storage of the amount of collection admins.645 #[pallet::storage]646 pub type AdminAmount<T> = StorageMap<647 Hasher = Blake2_128Concat,648 Key = CollectionId,649 Value = u32,650 QueryKind = ValueQuery,651 >;652653 /// List of collection admins.654 #[pallet::storage]655 pub type IsAdmin<T: Config> = StorageNMap<656 Key = (657 Key<Blake2_128Concat, CollectionId>,658 Key<Blake2_128Concat, T::CrossAccountId>,659 ),660 Value = bool,661 QueryKind = ValueQuery,662 >;663664 /// Allowlisted collection users.665 #[pallet::storage]666 pub type Allowlist<T: Config> = StorageNMap<667 Key = (668 Key<Blake2_128Concat, CollectionId>,669 Key<Blake2_128Concat, T::CrossAccountId>,670 ),671 Value = bool,672 QueryKind = ValueQuery,673 >;674675 /// Not used by code, exists only to provide some types to metadata.676 #[pallet::storage]677 pub type DummyStorageValue<T: Config> = StorageValue<678 Value = (679 CollectionStats,680 CollectionId,681 TokenId,682 TokenChild,683 PhantomType<(684 TokenData<T::CrossAccountId>,685 RpcCollection<T::AccountId>,686 // RMRK687 RmrkCollectionInfo<T::AccountId>,688 RmrkInstanceInfo<T::AccountId>,689 RmrkResourceInfo,690 RmrkPropertyInfo,691 RmrkBaseInfo<T::AccountId>,692 RmrkPartType,693 RmrkBoundedTheme,694 RmrkNftChild,695 )>,696 ),697 QueryKind = OptionQuery,698 >;699700 #[pallet::hooks]701 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {702 fn on_runtime_upgrade() -> Weight {703 StorageVersion::new(1).put::<Pallet<T>>();704705 Weight::zero()706 }707 }708}709710impl<T: Config> Pallet<T> {711 /// Enshure that receiver address is correct.712 ///713 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens.714 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {715 ensure!(716 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,717 <Error<T>>::AddressIsZero718 );719 Ok(())720 }721722 /// Get a vector of collection admins.723 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {724 <IsAdmin<T>>::iter_prefix((collection,))725 .map(|(a, _)| a)726 .collect()727 }728729 /// Get a vector of users allowed to mint tokens.730 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {731 <Allowlist<T>>::iter_prefix((collection,))732 .map(|(a, _)| a)733 .collect()734 }735736 /// Is `user` allowed to mint token in `collection`.737 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {738 <Allowlist<T>>::get((collection, user))739 }740741 /// Get statistics of collections.742 pub fn collection_stats() -> CollectionStats {743 let created = <CreatedCollectionCount<T>>::get();744 let destroyed = <DestroyedCollectionCount<T>>::get();745 CollectionStats {746 created: created.0,747 destroyed: destroyed.0,748 alive: created.0 - destroyed.0,749 }750 }751752 /// Get the effective limits for the collection.753 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {754 let collection = <CollectionById<T>>::get(collection)?;755 let limits = collection.limits;756 let effective_limits = CollectionLimits {757 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),758 sponsored_data_size: Some(limits.sponsored_data_size()),759 sponsored_data_rate_limit: Some(760 limits761 .sponsored_data_rate_limit762 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),763 ),764 token_limit: Some(limits.token_limit()),765 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(766 match collection.mode {767 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,768 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,769 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,770 },771 )),772 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),773 owner_can_transfer: Some(limits.owner_can_transfer()),774 owner_can_destroy: Some(limits.owner_can_destroy()),775 transfers_enabled: Some(limits.transfers_enabled()),776 };777778 Some(effective_limits)779 }780781 /// Returns information about the `collection` adapted for rpc.782 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {783 let Collection {784 name,785 description,786 owner,787 mode,788 token_prefix,789 sponsorship,790 limits,791 permissions,792 flags,793 } = <CollectionById<T>>::get(collection)?;794795 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)796 .into_iter()797 .map(|(key, permission)| PropertyKeyPermission { key, permission })798 .collect();799800 let properties = <CollectionProperties<T>>::get(collection)801 .into_iter()802 .map(|(key, value)| Property { key, value })803 .collect();804805 let permissions = CollectionPermissions {806 access: Some(permissions.access()),807 mint_mode: Some(permissions.mint_mode()),808 nesting: Some(permissions.nesting().clone()),809 };810811 Some(RpcCollection {812 name: name.into_inner(),813 description: description.into_inner(),814 owner,815 mode,816 token_prefix: token_prefix.into_inner(),817 sponsorship,818 limits,819 permissions,820 token_property_permissions,821 properties,822 read_only: flags.external,823824 flags: RpcCollectionFlags {825 foreign: flags.foreign,826 erc721metadata: flags.erc721metadata,827 },828 })829 }830}831832macro_rules! limit_default {833 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{834 $(835 if let Some($new) = $new.$field {836 let $old = $old.$field($($arg)?);837 let _ = $new;838 let _ = $old;839 $check840 } else {841 $new.$field = $old.$field842 }843 )*844 }};845}846macro_rules! limit_default_clone {847 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{848 $(849 if let Some($new) = $new.$field.clone() {850 let $old = $old.$field($($arg)?);851 let _ = $new;852 let _ = $old;853 $check854 } else {855 $new.$field = $old.$field.clone()856 }857 )*858 }};859}860861impl<T: Config> Pallet<T> {862 /// Create new collection.863 ///864 /// * `owner` - The owner of the collection.865 /// * `data` - Description of the created collection.866 /// * `flags` - Extra flags to store.867 pub fn init_collection(868 owner: T::CrossAccountId,869 payer: T::CrossAccountId,870 data: CreateCollectionData<T::AccountId>,871 flags: CollectionFlags,872 ) -> Result<CollectionId, DispatchError> {873 {874 ensure!(875 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,876 Error::<T>::CollectionTokenPrefixLimitExceeded877 );878 }879880 let created_count = <CreatedCollectionCount<T>>::get()881 .0882 .checked_add(1)883 .ok_or(ArithmeticError::Overflow)?;884 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;885 let id = CollectionId(created_count);886887 // bound Total number of collections888 ensure!(889 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,890 <Error<T>>::TotalCollectionsLimitExceeded891 );892893 // =========894895 let collection = Collection {896 owner: owner.as_sub().clone(),897 name: data.name,898 mode: data.mode.clone(),899 description: data.description,900 token_prefix: data.token_prefix,901 sponsorship: data902 .pending_sponsor903 .map(SponsorshipState::Unconfirmed)904 .unwrap_or_default(),905 limits: data906 .limits907 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))908 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,909 permissions: data910 .permissions911 .map(|permissions| {912 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)913 })914 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,915 flags,916 };917918 let mut collection_properties = up_data_structs::CollectionProperties::get();919 collection_properties920 .try_set_from_iter(data.properties.into_iter())921 .map_err(<Error<T>>::from)?;922923 CollectionProperties::<T>::insert(id, collection_properties);924925 let mut token_props_permissions = PropertiesPermissionMap::new();926 token_props_permissions927 .try_set_from_iter(data.token_property_permissions.into_iter())928 .map_err(<Error<T>>::from)?;929930 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);931932 // Take a (non-refundable) deposit of collection creation933 {934 let mut imbalance =935 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();936 imbalance.subsume(937 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(938 &T::TreasuryAccountId::get(),939 T::CollectionCreationPrice::get(),940 ),941 );942 <T as Config>::Currency::settle(943 payer.as_sub(),944 imbalance,945 WithdrawReasons::TRANSFER,946 ExistenceRequirement::KeepAlive,947 )948 .map_err(|_| Error::<T>::NotSufficientFounds)?;949 }950951 <CreatedCollectionCount<T>>::put(created_count);952 <Pallet<T>>::deposit_event(Event::CollectionCreated(953 id,954 data.mode.id(),955 owner.as_sub().clone(),956 ));957 <PalletEvm<T>>::deposit_log(958 erc::CollectionHelpersEvents::CollectionCreated {959 owner: *owner.as_eth(),960 collection_id: eth::collection_id_to_address(id),961 }962 .to_log(T::ContractAddress::get()),963 );964 <CollectionById<T>>::insert(id, collection);965 Ok(id)966 }967968 /// Destroy collection.969 ///970 /// * `collection` - Collection handler.971 /// * `sender` - The owner or administrator of the collection.972 pub fn destroy_collection(973 collection: CollectionHandle<T>,974 sender: &T::CrossAccountId,975 ) -> DispatchResult {976 ensure!(977 collection.limits.owner_can_destroy(),978 <Error<T>>::NoPermission,979 );980 collection.check_is_owner(sender)?;981982 let destroyed_collections = <DestroyedCollectionCount<T>>::get()983 .0984 .checked_add(1)985 .ok_or(ArithmeticError::Overflow)?;986987 // =========988989 <DestroyedCollectionCount<T>>::put(destroyed_collections);990 <CollectionById<T>>::remove(collection.id);991 <AdminAmount<T>>::remove(collection.id);992 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);993 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);994 <CollectionProperties<T>>::remove(collection.id);995996 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));997998 <PalletEvm<T>>::deposit_log(999 erc::CollectionHelpersEvents::CollectionDestroyed {1000 collection_id: eth::collection_id_to_address(collection.id),1001 }1002 .to_log(T::ContractAddress::get()),1003 );1004 Ok(())1005 }10061007 /// Set collection property.1008 ///1009 /// * `collection` - Collection handler.1010 /// * `sender` - The owner or administrator of the collection.1011 /// * `property` - The property to set.1012 pub fn set_collection_property(1013 collection: &CollectionHandle<T>,1014 sender: &T::CrossAccountId,1015 property: Property,1016 ) -> DispatchResult {1017 collection.check_is_owner_or_admin(sender)?;10181019 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1020 let property = property.clone();1021 properties.try_set(property.key, property.value)1022 })1023 .map_err(<Error<T>>::from)?;10241025 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));10261027 Ok(())1028 }10291030 /// Set scouped collection property.1031 ///1032 /// * `collection_id` - ID of the collection for which the property is being set.1033 /// * `scope` - Property scope.1034 /// * `property` - The property to set.1035 pub fn set_scoped_collection_property(1036 collection_id: CollectionId,1037 scope: PropertyScope,1038 property: Property,1039 ) -> DispatchResult {1040 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1041 properties.try_scoped_set(scope, property.key, property.value)1042 })1043 .map_err(<Error<T>>::from)?;10441045 Ok(())1046 }10471048 /// Set scouped collection properties.1049 ///1050 /// * `collection_id` - ID of the collection for which the properties is being set.1051 /// * `scope` - Property scope.1052 /// * `properties` - The properties to set.1053 pub fn set_scoped_collection_properties(1054 collection_id: CollectionId,1055 scope: PropertyScope,1056 properties: impl Iterator<Item = Property>,1057 ) -> DispatchResult {1058 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1059 stored_properties.try_scoped_set_from_iter(scope, properties)1060 })1061 .map_err(<Error<T>>::from)?;10621063 Ok(())1064 }10651066 /// Set collection properties.1067 ///1068 /// * `collection` - Collection handler.1069 /// * `sender` - The owner or administrator of the collection.1070 /// * `properties` - The properties to set.1071 #[transactional]1072 pub fn set_collection_properties(1073 collection: &CollectionHandle<T>,1074 sender: &T::CrossAccountId,1075 properties: Vec<Property>,1076 ) -> DispatchResult {1077 for property in properties {1078 Self::set_collection_property(collection, sender, property)?;1079 }10801081 Ok(())1082 }10831084 /// Delete collection property.1085 ///1086 /// * `collection` - Collection handler.1087 /// * `sender` - The owner or administrator of the collection.1088 /// * `property` - The property to delete.1089 pub fn delete_collection_property(1090 collection: &CollectionHandle<T>,1091 sender: &T::CrossAccountId,1092 property_key: PropertyKey,1093 ) -> DispatchResult {1094 collection.check_is_owner_or_admin(sender)?;10951096 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1097 properties.remove(&property_key)1098 })1099 .map_err(<Error<T>>::from)?;11001101 Self::deposit_event(Event::CollectionPropertyDeleted(1102 collection.id,1103 property_key,1104 ));11051106 Ok(())1107 }11081109 /// Delete collection properties.1110 ///1111 /// * `collection` - Collection handler.1112 /// * `sender` - The owner or administrator of the collection.1113 /// * `properties` - The properties to delete.1114 #[transactional]1115 pub fn delete_collection_properties(1116 collection: &CollectionHandle<T>,1117 sender: &T::CrossAccountId,1118 property_keys: Vec<PropertyKey>,1119 ) -> DispatchResult {1120 for key in property_keys {1121 Self::delete_collection_property(collection, sender, key)?;1122 }11231124 Ok(())1125 }11261127 /// Set collection propetry permission without any checks.1128 ///1129 /// Used for migrations.1130 ///1131 /// * `collection` - Collection handler.1132 /// * `property_permissions` - Property permissions.1133 pub fn set_property_permission_unchecked(1134 collection: CollectionId,1135 property_permission: PropertyKeyPermission,1136 ) -> DispatchResult {1137 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1138 permissions.try_set(property_permission.key, property_permission.permission)1139 })1140 .map_err(<Error<T>>::from)?;1141 Ok(())1142 }11431144 /// Set collection property permission.1145 ///1146 /// * `collection` - Collection handler.1147 /// * `sender` - The owner or administrator of the collection.1148 /// * `property_permission` - Property permission.1149 pub fn set_property_permission(1150 collection: &CollectionHandle<T>,1151 sender: &T::CrossAccountId,1152 property_permission: PropertyKeyPermission,1153 ) -> DispatchResult {1154 Self::set_scoped_property_permission(1155 collection,1156 sender,1157 PropertyScope::None,1158 property_permission,1159 )1160 }11611162 /// Set collection property permission with scope.1163 ///1164 /// * `collection` - Collection handler.1165 /// * `sender` - The owner or administrator of the collection.1166 /// * `scope` - Property scope.1167 /// * `property_permission` - Property permission.1168 pub fn set_scoped_property_permission(1169 collection: &CollectionHandle<T>,1170 sender: &T::CrossAccountId,1171 scope: PropertyScope,1172 property_permission: PropertyKeyPermission,1173 ) -> DispatchResult {1174 collection.check_is_owner_or_admin(sender)?;11751176 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1177 let current_permission = all_permissions.get(&property_permission.key);1178 if matches![1179 current_permission,1180 Some(PropertyPermission { mutable: false, .. })1181 ] {1182 return Err(<Error<T>>::NoPermission.into());1183 }11841185 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1186 let property_permission = property_permission.clone();1187 permissions.try_scoped_set(1188 scope,1189 property_permission.key,1190 property_permission.permission,1191 )1192 })1193 .map_err(<Error<T>>::from)?;11941195 Self::deposit_event(Event::PropertyPermissionSet(1196 collection.id,1197 property_permission.key,1198 ));11991200 Ok(())1201 }12021203 /// Set token property permission.1204 ///1205 /// * `collection` - Collection handler.1206 /// * `sender` - The owner or administrator of the collection.1207 /// * `property_permissions` - Property permissions.1208 #[transactional]1209 pub fn set_token_property_permissions(1210 collection: &CollectionHandle<T>,1211 sender: &T::CrossAccountId,1212 property_permissions: Vec<PropertyKeyPermission>,1213 ) -> DispatchResult {1214 Self::set_scoped_token_property_permissions(1215 collection,1216 sender,1217 PropertyScope::None,1218 property_permissions,1219 )1220 }12211222 /// Set token property permission with scope.1223 ///1224 /// * `collection` - Collection handler.1225 /// * `sender` - The owner or administrator of the collection.1226 /// * `scope` - Property scope.1227 /// * `property_permissions` - Property permissions.1228 #[transactional]1229 pub fn set_scoped_token_property_permissions(1230 collection: &CollectionHandle<T>,1231 sender: &T::CrossAccountId,1232 scope: PropertyScope,1233 property_permissions: Vec<PropertyKeyPermission>,1234 ) -> DispatchResult {1235 for prop_pemission in property_permissions {1236 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1237 }12381239 Ok(())1240 }12411242 /// Get collection property.1243 pub fn get_collection_property(1244 collection_id: CollectionId,1245 key: &PropertyKey,1246 ) -> Option<PropertyValue> {1247 Self::collection_properties(collection_id).get(key).cloned()1248 }12491250 /// Convert byte vector to property key vector.1251 pub fn bytes_keys_to_property_keys(1252 keys: Vec<Vec<u8>>,1253 ) -> Result<Vec<PropertyKey>, DispatchError> {1254 keys.into_iter()1255 .map(|key| -> Result<PropertyKey, DispatchError> {1256 key.try_into()1257 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1258 })1259 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1260 }12611262 /// Get properties according to given keys.1263 pub fn filter_collection_properties(1264 collection_id: CollectionId,1265 keys: Option<Vec<PropertyKey>>,1266 ) -> Result<Vec<Property>, DispatchError> {1267 let properties = Self::collection_properties(collection_id);12681269 let properties = keys1270 .map(|keys| {1271 keys.into_iter()1272 .filter_map(|key| {1273 properties.get(&key).map(|value| Property {1274 key,1275 value: value.clone(),1276 })1277 })1278 .collect()1279 })1280 .unwrap_or_else(|| {1281 properties1282 .into_iter()1283 .map(|(key, value)| Property { key, value })1284 .collect()1285 });12861287 Ok(properties)1288 }12891290 /// Get property permissions according to given keys.1291 pub fn filter_property_permissions(1292 collection_id: CollectionId,1293 keys: Option<Vec<PropertyKey>>,1294 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1295 let permissions = Self::property_permissions(collection_id);12961297 let key_permissions = keys1298 .map(|keys| {1299 keys.into_iter()1300 .filter_map(|key| {1301 permissions1302 .get(&key)1303 .map(|permission| PropertyKeyPermission {1304 key,1305 permission: permission.clone(),1306 })1307 })1308 .collect()1309 })1310 .unwrap_or_else(|| {1311 permissions1312 .into_iter()1313 .map(|(key, permission)| PropertyKeyPermission { key, permission })1314 .collect()1315 });13161317 Ok(key_permissions)1318 }13191320 /// Toggle `user` participation in the `collection`'s allow list.1321 /// #### Store read/writes1322 /// 1 writes1323 pub fn toggle_allowlist(1324 collection: &CollectionHandle<T>,1325 sender: &T::CrossAccountId,1326 user: &T::CrossAccountId,1327 allowed: bool,1328 ) -> DispatchResult {1329 collection.check_is_owner_or_admin(sender)?;13301331 // =========13321333 if allowed {1334 <Allowlist<T>>::insert((collection.id, user), true);1335 } else {1336 <Allowlist<T>>::remove((collection.id, user));1337 }13381339 Ok(())1340 }13411342 /// Toggle `user` participation in the `collection`'s admin list.1343 /// #### Store read/writes1344 /// 2 writes1345 pub fn toggle_admin(1346 collection: &CollectionHandle<T>,1347 sender: &T::CrossAccountId,1348 user: &T::CrossAccountId,1349 admin: bool,1350 ) -> DispatchResult {1351 collection.check_is_owner(sender)?;13521353 let was_admin = <IsAdmin<T>>::get((collection.id, user));1354 if was_admin == admin {1355 return Ok(());1356 }1357 let amount = <AdminAmount<T>>::get(collection.id);13581359 if admin {1360 let amount = amount1361 .checked_add(1)1362 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1363 ensure!(1364 amount <= Self::collection_admins_limit(),1365 <Error<T>>::CollectionAdminCountExceeded,1366 );13671368 // =========13691370 <AdminAmount<T>>::insert(collection.id, amount);1371 <IsAdmin<T>>::insert((collection.id, user), true);1372 } else {1373 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1374 <IsAdmin<T>>::remove((collection.id, user));1375 }13761377 Ok(())1378 }13791380 /// Merge set fields from `new_limit` to `old_limit`.1381 pub fn clamp_limits(1382 mode: CollectionMode,1383 old_limit: &CollectionLimits,1384 mut new_limit: CollectionLimits,1385 ) -> Result<CollectionLimits, DispatchError> {1386 let limits = old_limit;1387 limit_default!(old_limit, new_limit,1388 account_token_ownership_limit => ensure!(1389 new_limit <= MAX_TOKEN_OWNERSHIP,1390 <Error<T>>::CollectionLimitBoundsExceeded,1391 ),1392 sponsored_data_size => ensure!(1393 new_limit <= CUSTOM_DATA_LIMIT,1394 <Error<T>>::CollectionLimitBoundsExceeded,1395 ),13961397 sponsored_data_rate_limit => {},1398 token_limit => ensure!(1399 old_limit >= new_limit && new_limit > 0,1400 <Error<T>>::CollectionTokenLimitExceeded1401 ),14021403 sponsor_transfer_timeout(match mode {1404 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1405 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1406 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1407 }) => ensure!(1408 new_limit <= MAX_SPONSOR_TIMEOUT,1409 <Error<T>>::CollectionLimitBoundsExceeded,1410 ),1411 sponsor_approve_timeout => {},1412 owner_can_transfer => ensure!(1413 !limits.owner_can_transfer_instaled() ||1414 old_limit || !new_limit,1415 <Error<T>>::OwnerPermissionsCantBeReverted,1416 ),1417 owner_can_destroy => ensure!(1418 old_limit || !new_limit,1419 <Error<T>>::OwnerPermissionsCantBeReverted,1420 ),1421 transfers_enabled => {},1422 );1423 Ok(new_limit)1424 }14251426 /// Merge set fields from `new_permission` to `old_permission`.1427 pub fn clamp_permissions(1428 _mode: CollectionMode,1429 old_permission: &CollectionPermissions,1430 mut new_permission: CollectionPermissions,1431 ) -> Result<CollectionPermissions, DispatchError> {1432 limit_default_clone!(old_permission, new_permission,1433 access => {},1434 mint_mode => {},1435 nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1436 );1437 Ok(new_permission)1438 }1439}14401441/// Indicates unsupported methods by returning [Error::UnsupportedOperation].1442#[macro_export]1443macro_rules! unsupported {1444 ($runtime:path) => {1445 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1446 };1447}14481449/// Return weights for various worst-case operations.1450pub trait CommonWeightInfo<CrossAccountId> {1451 /// Weight of item creation.1452 fn create_item() -> Weight;14531454 /// Weight of items creation.1455 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;14561457 /// Weight of items creation.1458 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;14591460 /// The weight of the burning item.1461 fn burn_item() -> Weight;14621463 /// Property setting weight.1464 ///1465 /// * `amount`- The number of properties to set.1466 fn set_collection_properties(amount: u32) -> Weight;14671468 /// Collection property deletion weight.1469 ///1470 /// * `amount`- The number of properties to set.1471 fn delete_collection_properties(amount: u32) -> Weight;14721473 /// Token property setting weight.1474 ///1475 /// * `amount`- The number of properties to set.1476 fn set_token_properties(amount: u32) -> Weight;14771478 /// Token property deletion weight.1479 ///1480 /// * `amount`- The number of properties to delete.1481 fn delete_token_properties(amount: u32) -> Weight;14821483 /// Token property permissions set weight.1484 ///1485 /// * `amount`- The number of property permissions to set.1486 fn set_token_property_permissions(amount: u32) -> Weight;14871488 /// Transfer price of the token or its parts.1489 fn transfer() -> Weight;14901491 /// The price of setting the permission of the operation from another user.1492 fn approve() -> Weight;14931494 /// Transfer price from another user.1495 fn transfer_from() -> Weight;14961497 /// The price of burning a token from another user.1498 fn burn_from() -> Weight;14991500 /// Differs from burn_item in case of Fungible and Refungible, as it should burn1501 /// whole users's balance.1502 ///1503 /// This method shouldn't be used directly, as it doesn't count breadth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1504 fn burn_recursively_self_raw() -> Weight;15051506 /// Cost of iterating over `amount` children while burning, without counting child burning itself.1507 ///1508 /// This method shouldn't be used directly, as it doesn't count depth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1509 fn burn_recursively_breadth_raw(amount: u32) -> Weight;15101511 /// The price of recursive burning a token.1512 ///1513 /// `max_selfs` - The maximum burning weight of the token itself.1514 /// `max_breadth` - The maximum number of nested tokens to burn.1515 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1516 Self::burn_recursively_self_raw()1517 .saturating_mul(max_selfs.max(1) as u64)1518 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1519 }15201521 /// The price of retrieving token owner1522 fn token_owner() -> Weight;1523}15241525/// Weight info extension trait for refungible pallet.1526pub trait RefungibleExtensionsWeightInfo {1527 /// Weight of token repartition.1528 fn repartition() -> Weight;1529}15301531/// Common collection operations.1532///1533/// It wraps methods in Fungible, Nonfungible and Refungible pallets1534/// and adds weight info.1535pub trait CommonCollectionOperations<T: Config> {1536 /// Create token.1537 ///1538 /// * `sender` - The user who mint the token and pays for the transaction.1539 /// * `to` - The user who will own the token.1540 /// * `data` - Token data.1541 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1542 fn create_item(1543 &self,1544 sender: T::CrossAccountId,1545 to: T::CrossAccountId,1546 data: CreateItemData,1547 nesting_budget: &dyn Budget,1548 ) -> DispatchResultWithPostInfo;15491550 /// Create multiple tokens.1551 ///1552 /// * `sender` - The user who mint the token and pays for the transaction.1553 /// * `to` - The user who will own the token.1554 /// * `data` - Token data.1555 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1556 fn create_multiple_items(1557 &self,1558 sender: T::CrossAccountId,1559 to: T::CrossAccountId,1560 data: Vec<CreateItemData>,1561 nesting_budget: &dyn Budget,1562 ) -> DispatchResultWithPostInfo;15631564 /// Create multiple tokens.1565 ///1566 /// * `sender` - The user who mint the token and pays for the transaction.1567 /// * `to` - The user who will own the token.1568 /// * `data` - Token data.1569 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1570 fn create_multiple_items_ex(1571 &self,1572 sender: T::CrossAccountId,1573 data: CreateItemExData<T::CrossAccountId>,1574 nesting_budget: &dyn Budget,1575 ) -> DispatchResultWithPostInfo;15761577 /// Burn token.1578 ///1579 /// * `sender` - The user who owns the token.1580 /// * `token` - Token id that will burned.1581 /// * `amount` - The number of parts of the token that will be burned.1582 fn burn_item(1583 &self,1584 sender: T::CrossAccountId,1585 token: TokenId,1586 amount: u128,1587 ) -> DispatchResultWithPostInfo;15881589 /// Burn token and all nested tokens recursievly.1590 ///1591 /// * `sender` - The user who owns the token.1592 /// * `token` - Token id that will burned.1593 /// * `self_budget` - The budget that can be spent on burning tokens.1594 /// * `breadth_budget` - The budget that can be spent on burning nested tokens.1595 fn burn_item_recursively(1596 &self,1597 sender: T::CrossAccountId,1598 token: TokenId,1599 self_budget: &dyn Budget,1600 breadth_budget: &dyn Budget,1601 ) -> DispatchResultWithPostInfo;16021603 /// Set collection properties.1604 ///1605 /// * `sender` - Must be either the owner of the collection or its admin.1606 /// * `properties` - Properties to be set.1607 fn set_collection_properties(1608 &self,1609 sender: T::CrossAccountId,1610 properties: Vec<Property>,1611 ) -> DispatchResultWithPostInfo;16121613 /// Delete collection properties.1614 ///1615 /// * `sender` - Must be either the owner of the collection or its admin.1616 /// * `properties` - The properties to be removed.1617 fn delete_collection_properties(1618 &self,1619 sender: &T::CrossAccountId,1620 property_keys: Vec<PropertyKey>,1621 ) -> DispatchResultWithPostInfo;16221623 /// Set token properties.1624 ///1625 /// The appropriate [`PropertyPermission`] for the token property1626 /// must be set with [`Self::set_token_property_permissions`].1627 ///1628 /// * `sender` - Must be either the owner of the token or its admin.1629 /// * `token_id` - The token for which the properties are being set.1630 /// * `properties` - Properties to be set.1631 /// * `budget` - Budget for setting properties.1632 fn set_token_properties(1633 &self,1634 sender: T::CrossAccountId,1635 token_id: TokenId,1636 properties: Vec<Property>,1637 budget: &dyn Budget,1638 ) -> DispatchResultWithPostInfo;16391640 /// Remove token properties.1641 ///1642 /// The appropriate [`PropertyPermission`] for the token property1643 /// must be set with [`Self::set_token_property_permissions`].1644 ///1645 /// * `sender` - Must be either the owner of the token or its admin.1646 /// * `token_id` - The token for which the properties are being remove.1647 /// * `property_keys` - Keys to remove corresponding properties.1648 /// * `budget` - Budget for removing properties.1649 fn delete_token_properties(1650 &self,1651 sender: T::CrossAccountId,1652 token_id: TokenId,1653 property_keys: Vec<PropertyKey>,1654 budget: &dyn Budget,1655 ) -> DispatchResultWithPostInfo;16561657 /// Set token property permissions.1658 ///1659 /// * `sender` - Must be either the owner of the token or its admin.1660 /// * `token_id` - The token for which the properties are being set.1661 /// * `property_permissions` - Property permissions to be set.1662 /// * `budget` - Budget for setting properties.1663 fn set_token_property_permissions(1664 &self,1665 sender: &T::CrossAccountId,1666 property_permissions: Vec<PropertyKeyPermission>,1667 ) -> DispatchResultWithPostInfo;16681669 /// Transfer amount of token pieces.1670 ///1671 /// * `sender` - Donor user.1672 /// * `to` - Recepient user.1673 /// * `token` - The token of which parts are being sent.1674 /// * `amount` - The number of parts of the token that will be transferred.1675 /// * `budget` - The maximum budget that can be spent on the transfer.1676 fn transfer(1677 &self,1678 sender: T::CrossAccountId,1679 to: T::CrossAccountId,1680 token: TokenId,1681 amount: u128,1682 budget: &dyn Budget,1683 ) -> DispatchResultWithPostInfo;16841685 /// Grant access to another account to transfer parts of the token owned by the calling user via [Self::transfer_from].1686 ///1687 /// * `sender` - The user who grants access to the token.1688 /// * `spender` - The user to whom the rights are granted.1689 /// * `token` - The token to which access is granted.1690 /// * `amount` - The amount of pieces that another user can dispose of.1691 fn approve(1692 &self,1693 sender: T::CrossAccountId,1694 spender: T::CrossAccountId,1695 token: TokenId,1696 amount: u128,1697 ) -> DispatchResultWithPostInfo;16981699 /// Send parts of a token owned by another user.1700 ///1701 /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].1702 ///1703 /// * `sender` - The user who must have access to the token (see [`Self::approve`]).1704 /// * `from` - The user who owns the token.1705 /// * `to` - Recepient user.1706 /// * `token` - The token of which parts are being sent.1707 /// * `amount` - The number of parts of the token that will be transferred.1708 /// * `budget` - The maximum budget that can be spent on the transfer.1709 fn transfer_from(1710 &self,1711 sender: T::CrossAccountId,1712 from: T::CrossAccountId,1713 to: T::CrossAccountId,1714 token: TokenId,1715 amount: u128,1716 budget: &dyn Budget,1717 ) -> DispatchResultWithPostInfo;17181719 /// Burn parts of a token owned by another user.1720 ///1721 /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].1722 ///1723 /// * `sender` - The user who must have access to the token (see [`Self::approve`]).1724 /// * `from` - The user who owns the token.1725 /// * `token` - The token of which parts are being sent.1726 /// * `amount` - The number of parts of the token that will be transferred.1727 /// * `budget` - The maximum budget that can be spent on the burn.1728 fn burn_from(1729 &self,1730 sender: T::CrossAccountId,1731 from: T::CrossAccountId,1732 token: TokenId,1733 amount: u128,1734 budget: &dyn Budget,1735 ) -> DispatchResultWithPostInfo;17361737 /// Check permission to nest token.1738 ///1739 /// * `sender` - The user who initiated the check.1740 /// * `from` - The token that is checked for embedding.1741 /// * `under` - Token under which to check.1742 /// * `budget` - The maximum budget that can be spent on the check.1743 fn check_nesting(1744 &self,1745 sender: T::CrossAccountId,1746 from: (CollectionId, TokenId),1747 under: TokenId,1748 budget: &dyn Budget,1749 ) -> DispatchResult;17501751 /// Nest one token into another.1752 ///1753 /// * `under` - Token holder.1754 /// * `to_nest` - Nested token.1755 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17561757 /// Unnest token.1758 ///1759 /// * `under` - Token holder.1760 /// * `to_nest` - Token to unnest.1761 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17621763 /// Get all user tokens.1764 ///1765 /// * `account` - Account for which you need to get tokens.1766 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;17671768 /// Get all the tokens in the collection.1769 fn collection_tokens(&self) -> Vec<TokenId>;17701771 /// Check if the token exists.1772 ///1773 /// * `token` - Id token to check.1774 fn token_exists(&self, token: TokenId) -> bool;17751776 /// Get the id of the last minted token.1777 fn last_token_id(&self) -> TokenId;17781779 /// Get the owner of the token.1780 ///1781 /// * `token` - The token for which you need to find out the owner.1782 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;17831784 /// Returns 10 tokens owners in no particular order.1785 ///1786 /// * `token` - The token for which you need to find out the owners.1787 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;17881789 /// Get the value of the token property by key.1790 ///1791 /// * `token` - Token with the property to get.1792 /// * `key` - Property name.1793 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;17941795 /// Get a set of token properties by key vector.1796 ///1797 /// * `token` - Token with the property to get.1798 /// * `keys` - Vector of property keys. If this parameter is [None](sp_std::result::Result),1799 /// then all properties are returned.1800 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;18011802 /// Amount of unique collection tokens1803 fn total_supply(&self) -> u32;18041805 /// Amount of different tokens account has.1806 ///1807 /// * `account` - The account for which need to get the balance.1808 fn account_balance(&self, account: T::CrossAccountId) -> u32;18091810 /// Amount of specific token account have.1811 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;18121813 /// Amount of token pieces1814 fn total_pieces(&self, token: TokenId) -> Option<u128>;18151816 /// Get the number of parts of the token that a trusted user can manage.1817 ///1818 /// * `sender` - Trusted user.1819 /// * `spender` - Owner of the token.1820 /// * `token` - The token for which to get the value.1821 fn allowance(1822 &self,1823 sender: T::CrossAccountId,1824 spender: T::CrossAccountId,1825 token: TokenId,1826 ) -> u128;18271828 /// Get extension for RFT collection.1829 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1830}18311832/// Extension for RFT collection.1833pub trait RefungibleExtensions<T>1834where1835 T: Config,1836{1837 /// Change the number of parts of the token.1838 ///1839 /// When the value changes down, this function is equivalent to burning parts of the token.1840 ///1841 /// * `sender` - The user calling the repartition operation. Must be the owner of the token.1842 /// * `token` - The token for which you want to change the number of parts.1843 /// * `amount` - The new value of the parts of the token.1844 fn repartition(1845 &self,1846 sender: &T::CrossAccountId,1847 token: TokenId,1848 amount: u128,1849 ) -> DispatchResultWithPostInfo;1850}18511852/// Merge [`DispatchResult`] with [`Weight`] into [`DispatchResultWithPostInfo`].1853///1854/// Used for [`CommonCollectionOperations`] implementations and flexible enough to do so.1855pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1856 let post_info = PostDispatchInfo {1857 actual_weight: Some(weight),1858 pays_fee: Pays::Yes,1859 };1860 match res {1861 Ok(()) => Ok(post_info),1862 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1863 }1864}18651866impl<T: Config> From<PropertiesError> for Error<T> {1867 fn from(error: PropertiesError) -> Self {1868 match error {1869 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1870 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1871 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1872 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1873 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1874 }1875 }1876}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Common pallet18//!19//! The Common pallet provides functionality for handling collections.20//!21//! ## Overview22//!23//! The Common pallet provides an interface for common collection operations for different collection types24//! (see [CommonCollectionOperations]), as well as a generic dispatcher for these, see [dispatch] module.25//! It also provides this functionality to EVM, see [erc] and [eth] modules.26//!27//! The Common pallet provides functions for:28//!29//! - Setting and approving collection sponsor.30//! - Get\set\delete allow list.31//! - Get\set\delete collection properties.32//! - Get\set\delete collection property permissions.33//! - Get\set\delete token property permissions.34//! - Get\set\delete collection administrators.35//! - Checking access permissions.36//!37//! ### Terminology38//! **Collection sponsor** - For the collection, you can set a sponsor, at whose expense it will39//! be possible to mint tokens.40//!41//! **Allow list** - List of users who have the right to minting tokens.42//!43//! **Collection properties** - Collection properties are simply key-value stores where various44//! metadata can be placed.45//!46//! **Permissions on token properties** - For each property in the token can be set permission47//! to change, see [`PropertyPermission`].48//!49//! **Collection administrator** - For a collection, you can set administrators who have the right50//! to most actions on the collection.5152#![warn(missing_docs)]53#![cfg_attr(not(feature = "std"), no_std)]54extern crate alloc;5556use core::ops::{Deref, DerefMut};57use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};58use sp_std::vec::Vec;59use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};60use evm_coder::ToLog;61use frame_support::{62 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},63 ensure,64 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},65 dispatch::Pays,66 transactional,67};68use pallet_evm::GasWeightMapping;69use up_data_structs::{70 COLLECTION_NUMBER_LIMIT,71 Collection,72 RpcCollection,73 CollectionFlags,74 RpcCollectionFlags,75 CollectionId,76 CreateItemData,77 MAX_TOKEN_PREFIX_LENGTH,78 COLLECTION_ADMINS_LIMIT,79 TokenId,80 TokenChild,81 CollectionStats,82 MAX_TOKEN_OWNERSHIP,83 CollectionMode,84 NFT_SPONSOR_TRANSFER_TIMEOUT,85 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,86 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,87 MAX_SPONSOR_TIMEOUT,88 CUSTOM_DATA_LIMIT,89 CollectionLimits,90 CreateCollectionData,91 SponsorshipState,92 CreateItemExData,93 SponsoringRateLimit,94 budget::Budget,95 PhantomType,96 Property,97 Properties,98 PropertiesPermissionMap,99 PropertyKey,100 PropertyValue,101 PropertyPermission,102 PropertiesError,103 PropertyKeyPermission,104 TokenData,105 TrySetProperty,106 PropertyScope,107 // RMRK108 RmrkCollectionInfo,109 RmrkInstanceInfo,110 RmrkResourceInfo,111 RmrkPropertyInfo,112 RmrkBaseInfo,113 RmrkPartType,114 RmrkBoundedTheme,115 RmrkNftChild,116 CollectionPermissions,117};118119pub use pallet::*;120use sp_core::H160;121use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};122#[cfg(feature = "runtime-benchmarks")]123pub mod benchmarking;124pub mod dispatch;125pub mod erc;126pub mod eth;127pub mod weights;128129/// Weight info.130pub type SelfWeightOf<T> = <T as Config>::WeightInfo;131132/// Collection handle contains information about collection data and id.133/// Also provides functionality to count consumed gas.134///135/// CollectionHandle is used as a generic wrapper for collections of all types.136/// It allows to perform common operations and queries on any collection type,137/// both completely general for all, as well as their respective implementations of [`CommonCollectionOperations`].138#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]139pub struct CollectionHandle<T: Config> {140 /// Collection id141 pub id: CollectionId,142 collection: Collection<T::AccountId>,143 /// Substrate recorder for counting consumed gas144 pub recorder: SubstrateRecorder<T>,145}146147impl<T: Config> WithRecorder<T> for CollectionHandle<T> {148 fn recorder(&self) -> &SubstrateRecorder<T> {149 &self.recorder150 }151 fn into_recorder(self) -> SubstrateRecorder<T> {152 self.recorder153 }154}155156impl<T: Config> CollectionHandle<T> {157 /// Same as [CollectionHandle::new] but with an explicit gas limit.158 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {159 <CollectionById<T>>::get(id).map(|collection| Self {160 id,161 collection,162 recorder: SubstrateRecorder::new(gas_limit),163 })164 }165166 /// Same as [CollectionHandle::new] but with an existed [`SubstrateRecorder`].167 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {168 <CollectionById<T>>::get(id).map(|collection| Self {169 id,170 collection,171 recorder,172 })173 }174175 /// Retrives collection data from storage and creates collection handle with default parameters.176 /// If collection not found return `None`177 pub fn new(id: CollectionId) -> Option<Self> {178 Self::new_with_gas_limit(id, u64::MAX)179 }180181 /// Same as [`CollectionHandle::new`] but if collection not found [CollectionNotFound](Error::CollectionNotFound) returned.182 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {183 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)184 }185186 /// Consume gas for reading.187 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {188 self.recorder189 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(190 <T as frame_system::Config>::DbWeight::get()191 .read192 .saturating_mul(reads),193 )))194 }195196 /// Consume gas for writing.197 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {198 self.recorder199 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(200 <T as frame_system::Config>::DbWeight::get()201 .write202 .saturating_mul(writes),203 )))204 }205206 /// Consume gas for reading and writing.207 pub fn consume_store_reads_and_writes(208 &self,209 reads: u64,210 writes: u64,211 ) -> evm_coder::execution::Result<()> {212 let weight = <T as frame_system::Config>::DbWeight::get();213 let reads = weight.read.saturating_mul(reads);214 let writes = weight.read.saturating_mul(writes);215 self.recorder216 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(217 reads.saturating_add(writes),218 )))219 }220221 /// Save collection to storage.222 pub fn save(&self) -> DispatchResult {223 <CollectionById<T>>::insert(self.id, &self.collection);224 Ok(())225 }226227 /// Set collection sponsor.228 ///229 /// Unique collections allows sponsoring for certain actions.230 /// This method allows you to set the sponsor of the collection.231 /// In order for sponsorship to become active, it must be confirmed through [`Self::confirm_sponsorship`].232 pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {233 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);234 Ok(())235 }236237 /// Confirm sponsorship238 ///239 /// In order for the sponsorship to become active, the user set as the sponsor must confirm their participation.240 /// Before confirming sponsorship, the user must be specified as the sponsor of the collection via [`Self::set_sponsor`].241 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {242 if self.collection.sponsorship.pending_sponsor() != Some(sender) {243 return Ok(false);244 }245246 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());247 Ok(true)248 }249250 /// Remove collection sponsor.251 pub fn remove_sponsor(&mut self) -> DispatchResult {252 self.collection.sponsorship = SponsorshipState::Disabled;253 Ok(())254 }255256 /// Checks that the collection was created with, and must be operated upon through **Unique API**.257 /// Now check only the `external` flag and if it's **true**, then return [`Error::CollectionIsExternal`] error.258 pub fn check_is_internal(&self) -> DispatchResult {259 if self.flags.external {260 return Err(<Error<T>>::CollectionIsExternal)?;261 }262263 Ok(())264 }265266 /// Checks that the collection was created with, and must be operated upon through an **assimilated API**.267 /// Now check only the `external` flag and if it's **false**, then return [`Error::CollectionIsInternal`] error.268 pub fn check_is_external(&self) -> DispatchResult {269 if !self.flags.external {270 return Err(<Error<T>>::CollectionIsInternal)?;271 }272273 Ok(())274 }275}276277impl<T: Config> Deref for CollectionHandle<T> {278 type Target = Collection<T::AccountId>;279280 fn deref(&self) -> &Self::Target {281 &self.collection282 }283}284285impl<T: Config> DerefMut for CollectionHandle<T> {286 fn deref_mut(&mut self) -> &mut Self::Target {287 &mut self.collection288 }289}290291impl<T: Config> CollectionHandle<T> {292 /// Checks if the `user` is the owner of the collection.293 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {294 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);295 Ok(())296 }297298 /// Returns **true** if the `user` is the owner or administrator of the collection.299 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {300 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))301 }302303 /// Checks if the `user` is the owner or administrator of the collection.304 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {305 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);306 Ok(())307 }308309 /// Return **true** if `user` was not allowed to have tokens, and he can ignore such restrictions.310 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {311 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)312 }313314 /// Return **true** if `user` does not have enough token parts, and he can ignore such restrictions.315 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {316 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)317 }318319 /// Checks if the user is in the allow list. If not [Error::AddressNotInAllowlist] returns.320 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {321 ensure!(322 <Allowlist<T>>::get((self.id, user)),323 <Error<T>>::AddressNotInAllowlist324 );325 Ok(())326 }327328 /// Changes collection owner to another account329 /// #### Store read/writes330 /// 1 writes331 fn set_owner_internal(332 &mut self,333 caller: T::CrossAccountId,334 new_owner: T::CrossAccountId,335 ) -> DispatchResult {336 self.check_is_owner(&caller)?;337 self.collection.owner = new_owner.as_sub().clone();338 self.save()339 }340}341342#[frame_support::pallet]343pub mod pallet {344 use super::*;345 use dispatch::CollectionDispatch;346 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};347 use frame_system::pallet_prelude::*;348 use frame_support::traits::Currency;349 use up_data_structs::{TokenId, mapping::TokenAddressMapping};350 use scale_info::TypeInfo;351 use weights::WeightInfo;352353 #[pallet::config]354 pub trait Config:355 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo356 {357 /// Weight information for functions of this pallet.358 type WeightInfo: WeightInfo;359360 /// Events compatible with [`frame_system::Config::Event`].361 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;362363 /// Handler of accounts and payment.364 type Currency: Currency<Self::AccountId>;365366 /// Set price to create a collection.367 #[pallet::constant]368 type CollectionCreationPrice: Get<369 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,370 >;371372 /// Dispatcher of operations on collections.373 type CollectionDispatch: CollectionDispatch<Self>;374375 /// Account which holds the chain's treasury.376 type TreasuryAccountId: Get<Self::AccountId>;377378 /// Address under which the CollectionHelper contract would be available.379 #[pallet::constant]380 type ContractAddress: Get<H160>;381382 /// Mapper for token addresses to Ethereum addresses.383 type EvmTokenAddressMapping: TokenAddressMapping<H160>;384385 /// Mapper for token addresses to [`CrossAccountId`].386 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;387 }388389 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);390391 #[pallet::pallet]392 #[pallet::storage_version(STORAGE_VERSION)]393 #[pallet::generate_store(pub(super) trait Store)]394 pub struct Pallet<T>(_);395396 #[pallet::extra_constants]397 impl<T: Config> Pallet<T> {398 /// Maximum admins per collection.399 pub fn collection_admins_limit() -> u32 {400 COLLECTION_ADMINS_LIMIT401 }402 }403404 #[pallet::event]405 #[pallet::generate_deposit(pub fn deposit_event)]406 pub enum Event<T: Config> {407 /// New collection was created408 CollectionCreated(409 /// Globally unique identifier of newly created collection.410 CollectionId,411 /// [`CollectionMode`] converted into _u8_.412 u8,413 /// Collection owner.414 T::AccountId,415 ),416417 /// New collection was destroyed418 CollectionDestroyed(419 /// Globally unique identifier of collection.420 CollectionId,421 ),422423 /// New item was created.424 ItemCreated(425 /// Id of the collection where item was created.426 CollectionId,427 /// Id of an item. Unique within the collection.428 TokenId,429 /// Owner of newly created item430 T::CrossAccountId,431 /// Always 1 for NFT432 u128,433 ),434435 /// Collection item was burned.436 ItemDestroyed(437 /// Id of the collection where item was destroyed.438 CollectionId,439 /// Identifier of burned NFT.440 TokenId,441 /// Which user has destroyed its tokens.442 T::CrossAccountId,443 /// Amount of token pieces destroed. Always 1 for NFT.444 u128,445 ),446447 /// Item was transferred448 Transfer(449 /// Id of collection to which item is belong.450 CollectionId,451 /// Id of an item.452 TokenId,453 /// Original owner of item.454 T::CrossAccountId,455 /// New owner of item.456 T::CrossAccountId,457 /// Amount of token pieces transfered. Always 1 for NFT.458 u128,459 ),460461 /// Amount pieces of token owned by `sender` was approved for `spender`.462 Approved(463 /// Id of collection to which item is belong.464 CollectionId,465 /// Id of an item.466 TokenId,467 /// Original owner of item.468 T::CrossAccountId,469 /// Id for which the approval was granted.470 T::CrossAccountId,471 /// Amount of token pieces transfered. Always 1 for NFT.472 u128,473 ),474475 /// The colletion property has been added or edited.476 CollectionPropertySet(477 /// Id of collection to which property has been set.478 CollectionId,479 /// The property that was set.480 PropertyKey,481 ),482483 /// The property has been deleted.484 CollectionPropertyDeleted(485 /// Id of collection to which property has been deleted.486 CollectionId,487 /// The property that was deleted.488 PropertyKey,489 ),490491 /// The token property has been added or edited.492 TokenPropertySet(493 /// Identifier of the collection whose token has the property set.494 CollectionId,495 /// The token for which the property was set.496 TokenId,497 /// The property that was set.498 PropertyKey,499 ),500501 /// The token property has been deleted.502 TokenPropertyDeleted(503 /// Identifier of the collection whose token has the property deleted.504 CollectionId,505 /// The token for which the property was deleted.506 TokenId,507 /// The property that was deleted.508 PropertyKey,509 ),510511 /// The token property permission of a collection has been set.512 PropertyPermissionSet(513 /// ID of collection to which property permission has been set.514 CollectionId,515 /// The property permission that was set.516 PropertyKey,517 ),518 }519520 #[pallet::error]521 pub enum Error<T> {522 /// This collection does not exist.523 CollectionNotFound,524 /// Sender parameter and item owner must be equal.525 MustBeTokenOwner,526 /// No permission to perform action527 NoPermission,528 /// Destroying only empty collections is allowed529 CantDestroyNotEmptyCollection,530 /// Collection is not in mint mode.531 PublicMintingNotAllowed,532 /// Address is not in allow list.533 AddressNotInAllowlist,534535 /// Collection name can not be longer than 63 char.536 CollectionNameLimitExceeded,537 /// Collection description can not be longer than 255 char.538 CollectionDescriptionLimitExceeded,539 /// Token prefix can not be longer than 15 char.540 CollectionTokenPrefixLimitExceeded,541 /// Total collections bound exceeded.542 TotalCollectionsLimitExceeded,543 /// Exceeded max admin count544 CollectionAdminCountExceeded,545 /// Collection limit bounds per collection exceeded546 CollectionLimitBoundsExceeded,547 /// Tried to enable permissions which are only permitted to be disabled548 OwnerPermissionsCantBeReverted,549 /// Collection settings not allowing items transferring550 TransferNotAllowed,551 /// Account token limit exceeded per collection552 AccountTokenLimitExceeded,553 /// Collection token limit exceeded554 CollectionTokenLimitExceeded,555 /// Metadata flag frozen556 MetadataFlagFrozen,557558 /// Item does not exist559 TokenNotFound,560 /// Item is balance not enough561 TokenValueTooLow,562 /// Requested value is more than the approved563 ApprovedValueTooLow,564 /// Tried to approve more than owned565 CantApproveMoreThanOwned,566567 /// Can't transfer tokens to ethereum zero address568 AddressIsZero,569570 /// The operation is not supported571 UnsupportedOperation,572573 /// Insufficient funds to perform an action574 NotSufficientFounds,575576 /// User does not satisfy the nesting rule577 UserIsNotAllowedToNest,578 /// Only tokens from specific collections may nest tokens under this one579 SourceCollectionIsNotAllowedToNest,580581 /// Tried to store more data than allowed in collection field582 CollectionFieldSizeExceeded,583584 /// Tried to store more property data than allowed585 NoSpaceForProperty,586587 /// Tried to store more property keys than allowed588 PropertyLimitReached,589590 /// Property key is too long591 PropertyKeyIsTooLong,592593 /// Only ASCII letters, digits, and symbols `_`, `-`, and `.` are allowed594 InvalidCharacterInPropertyKey,595596 /// Empty property keys are forbidden597 EmptyPropertyKey,598599 /// Tried to access an external collection with an internal API600 CollectionIsExternal,601602 /// Tried to access an internal collection with an external API603 CollectionIsInternal,604 }605606 /// Storage of the count of created collections. Essentially contains the last collection ID.607 #[pallet::storage]608 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;609610 /// Storage of the count of deleted collections.611 #[pallet::storage]612 pub type DestroyedCollectionCount<T> =613 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;614615 /// Storage of collection info.616 #[pallet::storage]617 pub type CollectionById<T> = StorageMap<618 Hasher = Blake2_128Concat,619 Key = CollectionId,620 Value = Collection<<T as frame_system::Config>::AccountId>,621 QueryKind = OptionQuery,622 >;623624 /// Storage of collection properties.625 #[pallet::storage]626 #[pallet::getter(fn collection_properties)]627 pub type CollectionProperties<T> = StorageMap<628 Hasher = Blake2_128Concat,629 Key = CollectionId,630 Value = Properties,631 QueryKind = ValueQuery,632 OnEmpty = up_data_structs::CollectionProperties,633 >;634635 /// Storage of token property permissions of a collection.636 #[pallet::storage]637 #[pallet::getter(fn property_permissions)]638 pub type CollectionPropertyPermissions<T> = StorageMap<639 Hasher = Blake2_128Concat,640 Key = CollectionId,641 Value = PropertiesPermissionMap,642 QueryKind = ValueQuery,643 >;644645 /// Storage of the amount of collection admins.646 #[pallet::storage]647 pub type AdminAmount<T> = StorageMap<648 Hasher = Blake2_128Concat,649 Key = CollectionId,650 Value = u32,651 QueryKind = ValueQuery,652 >;653654 /// List of collection admins.655 #[pallet::storage]656 pub type IsAdmin<T: Config> = StorageNMap<657 Key = (658 Key<Blake2_128Concat, CollectionId>,659 Key<Blake2_128Concat, T::CrossAccountId>,660 ),661 Value = bool,662 QueryKind = ValueQuery,663 >;664665 /// Allowlisted collection users.666 #[pallet::storage]667 pub type Allowlist<T: Config> = StorageNMap<668 Key = (669 Key<Blake2_128Concat, CollectionId>,670 Key<Blake2_128Concat, T::CrossAccountId>,671 ),672 Value = bool,673 QueryKind = ValueQuery,674 >;675676 /// Not used by code, exists only to provide some types to metadata.677 #[pallet::storage]678 pub type DummyStorageValue<T: Config> = StorageValue<679 Value = (680 CollectionStats,681 CollectionId,682 TokenId,683 TokenChild,684 PhantomType<(685 TokenData<T::CrossAccountId>,686 RpcCollection<T::AccountId>,687 // RMRK688 RmrkCollectionInfo<T::AccountId>,689 RmrkInstanceInfo<T::AccountId>,690 RmrkResourceInfo,691 RmrkPropertyInfo,692 RmrkBaseInfo<T::AccountId>,693 RmrkPartType,694 RmrkBoundedTheme,695 RmrkNftChild,696 )>,697 ),698 QueryKind = OptionQuery,699 >;700701 #[pallet::hooks]702 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {703 fn on_runtime_upgrade() -> Weight {704 StorageVersion::new(1).put::<Pallet<T>>();705706 Weight::zero()707 }708 }709}710711impl<T: Config> Pallet<T> {712 /// Enshure that receiver address is correct.713 ///714 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens.715 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {716 ensure!(717 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,718 <Error<T>>::AddressIsZero719 );720 Ok(())721 }722723 /// Get a vector of collection admins.724 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {725 <IsAdmin<T>>::iter_prefix((collection,))726 .map(|(a, _)| a)727 .collect()728 }729730 /// Get a vector of users allowed to mint tokens.731 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {732 <Allowlist<T>>::iter_prefix((collection,))733 .map(|(a, _)| a)734 .collect()735 }736737 /// Is `user` allowed to mint token in `collection`.738 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {739 <Allowlist<T>>::get((collection, user))740 }741742 /// Get statistics of collections.743 pub fn collection_stats() -> CollectionStats {744 let created = <CreatedCollectionCount<T>>::get();745 let destroyed = <DestroyedCollectionCount<T>>::get();746 CollectionStats {747 created: created.0,748 destroyed: destroyed.0,749 alive: created.0 - destroyed.0,750 }751 }752753 /// Get the effective limits for the collection.754 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {755 let collection = <CollectionById<T>>::get(collection)?;756 let limits = collection.limits;757 let effective_limits = CollectionLimits {758 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),759 sponsored_data_size: Some(limits.sponsored_data_size()),760 sponsored_data_rate_limit: Some(761 limits762 .sponsored_data_rate_limit763 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),764 ),765 token_limit: Some(limits.token_limit()),766 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(767 match collection.mode {768 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,769 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,770 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,771 },772 )),773 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),774 owner_can_transfer: Some(limits.owner_can_transfer()),775 owner_can_destroy: Some(limits.owner_can_destroy()),776 transfers_enabled: Some(limits.transfers_enabled()),777 };778779 Some(effective_limits)780 }781782 /// Returns information about the `collection` adapted for rpc.783 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {784 let Collection {785 name,786 description,787 owner,788 mode,789 token_prefix,790 sponsorship,791 limits,792 permissions,793 flags,794 } = <CollectionById<T>>::get(collection)?;795796 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)797 .into_iter()798 .map(|(key, permission)| PropertyKeyPermission { key, permission })799 .collect();800801 let properties = <CollectionProperties<T>>::get(collection)802 .into_iter()803 .map(|(key, value)| Property { key, value })804 .collect();805806 let permissions = CollectionPermissions {807 access: Some(permissions.access()),808 mint_mode: Some(permissions.mint_mode()),809 nesting: Some(permissions.nesting().clone()),810 };811812 Some(RpcCollection {813 name: name.into_inner(),814 description: description.into_inner(),815 owner,816 mode,817 token_prefix: token_prefix.into_inner(),818 sponsorship,819 limits,820 permissions,821 token_property_permissions,822 properties,823 read_only: flags.external,824825 flags: RpcCollectionFlags {826 foreign: flags.foreign,827 erc721metadata: flags.erc721metadata,828 },829 })830 }831}832833macro_rules! limit_default {834 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{835 $(836 if let Some($new) = $new.$field {837 let $old = $old.$field($($arg)?);838 let _ = $new;839 let _ = $old;840 $check841 } else {842 $new.$field = $old.$field843 }844 )*845 }};846}847macro_rules! limit_default_clone {848 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{849 $(850 if let Some($new) = $new.$field.clone() {851 let $old = $old.$field($($arg)?);852 let _ = $new;853 let _ = $old;854 $check855 } else {856 $new.$field = $old.$field.clone()857 }858 )*859 }};860}861862impl<T: Config> Pallet<T> {863 /// Create new collection.864 ///865 /// * `owner` - The owner of the collection.866 /// * `data` - Description of the created collection.867 /// * `flags` - Extra flags to store.868 pub fn init_collection(869 owner: T::CrossAccountId,870 payer: T::CrossAccountId,871 data: CreateCollectionData<T::AccountId>,872 flags: CollectionFlags,873 ) -> Result<CollectionId, DispatchError> {874 {875 ensure!(876 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,877 Error::<T>::CollectionTokenPrefixLimitExceeded878 );879 }880881 let created_count = <CreatedCollectionCount<T>>::get()882 .0883 .checked_add(1)884 .ok_or(ArithmeticError::Overflow)?;885 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;886 let id = CollectionId(created_count);887888 // bound Total number of collections889 ensure!(890 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,891 <Error<T>>::TotalCollectionsLimitExceeded892 );893894 // =========895896 let collection = Collection {897 owner: owner.as_sub().clone(),898 name: data.name,899 mode: data.mode.clone(),900 description: data.description,901 token_prefix: data.token_prefix,902 sponsorship: data903 .pending_sponsor904 .map(SponsorshipState::Unconfirmed)905 .unwrap_or_default(),906 limits: data907 .limits908 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))909 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,910 permissions: data911 .permissions912 .map(|permissions| {913 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)914 })915 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,916 flags,917 };918919 let mut collection_properties = up_data_structs::CollectionProperties::get();920 collection_properties921 .try_set_from_iter(data.properties.into_iter())922 .map_err(<Error<T>>::from)?;923924 CollectionProperties::<T>::insert(id, collection_properties);925926 let mut token_props_permissions = PropertiesPermissionMap::new();927 token_props_permissions928 .try_set_from_iter(data.token_property_permissions.into_iter())929 .map_err(<Error<T>>::from)?;930931 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);932933 // Take a (non-refundable) deposit of collection creation934 {935 let mut imbalance =936 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();937 imbalance.subsume(938 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(939 &T::TreasuryAccountId::get(),940 T::CollectionCreationPrice::get(),941 ),942 );943 <T as Config>::Currency::settle(944 payer.as_sub(),945 imbalance,946 WithdrawReasons::TRANSFER,947 ExistenceRequirement::KeepAlive,948 )949 .map_err(|_| Error::<T>::NotSufficientFounds)?;950 }951952 <CreatedCollectionCount<T>>::put(created_count);953 <Pallet<T>>::deposit_event(Event::CollectionCreated(954 id,955 data.mode.id(),956 owner.as_sub().clone(),957 ));958 <PalletEvm<T>>::deposit_log(959 erc::CollectionHelpersEvents::CollectionCreated {960 owner: *owner.as_eth(),961 collection_id: eth::collection_id_to_address(id),962 }963 .to_log(T::ContractAddress::get()),964 );965 <CollectionById<T>>::insert(id, collection);966 Ok(id)967 }968969 /// Destroy collection.970 ///971 /// * `collection` - Collection handler.972 /// * `sender` - The owner or administrator of the collection.973 pub fn destroy_collection(974 collection: CollectionHandle<T>,975 sender: &T::CrossAccountId,976 ) -> DispatchResult {977 ensure!(978 collection.limits.owner_can_destroy(),979 <Error<T>>::NoPermission,980 );981 collection.check_is_owner(sender)?;982983 let destroyed_collections = <DestroyedCollectionCount<T>>::get()984 .0985 .checked_add(1)986 .ok_or(ArithmeticError::Overflow)?;987988 // =========989990 <DestroyedCollectionCount<T>>::put(destroyed_collections);991 <CollectionById<T>>::remove(collection.id);992 <AdminAmount<T>>::remove(collection.id);993 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);994 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);995 <CollectionProperties<T>>::remove(collection.id);996997 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));998999 <PalletEvm<T>>::deposit_log(1000 erc::CollectionHelpersEvents::CollectionDestroyed {1001 collection_id: eth::collection_id_to_address(collection.id),1002 }1003 .to_log(T::ContractAddress::get()),1004 );1005 Ok(())1006 }10071008 /// Set collection property.1009 ///1010 /// * `collection` - Collection handler.1011 /// * `sender` - The owner or administrator of the collection.1012 /// * `property` - The property to set.1013 pub fn set_collection_property(1014 collection: &CollectionHandle<T>,1015 sender: &T::CrossAccountId,1016 property: Property,1017 ) -> DispatchResult {1018 collection.check_is_owner_or_admin(sender)?;10191020 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1021 let property = property.clone();1022 properties.try_set(property.key, property.value)1023 })1024 .map_err(<Error<T>>::from)?;10251026 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));10271028 Ok(())1029 }10301031 /// Set scouped collection property.1032 ///1033 /// * `collection_id` - ID of the collection for which the property is being set.1034 /// * `scope` - Property scope.1035 /// * `property` - The property to set.1036 pub fn set_scoped_collection_property(1037 collection_id: CollectionId,1038 scope: PropertyScope,1039 property: Property,1040 ) -> DispatchResult {1041 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1042 properties.try_scoped_set(scope, property.key, property.value)1043 })1044 .map_err(<Error<T>>::from)?;10451046 Ok(())1047 }10481049 /// Set scouped collection properties.1050 ///1051 /// * `collection_id` - ID of the collection for which the properties is being set.1052 /// * `scope` - Property scope.1053 /// * `properties` - The properties to set.1054 pub fn set_scoped_collection_properties(1055 collection_id: CollectionId,1056 scope: PropertyScope,1057 properties: impl Iterator<Item = Property>,1058 ) -> DispatchResult {1059 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1060 stored_properties.try_scoped_set_from_iter(scope, properties)1061 })1062 .map_err(<Error<T>>::from)?;10631064 Ok(())1065 }10661067 /// Set collection properties.1068 ///1069 /// * `collection` - Collection handler.1070 /// * `sender` - The owner or administrator of the collection.1071 /// * `properties` - The properties to set.1072 #[transactional]1073 pub fn set_collection_properties(1074 collection: &CollectionHandle<T>,1075 sender: &T::CrossAccountId,1076 properties: Vec<Property>,1077 ) -> DispatchResult {1078 for property in properties {1079 Self::set_collection_property(collection, sender, property)?;1080 }10811082 Ok(())1083 }10841085 /// Delete collection property.1086 ///1087 /// * `collection` - Collection handler.1088 /// * `sender` - The owner or administrator of the collection.1089 /// * `property` - The property to delete.1090 pub fn delete_collection_property(1091 collection: &CollectionHandle<T>,1092 sender: &T::CrossAccountId,1093 property_key: PropertyKey,1094 ) -> DispatchResult {1095 collection.check_is_owner_or_admin(sender)?;10961097 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1098 properties.remove(&property_key)1099 })1100 .map_err(<Error<T>>::from)?;11011102 Self::deposit_event(Event::CollectionPropertyDeleted(1103 collection.id,1104 property_key,1105 ));11061107 Ok(())1108 }11091110 /// Delete collection properties.1111 ///1112 /// * `collection` - Collection handler.1113 /// * `sender` - The owner or administrator of the collection.1114 /// * `properties` - The properties to delete.1115 #[transactional]1116 pub fn delete_collection_properties(1117 collection: &CollectionHandle<T>,1118 sender: &T::CrossAccountId,1119 property_keys: Vec<PropertyKey>,1120 ) -> DispatchResult {1121 for key in property_keys {1122 Self::delete_collection_property(collection, sender, key)?;1123 }11241125 Ok(())1126 }11271128 /// Set collection propetry permission without any checks.1129 ///1130 /// Used for migrations.1131 ///1132 /// * `collection` - Collection handler.1133 /// * `property_permissions` - Property permissions.1134 pub fn set_property_permission_unchecked(1135 collection: CollectionId,1136 property_permission: PropertyKeyPermission,1137 ) -> DispatchResult {1138 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1139 permissions.try_set(property_permission.key, property_permission.permission)1140 })1141 .map_err(<Error<T>>::from)?;1142 Ok(())1143 }11441145 /// Set collection property permission.1146 ///1147 /// * `collection` - Collection handler.1148 /// * `sender` - The owner or administrator of the collection.1149 /// * `property_permission` - Property permission.1150 pub fn set_property_permission(1151 collection: &CollectionHandle<T>,1152 sender: &T::CrossAccountId,1153 property_permission: PropertyKeyPermission,1154 ) -> DispatchResult {1155 Self::set_scoped_property_permission(1156 collection,1157 sender,1158 PropertyScope::None,1159 property_permission,1160 )1161 }11621163 /// Set collection property permission with scope.1164 ///1165 /// * `collection` - Collection handler.1166 /// * `sender` - The owner or administrator of the collection.1167 /// * `scope` - Property scope.1168 /// * `property_permission` - Property permission.1169 pub fn set_scoped_property_permission(1170 collection: &CollectionHandle<T>,1171 sender: &T::CrossAccountId,1172 scope: PropertyScope,1173 property_permission: PropertyKeyPermission,1174 ) -> DispatchResult {1175 collection.check_is_owner_or_admin(sender)?;11761177 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1178 let current_permission = all_permissions.get(&property_permission.key);1179 if matches![1180 current_permission,1181 Some(PropertyPermission { mutable: false, .. })1182 ] {1183 return Err(<Error<T>>::NoPermission.into());1184 }11851186 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1187 let property_permission = property_permission.clone();1188 permissions.try_scoped_set(1189 scope,1190 property_permission.key,1191 property_permission.permission,1192 )1193 })1194 .map_err(<Error<T>>::from)?;11951196 Self::deposit_event(Event::PropertyPermissionSet(1197 collection.id,1198 property_permission.key,1199 ));12001201 Ok(())1202 }12031204 /// Set token property permission.1205 ///1206 /// * `collection` - Collection handler.1207 /// * `sender` - The owner or administrator of the collection.1208 /// * `property_permissions` - Property permissions.1209 #[transactional]1210 pub fn set_token_property_permissions(1211 collection: &CollectionHandle<T>,1212 sender: &T::CrossAccountId,1213 property_permissions: Vec<PropertyKeyPermission>,1214 ) -> DispatchResult {1215 Self::set_scoped_token_property_permissions(1216 collection,1217 sender,1218 PropertyScope::None,1219 property_permissions,1220 )1221 }12221223 /// Set token property permission with scope.1224 ///1225 /// * `collection` - Collection handler.1226 /// * `sender` - The owner or administrator of the collection.1227 /// * `scope` - Property scope.1228 /// * `property_permissions` - Property permissions.1229 #[transactional]1230 pub fn set_scoped_token_property_permissions(1231 collection: &CollectionHandle<T>,1232 sender: &T::CrossAccountId,1233 scope: PropertyScope,1234 property_permissions: Vec<PropertyKeyPermission>,1235 ) -> DispatchResult {1236 for prop_pemission in property_permissions {1237 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1238 }12391240 Ok(())1241 }12421243 /// Get collection property.1244 pub fn get_collection_property(1245 collection_id: CollectionId,1246 key: &PropertyKey,1247 ) -> Option<PropertyValue> {1248 Self::collection_properties(collection_id).get(key).cloned()1249 }12501251 /// Convert byte vector to property key vector.1252 pub fn bytes_keys_to_property_keys(1253 keys: Vec<Vec<u8>>,1254 ) -> Result<Vec<PropertyKey>, DispatchError> {1255 keys.into_iter()1256 .map(|key| -> Result<PropertyKey, DispatchError> {1257 key.try_into()1258 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1259 })1260 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1261 }12621263 /// Get properties according to given keys.1264 pub fn filter_collection_properties(1265 collection_id: CollectionId,1266 keys: Option<Vec<PropertyKey>>,1267 ) -> Result<Vec<Property>, DispatchError> {1268 let properties = Self::collection_properties(collection_id);12691270 let properties = keys1271 .map(|keys| {1272 keys.into_iter()1273 .filter_map(|key| {1274 properties.get(&key).map(|value| Property {1275 key,1276 value: value.clone(),1277 })1278 })1279 .collect()1280 })1281 .unwrap_or_else(|| {1282 properties1283 .into_iter()1284 .map(|(key, value)| Property { key, value })1285 .collect()1286 });12871288 Ok(properties)1289 }12901291 /// Get property permissions according to given keys.1292 pub fn filter_property_permissions(1293 collection_id: CollectionId,1294 keys: Option<Vec<PropertyKey>>,1295 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1296 let permissions = Self::property_permissions(collection_id);12971298 let key_permissions = keys1299 .map(|keys| {1300 keys.into_iter()1301 .filter_map(|key| {1302 permissions1303 .get(&key)1304 .map(|permission| PropertyKeyPermission {1305 key,1306 permission: permission.clone(),1307 })1308 })1309 .collect()1310 })1311 .unwrap_or_else(|| {1312 permissions1313 .into_iter()1314 .map(|(key, permission)| PropertyKeyPermission { key, permission })1315 .collect()1316 });13171318 Ok(key_permissions)1319 }13201321 /// Toggle `user` participation in the `collection`'s allow list.1322 /// #### Store read/writes1323 /// 1 writes1324 pub fn toggle_allowlist(1325 collection: &CollectionHandle<T>,1326 sender: &T::CrossAccountId,1327 user: &T::CrossAccountId,1328 allowed: bool,1329 ) -> DispatchResult {1330 collection.check_is_owner_or_admin(sender)?;13311332 // =========13331334 if allowed {1335 <Allowlist<T>>::insert((collection.id, user), true);1336 } else {1337 <Allowlist<T>>::remove((collection.id, user));1338 }13391340 Ok(())1341 }13421343 /// Toggle `user` participation in the `collection`'s admin list.1344 /// #### Store read/writes1345 /// 2 writes1346 pub fn toggle_admin(1347 collection: &CollectionHandle<T>,1348 sender: &T::CrossAccountId,1349 user: &T::CrossAccountId,1350 admin: bool,1351 ) -> DispatchResult {1352 collection.check_is_owner(sender)?;13531354 let was_admin = <IsAdmin<T>>::get((collection.id, user));1355 if was_admin == admin {1356 return Ok(());1357 }1358 let amount = <AdminAmount<T>>::get(collection.id);13591360 if admin {1361 let amount = amount1362 .checked_add(1)1363 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1364 ensure!(1365 amount <= Self::collection_admins_limit(),1366 <Error<T>>::CollectionAdminCountExceeded,1367 );13681369 // =========13701371 <AdminAmount<T>>::insert(collection.id, amount);1372 <IsAdmin<T>>::insert((collection.id, user), true);1373 } else {1374 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1375 <IsAdmin<T>>::remove((collection.id, user));1376 }13771378 Ok(())1379 }13801381 /// Merge set fields from `new_limit` to `old_limit`.1382 pub fn clamp_limits(1383 mode: CollectionMode,1384 old_limit: &CollectionLimits,1385 mut new_limit: CollectionLimits,1386 ) -> Result<CollectionLimits, DispatchError> {1387 let limits = old_limit;1388 limit_default!(old_limit, new_limit,1389 account_token_ownership_limit => ensure!(1390 new_limit <= MAX_TOKEN_OWNERSHIP,1391 <Error<T>>::CollectionLimitBoundsExceeded,1392 ),1393 sponsored_data_size => ensure!(1394 new_limit <= CUSTOM_DATA_LIMIT,1395 <Error<T>>::CollectionLimitBoundsExceeded,1396 ),13971398 sponsored_data_rate_limit => {},1399 token_limit => ensure!(1400 old_limit >= new_limit && new_limit > 0,1401 <Error<T>>::CollectionTokenLimitExceeded1402 ),14031404 sponsor_transfer_timeout(match mode {1405 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1406 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1407 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1408 }) => ensure!(1409 new_limit <= MAX_SPONSOR_TIMEOUT,1410 <Error<T>>::CollectionLimitBoundsExceeded,1411 ),1412 sponsor_approve_timeout => {},1413 owner_can_transfer => ensure!(1414 !limits.owner_can_transfer_instaled() ||1415 old_limit || !new_limit,1416 <Error<T>>::OwnerPermissionsCantBeReverted,1417 ),1418 owner_can_destroy => ensure!(1419 old_limit || !new_limit,1420 <Error<T>>::OwnerPermissionsCantBeReverted,1421 ),1422 transfers_enabled => {},1423 );1424 Ok(new_limit)1425 }14261427 /// Merge set fields from `new_permission` to `old_permission`.1428 pub fn clamp_permissions(1429 _mode: CollectionMode,1430 old_permission: &CollectionPermissions,1431 mut new_permission: CollectionPermissions,1432 ) -> Result<CollectionPermissions, DispatchError> {1433 limit_default_clone!(old_permission, new_permission,1434 access => {},1435 mint_mode => {},1436 nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1437 );1438 Ok(new_permission)1439 }1440}14411442/// Indicates unsupported methods by returning [Error::UnsupportedOperation].1443#[macro_export]1444macro_rules! unsupported {1445 ($runtime:path) => {1446 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1447 };1448}14491450/// Return weights for various worst-case operations.1451pub trait CommonWeightInfo<CrossAccountId> {1452 /// Weight of item creation.1453 fn create_item() -> Weight;14541455 /// Weight of items creation.1456 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;14571458 /// Weight of items creation.1459 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;14601461 /// The weight of the burning item.1462 fn burn_item() -> Weight;14631464 /// Property setting weight.1465 ///1466 /// * `amount`- The number of properties to set.1467 fn set_collection_properties(amount: u32) -> Weight;14681469 /// Collection property deletion weight.1470 ///1471 /// * `amount`- The number of properties to set.1472 fn delete_collection_properties(amount: u32) -> Weight;14731474 /// Token property setting weight.1475 ///1476 /// * `amount`- The number of properties to set.1477 fn set_token_properties(amount: u32) -> Weight;14781479 /// Token property deletion weight.1480 ///1481 /// * `amount`- The number of properties to delete.1482 fn delete_token_properties(amount: u32) -> Weight;14831484 /// Token property permissions set weight.1485 ///1486 /// * `amount`- The number of property permissions to set.1487 fn set_token_property_permissions(amount: u32) -> Weight;14881489 /// Transfer price of the token or its parts.1490 fn transfer() -> Weight;14911492 /// The price of setting the permission of the operation from another user.1493 fn approve() -> Weight;14941495 /// Transfer price from another user.1496 fn transfer_from() -> Weight;14971498 /// The price of burning a token from another user.1499 fn burn_from() -> Weight;15001501 /// Differs from burn_item in case of Fungible and Refungible, as it should burn1502 /// whole users's balance.1503 ///1504 /// This method shouldn't be used directly, as it doesn't count breadth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1505 fn burn_recursively_self_raw() -> Weight;15061507 /// Cost of iterating over `amount` children while burning, without counting child burning itself.1508 ///1509 /// This method shouldn't be used directly, as it doesn't count depth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1510 fn burn_recursively_breadth_raw(amount: u32) -> Weight;15111512 /// The price of recursive burning a token.1513 ///1514 /// `max_selfs` - The maximum burning weight of the token itself.1515 /// `max_breadth` - The maximum number of nested tokens to burn.1516 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1517 Self::burn_recursively_self_raw()1518 .saturating_mul(max_selfs.max(1) as u64)1519 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1520 }15211522 /// The price of retrieving token owner1523 fn token_owner() -> Weight;1524}15251526/// Weight info extension trait for refungible pallet.1527pub trait RefungibleExtensionsWeightInfo {1528 /// Weight of token repartition.1529 fn repartition() -> Weight;1530}15311532/// Common collection operations.1533///1534/// It wraps methods in Fungible, Nonfungible and Refungible pallets1535/// and adds weight info.1536pub trait CommonCollectionOperations<T: Config> {1537 /// Create token.1538 ///1539 /// * `sender` - The user who mint the token and pays for the transaction.1540 /// * `to` - The user who will own the token.1541 /// * `data` - Token data.1542 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1543 fn create_item(1544 &self,1545 sender: T::CrossAccountId,1546 to: T::CrossAccountId,1547 data: CreateItemData,1548 nesting_budget: &dyn Budget,1549 ) -> DispatchResultWithPostInfo;15501551 /// Create multiple tokens.1552 ///1553 /// * `sender` - The user who mint the token and pays for the transaction.1554 /// * `to` - The user who will own the token.1555 /// * `data` - Token data.1556 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1557 fn create_multiple_items(1558 &self,1559 sender: T::CrossAccountId,1560 to: T::CrossAccountId,1561 data: Vec<CreateItemData>,1562 nesting_budget: &dyn Budget,1563 ) -> DispatchResultWithPostInfo;15641565 /// Create multiple tokens.1566 ///1567 /// * `sender` - The user who mint the token and pays for the transaction.1568 /// * `to` - The user who will own the token.1569 /// * `data` - Token data.1570 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1571 fn create_multiple_items_ex(1572 &self,1573 sender: T::CrossAccountId,1574 data: CreateItemExData<T::CrossAccountId>,1575 nesting_budget: &dyn Budget,1576 ) -> DispatchResultWithPostInfo;15771578 /// Burn token.1579 ///1580 /// * `sender` - The user who owns the token.1581 /// * `token` - Token id that will burned.1582 /// * `amount` - The number of parts of the token that will be burned.1583 fn burn_item(1584 &self,1585 sender: T::CrossAccountId,1586 token: TokenId,1587 amount: u128,1588 ) -> DispatchResultWithPostInfo;15891590 /// Burn token and all nested tokens recursievly.1591 ///1592 /// * `sender` - The user who owns the token.1593 /// * `token` - Token id that will burned.1594 /// * `self_budget` - The budget that can be spent on burning tokens.1595 /// * `breadth_budget` - The budget that can be spent on burning nested tokens.1596 fn burn_item_recursively(1597 &self,1598 sender: T::CrossAccountId,1599 token: TokenId,1600 self_budget: &dyn Budget,1601 breadth_budget: &dyn Budget,1602 ) -> DispatchResultWithPostInfo;16031604 /// Set collection properties.1605 ///1606 /// * `sender` - Must be either the owner of the collection or its admin.1607 /// * `properties` - Properties to be set.1608 fn set_collection_properties(1609 &self,1610 sender: T::CrossAccountId,1611 properties: Vec<Property>,1612 ) -> DispatchResultWithPostInfo;16131614 /// Delete collection properties.1615 ///1616 /// * `sender` - Must be either the owner of the collection or its admin.1617 /// * `properties` - The properties to be removed.1618 fn delete_collection_properties(1619 &self,1620 sender: &T::CrossAccountId,1621 property_keys: Vec<PropertyKey>,1622 ) -> DispatchResultWithPostInfo;16231624 /// Set token properties.1625 ///1626 /// The appropriate [`PropertyPermission`] for the token property1627 /// must be set with [`Self::set_token_property_permissions`].1628 ///1629 /// * `sender` - Must be either the owner of the token or its admin.1630 /// * `token_id` - The token for which the properties are being set.1631 /// * `properties` - Properties to be set.1632 /// * `budget` - Budget for setting properties.1633 fn set_token_properties(1634 &self,1635 sender: T::CrossAccountId,1636 token_id: TokenId,1637 properties: Vec<Property>,1638 budget: &dyn Budget,1639 ) -> DispatchResultWithPostInfo;16401641 /// Remove token properties.1642 ///1643 /// The appropriate [`PropertyPermission`] for the token property1644 /// must be set with [`Self::set_token_property_permissions`].1645 ///1646 /// * `sender` - Must be either the owner of the token or its admin.1647 /// * `token_id` - The token for which the properties are being remove.1648 /// * `property_keys` - Keys to remove corresponding properties.1649 /// * `budget` - Budget for removing properties.1650 fn delete_token_properties(1651 &self,1652 sender: T::CrossAccountId,1653 token_id: TokenId,1654 property_keys: Vec<PropertyKey>,1655 budget: &dyn Budget,1656 ) -> DispatchResultWithPostInfo;16571658 /// Set token property permissions.1659 ///1660 /// * `sender` - Must be either the owner of the token or its admin.1661 /// * `token_id` - The token for which the properties are being set.1662 /// * `property_permissions` - Property permissions to be set.1663 /// * `budget` - Budget for setting properties.1664 fn set_token_property_permissions(1665 &self,1666 sender: &T::CrossAccountId,1667 property_permissions: Vec<PropertyKeyPermission>,1668 ) -> DispatchResultWithPostInfo;16691670 /// Transfer amount of token pieces.1671 ///1672 /// * `sender` - Donor user.1673 /// * `to` - Recepient user.1674 /// * `token` - The token of which parts are being sent.1675 /// * `amount` - The number of parts of the token that will be transferred.1676 /// * `budget` - The maximum budget that can be spent on the transfer.1677 fn transfer(1678 &self,1679 sender: T::CrossAccountId,1680 to: T::CrossAccountId,1681 token: TokenId,1682 amount: u128,1683 budget: &dyn Budget,1684 ) -> DispatchResultWithPostInfo;16851686 /// Grant access to another account to transfer parts of the token owned by the calling user via [Self::transfer_from].1687 ///1688 /// * `sender` - The user who grants access to the token.1689 /// * `spender` - The user to whom the rights are granted.1690 /// * `token` - The token to which access is granted.1691 /// * `amount` - The amount of pieces that another user can dispose of.1692 fn approve(1693 &self,1694 sender: T::CrossAccountId,1695 spender: T::CrossAccountId,1696 token: TokenId,1697 amount: u128,1698 ) -> DispatchResultWithPostInfo;16991700 /// Send parts of a token owned by another user.1701 ///1702 /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].1703 ///1704 /// * `sender` - The user who must have access to the token (see [`Self::approve`]).1705 /// * `from` - The user who owns the token.1706 /// * `to` - Recepient user.1707 /// * `token` - The token of which parts are being sent.1708 /// * `amount` - The number of parts of the token that will be transferred.1709 /// * `budget` - The maximum budget that can be spent on the transfer.1710 fn transfer_from(1711 &self,1712 sender: T::CrossAccountId,1713 from: T::CrossAccountId,1714 to: T::CrossAccountId,1715 token: TokenId,1716 amount: u128,1717 budget: &dyn Budget,1718 ) -> DispatchResultWithPostInfo;17191720 /// Burn parts of a token owned by another user.1721 ///1722 /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].1723 ///1724 /// * `sender` - The user who must have access to the token (see [`Self::approve`]).1725 /// * `from` - The user who owns the token.1726 /// * `token` - The token of which parts are being sent.1727 /// * `amount` - The number of parts of the token that will be transferred.1728 /// * `budget` - The maximum budget that can be spent on the burn.1729 fn burn_from(1730 &self,1731 sender: T::CrossAccountId,1732 from: T::CrossAccountId,1733 token: TokenId,1734 amount: u128,1735 budget: &dyn Budget,1736 ) -> DispatchResultWithPostInfo;17371738 /// Check permission to nest token.1739 ///1740 /// * `sender` - The user who initiated the check.1741 /// * `from` - The token that is checked for embedding.1742 /// * `under` - Token under which to check.1743 /// * `budget` - The maximum budget that can be spent on the check.1744 fn check_nesting(1745 &self,1746 sender: T::CrossAccountId,1747 from: (CollectionId, TokenId),1748 under: TokenId,1749 budget: &dyn Budget,1750 ) -> DispatchResult;17511752 /// Nest one token into another.1753 ///1754 /// * `under` - Token holder.1755 /// * `to_nest` - Nested token.1756 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17571758 /// Unnest token.1759 ///1760 /// * `under` - Token holder.1761 /// * `to_nest` - Token to unnest.1762 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17631764 /// Get all user tokens.1765 ///1766 /// * `account` - Account for which you need to get tokens.1767 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;17681769 /// Get all the tokens in the collection.1770 fn collection_tokens(&self) -> Vec<TokenId>;17711772 /// Check if the token exists.1773 ///1774 /// * `token` - Id token to check.1775 fn token_exists(&self, token: TokenId) -> bool;17761777 /// Get the id of the last minted token.1778 fn last_token_id(&self) -> TokenId;17791780 /// Get the owner of the token.1781 ///1782 /// * `token` - The token for which you need to find out the owner.1783 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;17841785 /// Returns 10 tokens owners in no particular order.1786 ///1787 /// * `token` - The token for which you need to find out the owners.1788 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;17891790 /// Get the value of the token property by key.1791 ///1792 /// * `token` - Token with the property to get.1793 /// * `key` - Property name.1794 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;17951796 /// Get a set of token properties by key vector.1797 ///1798 /// * `token` - Token with the property to get.1799 /// * `keys` - Vector of property keys. If this parameter is [None](sp_std::result::Result),1800 /// then all properties are returned.1801 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;18021803 /// Amount of unique collection tokens1804 fn total_supply(&self) -> u32;18051806 /// Amount of different tokens account has.1807 ///1808 /// * `account` - The account for which need to get the balance.1809 fn account_balance(&self, account: T::CrossAccountId) -> u32;18101811 /// Amount of specific token account have.1812 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;18131814 /// Amount of token pieces1815 fn total_pieces(&self, token: TokenId) -> Option<u128>;18161817 /// Get the number of parts of the token that a trusted user can manage.1818 ///1819 /// * `sender` - Trusted user.1820 /// * `spender` - Owner of the token.1821 /// * `token` - The token for which to get the value.1822 fn allowance(1823 &self,1824 sender: T::CrossAccountId,1825 spender: T::CrossAccountId,1826 token: TokenId,1827 ) -> u128;18281829 /// Get extension for RFT collection.1830 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1831}18321833/// Extension for RFT collection.1834pub trait RefungibleExtensions<T>1835where1836 T: Config,1837{1838 /// Change the number of parts of the token.1839 ///1840 /// When the value changes down, this function is equivalent to burning parts of the token.1841 ///1842 /// * `sender` - The user calling the repartition operation. Must be the owner of the token.1843 /// * `token` - The token for which you want to change the number of parts.1844 /// * `amount` - The new value of the parts of the token.1845 fn repartition(1846 &self,1847 sender: &T::CrossAccountId,1848 token: TokenId,1849 amount: u128,1850 ) -> DispatchResultWithPostInfo;1851}18521853/// Merge [`DispatchResult`] with [`Weight`] into [`DispatchResultWithPostInfo`].1854///1855/// Used for [`CommonCollectionOperations`] implementations and flexible enough to do so.1856pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1857 let post_info = PostDispatchInfo {1858 actual_weight: Some(weight),1859 pays_fee: Pays::Yes,1860 };1861 match res {1862 Ok(()) => Ok(post_info),1863 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1864 }1865}18661867impl<T: Config> From<PropertiesError> for Error<T> {1868 fn from(error: PropertiesError) -> Self {1869 match error {1870 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1871 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1872 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1873 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1874 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1875 }1876 }1877}pallets/evm-contract-helpers/src/lib.rsdiffbeforeafterboth--- a/pallets/evm-contract-helpers/src/lib.rs
+++ b/pallets/evm-contract-helpers/src/lib.rs
@@ -47,6 +47,7 @@
type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;
/// Address, under which magic contract will be available
+ #[pallet::constant]
type ContractAddress: Get<H160>;
/// In case of enabled sponsoring, but no sponsoring rate limit set,
pallets/evm-contract-helpers/src/stubs/ContractHelpers.rawdiffbeforeafterbothbinary blob — no preview
pallets/fungible/src/erc.rsdiffbeforeafterboth--- 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(<Allowance<T>>::get((self.id, owner, spender)).into())
}
+
+ /// @notice Returns collection helper contract address
+ fn collection_helper_address(&self) -> Result<address> {
+ Ok(T::ContractAddress::get())
+ }
}
#[solidity_interface(name = ERC20Mintable)]
pallets/fungible/src/stubs/UniqueFungible.rawdiffbeforeafterbothbinary blob — no preview
pallets/fungible/src/stubs/UniqueFungible.soldiffbeforeafterboth--- 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 {}
pallets/nonfungible/src/erc.rsdiffbeforeafterboth--- 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<address> {
+ Ok(T::ContractAddress::get())
+ }
}
/// @title ERC721 Token that can be irreversibly burned (destroyed).
pallets/nonfungible/src/stubs/UniqueNFT.rawdiffbeforeafterbothbinary blob — no preview
pallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth--- 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
pallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth--- 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);
pallets/refungible/src/erc.rsdiffbeforeafterboth--- 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<address> {
+ Ok(T::ContractAddress::get())
+ }
}
/// Returns amount of pieces of `token` that `owner` have
pallets/refungible/src/stubs/UniqueRefungible.rawdiffbeforeafterbothbinary blob — no preview
pallets/refungible/src/stubs/UniqueRefungible.soldiffbeforeafterboth--- 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
pallets/refungible/src/stubs/UniqueRefungibleToken.rawdiffbeforeafterbothbinary blob — no preview
pallets/unique/src/eth/mod.rsdiffbeforeafterboth--- 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<address> {
+ 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<uint32> {
+ 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`]
pallets/unique/src/eth/stubs/CollectionHelpers.rawdiffbeforeafterbothbinary blob — no preview
pallets/unique/src/eth/stubs/CollectionHelpers.soldiffbeforeafterboth--- 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;
+ }
}
pallets/unique/src/lib.rsdiffbeforeafterboth--- 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<T>;
+ #[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 {
primitives/data-structs/CHANGELOG.mddiffbeforeafterboth--- 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.
<!-- bureaucrate goes here -->
+
## [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`.
primitives/data-structs/src/lib.rsdiffbeforeafterboth--- 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
tests/package.jsondiffbeforeafterboth--- 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",
tests/src/apiConsts.test.tsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+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<T>(constValue: any, expectedValue: T) {
+ expect(constValue.toBigInt()).equal(expectedValue);
+}
\ No newline at end of file
tests/src/eth/abi/collectionHelpers.jsondiffbeforeafterboth--- 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" },
tests/src/eth/abi/fungible.jsondiffbeforeafterboth--- 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": [
{
tests/src/eth/abi/nonFungible.jsondiffbeforeafterboth--- 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": [
{
tests/src/eth/abi/reFungible.jsondiffbeforeafterboth--- 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": [
{
tests/src/eth/api/CollectionHelpers.soldiffbeforeafterboth--- 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);
}
tests/src/eth/api/UniqueFungible.soldiffbeforeafterboth--- 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 {}
tests/src/eth/api/UniqueNFT.soldiffbeforeafterboth--- 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
tests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth--- 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
tests/src/eth/collectionHelperAddress.test.tsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+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);
+ });
+
+});
tests/src/eth/util/playgrounds/unique.dev.tsdiffbeforeafterboth--- 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 {
tests/src/interfaces/augment-api-consts.tsdiffbeforeafterboth--- 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<ApiType extends ApiTypes> = AugmentedConst<ApiType>;
@@ -70,6 +70,10 @@
**/
collectionCreationPrice: u128 & AugmentedConst<ApiType>;
/**
+ * Address under which the CollectionHelper contract would be available.
+ **/
+ contractAddress: H160 & AugmentedConst<ApiType>;
+ /**
* Generic const
**/
[key: string]: Codec;
@@ -82,6 +86,16 @@
**/
[key: string]: Codec;
};
+ evmContractHelpers: {
+ /**
+ * Address, under which magic contract will be available
+ **/
+ contractAddress: H160 & AugmentedConst<ApiType>;
+ /**
+ * 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<ApiType>;
+ /**
+ * Default FT collection limit.
+ **/
+ ftDefaultCollectionLimits: UpDataStructsCollectionLimits & AugmentedConst<ApiType>;
+ /**
+ * Maximal length of a collection description.
+ **/
+ maxCollectionDescriptionLength: u32 & AugmentedConst<ApiType>;
+ /**
+ * Maximal length of a collection name.
+ **/
+ maxCollectionNameLength: u32 & AugmentedConst<ApiType>;
+ /**
+ * Maximum size for all collection properties.
+ **/
+ maxCollectionPropertiesSize: u32 & AugmentedConst<ApiType>;
+ /**
+ * A maximum number of token properties.
+ **/
+ maxPropertiesPerItem: u32 & AugmentedConst<ApiType>;
+ /**
+ * Maximal length of a property key.
+ **/
+ maxPropertyKeyLength: u32 & AugmentedConst<ApiType>;
+ /**
+ * Maximal length of a property value.
+ **/
+ maxPropertyValueLength: u32 & AugmentedConst<ApiType>;
+ /**
+ * Maximal length of a token prefix.
+ **/
+ maxTokenPrefixLength: u32 & AugmentedConst<ApiType>;
+ /**
+ * Maximum size of all token properties.
+ **/
+ maxTokenPropertiesSize: u32 & AugmentedConst<ApiType>;
+ /**
+ * A maximum number of levels of depth in the token nesting tree.
+ **/
+ nestingBudget: u32 & AugmentedConst<ApiType>;
+ /**
+ * Default NFT collection limit.
+ **/
+ nftDefaultCollectionLimits: UpDataStructsCollectionLimits & AugmentedConst<ApiType>;
+ /**
+ * Default RFT collection limit.
+ **/
+ rftDefaultCollectionLimits: UpDataStructsCollectionLimits & AugmentedConst<ApiType>;
+ /**
+ * Generic const
+ **/
+ [key: string]: Codec;
+ };
vesting: {
/**
* The minimum amount transferred to call `vested_transfer`.