--- a/Cargo.lock +++ b/Cargo.lock @@ -2646,8 +2646,8 @@ [[package]] name = "evm-coder" -version = "0.3.1" -source = "git+https://github.com/uniquenetwork/evm-coder?tag=v0.3.1#21e61c627f71337d6b8a03899fe8de14bfbf67aa" +version = "0.3.6" +source = "git+https://github.com/uniquenetwork/evm-coder?tag=v0.3.6#be59ae41a5d2ec9389bae22d54f81122b6ba55a4" dependencies = [ "ethereum", "evm-coder-procedural", @@ -2658,8 +2658,8 @@ [[package]] name = "evm-coder-procedural" -version = "0.3.1" -source = "git+https://github.com/uniquenetwork/evm-coder?tag=v0.3.1#21e61c627f71337d6b8a03899fe8de14bfbf67aa" +version = "0.3.6" +source = "git+https://github.com/uniquenetwork/evm-coder?tag=v0.3.6#be59ae41a5d2ec9389bae22d54f81122b6ba55a4" dependencies = [ "Inflector", "hex", @@ -6489,6 +6489,7 @@ name = "pallet-common" version = "0.1.14" dependencies = [ + "bondrewd", "ethereum", "evm-coder", "frame-benchmarking", @@ -7530,6 +7531,7 @@ "frame-benchmarking", "frame-support", "frame-system", + "log", "pallet-balances-adapter", "pallet-common", "pallet-evm", @@ -14097,6 +14099,7 @@ dependencies = [ "bondrewd", "derivative", + "evm-coder", "frame-support", "pallet-evm", "parity-scale-codec", --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ [workspace.dependencies] # Unique app-promotion-rpc = { path = "primitives/app_promotion_rpc", default-features = false } -evm-coder = { git = "https://github.com/uniquenetwork/evm-coder", tag = "v0.3.1", default-features = false } +evm-coder = { git = "https://github.com/uniquenetwork/evm-coder", tag = "v0.3.6", default-features = false, features = ['bondrewd'] } pallet-app-promotion = { path = "pallets/app-promotion", default-features = false } pallet-balances-adapter = { default-features = false, path = "pallets/balances-adapter" } pallet-charge-transaction = { package = "pallet-template-transaction-payment", default-features = false, git = "https://github.com/uniquenetwork/pallet-sponsoring", branch = "polkadot-v0.9.43" } --- a/pallets/common/Cargo.toml +++ b/pallets/common/Cargo.toml @@ -10,6 +10,7 @@ scale-info = { workspace = true } +bondrewd = { version = "0.1.14", features = ["derive"], default-features = false } ethereum = { workspace = true } evm-coder = { workspace = true } frame-benchmarking = { workspace = true, optional = true } --- a/pallets/common/src/benchmarking.rs +++ b/pallets/common/src/benchmarking.rs @@ -21,10 +21,9 @@ use pallet_evm::account::CrossAccountId; use frame_benchmarking::{benchmarks, account}; use up_data_structs::{ - CollectionMode, CollectionFlags, CreateCollectionData, CollectionId, Property, PropertyKey, - PropertyValue, CollectionPermissions, NestingPermissions, AccessMode, - MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH, - MAX_PROPERTIES_PER_ITEM, + CollectionMode, CreateCollectionData, CollectionId, Property, PropertyKey, PropertyValue, + CollectionPermissions, NestingPermissions, AccessMode, MAX_COLLECTION_NAME_LENGTH, + MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH, MAX_PROPERTIES_PER_ITEM, }; use frame_support::{ traits::{Get, fungible::Balanced, Imbalance, tokens::Precision}, @@ -47,12 +46,7 @@ .unwrap() } pub fn create_var_data(size: u32) -> BoundedVec> { - assert!( - size <= S, - "size ({}) should be less within bound ({})", - size, - S - ); + assert!(size <= S, "size ({size}) should be less within bound ({S})",); (0..size) .map(|v| (v & 0xff) as u8) .collect::>() @@ -81,7 +75,7 @@ mode: CollectionMode, handler: impl FnOnce( T::CrossAccountId, - CreateCollectionData, + CreateCollectionData, ) -> Result, cast: impl FnOnce(CollectionHandle) -> R, ) -> Result { @@ -124,9 +118,7 @@ create_collection_raw( owner, CollectionMode::NFT, - |owner: T::CrossAccountId, data| { - >::init_collection(owner.clone(), owner, data, CollectionFlags::default()) - }, + |owner: T::CrossAccountId, data| >::init_collection(owner.clone(), owner, data), |h| h, ) } --- a/pallets/common/src/dispatch.rs +++ b/pallets/common/src/dispatch.rs @@ -9,7 +9,7 @@ traits::Get, }; use sp_runtime::DispatchError; -use up_data_structs::{CollectionId, CreateCollectionData, CollectionFlags}; +use up_data_structs::{CollectionId, CreateCollectionData}; use crate::{pallet::Config, CommonCollectionOperations}; @@ -76,8 +76,7 @@ fn create( sender: T::CrossAccountId, payer: T::CrossAccountId, - data: CreateCollectionData, - flags: CollectionFlags, + data: CreateCollectionData, ) -> Result; /// Delete the collection. --- a/pallets/common/src/erc.rs +++ b/pallets/common/src/erc.rs @@ -402,10 +402,44 @@ Ok(()) } + #[solidity(rename_selector = "setCollectionNesting")] + fn set_nesting( + &mut self, + caller: Caller, + collection_nesting_and_permissions: eth::CollectionNestingAndPermission, + ) -> Result<()> { + self.consume_store_reads_and_writes(1, 1)?; + + let caller = T::CrossAccountId::from_eth(caller); + + let mut permissions = self.collection.permissions.clone(); + let mut nesting = permissions.nesting().clone(); + + let bv = if !collection_nesting_and_permissions.restricted.is_empty() { + let mut bv = OwnerRestrictedSet::new(); + for address in collection_nesting_and_permissions.restricted.iter() { + bv.try_insert(crate::eth::map_eth_to_id(address).ok_or_else(|| { + Error::Revert("Can't convert address into collection id".into()) + })?) + .map_err(|_| "too many collections")?; + } + Some(bv) + } else { + None + }; + + nesting.token_owner = collection_nesting_and_permissions.token_owner; + nesting.collection_admin = collection_nesting_and_permissions.collection_admin; + nesting.restricted = bv; + permissions.nesting = Some(nesting); + + >::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::) + } + /// Toggle accessibility of collection nesting. /// /// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled' - #[solidity(rename_selector = "setCollectionNesting")] + #[solidity(hide, rename_selector = "setCollectionNesting")] fn set_nesting_bool(&mut self, caller: Caller, enable: bool) -> Result<()> { self.consume_store_reads_and_writes(1, 1)?; @@ -424,8 +458,8 @@ /// /// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled' /// @param collections Addresses of collections that will be available for nesting. - #[solidity(rename_selector = "setCollectionNesting")] - fn set_nesting( + #[solidity(hide, rename_selector = "setCollectionNesting")] + fn set_nesting_collection_ids( &mut self, caller: Caller, enable: bool, @@ -464,8 +498,28 @@ >::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::) } + #[solidity(rename_selector = "collectionNesting")] + fn collection_nesting(&self) -> Result { + let nesting = self.collection.permissions.nesting(); + + Ok(eth::CollectionNestingAndPermission::new( + nesting.token_owner, + nesting.collection_admin, + nesting + .restricted + .clone() + .map(|b| { + b.0.into_inner() + .iter() + .map(|id| crate::eth::collection_id_to_address(id.0.into())) + .collect() + }) + .unwrap_or_default(), + )) + } + /// Returns nesting for a collection - #[solidity(rename_selector = "collectionNestingRestrictedCollectionIds")] + #[solidity(hide, rename_selector = "collectionNestingRestrictedCollectionIds")] fn collection_nesting_restricted_ids(&self) -> Result { let nesting = self.collection.permissions.nesting(); @@ -480,6 +534,7 @@ } /// Returns permissions for a collection + #[solidity(hide)] fn collection_nesting_permissions(&self) -> Result> { let nesting = self.collection.permissions.nesting(); Ok(vec![ --- a/pallets/common/src/eth.rs +++ b/pallets/common/src/eth.rs @@ -24,7 +24,8 @@ }; pub use pallet_evm::{Config, account::CrossAccountId}; use sp_core::{H160, U256}; -use up_data_structs::CollectionId; +use up_data_structs::{CollectionId, CollectionFlags}; +use pallet_evm_coder_substrate::execution::Error; // 0x17c4e6453Cc49AAAaEACA894e6D9683e00000001 - collection 1 // TODO: Unhardcode prefix @@ -104,10 +105,26 @@ sub: Default::default(), } } + + /// Converts [`CrossAddress`] to `Option`. + pub fn into_option_sub_cross_account(&self) -> Result, Error> + where + T: pallet_evm::Config, + T::AccountId: From<[u8; 32]>, + { + if self.eth == Default::default() && self.sub == Default::default() { + Ok(None) + } else if self.eth == Default::default() { + Ok(Some(convert_uint256_to_cross_account::(self.sub))) + } else if self.sub == Default::default() { + Ok(Some(T::CrossAccountId::from_eth(self.eth))) + } else { + Err(format!("All fields of cross account is non zeroed {:?}", self).into()) + } + } + /// Converts [`CrossAddress`] to `CrossAccountId`. - pub fn into_sub_cross_account( - &self, - ) -> pallet_evm_coder_substrate::execution::Result + pub fn into_sub_cross_account(&self) -> Result where T: pallet_evm::Config, T::AccountId: From<[u8; 32]>, @@ -124,6 +141,19 @@ } } +/// Type of tokens in collection +#[derive(AbiCoder, Copy, Clone, Default, Debug, PartialEq)] +#[repr(u8)] +pub enum CollectionMode { + /// Nonfungible + #[default] + Nonfungible, + /// Fungible + Fungible, + /// Refungible + Refungible, +} + /// Ethereum representation of collection [`PropertyKey`](up_data_structs::PropertyKey) and [`PropertyValue`](up_data_structs::PropertyValue). #[derive(Debug, Default, AbiCoder)] pub struct Property { @@ -144,7 +174,7 @@ } impl TryFrom for Property { - type Error = pallet_evm_coder_substrate::execution::Error; + type Error = Error; fn try_from(from: up_data_structs::Property) -> Result { let key = evm_coder::types::String::from_utf8(from.key.into()) @@ -155,7 +185,7 @@ } impl TryInto for Property { - type Error = pallet_evm_coder_substrate::execution::Error; + type Error = Error; fn try_into(self) -> Result { let key = >::from(self.key) @@ -220,17 +250,14 @@ pub fn has_value(&self) -> bool { self.value.is_some() } -} -impl TryInto for CollectionLimit { - type Error = pallet_evm_coder_substrate::execution::Error; - - fn try_into(self) -> Result { + /// Set corresponding property in CollectionLimits struct + pub fn apply_limit(&self, limits: &mut up_data_structs::CollectionLimits) -> Result<(), Error> { let value = self .value - .ok_or::("can't convert `None` value to boolean".into())?; + .ok_or::("can't convert `None` value to boolean".into())?; let value = Some(value.try_into().map_err(|error| { - Self::Error::Revert(format!( + Error::Revert(format!( "can't convert value to u32 \"{value}\" because: \"{error}\"" )) })?); @@ -239,14 +266,13 @@ Some(value) => match value { 0 => Ok(Some(false)), 1 => Ok(Some(true)), - _ => Err(Self::Error::Revert(format!( + _ => Err(Error::Revert(format!( "can't convert value to boolean \"{value}\"" ))), }, None => Ok(None), }; - let mut limits = up_data_structs::CollectionLimits::default(); match self.field { CollectionLimitField::AccountTokenOwnership => { limits.account_token_ownership_limit = value; @@ -277,10 +303,99 @@ limits.transfers_enabled = convert_value_to_bool()?; } }; + Ok(()) + } +} + +/// [`CollectionLimits`](up_data_structs::CollectionLimits) field representation for EVM. +#[derive(Debug, Default, AbiCoder)] +pub struct CollectionLimitValue { + field: CollectionLimitField, + value: U256, +} + +impl CollectionLimitValue { + /// Create [`CollectionLimitValue`] from field and value. + pub fn new(field: CollectionLimitField, value: u32) -> Self { + Self { + field, + value: value.into(), + } + } + + /// Set corresponding property in CollectionLimits struct + pub fn apply_limit(&self, limits: &mut up_data_structs::CollectionLimits) -> Result<(), Error> { + let value = self.value; + let value: u32 = value.try_into().map_err(|error| { + Error::Revert(format!( + "can't convert value to u32 \"{value}\" because: \"{error}\"" + )) + })?; + + let convert_value_to_bool = || match value { + 0 => Ok(Some(false)), + 1 => Ok(Some(true)), + _ => Err(Error::Revert(format!( + "can't convert value to boolean \"{value}\"" + ))), + }; + + match self.field { + CollectionLimitField::AccountTokenOwnership => { + limits.account_token_ownership_limit = Some(value); + } + CollectionLimitField::SponsoredDataSize => { + limits.sponsored_data_size = Some(value); + } + CollectionLimitField::SponsoredDataRateLimit => { + limits.sponsored_data_rate_limit = + Some(up_data_structs::SponsoringRateLimit::Blocks(value)); + } + CollectionLimitField::TokenLimit => { + limits.token_limit = Some(value); + } + CollectionLimitField::SponsorTransferTimeout => { + limits.sponsor_transfer_timeout = Some(value); + } + CollectionLimitField::SponsorApproveTimeout => { + limits.sponsor_approve_timeout = Some(value); + } + CollectionLimitField::OwnerCanTransfer => { + limits.owner_can_transfer = convert_value_to_bool()?; + } + CollectionLimitField::OwnerCanDestroy => { + limits.owner_can_destroy = convert_value_to_bool()?; + } + CollectionLimitField::TransferEnabled => { + limits.transfers_enabled = convert_value_to_bool()?; + } + }; + Ok(()) + } +} + +impl TryInto for CollectionLimit { + type Error = Error; + + fn try_into(self) -> Result { + let mut limits = up_data_structs::CollectionLimits::default(); + self.apply_limit(&mut limits)?; Ok(limits) } } +impl FromIterator for Result { + fn from_iter>( + iter: T, + ) -> Result { + let mut limits = up_data_structs::CollectionLimits::default(); + for value in iter.into_iter() { + value.apply_limit(&mut limits)?; + } + Ok(limits) + } +} + /// Ethereum representation of `NestingPermissions` (see [`up_data_structs::NestingPermissions`]) fields as an enumeration. #[derive(Default, Debug, Clone, Copy, AbiCoder)] #[repr(u8)] @@ -384,8 +499,7 @@ /// Convert vector of [`TokenPropertyPermission`] into vector of [`up_data_structs::PropertyKeyPermission`]. pub fn into_property_key_permissions( permissions: Vec, - ) -> pallet_evm_coder_substrate::execution::Result> - { + ) -> Result, Error> { let mut perms = Vec::new(); for TokenPropertyPermission { key, permissions } in permissions { @@ -410,6 +524,57 @@ pub uri: String, } +/// Nested collections and permissions +#[derive(Debug, Default, AbiCoder)] +pub struct CollectionNestingAndPermission { + /// Owner of token can nest tokens under it. + pub token_owner: bool, + /// Admin of token collection can nest tokens under token. + pub collection_admin: bool, + /// If set - only tokens from specified collections can be nested. + pub restricted: Vec
, +} + +impl CollectionNestingAndPermission { + /// Create [`CollectionNesting`]. + pub fn new(token_owner: bool, collection_admin: bool, restricted: Vec
) -> Self { + Self { + token_owner, + collection_admin, + restricted, + } + } +} + +/// Collection properties +#[derive(Debug, Default, AbiCoder)] +pub struct CreateCollectionData { + /// Collection sponsor + pub pending_sponsor: CrossAddress, + /// Collection name + pub name: String, + /// Collection description + pub description: String, + /// Token prefix + pub token_prefix: String, + /// Token type (NFT, FT or RFT) + pub mode: CollectionMode, + /// Fungible token precision + pub decimals: u8, + /// Custom Properties + pub properties: Vec, + /// Permissions for token properties + pub token_property_permissions: Vec, + /// Collection admins + pub admin_list: Vec, + /// Nesting settings + pub nesting_settings: CollectionNestingAndPermission, + /// Collection limits + pub limits: Vec, + /// Extra collection flags + pub flags: CollectionFlags, +} + /// Nested collections. #[derive(Debug, Default, AbiCoder)] pub struct CollectionNesting { --- a/pallets/common/src/lib.rs +++ b/pallets/common/src/lib.rs @@ -73,16 +73,15 @@ transactional, fail, }; use up_data_structs::{ - AccessMode, COLLECTION_NUMBER_LIMIT, Collection, RpcCollection, CollectionFlags, - RpcCollectionFlags, CollectionId, CreateItemData, MAX_TOKEN_PREFIX_LENGTH, - COLLECTION_ADMINS_LIMIT, TokenId, TokenChild, CollectionStats, MAX_TOKEN_OWNERSHIP, - CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, - REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, CUSTOM_DATA_LIMIT, CollectionLimits, - CreateCollectionData, SponsorshipState, CreateItemExData, SponsoringRateLimit, budget::Budget, - PhantomType, Property, CollectionProperties as CollectionPropertiesT, TokenProperties, - PropertiesPermissionMap, PropertyKey, PropertyValue, PropertyPermission, PropertiesError, - TokenOwnerError, PropertyKeyPermission, TokenData, TrySetProperty, PropertyScope, - CollectionPermissions, + AccessMode, COLLECTION_NUMBER_LIMIT, Collection, RpcCollection, RpcCollectionFlags, + CollectionId, CreateItemData, MAX_TOKEN_PREFIX_LENGTH, COLLECTION_ADMINS_LIMIT, TokenId, + TokenChild, CollectionStats, MAX_TOKEN_OWNERSHIP, CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT, + FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, + CUSTOM_DATA_LIMIT, CollectionLimits, CreateCollectionData, SponsorshipState, CreateItemExData, + SponsoringRateLimit, budget::Budget, PhantomType, Property, + CollectionProperties as CollectionPropertiesT, TokenProperties, PropertiesPermissionMap, + PropertyKey, PropertyValue, PropertyPermission, PropertiesError, TokenOwnerError, + PropertyKeyPermission, TokenData, TrySetProperty, PropertyScope, CollectionPermissions, }; use up_pov_estimate_rpc::PovInfo; @@ -1094,9 +1093,28 @@ pub fn init_collection( owner: T::CrossAccountId, payer: T::CrossAccountId, - data: CreateCollectionData, - flags: CollectionFlags, + data: CreateCollectionData, ) -> Result { + ensure!(data.flags.is_allowed_for_user(), >::NoPermission); + Self::init_collection_internal(owner, payer, data) + } + + /// Initializes the collection with ForeignCollection flag. Returns [CollectionId] on success, [DispatchError] otherwise. + pub fn init_foreign_collection( + owner: T::CrossAccountId, + payer: T::CrossAccountId, + mut data: CreateCollectionData, + ) -> Result { + data.flags.foreign = true; + let id = Self::init_collection_internal(owner, payer, data)?; + Ok(id) + } + + fn init_collection_internal( + owner: T::CrossAccountId, + payer: T::CrossAccountId, + data: CreateCollectionData, + ) -> Result { { ensure!( data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize, @@ -1127,7 +1145,7 @@ token_prefix: data.token_prefix, sponsorship: data .pending_sponsor - .map(SponsorshipState::Unconfirmed) + .map(|sponsor| SponsorshipState::Unconfirmed(sponsor.as_sub().clone())) .unwrap_or_default(), limits: data .limits @@ -1139,7 +1157,7 @@ Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions) }) .unwrap_or_else(|| Ok(CollectionPermissions::default()))?, - flags, + flags: data.flags, }; let mut collection_properties = CollectionPropertiesT::new(); @@ -1156,6 +1174,21 @@ CollectionPropertyPermissions::::insert(id, token_props_permissions); + let mut admin_amount = 0u32; + for admin in data.admin_list.iter() { + if !>::get((id, admin)) { + >::insert((id, admin), true); + admin_amount = admin_amount + .checked_add(1) + .ok_or(>::CollectionAdminCountExceeded)?; + } + } + ensure!( + admin_amount <= Self::collection_admins_limit(), + >::CollectionAdminCountExceeded, + ); + >::insert(id, admin_amount); + // Take a (non-refundable) deposit of collection creation { let mut imbalance = ::Currency>>::zero(); --- a/pallets/foreign-assets/src/lib.rs +++ b/pallets/foreign-assets/src/lib.rs @@ -42,9 +42,9 @@ RuntimeDebug, }; use frame_system::pallet_prelude::*; -use up_data_structs::{CollectionMode}; -use pallet_fungible::{Pallet as PalletFungible}; -use scale_info::{TypeInfo}; +use up_data_structs::CollectionMode; +use pallet_fungible::Pallet as PalletFungible; +use scale_info::TypeInfo; use sp_runtime::{ traits::{One, Zero}, ArithmeticError, @@ -304,7 +304,7 @@ .collect::>(); description.append(&mut name.clone()); - let data: CreateCollectionData = CreateCollectionData { + let data: CreateCollectionData = CreateCollectionData { name: name.try_into().unwrap(), description: description.try_into().unwrap(), mode: CollectionMode::Fungible(md.decimals), --- a/pallets/fungible/src/benchmarking.rs +++ b/pallets/fungible/src/benchmarking.rs @@ -31,9 +31,7 @@ create_collection_raw( owner, CollectionMode::Fungible(0), - |owner: T::CrossAccountId, data| { - >::init_collection(owner.clone(), owner, data, Default::default()) - }, + |owner: T::CrossAccountId, data| >::init_collection(owner.clone(), owner, data), FungibleHandle::cast, ) } --- a/pallets/fungible/src/lib.rs +++ b/pallets/fungible/src/lib.rs @@ -87,8 +87,8 @@ }; use pallet_evm::account::CrossAccountId; use up_data_structs::{ - AccessMode, CollectionId, CollectionFlags, TokenId, CreateCollectionData, - mapping::TokenAddressMapping, budget::Budget, PropertyKey, Property, + AccessMode, CollectionId, TokenId, CreateCollectionData, mapping::TokenAddressMapping, + budget::Budget, PropertyKey, Property, }; use pallet_common::{ Error as CommonError, Event as CommonEvent, Pallet as PalletCommon, @@ -219,28 +219,18 @@ pub fn init_collection( owner: T::CrossAccountId, payer: T::CrossAccountId, - data: CreateCollectionData, - flags: CollectionFlags, + data: CreateCollectionData, ) -> Result { - >::init_collection(owner, payer, data, flags) + >::init_collection(owner, payer, data) } /// Initializes the collection with ForeignCollection flag. Returns [CollectionId] on success, [DispatchError] otherwise. pub fn init_foreign_collection( owner: T::CrossAccountId, payer: T::CrossAccountId, - data: CreateCollectionData, + data: CreateCollectionData, ) -> Result { - let id = >::init_collection( - owner, - payer, - data, - CollectionFlags { - foreign: true, - ..Default::default() - }, - )?; - Ok(id) + >::init_foreign_collection(owner, payer, data) } /// Destroys a collection. --- a/pallets/fungible/src/stubs/UniqueFungible.sol +++ b/pallets/fungible/src/stubs/UniqueFungible.sol @@ -18,7 +18,7 @@ } /// @title A contract that allows you to work with collections. -/// @dev the ERC-165 identifier for this interface is 0x2a14cfd1 +/// @dev the ERC-165 identifier for this interface is 0xb34d97e9 contract Collection is Dummy, ERC165 { // /// Set collection property. // /// @@ -230,47 +230,63 @@ // dummy = 0; // } - /// Toggle accessibility of collection nesting. - /// - /// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled' - /// @dev EVM selector for this function is: 0x112d4586, - /// or in textual repr: setCollectionNesting(bool) - function setCollectionNesting(bool enable) public { + /// @dev EVM selector for this function is: 0x0b9f3890, + /// or in textual repr: setCollectionNesting((bool,bool,address[])) + function setCollectionNesting(CollectionNestingAndPermission memory collectionNestingAndPermissions) public { require(false, stub_error); - enable; + collectionNestingAndPermissions; dummy = 0; } - /// Toggle accessibility of collection nesting. - /// - /// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled' - /// @param collections Addresses of collections that will be available for nesting. - /// @dev EVM selector for this function is: 0x64872396, - /// or in textual repr: setCollectionNesting(bool,address[]) - function setCollectionNesting(bool enable, address[] memory collections) public { - require(false, stub_error); - enable; - collections; - dummy = 0; - } + // /// Toggle accessibility of collection nesting. + // /// + // /// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled' + // /// @dev EVM selector for this function is: 0x112d4586, + // /// or in textual repr: setCollectionNesting(bool) + // function setCollectionNesting(bool enable) public { + // require(false, stub_error); + // enable; + // dummy = 0; + // } + + // /// Toggle accessibility of collection nesting. + // /// + // /// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled' + // /// @param collections Addresses of collections that will be available for nesting. + // /// @dev EVM selector for this function is: 0x64872396, + // /// or in textual repr: setCollectionNesting(bool,address[]) + // function setCollectionNesting(bool enable, address[] memory collections) public { + // require(false, stub_error); + // enable; + // collections; + // dummy = 0; + // } - /// Returns nesting for a collection - /// @dev EVM selector for this function is: 0x22d25bfe, - /// or in textual repr: collectionNestingRestrictedCollectionIds() - function collectionNestingRestrictedCollectionIds() public view returns (CollectionNesting memory) { + /// @dev EVM selector for this function is: 0x92c660a8, + /// or in textual repr: collectionNesting() + function collectionNesting() public view returns (CollectionNestingAndPermission memory) { require(false, stub_error); dummy; - return CollectionNesting(false, new uint256[](0)); + return CollectionNestingAndPermission(false, false, new address[](0)); } - /// Returns permissions for a collection - /// @dev EVM selector for this function is: 0x5b2eaf4b, - /// or in textual repr: collectionNestingPermissions() - function collectionNestingPermissions() public view returns (CollectionNestingPermission[] memory) { - require(false, stub_error); - dummy; - return new CollectionNestingPermission[](0); - } + // /// Returns nesting for a collection + // /// @dev EVM selector for this function is: 0x22d25bfe, + // /// or in textual repr: collectionNestingRestrictedCollectionIds() + // function collectionNestingRestrictedCollectionIds() public view returns (CollectionNesting memory) { + // require(false, stub_error); + // dummy; + // return CollectionNesting(false,new uint256[](0)); + // } + + // /// Returns permissions for a collection + // /// @dev EVM selector for this function is: 0x5b2eaf4b, + // /// or in textual repr: collectionNestingPermissions() + // function collectionNestingPermissions() public view returns (CollectionNestingPermission[] memory) { + // require(false, stub_error); + // dummy; + // return new CollectionNestingPermission[](0); + // } /// Set the collection access method. /// @param mode Access mode @@ -469,6 +485,16 @@ uint256[] ids; } +/// Nested collections and permissions +struct CollectionNestingAndPermission { + /// Owner of token can nest tokens under it. + bool token_owner; + /// Admin of token collection can nest tokens under token. + bool collection_admin; + /// If set - only tokens from specified collections can be nested. + address[] restricted; +} + /// [`CollectionLimits`](up_data_structs::CollectionLimits) field representation for EVM. struct CollectionLimit { CollectionLimitField field; --- a/pallets/nonfungible/src/benchmarking.rs +++ b/pallets/nonfungible/src/benchmarking.rs @@ -57,9 +57,7 @@ create_collection_raw( owner, CollectionMode::NFT, - |owner: T::CrossAccountId, data| { - >::init_collection(owner.clone(), owner, data, Default::default()) - }, + |owner: T::CrossAccountId, data| >::init_collection(owner.clone(), owner, data), NonfungibleHandle::cast, ) } --- a/pallets/nonfungible/src/lib.rs +++ b/pallets/nonfungible/src/lib.rs @@ -100,10 +100,10 @@ dispatch::{PostDispatchInfo, Pays}, }; use up_data_structs::{ - AccessMode, CollectionId, CollectionFlags, CustomDataLimit, TokenId, CreateCollectionData, - CreateNftExData, mapping::TokenAddressMapping, budget::Budget, Property, PropertyKey, - PropertyValue, PropertyKeyPermission, PropertyScope, TrySetProperty, TokenChild, - AuxPropertyValue, PropertiesPermissionMap, TokenProperties as TokenPropertiesT, + AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData, CreateNftExData, + mapping::TokenAddressMapping, budget::Budget, Property, PropertyKey, PropertyValue, + PropertyKeyPermission, PropertyScope, TrySetProperty, TokenChild, AuxPropertyValue, + PropertiesPermissionMap, TokenProperties as TokenPropertiesT, }; use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm}; use pallet_common::{ @@ -424,10 +424,9 @@ pub fn init_collection( owner: T::CrossAccountId, payer: T::CrossAccountId, - data: CreateCollectionData, - flags: CollectionFlags, + data: CreateCollectionData, ) -> Result { - >::init_collection(owner, payer, data, flags) + >::init_collection(owner, payer, data) } /// Destroy NFT collection --- a/pallets/nonfungible/src/stubs/UniqueNFT.sol +++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol @@ -165,7 +165,7 @@ } /// @title A contract that allows you to work with collections. -/// @dev the ERC-165 identifier for this interface is 0x2a14cfd1 +/// @dev the ERC-165 identifier for this interface is 0xb34d97e9 contract Collection is Dummy, ERC165 { // /// Set collection property. // /// @@ -377,47 +377,63 @@ // dummy = 0; // } - /// Toggle accessibility of collection nesting. - /// - /// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled' - /// @dev EVM selector for this function is: 0x112d4586, - /// or in textual repr: setCollectionNesting(bool) - function setCollectionNesting(bool enable) public { + /// @dev EVM selector for this function is: 0x0b9f3890, + /// or in textual repr: setCollectionNesting((bool,bool,address[])) + function setCollectionNesting(CollectionNestingAndPermission memory collectionNestingAndPermissions) public { require(false, stub_error); - enable; + collectionNestingAndPermissions; dummy = 0; } - /// Toggle accessibility of collection nesting. - /// - /// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled' - /// @param collections Addresses of collections that will be available for nesting. - /// @dev EVM selector for this function is: 0x64872396, - /// or in textual repr: setCollectionNesting(bool,address[]) - function setCollectionNesting(bool enable, address[] memory collections) public { - require(false, stub_error); - enable; - collections; - dummy = 0; - } + // /// Toggle accessibility of collection nesting. + // /// + // /// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled' + // /// @dev EVM selector for this function is: 0x112d4586, + // /// or in textual repr: setCollectionNesting(bool) + // function setCollectionNesting(bool enable) public { + // require(false, stub_error); + // enable; + // dummy = 0; + // } + + // /// Toggle accessibility of collection nesting. + // /// + // /// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled' + // /// @param collections Addresses of collections that will be available for nesting. + // /// @dev EVM selector for this function is: 0x64872396, + // /// or in textual repr: setCollectionNesting(bool,address[]) + // function setCollectionNesting(bool enable, address[] memory collections) public { + // require(false, stub_error); + // enable; + // collections; + // dummy = 0; + // } - /// Returns nesting for a collection - /// @dev EVM selector for this function is: 0x22d25bfe, - /// or in textual repr: collectionNestingRestrictedCollectionIds() - function collectionNestingRestrictedCollectionIds() public view returns (CollectionNesting memory) { + /// @dev EVM selector for this function is: 0x92c660a8, + /// or in textual repr: collectionNesting() + function collectionNesting() public view returns (CollectionNestingAndPermission memory) { require(false, stub_error); dummy; - return CollectionNesting(false, new uint256[](0)); + return CollectionNestingAndPermission(false, false, new address[](0)); } - /// Returns permissions for a collection - /// @dev EVM selector for this function is: 0x5b2eaf4b, - /// or in textual repr: collectionNestingPermissions() - function collectionNestingPermissions() public view returns (CollectionNestingPermission[] memory) { - require(false, stub_error); - dummy; - return new CollectionNestingPermission[](0); - } + // /// Returns nesting for a collection + // /// @dev EVM selector for this function is: 0x22d25bfe, + // /// or in textual repr: collectionNestingRestrictedCollectionIds() + // function collectionNestingRestrictedCollectionIds() public view returns (CollectionNesting memory) { + // require(false, stub_error); + // dummy; + // return CollectionNesting(false,new uint256[](0)); + // } + + // /// Returns permissions for a collection + // /// @dev EVM selector for this function is: 0x5b2eaf4b, + // /// or in textual repr: collectionNestingPermissions() + // function collectionNestingPermissions() public view returns (CollectionNestingPermission[] memory) { + // require(false, stub_error); + // dummy; + // return new CollectionNestingPermission[](0); + // } /// Set the collection access method. /// @param mode Access mode @@ -616,6 +632,16 @@ uint256[] ids; } +/// Nested collections and permissions +struct CollectionNestingAndPermission { + /// Owner of token can nest tokens under it. + bool token_owner; + /// Admin of token collection can nest tokens under token. + bool collection_admin; + /// If set - only tokens from specified collections can be nested. + address[] restricted; +} + /// [`CollectionLimits`](up_data_structs::CollectionLimits) field representation for EVM. struct CollectionLimit { CollectionLimitField field; --- a/pallets/refungible/src/benchmarking.rs +++ b/pallets/refungible/src/benchmarking.rs @@ -61,9 +61,7 @@ create_collection_raw( owner, CollectionMode::ReFungible, - |owner: T::CrossAccountId, data| { - >::init_collection(owner.clone(), owner, data, Default::default()) - }, + |owner: T::CrossAccountId, data| >::init_collection(owner.clone(), owner, data), RefungibleHandle::cast, ) } --- a/pallets/refungible/src/lib.rs +++ b/pallets/refungible/src/lib.rs @@ -104,11 +104,10 @@ use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome}; use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap}; use up_data_structs::{ - AccessMode, budget::Budget, CollectionId, CollectionFlags, CreateCollectionData, - mapping::TokenAddressMapping, MAX_REFUNGIBLE_PIECES, Property, PropertyKey, - PropertyKeyPermission, PropertyScope, PropertyValue, TokenId, TrySetProperty, - PropertiesPermissionMap, CreateRefungibleExMultipleOwners, TokenOwnerError, - TokenProperties as TokenPropertiesT, + AccessMode, budget::Budget, CollectionId, CreateCollectionData, mapping::TokenAddressMapping, + MAX_REFUNGIBLE_PIECES, Property, PropertyKey, PropertyKeyPermission, PropertyScope, + PropertyValue, TokenId, TrySetProperty, PropertiesPermissionMap, + CreateRefungibleExMultipleOwners, TokenOwnerError, TokenProperties as TokenPropertiesT, }; pub use pallet::*; @@ -334,10 +333,9 @@ pub fn init_collection( owner: T::CrossAccountId, payer: T::CrossAccountId, - data: CreateCollectionData, - flags: CollectionFlags, + data: CreateCollectionData, ) -> Result { - >::init_collection(owner, payer, data, flags) + >::init_collection(owner, payer, data) } /// Destroy RFT collection --- a/pallets/refungible/src/stubs/UniqueRefungible.sol +++ b/pallets/refungible/src/stubs/UniqueRefungible.sol @@ -165,7 +165,7 @@ } /// @title A contract that allows you to work with collections. -/// @dev the ERC-165 identifier for this interface is 0x2a14cfd1 +/// @dev the ERC-165 identifier for this interface is 0xb34d97e9 contract Collection is Dummy, ERC165 { // /// Set collection property. // /// @@ -377,47 +377,63 @@ // dummy = 0; // } - /// Toggle accessibility of collection nesting. - /// - /// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled' - /// @dev EVM selector for this function is: 0x112d4586, - /// or in textual repr: setCollectionNesting(bool) - function setCollectionNesting(bool enable) public { + /// @dev EVM selector for this function is: 0x0b9f3890, + /// or in textual repr: setCollectionNesting((bool,bool,address[])) + function setCollectionNesting(CollectionNestingAndPermission memory collectionNestingAndPermissions) public { require(false, stub_error); - enable; + collectionNestingAndPermissions; dummy = 0; } - /// Toggle accessibility of collection nesting. - /// - /// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled' - /// @param collections Addresses of collections that will be available for nesting. - /// @dev EVM selector for this function is: 0x64872396, - /// or in textual repr: setCollectionNesting(bool,address[]) - function setCollectionNesting(bool enable, address[] memory collections) public { - require(false, stub_error); - enable; - collections; - dummy = 0; - } + // /// Toggle accessibility of collection nesting. + // /// + // /// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled' + // /// @dev EVM selector for this function is: 0x112d4586, + // /// or in textual repr: setCollectionNesting(bool) + // function setCollectionNesting(bool enable) public { + // require(false, stub_error); + // enable; + // dummy = 0; + // } + + // /// Toggle accessibility of collection nesting. + // /// + // /// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled' + // /// @param collections Addresses of collections that will be available for nesting. + // /// @dev EVM selector for this function is: 0x64872396, + // /// or in textual repr: setCollectionNesting(bool,address[]) + // function setCollectionNesting(bool enable, address[] memory collections) public { + // require(false, stub_error); + // enable; + // collections; + // dummy = 0; + // } - /// Returns nesting for a collection - /// @dev EVM selector for this function is: 0x22d25bfe, - /// or in textual repr: collectionNestingRestrictedCollectionIds() - function collectionNestingRestrictedCollectionIds() public view returns (CollectionNesting memory) { + /// @dev EVM selector for this function is: 0x92c660a8, + /// or in textual repr: collectionNesting() + function collectionNesting() public view returns (CollectionNestingAndPermission memory) { require(false, stub_error); dummy; - return CollectionNesting(false, new uint256[](0)); + return CollectionNestingAndPermission(false, false, new address[](0)); } - /// Returns permissions for a collection - /// @dev EVM selector for this function is: 0x5b2eaf4b, - /// or in textual repr: collectionNestingPermissions() - function collectionNestingPermissions() public view returns (CollectionNestingPermission[] memory) { - require(false, stub_error); - dummy; - return new CollectionNestingPermission[](0); - } + // /// Returns nesting for a collection + // /// @dev EVM selector for this function is: 0x22d25bfe, + // /// or in textual repr: collectionNestingRestrictedCollectionIds() + // function collectionNestingRestrictedCollectionIds() public view returns (CollectionNesting memory) { + // require(false, stub_error); + // dummy; + // return CollectionNesting(false,new uint256[](0)); + // } + + // /// Returns permissions for a collection + // /// @dev EVM selector for this function is: 0x5b2eaf4b, + // /// or in textual repr: collectionNestingPermissions() + // function collectionNestingPermissions() public view returns (CollectionNestingPermission[] memory) { + // require(false, stub_error); + // dummy; + // return new CollectionNestingPermission[](0); + // } /// Set the collection access method. /// @param mode Access mode @@ -616,6 +632,16 @@ uint256[] ids; } +/// Nested collections and permissions +struct CollectionNestingAndPermission { + /// Owner of token can nest tokens under it. + bool token_owner; + /// Admin of token collection can nest tokens under token. + bool collection_admin; + /// If set - only tokens from specified collections can be nested. + address[] restricted; +} + /// [`CollectionLimits`](up_data_structs::CollectionLimits) field representation for EVM. struct CollectionLimit { CollectionLimitField field; --- a/pallets/structure/src/benchmarking.rs +++ b/pallets/structure/src/benchmarking.rs @@ -40,7 +40,6 @@ mode: CollectionMode::NFT, ..Default::default() }, - CollectionFlags::default(), )?; let dispatch = T::CollectionDispatch::dispatch(CollectionId(1))?; let dispatch = dispatch.as_dyn(); --- a/pallets/unique/Cargo.toml +++ b/pallets/unique/Cargo.toml @@ -47,6 +47,7 @@ frame-benchmarking = { workspace = true, optional = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } pallet-balances-adapter = { workspace = true } pallet-common = { workspace = true } pallet-evm = { workspace = true } --- a/pallets/unique/src/eth/mod.rs +++ b/pallets/unique/src/eth/mod.rs @@ -15,18 +15,16 @@ // along with Unique Network. If not, see . //! Implementation of CollectionHelpers contract. - +//! use core::marker::PhantomData; use ethereum as _; use evm_coder::{abi::AbiType, generate_stubgen, solidity_interface, types::*}; -use frame_support::traits::Get; -use crate::Pallet; - +use frame_support::{BoundedVec, traits::Get}; use pallet_common::{ CollectionById, dispatch::CollectionDispatch, erc::{CollectionHelpersEvents, static_property::key}, - eth::{map_eth_to_id, collection_id_to_address}, + eth::{self, map_eth_to_id, collection_id_to_address}, Pallet as PalletCommon, CollectionHandle, }; use pallet_evm::{account::CrossAccountId, OnMethodCall, PrecompileHandle, PrecompileResult}; @@ -36,13 +34,13 @@ frontier_contract, }; use up_data_structs::{ - CollectionDescription, CollectionMode, CollectionName, CollectionTokenPrefix, - CreateCollectionData, + CollectionDescription, CollectionMode, CollectionName, CollectionPermissions, + CollectionTokenPrefix, CreateCollectionData, NestingPermissions, }; -use crate::{weights::WeightInfo, Config, SelfWeightOf}; +use crate::{weights::WeightInfo, Config, Pallet, SelfWeightOf}; -use alloc::format; +use alloc::{format, collections::BTreeSet}; use sp_std::vec::Vec; frontier_contract! { @@ -113,9 +111,8 @@ let collection_helpers_address = T::CrossAccountId::from_eth(::ContractAddress::get()); - let collection_id = - T::CollectionDispatch::create(caller, collection_helpers_address, data, Default::default()) - .map_err(pallet_evm_coder_substrate::dispatch_to_evm::)?; + let collection_id = T::CollectionDispatch::create(caller, collection_helpers_address, data) + .map_err(pallet_evm_coder_substrate::dispatch_to_evm::)?; let address = pallet_common::eth::collection_id_to_address(collection_id); Ok(address) } @@ -140,7 +137,119 @@ impl EvmCollectionHelpers where T: Config + pallet_common::Config + pallet_nonfungible::Config + pallet_refungible::Config, + T::AccountId: From<[u8; 32]>, { +/* + /// Create a collection + /// @return address Address of the newly created collection + #[weight(>::create_collection())] + #[solidity(rename_selector = "createCollection")] + fn create_collection( + &mut self, + caller: Caller, + value: Value, + data: eth::CreateCollectionData, + ) -> Result
{ + let (caller, name, description, token_prefix) = + convert_data::(caller, data.name, data.description, data.token_prefix)?; + if data.mode != eth::CollectionMode::Fungible && data.decimals != 0 { + return Err("decimals are only supported for NFT and RFT collections".into()); + } + let mode = match data.mode { + eth::CollectionMode::Fungible => CollectionMode::Fungible(data.decimals), + eth::CollectionMode::Nonfungible => CollectionMode::NFT, + eth::CollectionMode::Refungible => CollectionMode::ReFungible, + }; + + let properties: BoundedVec<_, _> = data + .properties + .into_iter() + .map(eth::Property::try_into) + .collect::>>()? + .try_into() + .map_err(|_| "too many properties")?; + + let token_property_permissions = + eth::TokenPropertyPermission::into_property_key_permissions( + data.token_property_permissions, + )? + .try_into() + .map_err(|_| "too many property permissions")?; + + let limits = if !data.limits.is_empty() { + Some( + data.limits + .into_iter() + .collect::>()?, + ) + } else { + None + }; + + let pending_sponsor = data.pending_sponsor.into_option_sub_cross_account::()?; + + let restricted = if !data.nesting_settings.restricted.is_empty() { + Some( + data.nesting_settings + .restricted + .iter() + .map(map_eth_to_id) + .collect::>>() + .ok_or("can't convert address into collection id")? + .try_into() + .map_err(|_| "too many collections")?, + ) + } else { + None + }; + + let admin_list = data + .admin_list + .into_iter() + .map(|admin| admin.into_sub_cross_account::()) + .collect::>>()?; + + let flags = data.flags; + if !flags.is_allowed_for_user() { + return Err("internal flags were used".into()); + } + + let data = CreateCollectionData { + name, + mode, + description, + token_prefix, + properties, + token_property_permissions, + limits, + pending_sponsor, + access: None, + permissions: Some(CollectionPermissions { + access: None, + mint_mode: None, + nesting: Some(NestingPermissions { + token_owner: data.nesting_settings.token_owner, + collection_admin: data.nesting_settings.collection_admin, + restricted, + #[cfg(feature = "runtime-benchmarks")] + permissive: true, + }), + }), + admin_list, + flags, + }; + check_sent_amount_equals_collection_creation_price::(value)?; + let collection_helpers_address = + T::CrossAccountId::from_eth(::ContractAddress::get()); + + let collection_id = T::CollectionDispatch::create(caller, collection_helpers_address, data) + .map_err(dispatch_to_evm::)?; + + let address = pallet_common::eth::collection_id_to_address(collection_id); + Ok(address) + } +*/ + /// Create an NFT collection /// @param name Name of the collection /// @param description Informative description of the collection @@ -168,13 +277,8 @@ check_sent_amount_equals_collection_creation_price::(value)?; let collection_helpers_address = T::CrossAccountId::from_eth(::ContractAddress::get()); - let collection_id = T::CollectionDispatch::create( - caller, - collection_helpers_address, - data, - Default::default(), - ) - .map_err(dispatch_to_evm::)?; + let collection_id = T::CollectionDispatch::create(caller, collection_helpers_address, data) + .map_err(dispatch_to_evm::)?; let address = pallet_common::eth::collection_id_to_address(collection_id); Ok(address) @@ -387,6 +491,8 @@ pub struct CollectionHelpersOnMethodCall(PhantomData<*const T>); impl OnMethodCall for CollectionHelpersOnMethodCall +where + T::AccountId: From<[u8; 32]>, { fn is_reserved(contract: &sp_core::H160) -> bool { contract == &T::ContractAddress::get() --- a/pallets/unique/src/eth/stubs/CollectionHelpers.sol +++ b/pallets/unique/src/eth/stubs/CollectionHelpers.sol @@ -25,8 +25,19 @@ } /// @title Contract, which allows users to operate with collections -/// @dev the ERC-165 identifier for this interface is 0xe65011aa +/// @dev the ERC-165 identifier for this interface is 0x4135fff1 contract CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents { + /// Create a collection + /// @return address Address of the newly created collection + /// @dev EVM selector for this function is: 0xa765ee5b, + /// or in textual repr: createCollection(((address,uint256),string,string,string,uint8,uint8,(string,bytes)[],(string,(uint8,bool)[])[],(address,uint256)[],(bool,bool,address[]),(uint8,uint256)[],uint8)) + function createCollection(CreateCollectionData memory data) public payable returns (address) { + require(false, stub_error); + data; + dummy = 0; + return 0x0000000000000000000000000000000000000000; + } + /// Create an NFT collection /// @param name Name of the collection /// @param description Informative description of the collection @@ -156,3 +167,135 @@ return 0; } } + +/// Collection properties +struct CreateCollectionData { + /// Collection sponsor + CrossAddress pending_sponsor; + /// Collection name + string name; + /// Collection description + string description; + /// Token prefix + string token_prefix; + /// Token type (NFT, FT or RFT) + CollectionMode mode; + /// Fungible token precision + uint8 decimals; + /// Custom Properties + Property[] properties; + /// Permissions for token properties + TokenPropertyPermission[] token_property_permissions; + /// Collection admins + CrossAddress[] admin_list; + /// Nesting settings + CollectionNestingAndPermission nesting_settings; + /// Collection limits + CollectionLimitValue[] limits; + /// Extra collection flags + CollectionFlags flags; +} + +/// Cross account struct +type CollectionFlags is uint8; + +library CollectionFlagsLib { + /// Tokens in foreign collections can be transferred, but not burnt + CollectionFlags constant foreignField = CollectionFlags.wrap(128); + /// Supports ERC721Metadata + CollectionFlags constant erc721metadataField = CollectionFlags.wrap(64); + /// External collections can't be managed using `unique` api + CollectionFlags constant externalField = CollectionFlags.wrap(1); + + /// Reserved bits + function reservedField(uint8 value) public pure returns (CollectionFlags) { + require(value < 1 << 5, "out of bound value"); + return CollectionFlags.wrap(value << 1); + } +} + +/// [`CollectionLimits`](up_data_structs::CollectionLimits) field representation for EVM. +struct CollectionLimitValue { + CollectionLimitField field; + uint256 value; +} + +/// [`CollectionLimits`](up_data_structs::CollectionLimits) fields representation for EVM. +enum CollectionLimitField { + /// How many tokens can a user have on one account. + AccountTokenOwnership, + /// How many bytes of data are available for sponsorship. + SponsoredDataSize, + /// In any case, chain default: [`SponsoringRateLimit::SponsoringDisabled`] + SponsoredDataRateLimit, + /// How many tokens can be mined into this collection. + TokenLimit, + /// Timeouts for transfer sponsoring. + SponsorTransferTimeout, + /// Timeout for sponsoring an approval in passed blocks. + SponsorApproveTimeout, + /// Whether the collection owner of the collection can send tokens (which belong to other users). + OwnerCanTransfer, + /// Can the collection owner burn other people's tokens. + OwnerCanDestroy, + /// Is it possible to send tokens from this collection between users. + TransferEnabled +} + +/// Nested collections and permissions +struct CollectionNestingAndPermission { + /// Owner of token can nest tokens under it. + bool token_owner; + /// Admin of token collection can nest tokens under token. + bool collection_admin; + /// If set - only tokens from specified collections can be nested. + address[] restricted; +} + +/// Cross account struct +struct CrossAddress { + address eth; + uint256 sub; +} + +/// Ethereum representation of Token Property Permissions. +struct TokenPropertyPermission { + /// Token property key. + string key; + /// Token property permissions. + PropertyPermission[] permissions; +} + +/// Ethereum representation of TokenPermissions (see [`up_data_structs::PropertyPermission`]) as an key and value. +struct PropertyPermission { + /// TokenPermission field. + TokenPermissionField code; + /// TokenPermission value. + bool value; +} + +/// Ethereum representation of TokenPermissions (see [`up_data_structs::PropertyPermission`]) fields as an enumeration. +enum TokenPermissionField { + /// Permission to change the property and property permission. See [`up_data_structs::PropertyPermission::mutable`] + Mutable, + /// Change permission for the collection administrator. See [`up_data_structs::PropertyPermission::token_owner`] + TokenOwner, + /// Permission to change the property for the owner of the token. See [`up_data_structs::PropertyPermission::collection_admin`] + CollectionAdmin +} + +/// Ethereum representation of collection [`PropertyKey`](up_data_structs::PropertyKey) and [`PropertyValue`](up_data_structs::PropertyValue). +struct Property { + string key; + bytes value; +} + +/// Type of tokens in collection +enum CollectionMode { + /// Fungible + Fungible, + /// Nonfungible + Nonfungible, + /// Refungible + Refungible +} --- a/pallets/unique/src/lib.rs +++ b/pallets/unique/src/lib.rs @@ -365,7 +365,7 @@ token_prefix: BoundedVec>, mode: CollectionMode, ) -> DispatchResult { - let data: CreateCollectionData = CreateCollectionData { + let data: CreateCollectionData = CreateCollectionData { name: collection_name, description: collection_description, token_prefix, @@ -390,14 +390,13 @@ #[pallet::weight(>::create_collection())] pub fn create_collection_ex( origin: OriginFor, - data: CreateCollectionData, + data: CreateCollectionData, ) -> DispatchResult { let sender = ensure_signed(origin)?; // ========= let sender = T::CrossAccountId::from_sub(sender); - let _id = - T::CollectionDispatch::create(sender.clone(), sender, data, Default::default())?; + let _id = T::CollectionDispatch::create(sender.clone(), sender, data)?; Ok(()) } --- a/primitives/data-structs/Cargo.toml +++ b/primitives/data-structs/Cargo.toml @@ -22,6 +22,7 @@ sp-std = { workspace = true } bondrewd = { version = "0.1.14", features = ["derive"], default-features = false } struct-versioning = { workspace = true } +evm-coder = { workspace = true } [features] default = ["std"] --- a/primitives/data-structs/src/lib.rs +++ b/primitives/data-structs/src/lib.rs @@ -32,11 +32,13 @@ use sp_core::U256; use sp_runtime::{ArithmeticError, sp_std::prelude::Vec}; +use sp_std::collections::btree_set::BTreeSet; use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; -use bondrewd::Bitfields; use frame_support::{BoundedVec, traits::ConstU32}; use derivative::Derivative; use scale_info::TypeInfo; +use evm_coder::AbiCoderFlags; +use bondrewd::Bitfields; mod bondrewd_codec; mod bounded; @@ -163,6 +165,14 @@ } } +impl Deref for CollectionId { + type Target = u32; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + /// Token id. #[derive( Encode, @@ -350,7 +360,7 @@ pub type CollectionDescription = BoundedVec>; pub type CollectionTokenPrefix = BoundedVec>; -#[derive(Bitfields, Clone, Copy, PartialEq, Eq, Debug, Default)] +#[derive(AbiCoderFlags, Bitfields, Clone, Copy, PartialEq, Eq, Debug, Default)] #[bondrewd(enforce_bytes = 1)] pub struct CollectionFlags { /// Tokens in foreign collections can be transferred, but not burnt @@ -362,12 +372,18 @@ /// External collections can't be managed using `unique` api #[bondrewd(bits = "7..8")] pub external: bool, - - #[bondrewd(reserve, bits = "2..7")] + /// Reserved flags + #[bondrewd(bits = "2..7")] pub reserved: u8, } bondrewd_codec!(CollectionFlags); +impl CollectionFlags { + pub fn is_allowed_for_user(self) -> bool { + !self.foreign && !self.external && self.reserved == 0 + } +} + /// Base structure for represent collection. /// /// Used to provide basic functionality for all types of collections. @@ -545,7 +561,7 @@ /// All fields are wrapped in [`Option`], where `None` means chain default. #[derive(Encode, Decode, Clone, PartialEq, TypeInfo, Derivative, MaxEncodedLen)] #[derivative(Debug, Default(bound = ""))] -pub struct CreateCollectionData { +pub struct CreateCollectionData { /// Collection mode. #[derivative(Default(value = "CollectionMode::NFT"))] pub mode: CollectionMode, @@ -562,9 +578,6 @@ /// Token prefix. pub token_prefix: CollectionTokenPrefix, - /// Pending collection sponsor. - pub pending_sponsor: Option, - /// Collection limits. pub limits: Option, @@ -576,6 +589,13 @@ /// Collection properties. pub properties: CollectionPropertiesVec, + + pub admin_list: Vec, + + /// Pending collection sponsor. + pub pending_sponsor: Option, + + pub flags: CollectionFlags, } /// Bounded vector of properties permissions. Max length is [`MAX_PROPERTIES_PER_ITEM`]. @@ -833,6 +853,14 @@ } } +impl TryFrom> for OwnerRestrictedSet { + type Error = (); + + fn try_from(value: BTreeSet) -> Result { + Ok(Self(value.try_into()?)) + } +} + /// Part of collection permissions, if set, defines who is able to nest tokens into other tokens. #[derive(Encode, Decode, Clone, PartialEq, TypeInfo, MaxEncodedLen, Derivative)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] --- a/runtime/common/dispatch.rs +++ b/runtime/common/dispatch.rs @@ -25,14 +25,14 @@ }; pub use pallet_common::dispatch::CollectionDispatch; use pallet_fungible::{Pallet as PalletFungible, FungibleHandle}; -use pallet_balances_adapter::{NativeFungibleHandle}; +use pallet_balances_adapter::NativeFungibleHandle; use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle}; use pallet_refungible::{ Pallet as PalletRefungible, RefungibleHandle, erc_token::RefungibleTokenHandle, }; use up_data_structs::{ CollectionMode, CreateCollectionData, MAX_DECIMAL_POINTS, mapping::TokenAddressMapping, - CollectionId, CollectionFlags, + CollectionId, }; #[cfg(not(feature = "refungible"))] @@ -72,26 +72,21 @@ fn create( sender: T::CrossAccountId, payer: T::CrossAccountId, - data: CreateCollectionData, - flags: CollectionFlags, + data: CreateCollectionData, ) -> Result { let id = match data.mode { - CollectionMode::NFT => { - >::init_collection(sender, payer, data, flags)? - } + CollectionMode::NFT => >::init_collection(sender, payer, data)?, CollectionMode::Fungible(decimal_points) => { // check params ensure!( decimal_points <= MAX_DECIMAL_POINTS, pallet_unique::Error::::CollectionDecimalPointLimitExceeded ); - >::init_collection(sender, payer, data, flags)? + >::init_collection(sender, payer, data)? } #[cfg(feature = "refungible")] - CollectionMode::ReFungible => { - >::init_collection(sender, payer, data, flags)? - } + CollectionMode::ReFungible => >::init_collection(sender, payer, data)?, #[cfg(not(feature = "refungible"))] CollectionMode::ReFungible => return unsupported!(T), --- a/runtime/common/ethereum/sponsoring.rs +++ b/runtime/common/ethereum/sponsoring.rs @@ -234,6 +234,7 @@ | CollectionOwner | CollectionAdmins | CollectionLimits + | CollectionNesting | CollectionNestingRestrictedIds | CollectionNestingPermissions | UniqueCollectionType => None, @@ -249,6 +250,7 @@ | RemoveCollectionAdmin { .. } | SetNestingBool { .. } | SetNesting { .. } + | SetNestingCollectionIds { .. } | SetCollectionAccess { .. } | SetCollectionMintMode { .. } | SetOwner { .. } --- a/tests/src/benchmarks/opsFee/index.ts +++ b/tests/src/benchmarks/opsFee/index.ts @@ -1,6 +1,6 @@ import {EthUniqueHelper, usingEthPlaygrounds} from '../../eth/util'; import {readFile} from 'fs/promises'; -import {CollectionLimitField, TokenPermissionField} from '../../eth/util/playgrounds/types'; +import {CollectionLimitField, CreateCollectionData, TokenPermissionField} from '../../eth/util/playgrounds/types'; import {IKeyringPair} from '@polkadot/types/types'; import {UniqueFTCollection, UniqueNFTCollection} from '../../util/playgrounds/unique'; import {Contract} from 'web3-eth-contract'; @@ -399,7 +399,7 @@ () => collection.setLimits(donor, {accountTokenOwnershipLimit: 1000}), ))); - const {collectionAddress} = await helper.eth.createCollection('nft', ethSigner, 'A', 'B', 'C'); + const {collectionAddress} = await helper.eth.createCollection(ethSigner, new CreateCollectionData('A', 'B', 'C', 'nft')).send(); const collectionWithEthOwner = await helper.ethNativeContract.collection(collectionAddress, 'nft', ethSigner, true); @@ -775,7 +775,7 @@ () => collection.setLimits(donor, {accountTokenOwnershipLimit: 1000}), ))); - const {collectionAddress} = await helper.eth.createCollection('nft', ethSigner, 'A', 'B', 'C'); + const {collectionAddress} = await helper.eth.createCollection(ethSigner, new CreateCollectionData('A', 'B', 'C', 'nft')).send(); const collectionWithEthOwner = await helper.ethNativeContract.collection(collectionAddress, 'nft', ethSigner, true); --- a/tests/src/createCollection.test.ts +++ b/tests/src/createCollection.test.ts @@ -16,7 +16,7 @@ import {IKeyringPair} from '@polkadot/types/types'; import {usingPlaygrounds, expect, itSub, Pallets} from './util'; -import {ICollectionCreationOptions, IProperty} from './util/playgrounds/types'; +import {CollectionFlag, ICollectionCreationOptions, IProperty} from './util/playgrounds/types'; import {UniqueHelper} from './util/playgrounds/unique'; async function mintCollectionHelper(helper: UniqueHelper, signer: IKeyringPair, options: ICollectionCreationOptions, type?: 'nft' | 'fungible' | 'refungible') { @@ -36,6 +36,16 @@ if(options.properties) { expect(data?.raw.properties).to.be.deep.equal(options.properties); } + if(options.adminList) { + expect(data?.admins).to.be.deep.equal(options.adminList); + } + + if(options.flags) { + if((options.flags[0] & 64) != 0) + expect(data?.raw.flags.erc721metadata).to.be.true; + if((options.flags[0] & 128) != 0) + expect(data?.raw.flags.foreign).to.be.false; + } if(options.tokenPropertyPermissions) { expect(data?.raw.tokenPropertyPermissions).to.be.deep.equal(options.tokenPropertyPermissions); @@ -46,11 +56,12 @@ describe('integration test: ext. createCollection():', () => { let alice: IKeyringPair; + let bob: IKeyringPair; before(async () => { await usingPlaygrounds(async (helper, privateKey) => { const donor = await privateKey({url: import.meta.url}); - [alice] = await helper.arrange.createAccounts([100n], donor); + [alice, bob] = await helper.arrange.createAccounts([100n, 100n], donor); }); }); itSub('Create new NFT collection', async ({helper}) => { @@ -82,6 +93,30 @@ }, 'nft'); }); + itSub('create new collection with admin', async ({helper}) => { + await mintCollectionHelper(helper, alice, { + name: 'name', description: 'descr', tokenPrefix: 'COL', + adminList: [{Substrate: bob.address}], + }, 'nft'); + }); + + itSub('create new collection with flags', async ({helper}) => { + await mintCollectionHelper(helper, alice, { + name: 'name', description: 'descr', tokenPrefix: 'COL', + flags: [CollectionFlag.Erc721metadata], + }, 'nft'); + + await mintCollectionHelper(helper, alice, { + name: 'name', description: 'descr', tokenPrefix: 'COL', + flags: [CollectionFlag.Foreign], + }, 'nft'); + + await mintCollectionHelper(helper, alice, { + name: 'name', description: 'descr', tokenPrefix: 'COL', + flags: [CollectionFlag.Erc721metadata, CollectionFlag.Foreign], + }, 'nft'); + }); + itSub('Create new collection with extra fields', async ({helper}) => { const collection = await mintCollectionHelper(helper, alice, {name: 'name', description: 'descr', tokenPrefix: 'COL'}, 'fungible'); await collection.setPermissions(alice, {access: 'AllowList'}); --- a/tests/src/eth/abi/collectionHelpers.json +++ b/tests/src/eth/abi/collectionHelpers.json @@ -75,6 +75,118 @@ }, { "inputs": [ + { + "components": [ + { + "components": [ + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } + ], + "internalType": "struct CrossAddress", + "name": "pending_sponsor", + "type": "tuple" + }, + { "internalType": "string", "name": "name", "type": "string" }, + { "internalType": "string", "name": "description", "type": "string" }, + { + "internalType": "string", + "name": "token_prefix", + "type": "string" + }, + { + "internalType": "enum CollectionMode", + "name": "mode", + "type": "uint8" + }, + { "internalType": "uint8", "name": "decimals", "type": "uint8" }, + { + "components": [ + { "internalType": "string", "name": "key", "type": "string" }, + { "internalType": "bytes", "name": "value", "type": "bytes" } + ], + "internalType": "struct Property[]", + "name": "properties", + "type": "tuple[]" + }, + { + "components": [ + { "internalType": "string", "name": "key", "type": "string" }, + { + "components": [ + { + "internalType": "enum TokenPermissionField", + "name": "code", + "type": "uint8" + }, + { "internalType": "bool", "name": "value", "type": "bool" } + ], + "internalType": "struct PropertyPermission[]", + "name": "permissions", + "type": "tuple[]" + } + ], + "internalType": "struct TokenPropertyPermission[]", + "name": "token_property_permissions", + "type": "tuple[]" + }, + { + "components": [ + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } + ], + "internalType": "struct CrossAddress[]", + "name": "admin_list", + "type": "tuple[]" + }, + { + "components": [ + { "internalType": "bool", "name": "token_owner", "type": "bool" }, + { + "internalType": "bool", + "name": "collection_admin", + "type": "bool" + }, + { + "internalType": "address[]", + "name": "restricted", + "type": "address[]" + } + ], + "internalType": "struct CollectionNestingAndPermission", + "name": "nesting_settings", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "enum CollectionLimitField", + "name": "field", + "type": "uint8" + }, + { "internalType": "uint256", "name": "value", "type": "uint256" } + ], + "internalType": "struct CollectionLimitValue[]", + "name": "limits", + "type": "tuple[]" + }, + { + "internalType": "CollectionFlags", + "name": "flags", + "type": "uint8" + } + ], + "internalType": "struct CreateCollectionData", + "name": "data", + "type": "tuple" + } + ], + "name": "createCollection", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ { "internalType": "string", "name": "name", "type": "string" }, { "internalType": "uint8", "name": "decimals", "type": "uint8" }, { "internalType": "string", "name": "description", "type": "string" }, --- a/tests/src/eth/abi/fungible.json +++ b/tests/src/eth/abi/fungible.json @@ -280,35 +280,23 @@ }, { "inputs": [], - "name": "collectionNestingPermissions", + "name": "collectionNesting", "outputs": [ { "components": [ + { "internalType": "bool", "name": "token_owner", "type": "bool" }, { - "internalType": "enum CollectionPermissionField", - "name": "field", - "type": "uint8" + "internalType": "bool", + "name": "collection_admin", + "type": "bool" }, - { "internalType": "bool", "name": "value", "type": "bool" } - ], - "internalType": "struct CollectionNestingPermission[]", - "name": "", - "type": "tuple[]" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "collectionNestingRestrictedCollectionIds", - "outputs": [ - { - "components": [ - { "internalType": "bool", "name": "token_owner", "type": "bool" }, - { "internalType": "uint256[]", "name": "ids", "type": "uint256[]" } + { + "internalType": "address[]", + "name": "restricted", + "type": "address[]" + } ], - "internalType": "struct CollectionNesting", + "internalType": "struct CollectionNestingAndPermission", "name": "", "type": "tuple" } @@ -576,19 +564,24 @@ "type": "function" }, { - "inputs": [{ "internalType": "bool", "name": "enable", "type": "bool" }], - "name": "setCollectionNesting", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { "inputs": [ - { "internalType": "bool", "name": "enable", "type": "bool" }, { - "internalType": "address[]", - "name": "collections", - "type": "address[]" + "components": [ + { "internalType": "bool", "name": "token_owner", "type": "bool" }, + { + "internalType": "bool", + "name": "collection_admin", + "type": "bool" + }, + { + "internalType": "address[]", + "name": "restricted", + "type": "address[]" + } + ], + "internalType": "struct CollectionNestingAndPermission", + "name": "collectionNestingAndPermissions", + "type": "tuple" } ], "name": "setCollectionNesting", --- a/tests/src/eth/abi/fungibleDeprecated.json +++ b/tests/src/eth/abi/fungibleDeprecated.json @@ -88,5 +88,64 @@ "outputs": [], "stateMutability": "nonpayable", "type": "function" + }, + { + "inputs": [], + "name": "collectionNestingPermissions", + "outputs": [ + { + "components": [ + { + "internalType": "enum CollectionPermissionField", + "name": "field", + "type": "uint8" + }, + { "internalType": "bool", "name": "value", "type": "bool" } + ], + "internalType": "struct CollectionNestingPermission[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "collectionNestingRestrictedCollectionIds", + "outputs": [ + { + "components": [ + { "internalType": "bool", "name": "token_owner", "type": "bool" }, + { "internalType": "uint256[]", "name": "ids", "type": "uint256[]" } + ], + "internalType": "struct CollectionNesting", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "bool", "name": "enable", "type": "bool" }], + "name": "setCollectionNesting", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bool", "name": "enable", "type": "bool" }, + { + "internalType": "address[]", + "name": "collections", + "type": "address[]" + } + ], + "name": "setCollectionNesting", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" } ] --- a/tests/src/eth/abi/nonFungible.json +++ b/tests/src/eth/abi/nonFungible.json @@ -291,35 +291,23 @@ }, { "inputs": [], - "name": "collectionNestingPermissions", + "name": "collectionNesting", "outputs": [ { "components": [ + { "internalType": "bool", "name": "token_owner", "type": "bool" }, { - "internalType": "enum CollectionPermissionField", - "name": "field", - "type": "uint8" + "internalType": "bool", + "name": "collection_admin", + "type": "bool" }, - { "internalType": "bool", "name": "value", "type": "bool" } - ], - "internalType": "struct CollectionNestingPermission[]", - "name": "", - "type": "tuple[]" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "collectionNestingRestrictedCollectionIds", - "outputs": [ - { - "components": [ - { "internalType": "bool", "name": "token_owner", "type": "bool" }, - { "internalType": "uint256[]", "name": "ids", "type": "uint256[]" } + { + "internalType": "address[]", + "name": "restricted", + "type": "address[]" + } ], - "internalType": "struct CollectionNesting", + "internalType": "struct CollectionNestingAndPermission", "name": "", "type": "tuple" } @@ -705,19 +693,24 @@ "type": "function" }, { - "inputs": [{ "internalType": "bool", "name": "enable", "type": "bool" }], - "name": "setCollectionNesting", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { "inputs": [ - { "internalType": "bool", "name": "enable", "type": "bool" }, { - "internalType": "address[]", - "name": "collections", - "type": "address[]" + "components": [ + { "internalType": "bool", "name": "token_owner", "type": "bool" }, + { + "internalType": "bool", + "name": "collection_admin", + "type": "bool" + }, + { + "internalType": "address[]", + "name": "restricted", + "type": "address[]" + } + ], + "internalType": "struct CollectionNestingAndPermission", + "name": "collectionNestingAndPermissions", + "type": "tuple" } ], "name": "setCollectionNesting", --- a/tests/src/eth/abi/nonFungibleDeprecated.json +++ b/tests/src/eth/abi/nonFungibleDeprecated.json @@ -109,5 +109,64 @@ "outputs": [], "stateMutability": "nonpayable", "type": "function" + }, + { + "inputs": [], + "name": "collectionNestingPermissions", + "outputs": [ + { + "components": [ + { + "internalType": "enum CollectionPermissionField", + "name": "field", + "type": "uint8" + }, + { "internalType": "bool", "name": "value", "type": "bool" } + ], + "internalType": "struct CollectionNestingPermission[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "collectionNestingRestrictedCollectionIds", + "outputs": [ + { + "components": [ + { "internalType": "bool", "name": "token_owner", "type": "bool" }, + { "internalType": "uint256[]", "name": "ids", "type": "uint256[]" } + ], + "internalType": "struct CollectionNesting", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "bool", "name": "enable", "type": "bool" }], + "name": "setCollectionNesting", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bool", "name": "enable", "type": "bool" }, + { + "internalType": "address[]", + "name": "collections", + "type": "address[]" + } + ], + "name": "setCollectionNesting", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" } ] --- a/tests/src/eth/abi/reFungible.json +++ b/tests/src/eth/abi/reFungible.json @@ -273,35 +273,23 @@ }, { "inputs": [], - "name": "collectionNestingPermissions", + "name": "collectionNesting", "outputs": [ { "components": [ + { "internalType": "bool", "name": "token_owner", "type": "bool" }, { - "internalType": "enum CollectionPermissionField", - "name": "field", - "type": "uint8" + "internalType": "bool", + "name": "collection_admin", + "type": "bool" }, - { "internalType": "bool", "name": "value", "type": "bool" } - ], - "internalType": "struct CollectionNestingPermission[]", - "name": "", - "type": "tuple[]" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "collectionNestingRestrictedCollectionIds", - "outputs": [ - { - "components": [ - { "internalType": "bool", "name": "token_owner", "type": "bool" }, - { "internalType": "uint256[]", "name": "ids", "type": "uint256[]" } + { + "internalType": "address[]", + "name": "restricted", + "type": "address[]" + } ], - "internalType": "struct CollectionNesting", + "internalType": "struct CollectionNestingAndPermission", "name": "", "type": "tuple" } @@ -687,19 +675,24 @@ "type": "function" }, { - "inputs": [{ "internalType": "bool", "name": "enable", "type": "bool" }], - "name": "setCollectionNesting", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { "inputs": [ - { "internalType": "bool", "name": "enable", "type": "bool" }, { - "internalType": "address[]", - "name": "collections", - "type": "address[]" + "components": [ + { "internalType": "bool", "name": "token_owner", "type": "bool" }, + { + "internalType": "bool", + "name": "collection_admin", + "type": "bool" + }, + { + "internalType": "address[]", + "name": "restricted", + "type": "address[]" + } + ], + "internalType": "struct CollectionNestingAndPermission", + "name": "collectionNestingAndPermissions", + "type": "tuple" } ], "name": "setCollectionNesting", --- a/tests/src/eth/abi/reFungibleDeprecated.json +++ b/tests/src/eth/abi/reFungibleDeprecated.json @@ -137,5 +137,64 @@ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "nonpayable", "type": "function" + }, + { + "inputs": [], + "name": "collectionNestingPermissions", + "outputs": [ + { + "components": [ + { + "internalType": "enum CollectionPermissionField", + "name": "field", + "type": "uint8" + }, + { "internalType": "bool", "name": "value", "type": "bool" } + ], + "internalType": "struct CollectionNestingPermission[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "collectionNestingRestrictedCollectionIds", + "outputs": [ + { + "components": [ + { "internalType": "bool", "name": "token_owner", "type": "bool" }, + { "internalType": "uint256[]", "name": "ids", "type": "uint256[]" } + ], + "internalType": "struct CollectionNesting", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "bool", "name": "enable", "type": "bool" }], + "name": "setCollectionNesting", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bool", "name": "enable", "type": "bool" }, + { + "internalType": "address[]", + "name": "collections", + "type": "address[]" + } + ], + "name": "setCollectionNesting", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" } ] --- a/tests/src/eth/allowlist.test.ts +++ b/tests/src/eth/allowlist.test.ts @@ -17,6 +17,7 @@ import {IKeyringPair} from '@polkadot/types/types'; import {Pallets} from '../util'; import {itEth, usingEthPlaygrounds, expect, SponsoringMode} from './util'; +import {CreateCollectionData} from './util/playgrounds/types'; describe('EVM contract allowlist', () => { let donor: IKeyringPair; @@ -104,7 +105,7 @@ const userEth = await helper.eth.createAccountWithBalance(donor); const mintParams = testCase.mode === 'ft' ? [userEth, 100] : [userEth]; - const {collectionAddress, collectionId} = await helper.eth.createCollection(testCase.mode, owner, 'A', 'B', 'C'); + const {collectionAddress, collectionId} = await helper.eth.createCollection(owner, new CreateCollectionData('A', 'B', 'C', testCase.mode)).send(); const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner); const userCrossSub = helper.ethCrossAccount.fromKeyringPair(userSub); const userCrossEth = helper.ethCrossAccount.fromAddress(userEth); @@ -178,7 +179,7 @@ const userEth = helper.eth.createAccount(); const userCrossEth = helper.ethCrossAccount.fromAddress(userEth); - const {collectionAddress, collectionId} = await helper.eth.createCollection(testCase.mode, owner, 'A', 'B', 'C'); + const {collectionAddress, collectionId} = await helper.eth.createCollection(owner, new CreateCollectionData('A', 'B', 'C', testCase.mode)).send(); const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner, !testCase.cross); expect(await helper.collection.allowed(collectionId, {Substrate: userSub.address})).to.be.false; --- a/tests/src/eth/api/CollectionHelpers.sol +++ b/tests/src/eth/api/CollectionHelpers.sol @@ -20,8 +20,14 @@ } /// @title Contract, which allows users to operate with collections -/// @dev the ERC-165 identifier for this interface is 0xe65011aa +/// @dev the ERC-165 identifier for this interface is 0x4135fff1 interface CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents { + /// Create a collection + /// @return address Address of the newly created collection + /// @dev EVM selector for this function is: 0xa765ee5b, + /// or in textual repr: createCollection(((address,uint256),string,string,string,uint8,uint8,(string,bytes)[],(string,(uint8,bool)[])[],(address,uint256)[],(bool,bool,address[]),(uint8,uint256)[],uint8)) + function createCollection(CreateCollectionData memory data) external payable returns (address); + /// Create an NFT collection /// @param name Name of the collection /// @param description Informative description of the collection @@ -94,3 +100,135 @@ /// or in textual repr: collectionId(address) function collectionId(address collectionAddress) external view returns (uint32); } + +/// Collection properties +struct CreateCollectionData { + /// Collection sponsor + CrossAddress pending_sponsor; + /// Collection name + string name; + /// Collection description + string description; + /// Token prefix + string token_prefix; + /// Token type (NFT, FT or RFT) + CollectionMode mode; + /// Fungible token precision + uint8 decimals; + /// Custom Properties + Property[] properties; + /// Permissions for token properties + TokenPropertyPermission[] token_property_permissions; + /// Collection admins + CrossAddress[] admin_list; + /// Nesting settings + CollectionNestingAndPermission nesting_settings; + /// Collection limits + CollectionLimitValue[] limits; + /// Extra collection flags + CollectionFlags flags; +} + +/// Cross account struct +type CollectionFlags is uint8; + +library CollectionFlagsLib { + /// Tokens in foreign collections can be transferred, but not burnt + CollectionFlags constant foreignField = CollectionFlags.wrap(128); + /// Supports ERC721Metadata + CollectionFlags constant erc721metadataField = CollectionFlags.wrap(64); + /// External collections can't be managed using `unique` api + CollectionFlags constant externalField = CollectionFlags.wrap(1); + + /// Reserved bits + function reservedField(uint8 value) public pure returns (CollectionFlags) { + require(value < 1 << 5, "out of bound value"); + return CollectionFlags.wrap(value << 1); + } +} + +/// [`CollectionLimits`](up_data_structs::CollectionLimits) field representation for EVM. +struct CollectionLimitValue { + CollectionLimitField field; + uint256 value; +} + +/// [`CollectionLimits`](up_data_structs::CollectionLimits) fields representation for EVM. +enum CollectionLimitField { + /// How many tokens can a user have on one account. + AccountTokenOwnership, + /// How many bytes of data are available for sponsorship. + SponsoredDataSize, + /// In any case, chain default: [`SponsoringRateLimit::SponsoringDisabled`] + SponsoredDataRateLimit, + /// How many tokens can be mined into this collection. + TokenLimit, + /// Timeouts for transfer sponsoring. + SponsorTransferTimeout, + /// Timeout for sponsoring an approval in passed blocks. + SponsorApproveTimeout, + /// Whether the collection owner of the collection can send tokens (which belong to other users). + OwnerCanTransfer, + /// Can the collection owner burn other people's tokens. + OwnerCanDestroy, + /// Is it possible to send tokens from this collection between users. + TransferEnabled +} + +/// Nested collections and permissions +struct CollectionNestingAndPermission { + /// Owner of token can nest tokens under it. + bool token_owner; + /// Admin of token collection can nest tokens under token. + bool collection_admin; + /// If set - only tokens from specified collections can be nested. + address[] restricted; +} + +/// Cross account struct +struct CrossAddress { + address eth; + uint256 sub; +} + +/// Ethereum representation of Token Property Permissions. +struct TokenPropertyPermission { + /// Token property key. + string key; + /// Token property permissions. + PropertyPermission[] permissions; +} + +/// Ethereum representation of TokenPermissions (see [`up_data_structs::PropertyPermission`]) as an key and value. +struct PropertyPermission { + /// TokenPermission field. + TokenPermissionField code; + /// TokenPermission value. + bool value; +} + +/// Ethereum representation of TokenPermissions (see [`up_data_structs::PropertyPermission`]) fields as an enumeration. +enum TokenPermissionField { + /// Permission to change the property and property permission. See [`up_data_structs::PropertyPermission::mutable`] + Mutable, + /// Change permission for the collection administrator. See [`up_data_structs::PropertyPermission::token_owner`] + TokenOwner, + /// Permission to change the property for the owner of the token. See [`up_data_structs::PropertyPermission::collection_admin`] + CollectionAdmin +} + +/// Ethereum representation of collection [`PropertyKey`](up_data_structs::PropertyKey) and [`PropertyValue`](up_data_structs::PropertyValue). +struct Property { + string key; + bytes value; +} + +/// Type of tokens in collection +enum CollectionMode { + /// Fungible + Fungible, + /// Nonfungible + Nonfungible, + /// Refungible + Refungible +} --- a/tests/src/eth/api/UniqueFungible.sol +++ b/tests/src/eth/api/UniqueFungible.sol @@ -13,7 +13,7 @@ } /// @title A contract that allows you to work with collections. -/// @dev the ERC-165 identifier for this interface is 0x2a14cfd1 +/// @dev the ERC-165 identifier for this interface is 0xb34d97e9 interface Collection is Dummy, ERC165 { // /// Set collection property. // /// @@ -148,30 +148,38 @@ // /// or in textual repr: removeCollectionAdmin(address) // function removeCollectionAdmin(address admin) external; - /// Toggle accessibility of collection nesting. - /// - /// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled' - /// @dev EVM selector for this function is: 0x112d4586, - /// or in textual repr: setCollectionNesting(bool) - function setCollectionNesting(bool enable) external; + /// @dev EVM selector for this function is: 0x0b9f3890, + /// or in textual repr: setCollectionNesting((bool,bool,address[])) + function setCollectionNesting(CollectionNestingAndPermission memory collectionNestingAndPermissions) external; - /// Toggle accessibility of collection nesting. - /// - /// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled' - /// @param collections Addresses of collections that will be available for nesting. - /// @dev EVM selector for this function is: 0x64872396, - /// or in textual repr: setCollectionNesting(bool,address[]) - function setCollectionNesting(bool enable, address[] memory collections) external; + // /// Toggle accessibility of collection nesting. + // /// + // /// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled' + // /// @dev EVM selector for this function is: 0x112d4586, + // /// or in textual repr: setCollectionNesting(bool) + // function setCollectionNesting(bool enable) external; + + // /// Toggle accessibility of collection nesting. + // /// + // /// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled' + // /// @param collections Addresses of collections that will be available for nesting. + // /// @dev EVM selector for this function is: 0x64872396, + // /// or in textual repr: setCollectionNesting(bool,address[]) + // function setCollectionNesting(bool enable, address[] memory collections) external; + + /// @dev EVM selector for this function is: 0x92c660a8, + /// or in textual repr: collectionNesting() + function collectionNesting() external view returns (CollectionNestingAndPermission memory); - /// Returns nesting for a collection - /// @dev EVM selector for this function is: 0x22d25bfe, - /// or in textual repr: collectionNestingRestrictedCollectionIds() - function collectionNestingRestrictedCollectionIds() external view returns (CollectionNesting memory); + // /// Returns nesting for a collection + // /// @dev EVM selector for this function is: 0x22d25bfe, + // /// or in textual repr: collectionNestingRestrictedCollectionIds() + // function collectionNestingRestrictedCollectionIds() external view returns (CollectionNesting memory); - /// Returns permissions for a collection - /// @dev EVM selector for this function is: 0x5b2eaf4b, - /// or in textual repr: collectionNestingPermissions() - function collectionNestingPermissions() external view returns (CollectionNestingPermission[] memory); + // /// Returns permissions for a collection + // /// @dev EVM selector for this function is: 0x5b2eaf4b, + // /// or in textual repr: collectionNestingPermissions() + // function collectionNestingPermissions() external view returns (CollectionNestingPermission[] memory); /// Set the collection access method. /// @param mode Access mode @@ -311,6 +319,16 @@ uint256[] ids; } +/// Nested collections and permissions +struct CollectionNestingAndPermission { + /// Owner of token can nest tokens under it. + bool token_owner; + /// Admin of token collection can nest tokens under token. + bool collection_admin; + /// If set - only tokens from specified collections can be nested. + address[] restricted; +} + /// [`CollectionLimits`](up_data_structs::CollectionLimits) field representation for EVM. struct CollectionLimit { CollectionLimitField field; --- a/tests/src/eth/api/UniqueNFT.sol +++ b/tests/src/eth/api/UniqueNFT.sol @@ -118,7 +118,7 @@ } /// @title A contract that allows you to work with collections. -/// @dev the ERC-165 identifier for this interface is 0x2a14cfd1 +/// @dev the ERC-165 identifier for this interface is 0xb34d97e9 interface Collection is Dummy, ERC165 { // /// Set collection property. // /// @@ -253,30 +253,38 @@ // /// or in textual repr: removeCollectionAdmin(address) // function removeCollectionAdmin(address admin) external; - /// Toggle accessibility of collection nesting. - /// - /// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled' - /// @dev EVM selector for this function is: 0x112d4586, - /// or in textual repr: setCollectionNesting(bool) - function setCollectionNesting(bool enable) external; + /// @dev EVM selector for this function is: 0x0b9f3890, + /// or in textual repr: setCollectionNesting((bool,bool,address[])) + function setCollectionNesting(CollectionNestingAndPermission memory collectionNestingAndPermissions) external; - /// Toggle accessibility of collection nesting. - /// - /// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled' - /// @param collections Addresses of collections that will be available for nesting. - /// @dev EVM selector for this function is: 0x64872396, - /// or in textual repr: setCollectionNesting(bool,address[]) - function setCollectionNesting(bool enable, address[] memory collections) external; + // /// Toggle accessibility of collection nesting. + // /// + // /// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled' + // /// @dev EVM selector for this function is: 0x112d4586, + // /// or in textual repr: setCollectionNesting(bool) + // function setCollectionNesting(bool enable) external; + + // /// Toggle accessibility of collection nesting. + // /// + // /// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled' + // /// @param collections Addresses of collections that will be available for nesting. + // /// @dev EVM selector for this function is: 0x64872396, + // /// or in textual repr: setCollectionNesting(bool,address[]) + // function setCollectionNesting(bool enable, address[] memory collections) external; + + /// @dev EVM selector for this function is: 0x92c660a8, + /// or in textual repr: collectionNesting() + function collectionNesting() external view returns (CollectionNestingAndPermission memory); - /// Returns nesting for a collection - /// @dev EVM selector for this function is: 0x22d25bfe, - /// or in textual repr: collectionNestingRestrictedCollectionIds() - function collectionNestingRestrictedCollectionIds() external view returns (CollectionNesting memory); + // /// Returns nesting for a collection + // /// @dev EVM selector for this function is: 0x22d25bfe, + // /// or in textual repr: collectionNestingRestrictedCollectionIds() + // function collectionNestingRestrictedCollectionIds() external view returns (CollectionNesting memory); - /// Returns permissions for a collection - /// @dev EVM selector for this function is: 0x5b2eaf4b, - /// or in textual repr: collectionNestingPermissions() - function collectionNestingPermissions() external view returns (CollectionNestingPermission[] memory); + // /// Returns permissions for a collection + // /// @dev EVM selector for this function is: 0x5b2eaf4b, + // /// or in textual repr: collectionNestingPermissions() + // function collectionNestingPermissions() external view returns (CollectionNestingPermission[] memory); /// Set the collection access method. /// @param mode Access mode @@ -416,6 +424,16 @@ uint256[] ids; } +/// Nested collections and permissions +struct CollectionNestingAndPermission { + /// Owner of token can nest tokens under it. + bool token_owner; + /// Admin of token collection can nest tokens under token. + bool collection_admin; + /// If set - only tokens from specified collections can be nested. + address[] restricted; +} + /// [`CollectionLimits`](up_data_structs::CollectionLimits) field representation for EVM. struct CollectionLimit { CollectionLimitField field; --- a/tests/src/eth/api/UniqueRefungible.sol +++ b/tests/src/eth/api/UniqueRefungible.sol @@ -118,7 +118,7 @@ } /// @title A contract that allows you to work with collections. -/// @dev the ERC-165 identifier for this interface is 0x2a14cfd1 +/// @dev the ERC-165 identifier for this interface is 0xb34d97e9 interface Collection is Dummy, ERC165 { // /// Set collection property. // /// @@ -253,30 +253,38 @@ // /// or in textual repr: removeCollectionAdmin(address) // function removeCollectionAdmin(address admin) external; - /// Toggle accessibility of collection nesting. - /// - /// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled' - /// @dev EVM selector for this function is: 0x112d4586, - /// or in textual repr: setCollectionNesting(bool) - function setCollectionNesting(bool enable) external; + /// @dev EVM selector for this function is: 0x0b9f3890, + /// or in textual repr: setCollectionNesting((bool,bool,address[])) + function setCollectionNesting(CollectionNestingAndPermission memory collectionNestingAndPermissions) external; - /// Toggle accessibility of collection nesting. - /// - /// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled' - /// @param collections Addresses of collections that will be available for nesting. - /// @dev EVM selector for this function is: 0x64872396, - /// or in textual repr: setCollectionNesting(bool,address[]) - function setCollectionNesting(bool enable, address[] memory collections) external; + // /// Toggle accessibility of collection nesting. + // /// + // /// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled' + // /// @dev EVM selector for this function is: 0x112d4586, + // /// or in textual repr: setCollectionNesting(bool) + // function setCollectionNesting(bool enable) external; + + // /// Toggle accessibility of collection nesting. + // /// + // /// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled' + // /// @param collections Addresses of collections that will be available for nesting. + // /// @dev EVM selector for this function is: 0x64872396, + // /// or in textual repr: setCollectionNesting(bool,address[]) + // function setCollectionNesting(bool enable, address[] memory collections) external; + + /// @dev EVM selector for this function is: 0x92c660a8, + /// or in textual repr: collectionNesting() + function collectionNesting() external view returns (CollectionNestingAndPermission memory); - /// Returns nesting for a collection - /// @dev EVM selector for this function is: 0x22d25bfe, - /// or in textual repr: collectionNestingRestrictedCollectionIds() - function collectionNestingRestrictedCollectionIds() external view returns (CollectionNesting memory); + // /// Returns nesting for a collection + // /// @dev EVM selector for this function is: 0x22d25bfe, + // /// or in textual repr: collectionNestingRestrictedCollectionIds() + // function collectionNestingRestrictedCollectionIds() external view returns (CollectionNesting memory); - /// Returns permissions for a collection - /// @dev EVM selector for this function is: 0x5b2eaf4b, - /// or in textual repr: collectionNestingPermissions() - function collectionNestingPermissions() external view returns (CollectionNestingPermission[] memory); + // /// Returns permissions for a collection + // /// @dev EVM selector for this function is: 0x5b2eaf4b, + // /// or in textual repr: collectionNestingPermissions() + // function collectionNestingPermissions() external view returns (CollectionNestingPermission[] memory); /// Set the collection access method. /// @param mode Access mode @@ -416,6 +424,16 @@ uint256[] ids; } +/// Nested collections and permissions +struct CollectionNestingAndPermission { + /// Owner of token can nest tokens under it. + bool token_owner; + /// Admin of token collection can nest tokens under token. + bool collection_admin; + /// If set - only tokens from specified collections can be nested. + address[] restricted; +} + /// [`CollectionLimits`](up_data_structs::CollectionLimits) field representation for EVM. struct CollectionLimit { CollectionLimitField field; --- a/tests/src/eth/collectionAdmin.test.ts +++ b/tests/src/eth/collectionAdmin.test.ts @@ -19,6 +19,7 @@ import {IEthCrossAccountId} from '../util/playgrounds/types'; import {usingEthPlaygrounds, itEth} from './util'; import {EthUniqueHelper} from './util/playgrounds/unique.dev'; +import {CreateCollectionData} from './util/playgrounds/types'; async function recordEthFee(helper: EthUniqueHelper, userAddress: string, call: () => Promise) { const before = await helper.balance.getSubstrate(helper.address.ethToSubstrate(userAddress)); @@ -55,7 +56,7 @@ const adminCrossSub = helper.ethCrossAccount.fromKeyringPair(adminSub); const adminCrossEth = helper.ethCrossAccount.fromAddress(adminEth); - const {collectionAddress, collectionId} = await helper.eth.createCollection(testCase.mode, owner, 'A', 'B', 'C'); + const {collectionAddress, collectionId} = await helper.eth.createCollection(owner, new CreateCollectionData('A', 'B', 'C', testCase.mode)).send(); const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner, true); // Check isOwnerOrAdminCross returns false: --- a/tests/src/eth/collectionLimits.test.ts +++ b/tests/src/eth/collectionLimits.test.ts @@ -1,7 +1,7 @@ import {IKeyringPair} from '@polkadot/types/types'; import {Pallets} from '../util'; import {expect, itEth, usingEthPlaygrounds} from './util'; -import {CollectionLimitField} from './util/playgrounds/types'; +import {CollectionLimitField, CreateCollectionData} from './util/playgrounds/types'; describe('Can set collection limits', () => { @@ -20,7 +20,7 @@ ].map(testCase => itEth.ifWithPallets(`for ${testCase.case}`, testCase.requiredPallets || [], async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); - const {collectionId, collectionAddress} = await helper.eth.createCollection(testCase.case, owner, 'Limits', 'absolutely anything', 'FLO', 18); + const {collectionId, collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('Limits', 'absolutely anything', 'FLO', testCase.case, 18)).send(); const limits = { accountTokenOwnershipLimit: 1000, sponsoredDataSize: 1024, @@ -96,13 +96,13 @@ }; const owner = await helper.eth.createAccountWithBalance(donor); - const {collectionAddress} = await helper.eth.createCollection(testCase.case, owner, 'Limits', 'absolutely anything', 'ISNI', 18); + const {collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('Limits', 'absolutely anything', 'ISNI', testCase.case, 18)).send(); const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, testCase.case, owner); // Cannot set non-existing limit await expect(collectionEvm.methods .setCollectionLimit({field: 9, value: {status: true, value: 1}}) - .call()).to.be.rejectedWith('Returned error: VM Exception while processing transaction: revert Value not convertible into enum "CollectionLimitField"'); + .call()).to.be.rejectedWith('Returned error: VM Exception while processing transaction: revert value not convertible into enum "CollectionLimitField"'); // Cannot disable limits await expect(collectionEvm.methods @@ -129,7 +129,7 @@ itEth.ifWithPallets(`Non-owner and non-admin cannot set collection limits for ${testCase.case}`, testCase.requiredPallets || [], async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); const nonOwner = await helper.eth.createAccountWithBalance(donor); - const {collectionAddress} = await helper.eth.createCollection(testCase.case, owner, 'Limits', 'absolutely anything', 'FLO', 18); + const {collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('Limits', 'absolutely anything', 'FLO', testCase.case, 18)).send(); const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, testCase.case, owner); await expect(collectionEvm.methods --- /dev/null +++ b/tests/src/eth/createCollection.test.ts @@ -0,0 +1,1459 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +import {IKeyringPair} from '@polkadot/types/types'; +import {evmToAddress} from '@polkadot/util-crypto'; +import {Pallets, requirePalletsOrSkip} from '../util'; +import {expect, itEth, usingEthPlaygrounds} from './util'; +import {CREATE_COLLECTION_DATA_DEFAULTS, CollectionLimitField, CollectionMode, CreateCollectionData, TokenPermissionField, emptyAddress} from './util/playgrounds/types'; +import {CollectionFlag, IEthCrossAccountId, TCollectionMode} from '../util/playgrounds/types'; + +const DECIMALS = 18; +const CREATE_COLLECTION_DATA_DEFAULTS_ARRAY = [ + [], + [], + [], + [false, false, []], + [], + [0], +]; + +type ElementOf = A extends readonly (infer T)[] ? T : never; +function* cartesian>, R extends Array>(internalRest: [...R], ...args: [...T]): Generator<[...R, ...{[K in keyof T]: ElementOf}]> { + if(args.length === 0) { + yield internalRest as any; + return; + } + for(const value of args[0]) { + yield* cartesian([...internalRest, value], ...args.slice(1)) as any; + } +} + +describe('Create collection from EVM', () => { + let donor: IKeyringPair; + let nominal: bigint; + + before(async function() { + await usingEthPlaygrounds(async (helper, privateKey) => { + donor = await privateKey({url: import.meta.url}); + nominal = helper.balance.getOneTokenNominal(); + }); + }); + + describe('Fungible collection', () => { + before(async function() { + await usingEthPlaygrounds((helper) => { + requirePalletsOrSkip(this, helper, [Pallets.Fungible]); + return Promise.resolve(); + }); + }); + + itEth('Collection address exist', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const collectionAddressForNonexistentCollection = '0x17C4E6453CC49AAAAEACA894E6D9683E00112233'; + const collectionHelpers = await helper.ethNativeContract.collectionHelpers(owner); + + expect(await collectionHelpers + .methods.isCollectionExist(collectionAddressForNonexistentCollection).call()) + .to.be.false; + + const {collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('Exister', 'absolutely anything', 'WIWT', 'ft', DECIMALS)).send(); + expect(await collectionHelpers + .methods.isCollectionExist(collectionAddress).call()) + .to.be.true; + + // check collectionOwner: + const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner, true); + const collectionOwner = await collectionEvm.methods.collectionOwner().call(); + expect(helper.address.restoreCrossAccountFromBigInt(BigInt(collectionOwner.sub))).to.eq(helper.address.ethToSubstrate(owner, true)); + }); + + itEth('destroyCollection', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const {collectionAddress, collectionId} = await helper.eth.createCollection(owner, new CreateCollectionData('Exister', 'absolutely anything', 'WIWT', 'ft', DECIMALS)).send(); + const collectionHelper = await helper.ethNativeContract.collectionHelpers(owner); + + const result = await collectionHelper.methods + .destroyCollection(collectionAddress) + .send({from: owner}); + + const events = helper.eth.normalizeEvents(result.events); + + expect(events).to.be.deep.equal([ + { + address: collectionHelper.options.address, + event: 'CollectionDestroyed', + args: { + collectionId: collectionAddress, + }, + }, + ]); + + expect(await collectionHelper.methods + .isCollectionExist(collectionAddress) + .call()).to.be.false; + expect(await helper.collection.getData(collectionId)).to.be.null; + }); + + itEth('(!negative test!) Create collection (bad lengths)', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const collectionHelper = await helper.ethNativeContract.collectionHelpers(owner); + { + const MAX_NAME_LENGTH = 64; + const collectionName = 'A'.repeat(MAX_NAME_LENGTH + 1); + const description = 'A'; + const tokenPrefix = 'A'; + + await expect(collectionHelper.methods + .createCollection([ + emptyAddress, + collectionName, + description, + tokenPrefix, + CollectionMode.Fungible, + DECIMALS, + ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY, + ]) + .call({value: Number(2n * nominal)})).to.be.rejectedWith('name is too long. Max length is ' + MAX_NAME_LENGTH); + } + { + const MAX_DESCRIPTION_LENGTH = 256; + const collectionName = 'A'; + const description = 'A'.repeat(MAX_DESCRIPTION_LENGTH + 1); + const tokenPrefix = 'A'; + await expect(collectionHelper.methods + .createCollection([ + emptyAddress, + collectionName, + description, + tokenPrefix, + CollectionMode.Fungible, + DECIMALS, + ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY, + ]) + .call({value: Number(2n * nominal)})).to.be.rejectedWith('description is too long. Max length is ' + MAX_DESCRIPTION_LENGTH); + } + { + const MAX_TOKEN_PREFIX_LENGTH = 16; + const collectionName = 'A'; + const description = 'A'; + const tokenPrefix = 'A'.repeat(MAX_TOKEN_PREFIX_LENGTH + 1); + await expect(collectionHelper.methods + .createCollection([ + emptyAddress, + collectionName, + description, + tokenPrefix, + CollectionMode.Fungible, + DECIMALS, + ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY, + ]) + .call({value: Number(2n * nominal)})).to.be.rejectedWith('token_prefix is too long. Max length is ' + MAX_TOKEN_PREFIX_LENGTH); + } + }); + + itEth('(!negative test!) cannot create collection if value !== 2', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const collectionHelper = await helper.ethNativeContract.collectionHelpers(owner); + const expects = [0n, 1n, 30n].map(async value => { + await expect(collectionHelper.methods + .createCollection([ + emptyAddress, + 'Peasantry', + 'absolutely anything', + 'TWIW', + CollectionMode.Fungible, + DECIMALS, + ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY, + ]) + .call({value: Number(value * nominal)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)'); + }); + await Promise.all(expects); + }); + }); + + describe('Nonfungible collection', () => { + before(async function() { + await usingEthPlaygrounds((helper) => { + requirePalletsOrSkip(this, helper, [Pallets.NFT]); + return Promise.resolve(); + }); + }); + + itEth('Create collection', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + + const name = 'CollectionEVM'; + const description = 'Some description'; + const prefix = 'token prefix'; + + // todo:playgrounds this might fail when in async environment. + const collectionCountBefore = +(await helper.callRpc('api.rpc.unique.collectionStats')).created; + const {collectionId, collectionAddress, events} = await helper.eth.createCollection(owner, new CreateCollectionData(name, description, prefix, 'nft')).send(); + + expect(events).to.be.deep.equal([ + { + address: '0x6C4E9fE1AE37a41E93CEE429e8E1881aBdcbb54F', + event: 'CollectionCreated', + args: { + owner: owner, + collectionId: collectionAddress, + }, + }, + ]); + + const collectionCountAfter = +(await helper.callRpc('api.rpc.unique.collectionStats')).created; + + const collection = helper.nft.getCollectionObject(collectionId); + const data = (await collection.getData())!; + + expect(collectionCountAfter - collectionCountBefore).to.be.eq(1); + expect(collectionId).to.be.eq(collectionCountAfter); + expect(data.name).to.be.eq(name); + expect(data.description).to.be.eq(description); + expect(data.raw.tokenPrefix).to.be.eq(prefix); + expect(data.raw.mode).to.be.eq('NFT'); + + const options = await collection.getOptions(); + + expect(options.tokenPropertyPermissions).to.be.empty; + }); + + // this test will occasionally fail when in async environment. + itEth('Check collection address exist', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + + const expectedCollectionId = +(await helper.callRpc('api.rpc.unique.collectionStats')).created + 1; + const expectedCollectionAddress = helper.ethAddress.fromCollectionId(expectedCollectionId); + const collectionHelpers = await helper.ethNativeContract.collectionHelpers(owner); + + expect(await collectionHelpers.methods + .isCollectionExist(expectedCollectionAddress) + .call()).to.be.false; + + await collectionHelpers.methods + .createCollection([ + emptyAddress, + 'A', + 'A', + 'A', + CollectionMode.Nonfungible, + 0, + ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY, + ]) + .send({value: Number(2n * helper.balance.getOneTokenNominal())}); + + expect(await collectionHelpers.methods + .isCollectionExist(expectedCollectionAddress) + .call()).to.be.true; + }); + }); + + describe('Create RFT collection from EVM', () => { + before(async function() { + await usingEthPlaygrounds((helper) => { + requirePalletsOrSkip(this, helper, [Pallets.ReFungible]); + return Promise.resolve(); + }); + }); + + itEth('Create collection', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + + const name = 'CollectionEVM'; + const description = 'Some description'; + const prefix = 'token prefix'; + + const {collectionId} = await helper.eth.createCollection(owner, new CreateCollectionData(name, description, prefix, 'rft')).send(); + const data = (await helper.rft.getData(collectionId))!; + const collection = helper.rft.getCollectionObject(collectionId); + + expect(data.name).to.be.eq(name); + expect(data.description).to.be.eq(description); + expect(data.raw.tokenPrefix).to.be.eq(prefix); + expect(data.raw.mode).to.be.eq('ReFungible'); + + const options = await collection.getOptions(); + + expect(options.tokenPropertyPermissions).to.be.empty; + }); + + itEth('Create collection with properties & get description', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + + const name = 'CollectionEVM'; + const description = 'Some description'; + const prefix = 'token prefix'; + const baseUri = 'BaseURI'; + + const {collectionId, collectionAddress} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner, name, description, prefix, baseUri); + const contract = helper.ethNativeContract.collection(collectionAddress, 'nft'); + + const collection = helper.rft.getCollectionObject(collectionId); + const data = (await collection.getData())!; + + expect(data.name).to.be.eq(name); + expect(data.description).to.be.eq(description); + expect(data.raw.tokenPrefix).to.be.eq(prefix); + expect(data.raw.mode).to.be.eq('ReFungible'); + + expect(await contract.methods.description().call()).to.deep.equal(description); + + const options = await collection.getOptions(); + expect(options.tokenPropertyPermissions).to.be.deep.equal([ + { + key: 'URI', + permission: {mutable: true, collectionAdmin: true, tokenOwner: false}, + }, + { + key: 'URISuffix', + permission: {mutable: true, collectionAdmin: true, tokenOwner: false}, + }, + ]); + }); + + itEth('Set sponsorship', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const sponsor = await helper.eth.createAccountWithBalance(donor); + const ss58Format = helper.chain.getChainProperties().ss58Format; + const {collectionId, collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('Sponsor', 'absolutely anything', 'ENVY', 'rft')).send(); + + const collection = await helper.ethNativeContract.collection(collectionAddress, 'rft', owner); + const sponsorCross = helper.ethCrossAccount.fromAddress(sponsor); + await collection.methods.setCollectionSponsorCross(sponsorCross).send(); + + let data = (await helper.rft.getData(collectionId))!; + expect(data.raw.sponsorship.Unconfirmed).to.be.equal(evmToAddress(sponsor, Number(ss58Format))); + + await expect(collection.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmSponsorshipFail'); + + const sponsorCollection = await helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor); + await sponsorCollection.methods.confirmCollectionSponsorship().send(); + + data = (await helper.rft.getData(collectionId))!; + expect(data.raw.sponsorship.Confirmed).to.be.equal(evmToAddress(sponsor, Number(ss58Format))); + }); + + itEth('Collection address exist', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const collectionAddressForNonexistentCollection = '0x17C4E6453CC49AAAAEACA894E6D9683E00112233'; + const collectionHelpers = await helper.ethNativeContract.collectionHelpers(owner); + + expect(await collectionHelpers + .methods.isCollectionExist(collectionAddressForNonexistentCollection).call()) + .to.be.false; + + const {collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('Exister', 'absolutely anything', 'WIWT', 'rft')).send(); + expect(await collectionHelpers + .methods.isCollectionExist(collectionAddress).call()) + .to.be.true; + + // check collectionOwner: + const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner, true); + const collectionOwner = await collectionEvm.methods.collectionOwner().call(); + expect(helper.address.restoreCrossAccountFromBigInt(BigInt(collectionOwner.sub))).to.eq(helper.address.ethToSubstrate(owner, true)); + }); + + itEth('destroyCollection', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const {collectionAddress, collectionId} = await helper.eth.createCollection(owner, new CreateCollectionData('Limits', 'absolutely anything', 'OLF', 'rft')).send(); + const collectionHelper = await helper.ethNativeContract.collectionHelpers(owner); + + await expect(collectionHelper.methods + .destroyCollection(collectionAddress) + .send({from: owner})).to.be.fulfilled; + + expect(await collectionHelper.methods + .isCollectionExist(collectionAddress) + .call()).to.be.false; + expect(await helper.collection.getData(collectionId)).to.be.null; + }); + + itEth('(!negative test!) Create collection (bad lengths)', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const collectionHelper = await helper.ethNativeContract.collectionHelpers(owner); + { + const MAX_NAME_LENGTH = 64; + const collectionName = 'A'.repeat(MAX_NAME_LENGTH + 1); + const description = 'A'; + const tokenPrefix = 'A'; + + await expect(collectionHelper.methods + .createCollection([ + emptyAddress, + collectionName, + description, + tokenPrefix, + CollectionMode.Refungible, + DECIMALS, + ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY, + ]) + .call({value: Number(2n * nominal)})).to.be.rejectedWith('name is too long. Max length is ' + MAX_NAME_LENGTH); + } + { + const MAX_DESCRIPTION_LENGTH = 256; + const collectionName = 'A'; + const description = 'A'.repeat(MAX_DESCRIPTION_LENGTH + 1); + const tokenPrefix = 'A'; + await expect(collectionHelper.methods + .createCollection([ + emptyAddress, + collectionName, + description, + tokenPrefix, + CollectionMode.Refungible, + DECIMALS, + ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY, + ]) + .call({value: Number(2n * nominal)})).to.be.rejectedWith('description is too long. Max length is ' + MAX_DESCRIPTION_LENGTH); + } + { + const MAX_TOKEN_PREFIX_LENGTH = 16; + const collectionName = 'A'; + const description = 'A'; + const tokenPrefix = 'A'.repeat(MAX_TOKEN_PREFIX_LENGTH + 1); + await expect(collectionHelper.methods + .createCollection([ + emptyAddress, + collectionName, + description, + tokenPrefix, + CollectionMode.Refungible, + DECIMALS, + ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY, + ]) + .call({value: Number(2n * nominal)})).to.be.rejectedWith('token_prefix is too long. Max length is ' + MAX_TOKEN_PREFIX_LENGTH); + } + }); + + itEth('(!negative test!) Create collection (no funds)', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const collectionHelper = await helper.ethNativeContract.collectionHelpers(owner); + await expect(collectionHelper.methods + .createCollection([ + emptyAddress, + 'Peasantry', + 'absolutely anything', + 'TWIW', + CollectionMode.Refungible, + 0, + ...CREATE_COLLECTION_DATA_DEFAULTS_ARRAY, + ]) + .call({value: Number(1n * nominal)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)'); + }); + }); + + describe('Sponsoring', () => { + itEth('Сan remove collection sponsor', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const sponsor = await helper.eth.createAccountWithBalance(donor); + const sponsorCross = helper.ethCrossAccount.fromAddr(sponsor); + + const {collectionAddress} = await helper.eth.createCollection( + owner, + { + ...CREATE_COLLECTION_DATA_DEFAULTS, + name: 'Sponsor collection', + description: '1', + tokenPrefix: '1', + collectionMode: 'nft', + pendingSponsor: sponsorCross, + }, + ).send(); + const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner); + + expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.true; + + await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor}); + let sponsorStruct = await collectionEvm.methods.collectionSponsor().call({from: owner}); + expect(helper.address.restoreCrossAccountFromBigInt(BigInt(sponsorStruct.sub))).to.be.eq(helper.address.ethToSubstrate(sponsor, true)); + expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.false; + + await collectionEvm.methods.removeCollectionSponsor().send({from: owner}); + + sponsorStruct = await collectionEvm.methods.collectionSponsor().call({from: owner}); + expect(sponsorStruct.eth).to.be.eq('0x0000000000000000000000000000000000000000'); + }); + + itEth('Can sponsor from evm address via access list', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const sponsorEth = await helper.eth.createAccountWithBalance(donor); + const sponsorCross = helper.ethCrossAccount.fromAddr(sponsorEth); + + const {collectionId, collectionAddress} = await helper.eth.createERC721MetadataCompatibleCollection( + owner, + { + ...CREATE_COLLECTION_DATA_DEFAULTS, + name: 'Sponsor collection', + description: '1', + tokenPrefix: '1', + collectionMode: 'nft', + pendingSponsor: sponsorCross, + limits: [{field: CollectionLimitField.SponsoredDataRateLimit, value: 30n}], + tokenPropertyPermissions: [{key: 'key', permissions: [{code: TokenPermissionField.TokenOwner, value: true}]}], + }, + '', + ); + + const collectionSub = helper.nft.getCollectionObject(collectionId); + const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner); + + let sponsorship = (await collectionSub.getData())!.raw.sponsorship; + expect(sponsorship.Unconfirmed).to.be.eq(helper.address.ethToSubstrate(sponsorEth, true)); + // Account cannot confirm sponsorship if it is not set as a sponsor + await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmSponsorshipFail'); + + // Sponsor can confirm sponsorship: + await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsorEth}); + sponsorship = (await collectionSub.getData())!.raw.sponsorship; + expect(sponsorship.Confirmed).to.be.eq(helper.address.ethToSubstrate(sponsorEth, true)); + + // Create user with no balance: + const user = helper.ethCrossAccount.createAccount(); + const nextTokenId = await collectionEvm.methods.nextTokenId().call(); + expect(nextTokenId).to.be.equal('1'); + + // Set collection permissions: + const oldPermissions = (await collectionSub.getData())!.raw.permissions; + expect(oldPermissions.mintMode).to.be.false; + expect(oldPermissions.access).to.be.equal('Normal'); + + await collectionEvm.methods.setCollectionAccess(1 /*'AllowList'*/).send({from: owner}); + await collectionEvm.methods.addToCollectionAllowListCross(user).send({from: owner}); + await collectionEvm.methods.setCollectionMintMode(true).send({from: owner}); + + const newPermissions = (await collectionSub.getData())!.raw.permissions; + expect(newPermissions.mintMode).to.be.true; + expect(newPermissions.access).to.be.equal('AllowList'); + + const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner)); + const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsorEth)); + const userBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user.eth)); + + // User can mint token without balance: + { + const result = await collectionEvm.methods.mintCross(user, [{key: 'key', value: Buffer.from('Value')}]).send({from: user.eth}); + const event = helper.eth.normalizeEvents(result.events) + .find(event => event.event === 'Transfer'); + + expect(event).to.be.deep.equal({ + address: collectionAddress, + event: 'Transfer', + args: { + from: '0x0000000000000000000000000000000000000000', + to: user.eth, + tokenId: '1', + }, + }); + + // await collectionEvm.methods.setProperties(1, [{key: 'key', value: Buffer.from('Value1')}]).send({from: user.eth}); + + const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner)); + const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsorEth)); + const userBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user.eth)); + + expect(await collectionEvm.methods.properties(nextTokenId, []).call()) + .to.be.like([ + [ + 'key', + '0x' + Buffer.from('Value').toString('hex'), + ], + ]); + expect(ownerBalanceBefore).to.be.eq(ownerBalanceAfter); + expect(userBalanceAfter).to.be.eq(userBalanceBefore); + expect(sponsorBalanceBefore > sponsorBalanceAfter).to.be.true; + } + }); + + itEth('Check that transaction via EVM spend money from sponsor address', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const sponsor = await helper.eth.createAccountWithBalance(donor); + const sponsorCross = helper.ethCrossAccount.fromAddr(sponsor); + const user = helper.eth.createAccount(); + const userCross = helper.ethCrossAccount.fromAddress(user); + + const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleCollection( + owner, + { + ...CREATE_COLLECTION_DATA_DEFAULTS, + name: 'Sponsor collection', + description: '1', + tokenPrefix: '1', + collectionMode: 'nft', + pendingSponsor: sponsorCross, + adminList: [userCross], + }, + '', + ); + + const collectionSub = helper.nft.getCollectionObject(collectionId); + const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner); + // Set collection sponsor: + let collectionData = (await collectionSub.getData())!; + expect(collectionData.raw.sponsorship.Unconfirmed).to.be.eq(helper.address.ethToSubstrate(sponsor, true)); + await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmSponsorshipFail'); + + await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor}); + collectionData = (await collectionSub.getData())!; + expect(collectionData.raw.sponsorship.Confirmed).to.be.eq(helper.address.ethToSubstrate(sponsor, true)); + + const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner)); + const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor)); + + const mintingResult = await collectionEvm.methods.mintWithTokenURI(user, 'Test URI').send({from: user}); + const tokenId = mintingResult.events.Transfer.returnValues.tokenId; + + const event = helper.eth.normalizeEvents(mintingResult.events) + .find(event => event.event === 'Transfer'); + const address = helper.ethAddress.fromCollectionId(collectionId); + + expect(event).to.be.deep.equal({ + address, + event: 'Transfer', + args: { + from: '0x0000000000000000000000000000000000000000', + to: user, + tokenId: '1', + }, + }); + expect(await collectionEvm.methods.tokenURI(tokenId).call({from: user})).to.be.equal('Test URI'); + + const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner)); + expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore); + const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor)); + expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true; + }); + + itEth('Can reassign collection sponsor', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const sponsorEth = await helper.eth.createAccountWithBalance(donor); + const sponsorCrossEth = helper.ethCrossAccount.fromAddr(sponsorEth); + const [sponsorSub] = await helper.arrange.createAccounts([100n], donor); + const sponsorCrossSub = helper.ethCrossAccount.fromKeyringPair(sponsorSub); + + const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleCollection( + owner, + { + ...CREATE_COLLECTION_DATA_DEFAULTS, + name: 'Sponsor collection', + description: '1', + tokenPrefix: '1', + collectionMode: 'nft', + pendingSponsor: sponsorCrossEth, + }, + '', + ); + const collectionSub = helper.nft.getCollectionObject(collectionId); + const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner); + + // Set and confirm sponsor: + await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsorEth}); + + // Can reassign sponsor: + await collectionEvm.methods.setCollectionSponsorCross(sponsorCrossSub).send({from: owner}); + const collectionSponsor = (await collectionSub.getData())?.raw.sponsorship; + expect(collectionSponsor).to.deep.eq({Unconfirmed: sponsorSub.address}); + }); + + [ + 'transfer', + 'transferCross', + 'transferFrom', + 'transferFromCross', + ].map(testCase => + itEth(`[${testCase}] Check that transfer via EVM spend money from sponsor address`, async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const sponsor = await helper.eth.createAccountWithBalance(donor); + const sponsorCross = helper.ethCrossAccount.fromAddr(sponsor); + const user = await helper.eth.createAccountWithBalance(donor); + const userCross = helper.ethCrossAccount.fromAddress(user); + + const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleCollection( + owner, + { + ...CREATE_COLLECTION_DATA_DEFAULTS, + name: 'Sponsor collection', + description: '1', + tokenPrefix: '1', + collectionMode: 'rft', + pendingSponsor: sponsorCross, + adminList: [userCross], + }, + '', + ); + const receiver = await helper.eth.createAccountWithBalance(donor); + const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'rft', owner); + + await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor}); + + const result = await collectionEvm.methods.mintWithTokenURI(user, 'Test URI').send({from: user}); + const tokenId = result.events.Transfer.returnValues.tokenId; + + const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner)); + const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor)); + const userBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user)); + + switch (testCase) { + case 'transfer': + await collectionEvm.methods.transfer(receiver, tokenId).send({from: user}); + break; + case 'transferCross': + await collectionEvm.methods.transferCross(helper.ethCrossAccount.fromAddress(receiver), tokenId).send({from: user}); + break; + case 'transferFrom': + await collectionEvm.methods.transferFrom(user, receiver, tokenId).send({from: user}); + break; + case 'transferFromCross': + await collectionEvm.methods.transferFromCross(helper.ethCrossAccount.fromAddress(user), helper.ethCrossAccount.fromAddress(receiver), tokenId).send({from: user}); + break; + } + + const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner)); + expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore); + const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor)); + expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true; + const userBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user)); + expect(userBalanceAfter).to.be.eq(userBalanceBefore); + })); + }); + + describe('Collection admins', () => { + let donor: IKeyringPair; + + before(async function() { + await usingEthPlaygrounds(async (_helper, privateKey) => { + donor = await privateKey({url: import.meta.url}); + }); + }); + + [ + {mode: 'nft' as const, requiredPallets: []}, + {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]}, + {mode: 'ft' as const, requiredPallets: []}, + ].map(testCase => { + itEth.ifWithPallets(`can add account admin by owner for ${testCase.mode}`, testCase.requiredPallets, async ({helper, privateKey}) => { + // arrange + const owner = await helper.eth.createAccountWithBalance(donor); + const adminSub = await privateKey('//admin2'); + const adminEth = helper.eth.createAccount().toLowerCase(); + + const adminCrossSub = helper.ethCrossAccount.fromKeyringPair(adminSub); + const adminCrossEth = helper.ethCrossAccount.fromAddress(adminEth); + + const {collectionAddress, collectionId} = await helper.eth.createCollection( + owner, + { + ...CREATE_COLLECTION_DATA_DEFAULTS, + name: 'Mint collection', + description: 'a', + tokenPrefix: 'b', + collectionMode: testCase.mode, + adminList: [adminCrossSub, adminCrossEth], + }, + ).send(); + const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner, true); + + // 1. Expect api.rpc.unique.adminlist returns admins: + const adminListRpc = await helper.collection.getAdmins(collectionId); + expect(adminListRpc).to.has.length(2); + expect(adminListRpc).to.be.deep.contain.members([{Substrate: adminSub.address}, {Ethereum: adminEth}]); + + // 2. Expect methods.collectionAdmins == api.rpc.unique.adminlist + let adminListEth = await collectionEvm.methods.collectionAdmins().call(); + adminListEth = adminListEth.map((element: IEthCrossAccountId) => helper.address.convertCrossAccountFromEthCrossAccount(element)); + expect(adminListRpc).to.be.like(adminListEth); + + // 3. check isOwnerOrAdminCross returns true: + expect(await collectionEvm.methods.isOwnerOrAdminCross(adminCrossSub).call()).to.be.true; + expect(await collectionEvm.methods.isOwnerOrAdminCross(adminCrossEth).call()).to.be.true; + }); + }); + + itEth('cross account admin can mint', async ({helper}) => { + // arrange: create collection and accounts + const owner = await helper.eth.createAccountWithBalance(donor); + const adminEth = (await helper.eth.createAccountWithBalance(donor)).toLowerCase(); + const adminCrossEth = helper.ethCrossAccount.fromAddress(adminEth); + const [adminSub] = await helper.arrange.createAccounts([100n], donor); + const adminCrossSub = helper.ethCrossAccount.fromKeyringPair(adminSub); + const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleCollection( + owner, + { + ...CREATE_COLLECTION_DATA_DEFAULTS, + name: 'Mint collection', + description: 'a', + tokenPrefix: 'b', + collectionMode: 'nft', + adminList: [adminCrossSub, adminCrossEth], + }, + 'uri', + ); + const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner, true); + + // admin (sub and eth) can mint token: + await collectionEvm.methods.mint(owner).send({from: adminEth}); + await helper.nft.mintToken(adminSub, {collectionId, owner: {Ethereum: owner}}); + + expect(await helper.collection.getLastTokenId(collectionId)).to.eq(2); + }); + + itEth('cannot add invalid cross account admin', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const [admin] = await helper.arrange.createAccounts([100n, 100n], donor); + + const adminCross = { + eth: helper.address.substrateToEth(admin.address), + sub: admin.addressRaw, + }; + + await expect(helper.eth.createCollection( + owner, + { + ...CREATE_COLLECTION_DATA_DEFAULTS, + name: 'A', + description: 'B', + tokenPrefix: 'C', + collectionMode: 'nft', + adminList: [adminCross], + }, + ).call()).to.be.rejected; + }); + + itEth('Remove [cross] admin by owner', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + + const [adminSub] = await helper.arrange.createAccounts([10n], donor); + const adminEth = (await helper.eth.createAccountWithBalance(donor)).toLowerCase(); + const adminCrossSub = helper.ethCrossAccount.fromKeyringPair(adminSub); + const adminCrossEth = helper.ethCrossAccount.fromAddress(adminEth); + + const {collectionAddress, collectionId} = await helper.eth.createCollection( + owner, + { + ...CREATE_COLLECTION_DATA_DEFAULTS, + name: 'A', + description: 'B', + tokenPrefix: 'C', + collectionMode: 'nft', + adminList: [adminCrossSub, adminCrossEth], + }, + ).send(); + + const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner); + + { + const adminList = await helper.collection.getAdmins(collectionId); + expect(adminList).to.deep.include({Substrate: adminSub.address}); + expect(adminList).to.deep.include({Ethereum: adminEth}); + } + + await collectionEvm.methods.removeCollectionAdminCross(adminCrossSub).send(); + await collectionEvm.methods.removeCollectionAdminCross(adminCrossEth).send(); + const adminList = await helper.collection.getAdmins(collectionId); + expect(adminList.length).to.be.eq(0); + + // Non admin cannot mint: + await expect(helper.nft.mintToken(adminSub, {collectionId, owner: {Substrate: adminSub.address}})).to.be.rejectedWith(/common.PublicMintingNotAllowed/); + await expect(collectionEvm.methods.mint(adminEth).send({from: adminEth})).to.be.rejected; + }); + }); + + describe('Collection limits', () => { + describe('Can set collection limits', () => { + [ + {case: 'nft' as const}, + {case: 'rft' as const, requiredPallets: [Pallets.ReFungible]}, + {case: 'ft' as const}, + ].map(testCase => + itEth.ifWithPallets(`for ${testCase.case}`, testCase.requiredPallets || [], async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + + const limits = { + accountTokenOwnershipLimit: 1000n, + sponsoredDataSize: 1024n, + sponsoredDataRateLimit: 30n, + tokenLimit: 1000000n, + sponsorTransferTimeout: 6n, + sponsorApproveTimeout: 6n, + ownerCanTransfer: 1n, + ownerCanDestroy: 0n, + transfersEnabled: 0n, + }; + + const {collectionId, collectionAddress} = await helper.eth.createCollection( + owner, + { + ...CREATE_COLLECTION_DATA_DEFAULTS, + name: 'Limits', + description: 'absolutely anything', + tokenPrefix: 'FLO', + collectionMode: testCase.case, + limits: [ + {field: CollectionLimitField.AccountTokenOwnership, value: limits.accountTokenOwnershipLimit}, + {field: CollectionLimitField.SponsoredDataSize, value: limits.sponsoredDataSize}, + {field: CollectionLimitField.SponsoredDataRateLimit, value: limits.sponsoredDataRateLimit}, + {field: CollectionLimitField.TokenLimit, value: limits.tokenLimit}, + {field: CollectionLimitField.SponsorTransferTimeout, value: limits.sponsorTransferTimeout}, + {field: CollectionLimitField.SponsorApproveTimeout, value: limits.sponsorApproveTimeout}, + {field: CollectionLimitField.OwnerCanTransfer, value: limits.ownerCanTransfer}, + {field: CollectionLimitField.OwnerCanDestroy, value: limits.ownerCanDestroy}, + {field: CollectionLimitField.TransferEnabled, value: limits.transfersEnabled}, + ], + }, + ).send(); + + const expectedLimits = { + accountTokenOwnershipLimit: 1000, + sponsoredDataSize: 1024, + sponsoredDataRateLimit: {blocks: 30}, + tokenLimit: 1000000, + sponsorTransferTimeout: 6, + sponsorApproveTimeout: 6, + ownerCanTransfer: true, + ownerCanDestroy: false, + transfersEnabled: false, + }; + + const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, testCase.case, owner); + + // Check limits from sub: + const data = (await helper.rft.getData(collectionId))!; + expect(data.raw.limits).to.deep.eq(expectedLimits); + expect(await helper.collection.getEffectiveLimits(collectionId)).to.deep.eq(expectedLimits); + // Check limits from eth: + const limitsEvm = await collectionEvm.methods.collectionLimits().call({from: owner}); + expect(limitsEvm).to.have.length(9); + expect(limitsEvm[0]).to.deep.eq([CollectionLimitField.AccountTokenOwnership.toString(), [true, limits.accountTokenOwnershipLimit.toString()]]); + expect(limitsEvm[1]).to.deep.eq([CollectionLimitField.SponsoredDataSize.toString(), [true, limits.sponsoredDataSize.toString()]]); + expect(limitsEvm[2]).to.deep.eq([CollectionLimitField.SponsoredDataRateLimit.toString(), [true, limits.sponsoredDataRateLimit.toString()]]); + expect(limitsEvm[3]).to.deep.eq([CollectionLimitField.TokenLimit.toString(), [true, limits.tokenLimit.toString()]]); + expect(limitsEvm[4]).to.deep.eq([CollectionLimitField.SponsorTransferTimeout.toString(), [true, limits.sponsorTransferTimeout.toString()]]); + expect(limitsEvm[5]).to.deep.eq([CollectionLimitField.SponsorApproveTimeout.toString(), [true, limits.sponsorApproveTimeout.toString()]]); + expect(limitsEvm[6]).to.deep.eq([CollectionLimitField.OwnerCanTransfer.toString(), [true, limits.ownerCanTransfer.toString()]]); + expect(limitsEvm[7]).to.deep.eq([CollectionLimitField.OwnerCanDestroy.toString(), [true, limits.ownerCanDestroy.toString()]]); + expect(limitsEvm[8]).to.deep.eq([CollectionLimitField.TransferEnabled.toString(), [true, limits.transfersEnabled.toString()]]); + })); + }); + + describe('(!negative test!) Cannot set invalid collection limits', () => { + [ + {case: 'nft' as const}, + {case: 'rft' as const, requiredPallets: [Pallets.ReFungible]}, + {case: 'ft' as const}, + ].map(testCase => + itEth.ifWithPallets(`for ${testCase.case}`, testCase.requiredPallets || [], async ({helper}) => { + const invalidLimits = { + accountTokenOwnershipLimit: BigInt(Number.MAX_SAFE_INTEGER), + transfersEnabled: 3, + }; + + const createCollectionData = { + ...CREATE_COLLECTION_DATA_DEFAULTS, + name: 'Limits', + description: 'absolutely anything', + tokenPrefix: 'ISNI', + collectionMode: testCase.case, + }; + + const owner = await helper.eth.createAccountWithBalance(donor); + await expect(helper.eth.createCollection( + owner, + { + ...createCollectionData, + limits: [{field: 9 as CollectionLimitField, value: 1n}], + }, + ).call()).to.be.rejectedWith('value not convertible into enum "CollectionLimitField"'); + + await expect(helper.eth.createCollection( + owner, + { + ...createCollectionData, + limits: [{field: CollectionLimitField.AccountTokenOwnership, value: invalidLimits.accountTokenOwnershipLimit}], + }, + ).call()).to.be.rejectedWith(`can't convert value to u32 "${invalidLimits.accountTokenOwnershipLimit}"`); + + await expect(helper.eth.createCollection( + owner, + { + ...createCollectionData, + limits: [{field: CollectionLimitField.TransferEnabled, value: 3n}], + }, + ).call()).to.be.rejectedWith(`can't convert value to boolean "${invalidLimits.transfersEnabled}"`); + + await expect(helper.eth.createCollection( + owner, + { + ...createCollectionData, + limits: [{field: CollectionLimitField.SponsoredDataSize, value: -1n}], + }, + ).call()).to.be.rejectedWith('value out-of-bounds'); + })); + }); + }); + + describe('Collection properties', () => { + + [ + {mode: 'nft' as const, methodParams: [{key: 'testKey1', value: Buffer.from('testValue1')}, {key: 'testKey2', value: Buffer.from('testValue2')}], expectedProps: [{key: 'testKey1', value: 'testValue1'}, {key: 'testKey2', value: 'testValue2'}]}, + {mode: 'rft' as const, methodParams: [{key: 'testKey1', value: Buffer.from('testValue1')}, {key: 'testKey2', value: Buffer.from('testValue2')}], expectedProps: [{key: 'testKey1', value: 'testValue1'}, {key: 'testKey2', value: 'testValue2'}]}, + {mode: 'ft' as const, methodParams: [{key: 'testKey1', value: Buffer.from('testValue1')}, {key: 'testKey2', value: Buffer.from('testValue2')}], expectedProps: [{key: 'testKey1', value: 'testValue1'}, {key: 'testKey2', value: 'testValue2'}]}, + ].map(testCase => + itEth.ifWithPallets(`Collection properties can be set for ${testCase.mode}`, testCase.mode === 'rft' ? [Pallets.ReFungible] : [], async({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); + const callerCross = helper.ethCrossAccount.fromAddress(caller); + const owner = await helper.eth.createAccountWithBalance(donor); + const {collectionId, collectionAddress} = await helper.eth.createCollection( + owner, + { + ...CREATE_COLLECTION_DATA_DEFAULTS, + name: 'name', + description: 'test', + tokenPrefix: 'test', + collectionMode: testCase.mode, + adminList: [callerCross], + properties: testCase.methodParams, + }, + ).send(); + + const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, caller); + + const raw = (await helper[testCase.mode].getData(collectionId))?.raw; + expect(raw.properties).to.deep.equal(testCase.expectedProps); + + // collectionProperties returns properties: + expect(await collectionEvm.methods.collectionProperties([]).call()).to.be.like(testCase.expectedProps.map(prop => helper.ethProperty.property(prop.key, prop.value))); + })); + + [ + {mode: 'nft' as const}, + {mode: 'rft' as const}, + {mode: 'ft' as const}, + ].map(testCase => + itEth.ifWithPallets(`Collection properties can be deleted for ${testCase.mode}`, testCase.mode === 'rft' ? [Pallets.ReFungible] : [], async({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); + const callerCross = helper.ethCrossAccount.fromAddress(caller); + const owner = await helper.eth.createAccountWithBalance(donor); + const {collectionId, collectionAddress} = await helper.eth.createCollection( + owner, + { + ...CREATE_COLLECTION_DATA_DEFAULTS, + name: 'name', + description: 'test', + tokenPrefix: 'test', + collectionMode: testCase.mode, + adminList: [callerCross], + properties:[ + {key: 'testKey1', value: Buffer.from('testValue1')}, + {key: 'testKey2', value: Buffer.from('testValue2')}, + {key: 'testKey3', value: Buffer.from('testValue3')}], + }, + ).send(); + + const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, caller); + + await collectionEvm.methods.deleteCollectionProperties(['testKey1', 'testKey2']).send({from: caller}); + + const raw = (await helper[testCase.mode].getData(collectionId))?.raw; + + expect(raw.properties.length).to.equal(1); + expect(raw.properties).to.deep.equal([{key: 'testKey3', value: 'testValue3'}]); + })); + + itEth('(!negative test!) Cannot set invalid properties', async({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); + const callerCross = helper.ethCrossAccount.fromAddress(caller); + const owner = await helper.eth.createAccountWithBalance(donor); + const createCollectionData = { + ...CREATE_COLLECTION_DATA_DEFAULTS, + name: 'name', + description: 'test', + tokenPrefix: 'test', + collectionMode: 'nft' as TCollectionMode, + adminList: [callerCross], + }; + await expect(helper.eth.createCollection( + owner, + { + ...createCollectionData, + properties: [{key: '', value: Buffer.from('val1')}], + }, + ).call()).to.be.rejected; + + await expect(helper.eth.createCollection( + owner, + { + ...createCollectionData, + properties: [{key: 'déjà vu', value: Buffer.from('hmm...')}], + }, + ).call()).to.be.rejected; + + await expect(helper.eth.createCollection( + owner, + { + ...createCollectionData, + properties: [{key: 'a'.repeat(257), value: Buffer.from('val3')}], + }, + ).call()).to.be.rejected; + }); + + itEth('(!negative test!) cannot delete properties of non-owned collections', async ({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); + const owner = await helper.eth.createAccountWithBalance(donor); + const {collectionAddress} = await helper.eth.createCollection( + owner, + { + ...CREATE_COLLECTION_DATA_DEFAULTS, + name: 'name', + description: 'test', + tokenPrefix: 'test', + collectionMode: 'nft', + properties:[ + {key: 'testKey1', value: Buffer.from('testValue1')}, + {key: 'testKey2', value: Buffer.from('testValue2')}], + }, + ).send(); + + const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', caller); + + await expect(collectionEvm.methods.deleteCollectionProperties(['testKey2']).send({from: caller})).to.be.rejected; + }); + }); + + describe('Token property permissions', () => { + [ + {mode: 'nft' as const, requiredPallets: []}, + {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]}, + ].map(testCase => + itEth.ifWithPallets(`[${testCase.mode}] Can set all possible token property permissions`, testCase.requiredPallets, async({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const caller = await helper.ethCrossAccount.createAccountWithBalance(donor); + for(const [mutable,collectionAdmin, tokenOwner] of cartesian([], [false, true], [false, true], [false, true])) { + const {collectionId, collectionAddress} = await helper.eth.createCollection( + owner, + { + ...CREATE_COLLECTION_DATA_DEFAULTS, + name: 'A', + description: 'B', + tokenPrefix: 'C', + collectionMode: testCase.mode, + adminList: [caller], + tokenPropertyPermissions: [ + { + key: 'testKey', + permissions: [ + {code: TokenPermissionField.Mutable, value: mutable}, + {code: TokenPermissionField.TokenOwner, value: tokenOwner}, + {code: TokenPermissionField.CollectionAdmin, value: collectionAdmin}, + ], + }, + ], + }, + ).send(); + const collection = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner); + + expect(await helper[testCase.mode].getPropertyPermissions(collectionId)).to.be.deep.equal([{ + key: 'testKey', + permission: {mutable, collectionAdmin, tokenOwner}, + }]); + + expect(await collection.methods.tokenPropertyPermissions().call({from: caller.eth})).to.be.like([ + ['testKey', [ + [TokenPermissionField.Mutable.toString(), mutable], + [TokenPermissionField.TokenOwner.toString(), tokenOwner], + [TokenPermissionField.CollectionAdmin.toString(), collectionAdmin]], + ], + ]); + } + })); + + [ + {mode: 'nft' as const, requiredPallets: []}, + {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]}, + ].map(testCase => + itEth.ifWithPallets(`[${testCase.mode}] Can set multiple token property permissions`, testCase.requiredPallets, async({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const {collectionId, collectionAddress} = await helper.eth.createCollection( + owner, + { + ...CREATE_COLLECTION_DATA_DEFAULTS, + name: 'A', + description: 'B', + tokenPrefix: 'C', + collectionMode: testCase.mode, + tokenPropertyPermissions: [ + { + key: 'testKey_0', + permissions: [ + {code: TokenPermissionField.Mutable, value: true}, + {code: TokenPermissionField.TokenOwner, value: true}, + {code: TokenPermissionField.CollectionAdmin, value: true}], + }, + { + key: 'testKey_1', + permissions: [ + {code: TokenPermissionField.Mutable, value: true}, + {code: TokenPermissionField.TokenOwner, value: false}, + {code: TokenPermissionField.CollectionAdmin, value: true}], + }, + { + key: 'testKey_2', + permissions: [ + {code: TokenPermissionField.Mutable, value: false}, + {code: TokenPermissionField.TokenOwner, value: true}, + {code: TokenPermissionField.CollectionAdmin, value: false}], + }, + ], + }, + ).send(); + const collection = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner); + + expect(await helper[testCase.mode].getPropertyPermissions(collectionId)).to.be.deep.equal([ + { + key: 'testKey_0', + permission: {mutable: true, tokenOwner: true, collectionAdmin: true}, + }, + { + key: 'testKey_1', + permission: {mutable: true, tokenOwner: false, collectionAdmin: true}, + }, + { + key: 'testKey_2', + permission: {mutable: false, tokenOwner: true, collectionAdmin: false}, + }, + ]); + + expect(await collection.methods.tokenPropertyPermissions().call({from: owner})).to.be.like([ + ['testKey_0', [ + [TokenPermissionField.Mutable.toString(), true], + [TokenPermissionField.TokenOwner.toString(), true], + [TokenPermissionField.CollectionAdmin.toString(), true]], + ], + ['testKey_1', [ + [TokenPermissionField.Mutable.toString(), true], + [TokenPermissionField.TokenOwner.toString(), false], + [TokenPermissionField.CollectionAdmin.toString(), true]], + ], + ['testKey_2', [ + [TokenPermissionField.Mutable.toString(), false], + [TokenPermissionField.TokenOwner.toString(), true], + [TokenPermissionField.CollectionAdmin.toString(), false]], + ], + ]); + })); + + [ + {mode: 'nft' as const, requiredPallets: []}, + {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]}, + ].map(testCase => + itEth.ifWithPallets(`Can be deleted for ${testCase.mode}`, testCase.requiredPallets, async({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); + const receiver = await helper.ethCrossAccount.createAccountWithBalance(donor); + const {collectionAddress} = await helper.eth.createCollection( + caller, + { + ...CREATE_COLLECTION_DATA_DEFAULTS, + name: 'A', + description: 'B', + tokenPrefix: 'C', + collectionMode: testCase.mode, + adminList: [receiver], + tokenPropertyPermissions: [ + { + key: 'testKey', + permissions: [ + {code: TokenPermissionField.Mutable, value: true}, + {code: TokenPermissionField.CollectionAdmin, value: true}], + }, + { + key: 'testKey_1', + permissions: [ + {code: TokenPermissionField.Mutable, value: true}, + {code: TokenPermissionField.CollectionAdmin, value: true}], + }, + ], + }, + ).send(); + + const collection = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, caller); + const tokenId = (await collection.methods.mintCross(receiver, [{key: 'testKey', value: Buffer.from('testValue')}, {key: 'testKey_1', value: Buffer.from('testValue_1')}]).send()).events.Transfer.returnValues.tokenId; + expect(await collection.methods.properties(tokenId, ['testKey', 'testKey_1']).call()).to.has.length(2); + + await collection.methods.deleteProperties(tokenId, ['testKey', 'testKey_1']).send({from: caller}); + expect(await collection.methods.properties(tokenId, ['testKey', 'testKey_1']).call()).to.has.length(0); + })); + }); + + describe('Nesting', () => { + itEth('NFT: allows an Owner to nest/unnest their token', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const {collectionAddress, collectionId} = await helper.eth.createCollection( + owner, + { + ...CREATE_COLLECTION_DATA_DEFAULTS, + name: 'A', + description: 'B', + tokenPrefix: 'C', + collectionMode: 'nft', + nestingSettings: {token_owner: true, collection_admin: false, restricted: []}, + }, + ).send(); + + const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner); + + // Create a token to be nested to + const mintingTargetNFTTokenIdResult = await contract.methods.mint(owner).send({from: owner}); + const targetNFTTokenId = mintingTargetNFTTokenIdResult.events.Transfer.returnValues.tokenId; + const targetNftTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetNFTTokenId); + + // Create a nested token + const mintingFirstTokenIdResult = await contract.methods.mint(targetNftTokenAddress).send({from: owner}); + const firstTokenId = mintingFirstTokenIdResult.events.Transfer.returnValues.tokenId; + expect(await contract.methods.ownerOf(firstTokenId).call()).to.be.equal(targetNftTokenAddress); + + // Create a token to be nested and nest + const mintingSecondTokenIdResult = await contract.methods.mint(owner).send({from: owner}); + const secondTokenId = mintingSecondTokenIdResult.events.Transfer.returnValues.tokenId; + + await contract.methods.transfer(targetNftTokenAddress, secondTokenId).send({from: owner}); + expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(targetNftTokenAddress); + + // Unnest token back + await contract.methods.transferFrom(targetNftTokenAddress, owner, secondTokenId).send({from: owner}); + expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(owner); + }); + + itEth('NFT: collectionNesting()', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const {collectionAddress: unnestedCollectionAddress} = await helper.eth.createCollection( + owner, + new CreateCollectionData('A', 'B', 'C', 'nft'), + ).send(); + + const unnestedContract = await helper.ethNativeContract.collection(unnestedCollectionAddress, 'nft', owner); + expect(await unnestedContract.methods.collectionNesting().call({from: owner})).to.be.like([false, false, []]); + + const {collectionAddress} = await helper.eth.createCollection( + owner, + { + ...CREATE_COLLECTION_DATA_DEFAULTS, + name: 'A', + description: 'B', + tokenPrefix: 'C', + collectionMode: 'nft', + nestingSettings: {token_owner: true, collection_admin: false, restricted: [unnestedCollectionAddress.toString()]}, + }, + ).send(); + + const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner); + expect(await contract.methods.collectionNesting().call({from: owner})).to.be.like([true, false, [unnestedCollectionAddress.toString()]]); + await contract.methods.setCollectionNesting([false, false, []]).send({from: owner}); + expect(await contract.methods.collectionNesting().call({from: owner})).to.be.like([false, false, []]); + }); + + itEth('NFT: disallows to nest token if nesting is disabled', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + + const {collectionId, collectionAddress} = await helper.eth.createCollection( + owner, + { + ...CREATE_COLLECTION_DATA_DEFAULTS, + name: 'A', + description: 'B', + tokenPrefix: 'C', + collectionMode: 'nft', + nestingSettings: {token_owner: false, collection_admin: false, restricted: []}, + }, + ).send(); + + const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner); + + // Create a token to nest into + const mintingTargetTokenIdResult = await contract.methods.mint(owner).send({from: owner}); + const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId; + const targetNftTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetTokenId); + + // Create a token to nest + const mintingNftTokenIdResult = await contract.methods.mint(owner).send({from: owner}); + const nftTokenId = mintingNftTokenIdResult.events.Transfer.returnValues.tokenId; + + // Try to nest + await expect(contract.methods + .transfer(targetNftTokenAddress, nftTokenId) + .call({from: owner})).to.be.rejectedWith('UserIsNotAllowedToNest'); + }); + }); + + describe('Flags', () => { + const createCollectionData = { + ...CREATE_COLLECTION_DATA_DEFAULTS, + name: 'A', + description: 'B', + tokenPrefix: 'C', + collectionMode: 'nft' as TCollectionMode, + }; + + itEth('NFT: use numbers for flags', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + + { + const {collectionId} = await helper.eth.createCollection(owner, {...createCollectionData, flags: 0}).send(); + expect((await helper.nft.getData(collectionId))?.raw.flags).to.be.deep.equal({foreign: false, erc721metadata: false}); + } + + { + const {collectionId} = await helper.eth.createCollection(owner, {...createCollectionData, flags: 64}).send(); + expect((await helper.nft.getData(collectionId))?.raw.flags).to.be.deep.equal({foreign: false, erc721metadata: true}); + } + }); + + itEth('NFT: foreign flag number is ignored', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + + { + const {collectionId} = await helper.eth.createCollection(owner, {...createCollectionData, flags: 128}).send(); + expect((await helper.nft.getData(collectionId))?.raw.flags).to.be.deep.equal({foreign: false, erc721metadata: false}); + } + + { + const {collectionId} = await helper.eth.createCollection(owner, {...createCollectionData, flags: 192}).send(); + expect((await helper.nft.getData(collectionId))?.raw.flags).to.be.deep.equal({foreign: false, erc721metadata: true}); + } + }); + + itEth('NFT: use enum for flags', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + + { + const {collectionId} = await helper.eth.createCollection(owner, {...createCollectionData, flags: [CollectionFlag.Erc721metadata]}).send(); + expect((await helper.nft.getData(collectionId))?.raw.flags).to.be.deep.equal({foreign: false, erc721metadata: true}); + } + }); + + itEth('NFT: foreign flag enum is ignored', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + + { + const {collectionId} = await helper.eth.createCollection(owner, {...createCollectionData, flags: [CollectionFlag.Foreign]}).send(); + expect((await helper.nft.getData(collectionId))?.raw.flags).to.be.deep.equal({foreign: false, erc721metadata: false}); + } + + { + const {collectionId} = await helper.eth.createCollection(owner, {...createCollectionData, flags: [CollectionFlag.Erc721metadata | CollectionFlag.Foreign]}).send(); + expect((await helper.nft.getData(collectionId))?.raw.flags).to.be.deep.equal({foreign: false, erc721metadata: true}); + } + }); + }); +}); --- a/tests/src/eth/destroyCollection.test.ts +++ b/tests/src/eth/destroyCollection.test.ts @@ -17,13 +17,15 @@ import {IKeyringPair} from '@polkadot/types/types'; import {Pallets} from '../util'; import {expect, itEth, usingEthPlaygrounds} from './util'; +import {TCollectionMode} from '../util/playgrounds/types'; +import {CreateCollectionData} from './util/playgrounds/types'; describe('Destroy Collection from EVM', function() { let donor: IKeyringPair; const testCases = [ - {case: 'rft' as const, params: ['Limits', 'absolutely anything', 'OLF'], requiredPallets: [Pallets.ReFungible]}, - {case: 'nft' as const, params: ['Limits', 'absolutely anything', 'OLF'], requiredPallets: [Pallets.NFT]}, - {case: 'ft' as const, params: ['Limits', 'absolutely anything', 'OLF', 18], requiredPallets: [Pallets.Fungible]}, + {case: 'rft' as const, params: ['Limits', 'absolutely anything', 'OLF', 'rft'], requiredPallets: [Pallets.ReFungible]}, + {case: 'nft' as const, params: ['Limits', 'absolutely anything', 'OLF', 'nft'], requiredPallets: [Pallets.NFT]}, + {case: 'ft' as const, params: ['Limits', 'absolutely anything', 'OLF', 'ft', 18], requiredPallets: [Pallets.Fungible]}, ]; before(async function() { @@ -40,8 +42,7 @@ const unexistedCollection = helper.ethAddress.fromCollectionId(1000000); const collectionHelpers = await helper.ethNativeContract.collectionHelpers(signer); - const {collectionAddress} = await helper.eth.createCollection(testCase.case, owner, ...testCase.params as [string, string, string, number?]); - + const {collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData(...testCase.params as [string, string, string, TCollectionMode, number?])).send(); // cannot burn collec await expect(collectionHelpers.methods .destroyCollection(collectionAddress) --- a/tests/src/eth/events.test.ts +++ b/tests/src/eth/events.test.ts @@ -19,7 +19,7 @@ import {EthUniqueHelper, itEth, usingEthPlaygrounds} from './util'; import {IEvent, TCollectionMode} from '../util/playgrounds/types'; import {Pallets, requirePalletsOrSkip} from '../util'; -import {CollectionLimitField, TokenPermissionField, NormalizedEvent} from './util/playgrounds/types'; +import {CollectionLimitField, TokenPermissionField, NormalizedEvent, CreateCollectionData} from './util/playgrounds/types'; let donor: IKeyringPair; @@ -39,7 +39,8 @@ async function testCollectionCreatedAndDestroy(helper: EthUniqueHelper, mode: TCollectionMode) { const owner = await helper.eth.createAccountWithBalance(donor); const {unsubscribe, collectedEvents: subEvents} = await helper.subscribeEvents([{section: 'common', names: ['CollectionCreated', 'CollectionDestroyed']}]); - const {collectionAddress, events: ethEvents} = await helper.eth.createCollection(mode, owner, 'A', 'B', 'C'); + const {collectionAddress, events: ethEvents} = await helper.eth.createCollection(owner, new CreateCollectionData('A', 'B', 'C', mode, 18)).send(); + await helper.wait.newBlocks(1); { expect(ethEvents).to.containSubset([ @@ -72,7 +73,7 @@ async function testCollectionPropertySetAndDeleted(helper: EthUniqueHelper, mode: TCollectionMode) { const owner = await helper.eth.createAccountWithBalance(donor); - const {collectionAddress} = await helper.eth.createCollection(mode, owner, 'A', 'B', 'C'); + const {collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('A', 'B', 'C', mode, 18)).send(); const collection = await helper.ethNativeContract.collection(collectionAddress, mode, owner); const collectionHelper = await helper.ethNativeContract.collectionHelpers(owner); @@ -113,7 +114,7 @@ async function testPropertyPermissionSet(helper: EthUniqueHelper, mode: TCollectionMode) { const owner = await helper.eth.createAccountWithBalance(donor); - const {collectionAddress} = await helper.eth.createCollection(mode, owner, 'A', 'B', 'C'); + const {collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('A', 'B', 'C', mode, 18)).send(); const collection = await helper.ethNativeContract.collection(collectionAddress, mode, owner); const collectionHelper = await helper.ethNativeContract.collectionHelpers(owner); const ethEvents: any = []; @@ -144,7 +145,7 @@ async function testAllowListAddressAddedAndRemoved(helper: EthUniqueHelper, mode: TCollectionMode) { const owner = await helper.eth.createAccountWithBalance(donor); const user = helper.ethCrossAccount.createAccount(); - const {collectionAddress} = await helper.eth.createCollection(mode, owner, 'A', 'B', 'C'); + const {collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('A', 'B', 'C', mode, 18)).send(); const collection = await helper.ethNativeContract.collection(collectionAddress, mode, owner); const collectionHelper = await helper.ethNativeContract.collectionHelpers(owner); const ethEvents: any[] = []; @@ -186,7 +187,7 @@ async function testCollectionAdminAddedAndRemoved(helper: EthUniqueHelper, mode: TCollectionMode) { const owner = await helper.eth.createAccountWithBalance(donor); const user = helper.ethCrossAccount.createAccount(); - const {collectionAddress} = await helper.eth.createCollection(mode, owner, 'A', 'B', 'C'); + const {collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('A', 'B', 'C', mode, 18)).send(); const collection = await helper.ethNativeContract.collection(collectionAddress, mode, owner); const collectionHelper = await helper.ethNativeContract.collectionHelpers(owner); const ethEvents: any = []; @@ -226,7 +227,7 @@ async function testCollectionLimitSet(helper: EthUniqueHelper, mode: TCollectionMode) { const owner = await helper.eth.createAccountWithBalance(donor); - const {collectionAddress} = await helper.eth.createCollection(mode, owner, 'A', 'B', 'C'); + const {collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('A', 'B', 'C', mode, 18)).send(); const collection = await helper.ethNativeContract.collection(collectionAddress, mode, owner); const collectionHelper = await helper.ethNativeContract.collectionHelpers(owner); const ethEvents: any = []; @@ -253,7 +254,7 @@ async function testCollectionOwnerChanged(helper: EthUniqueHelper, mode: TCollectionMode) { const owner = await helper.eth.createAccountWithBalance(donor); const newOwner = helper.ethCrossAccount.createAccount(); - const {collectionAddress} = await helper.eth.createCollection(mode, owner, 'A', 'B', 'C'); + const {collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('A', 'B', 'C', mode, 18)).send(); const collection = await helper.ethNativeContract.collection(collectionAddress, mode, owner); const collectionHelper = await helper.ethNativeContract.collectionHelpers(owner); const ethEvents: any = []; @@ -279,7 +280,7 @@ async function testCollectionPermissionSet(helper: EthUniqueHelper, mode: TCollectionMode) { const owner = await helper.eth.createAccountWithBalance(donor); - const {collectionAddress} = await helper.eth.createCollection(mode, owner, 'A', 'B', 'C'); + const {collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('A', 'B', 'C', mode, 18)).send(); const collection = await helper.ethNativeContract.collection(collectionAddress, mode, owner); const collectionHelper = await helper.ethNativeContract.collectionHelpers(owner); const ethEvents: any = []; @@ -320,7 +321,7 @@ async function testCollectionSponsorSetAndConfirmedAndThenRemoved(helper: EthUniqueHelper, mode: TCollectionMode) { const owner = await helper.eth.createAccountWithBalance(donor); const sponsor = await helper.ethCrossAccount.createAccountWithBalance(donor); - const {collectionAddress} = await helper.eth.createCollection(mode, owner, 'A', 'B', 'C'); + const {collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('A', 'B', 'C', mode, 18)).send(); const collection = await helper.ethNativeContract.collection(collectionAddress, mode, owner); const collectionHelper = await helper.ethNativeContract.collectionHelpers(owner); const ethEvents: any = []; @@ -374,7 +375,7 @@ async function testTokenPropertySetAndDeleted(helper: EthUniqueHelper, mode: TCollectionMode) { const owner = await helper.eth.createAccountWithBalance(donor); - const {collectionAddress} = await helper.eth.createCollection(mode, owner, 'A', 'B', 'C'); + const {collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('A', 'B', 'C', mode, 18)).send(); const collection = await helper.ethNativeContract.collection(collectionAddress, mode, owner); const result = await collection.methods.mint(owner).send({from: owner}); const tokenId = result.events.Transfer.returnValues.tokenId; --- a/tests/src/eth/marketplace-v2/Market.sol +++ b/tests/src/eth/marketplace-v2/Market.sol @@ -7,7 +7,7 @@ import "@openzeppelin/contracts/access/Ownable.sol"; import { UniqueNFT, CrossAddress } from "@unique-nft/solidity-interfaces/contracts/UniqueNFT.sol"; import { UniqueFungible, CrossAddress as CrossAddressF } from "@unique-nft/solidity-interfaces/contracts/UniqueFungible.sol"; -import "@unique-nft/solidity-interfaces/contracts/CollectionHelpers.sol"; +import { CollectionHelpers } from "@unique-nft/solidity-interfaces/contracts/CollectionHelpers.sol"; import "./royalty/UniqueRoyaltyHelper.sol"; contract Market is Ownable, ReentrancyGuard { --- a/tests/src/eth/marketplace/marketplace.test.ts +++ b/tests/src/eth/marketplace/marketplace.test.ts @@ -60,7 +60,7 @@ await helpers.methods.setSponsor(matcher.options.address, sponsor).send({from: matcherOwner}); await helpers.methods.confirmSponsorship(matcher.options.address).send({from: sponsor}); - const collection = await helper.nft.mintCollection(alice, {limits: {sponsorApproveTimeout: 1}, pendingSponsor: alice.address}); + const collection = await helper.nft.mintCollection(alice, {limits: {sponsorApproveTimeout: 1}, pendingSponsor: {Substrate: alice.address}}); await collection.confirmSponsorship(alice); await collection.addToAllowList(alice, {Substrate: aliceDoubleMirror}); const evmCollection = await helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft'); @@ -114,7 +114,7 @@ await helpers.methods.setSponsor(matcher.options.address, sponsor).send({from: matcherOwner}); await helpers.methods.confirmSponsorship(matcher.options.address).send({from: sponsor}); - const collection = await helper.nft.mintCollection(alice, {limits: {sponsorApproveTimeout: 1}, pendingSponsor: alice.address}); + const collection = await helper.nft.mintCollection(alice, {limits: {sponsorApproveTimeout: 1}, pendingSponsor: {Substrate: alice.address}}); await collection.confirmSponsorship(alice); await collection.addToAllowList(alice, {Substrate: aliceDoubleMirror}); const evmCollection = await helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft'); --- a/tests/src/eth/nesting/nest.test.ts +++ b/tests/src/eth/nesting/nest.test.ts @@ -6,11 +6,12 @@ const createNestingCollection = async ( helper: EthUniqueHelper, owner: string, + mergeDeprecated = false, ): Promise<{ collectionId: number, collectionAddress: string, contract: Contract }> => { const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C'); - const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner); - await contract.methods.setCollectionNesting(true).send({from: owner}); + const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner, mergeDeprecated); + await contract.methods.setCollectionNesting([true, false, []]).send({from: owner}); return {collectionId, collectionAddress, contract}; }; @@ -52,27 +53,44 @@ expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(owner); }); + itEth('NFT: collectionNesting()', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const {collectionAddress: unnestedCollectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C'); + const unnestedContract = await helper.ethNativeContract.collection(unnestedCollectionAddress, 'nft', owner); + expect(await unnestedContract.methods.collectionNesting().call({from: owner})).to.be.like([false, false, []]); + + const {contract} = await createNestingCollection(helper, owner); + expect(await contract.methods.collectionNesting().call({from: owner})).to.be.like([true, false, []]); + await contract.methods.setCollectionNesting([true, false, [unnestedCollectionAddress]]).send({from: owner}); + expect(await contract.methods.collectionNesting().call({from: owner})).to.be.like([true, false, [unnestedCollectionAddress]]); + await contract.methods.setCollectionNesting([false, true, [unnestedCollectionAddress]]).send({from: owner}); + expect(await contract.methods.collectionNesting().call({from: owner})).to.be.like([false, true, [unnestedCollectionAddress]]); + await contract.methods.setCollectionNesting([false, false, []]).send({from: owner}); + expect(await contract.methods.collectionNesting().call({from: owner})).to.be.like([false, false, []]); + }); + + // Sof-deprecated itEth('NFT: collectionNestingRestrictedCollectionIds() & collectionNestingPermissions', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); const {collectionId: unnestedCollsectionId, collectionAddress: unnsetedCollectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C'); - const unnestedContract = await helper.ethNativeContract.collection(unnsetedCollectionAddress, 'nft', owner); + const unnestedContract = await helper.ethNativeContract.collection(unnsetedCollectionAddress, 'nft', owner, true); expect(await unnestedContract.methods.collectionNestingRestrictedCollectionIds().call({from: owner})).to.be.like([false, []]); - const {contract} = await createNestingCollection(helper, owner); + const {contract} = await createNestingCollection(helper, owner, true); expect(await contract.methods.collectionNestingRestrictedCollectionIds().call({from: owner})).to.be.like([true, []]); - await contract.methods.setCollectionNesting(true, [unnsetedCollectionAddress]).send({from: owner}); + await contract.methods['setCollectionNesting(bool,address[])'](true, [unnsetedCollectionAddress]).send({from: owner}); expect(await contract.methods.collectionNestingRestrictedCollectionIds().call({from: owner})).to.be.like([true, [unnestedCollsectionId.toString()]]); expect(await contract.methods.collectionNestingPermissions().call({from: owner})).to.be.like([['1', false], ['0', true]]); - await contract.methods.setCollectionNesting(false).send({from: owner}); + await contract.methods['setCollectionNesting(bool)'](false).send({from: owner}); expect(await contract.methods.collectionNestingPermissions().call({from: owner})).to.be.like([['1', false], ['0', false]]); }); itEth('NFT: allows an Owner to nest/unnest their token (Restricted nesting)', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); - const {collectionId: collectionIdA, collectionAddress: collectionAddressA, contract: contractA} = await createNestingCollection(helper, owner); - const {collectionAddress: collectionAddressB, contract: contractB} = await createNestingCollection(helper, owner); - await contractA.methods.setCollectionNesting(true, [collectionAddressA, collectionAddressB]).send({from: owner}); + const {collectionId: collectionIdA, contract: contractA} = await createNestingCollection(helper, owner); + const {contract: contractB} = await createNestingCollection(helper, owner); + await contractA.methods.setCollectionNesting([true, false, [contractA.options.address, contractB.options.address]]).send({from: owner}); // Create a token to nest into const mintingtargetNftTokenIdResult = await contractA.methods.mint(owner).send({from: owner}); @@ -101,7 +119,7 @@ const owner = await helper.eth.createAccountWithBalance(donor); const {collectionId, contract} = await createNestingCollection(helper, owner); - await contract.methods.setCollectionNesting(false).send({from: owner}); + await contract.methods.setCollectionNesting([false, false, []]).send({from: owner}); // Create a token to nest into const mintingTargetTokenIdResult = await contract.methods.mint(owner).send({from: owner}); @@ -143,10 +161,10 @@ const owner = await helper.eth.createAccountWithBalance(donor); const malignant = await helper.eth.createAccountWithBalance(donor); - const {collectionId: collectionIdA, collectionAddress: collectionAddressA, contract: contractA} = await createNestingCollection(helper, owner); - const {collectionAddress: collectionAddressB, contract: contractB} = await createNestingCollection(helper, owner); + const {collectionId: collectionIdA, contract: contractA} = await createNestingCollection(helper, owner); + const {collectionId: collectionIdB, contract: contractB} = await createNestingCollection(helper, owner); - await contractA.methods.setCollectionNesting(true, [collectionAddressA, collectionAddressB]).send({from: owner}); + await contractA.methods.setCollectionNesting([true, false, [contractA.options.address, contractB.options.address]]).send({from: owner}); // Create a token in one collection const mintingTokenIdAResult = await contractA.methods.mint(owner).send({from: owner}); @@ -166,10 +184,10 @@ itEth('NFT: disallows to nest token in an unlisted collection', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); - const {collectionId: collectionIdA, collectionAddress: collectionAddressA, contract: contractA} = await createNestingCollection(helper, owner); + const {collectionId: collectionIdA, contract: contractA} = await createNestingCollection(helper, owner); const {contract: contractB} = await createNestingCollection(helper, owner); - await contractA.methods.setCollectionNesting(true, [collectionAddressA]).send({from: owner}); + await contractA.methods.setCollectionNesting([true, false, [contractA.options.address]]).send({from: owner}); // Create a token in one collection const mintingTokenIdAResult = await contractA.methods.mint(owner).send({from: owner}); @@ -251,7 +269,7 @@ itEth(`Disallow nest into collection without nesting permission [${testCase.mode}] (except for native fungible collection)`, async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); const {collectionId: targetCollectionId, contract: targetContract} = await createNestingCollection(helper, owner); - await targetContract.methods.setCollectionNesting(false).send({from: owner}); + await targetContract.methods.setCollectionNesting([false, false, []]).send({from: owner}); const {contract: ftContract} = await createFungibleCollection(helper, owner, testCase.mode); --- a/tests/src/eth/tokenProperties.test.ts +++ b/tests/src/eth/tokenProperties.test.ts @@ -20,7 +20,7 @@ import {ITokenPropertyPermission} from '../util/playgrounds/types'; import {Pallets} from '../util'; import {UniqueNFTCollection, UniqueNFToken, UniqueRFTCollection} from '../util/playgrounds/unique'; -import {TokenPermissionField} from './util/playgrounds/types'; +import {CreateCollectionData, TokenPermissionField} from './util/playgrounds/types'; describe('EVM token properties', () => { let donor: IKeyringPair; @@ -41,7 +41,7 @@ const owner = await helper.eth.createAccountWithBalance(donor); const caller = await helper.ethCrossAccount.createAccountWithBalance(donor); for(const [mutable,collectionAdmin, tokenOwner] of cartesian([], [false, true], [false, true], [false, true])) { - const {collectionId, collectionAddress} = await helper.eth.createCollection(testCase.mode, owner, 'A', 'B', 'C'); + const {collectionId, collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('A', 'B', 'C', testCase.mode)).send(); const collection = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner); await collection.methods.addCollectionAdminCross(caller).send({from: owner}); @@ -75,7 +75,7 @@ itEth.ifWithPallets(`[${testCase.mode}] Can set multiple token property permissions as owner`, testCase.requiredPallets, async({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); - const {collectionId, collectionAddress} = await helper.eth.createCollection(testCase.mode, owner, 'A', 'B', 'C'); + const {collectionId, collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('A', 'B', 'C', testCase.mode)).send(); const collection = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner); await collection.methods.setTokenPropertyPermissions([ @@ -138,7 +138,7 @@ const owner = await helper.eth.createAccountWithBalance(donor); const caller = await helper.ethCrossAccount.createAccountWithBalance(donor); - const {collectionId, collectionAddress} = await helper.eth.createCollection(testCase.mode, owner, 'A', 'B', 'C'); + const {collectionId, collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('A', 'B', 'C', testCase.mode)).send(); const collection = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner); await collection.methods.addCollectionAdminCross(caller).send({from: owner}); @@ -455,7 +455,7 @@ const owner = await helper.eth.createAccountWithBalance(donor); const caller = await helper.eth.createAccountWithBalance(donor); - const {collectionAddress} = await helper.eth.createCollection(testCase.mode, owner, 'A', 'B', 'C'); + const {collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('A', 'B', 'C', testCase.mode)).send(); const collection = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner); await expect(collection.methods.setTokenPropertyPermissions([ @@ -474,7 +474,7 @@ itEth.ifWithPallets(`[${testCase.mode}] Cannot set token property permissions with invalid character`, testCase.requiredPallets, async({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); - const {collectionAddress} = await helper.eth.createCollection(testCase.mode, owner, 'A', 'B', 'C'); + const {collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('A', 'B', 'C', testCase.mode)).send(); const collection = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner); await expect(collection.methods.setTokenPropertyPermissions([ @@ -494,7 +494,7 @@ itEth.ifWithPallets(`[${testCase.mode}] Can reconfigure token property permissions to stricter ones`, testCase.requiredPallets, async({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); - const {collectionAddress, collectionId} = await helper.eth.createCollection(testCase.mode, owner, 'A', 'B', 'C'); + const {collectionAddress, collectionId} = await helper.eth.createCollection(owner, new CreateCollectionData('A', 'B', 'C', testCase.mode)).send(); const collection = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner); // 1. Owner sets strict property-permissions: @@ -530,7 +530,7 @@ itEth.ifWithPallets(`[${testCase.mode}] Cannot reconfigure token property permissions to less strict ones`, testCase.requiredPallets, async({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); - const {collectionAddress} = await helper.eth.createCollection(testCase.mode, owner, 'A', 'B', 'C'); + const {collectionAddress} = await helper.eth.createCollection(owner, new CreateCollectionData('A', 'B', 'C', testCase.mode)).send(); const collection = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner); // 1. Owner sets strict property-permissions: --- a/tests/src/eth/tokens/callMethodsERC20.test.ts +++ b/tests/src/eth/tokens/callMethodsERC20.test.ts @@ -17,6 +17,7 @@ import {Pallets, requirePalletsOrSkip} from '../../util'; import {expect, itEth, usingEthPlaygrounds} from '../util'; import {IKeyringPair} from '@polkadot/types/types'; +import {CreateCollectionData} from '../util/playgrounds/types'; [ {mode: 'ft' as const, requiredPallets: []}, @@ -36,7 +37,7 @@ const caller = await helper.eth.createAccountWithBalance(donor); const mintingParams = testCase.mode === 'ft' ? [caller, 200n] : [caller]; - const {collection, collectionId} = await helper.eth.createCollection(testCase.mode, caller, 'TotalSupply', '6', '6'); + const {collection, collectionId} = await helper.eth.createCollection(caller, new CreateCollectionData('TotalSupply', '6', '6', testCase.mode)).send(); if(testCase.mode === 'rft') await collection.methods.mint(caller).send({from: caller}); // Use collection contract for FT or token contract for RFT: @@ -57,7 +58,7 @@ const caller = await helper.eth.createAccountWithBalance(donor); const mintingParams = testCase.mode === 'ft' ? [caller, 200n] : [caller]; - const {collection, collectionId} = await helper.eth.createCollection(testCase.mode, caller, 'BalanceOf', 'Descroption', 'Prefix'); + const {collection, collectionId} = await helper.eth.createCollection(caller, new CreateCollectionData('BalanceOf', 'Descroption', 'Prefix', testCase.mode)).send(); if(testCase.mode === 'rft') await collection.methods.mint(caller).send({from: caller}); // Use collection contract for FT or token contract for RFT: @@ -76,7 +77,7 @@ itEth('decimals', async ({helper}) => { const caller = await helper.eth.createAccountWithBalance(donor); - const {collection, collectionId} = await helper.eth.createCollection(testCase.mode, caller, 'BalanceOf', 'Descroption', 'Prefix'); + const {collection, collectionId} = await helper.eth.createCollection(caller, new CreateCollectionData('BalanceOf', 'Descroption', 'Prefix', testCase.mode)).send(); if(testCase.mode === 'rft') await collection.methods.mint(caller).send({from: caller}); // Use collection contract for FT or token contract for RFT: --- a/tests/src/eth/tokens/callMethodsERC721.test.ts +++ b/tests/src/eth/tokens/callMethodsERC721.test.ts @@ -17,6 +17,7 @@ import {Pallets} from '../../util'; import {expect, itEth, usingEthPlaygrounds} from '../util'; import {IKeyringPair} from '@polkadot/types/types'; +import {CreateCollectionData} from '../util/playgrounds/types'; describe('ERC-721 call methods', () => { @@ -37,7 +38,7 @@ const [callerSub] = await helper.arrange.createAccounts([100n], donor); const [name, description, tokenPrefix] = ['Name', 'Description', 'Symbol']; - const {collection: collectionEth} = await helper.eth.createCollection(testCase.mode, callerEth, name, description, tokenPrefix); + const {collection: collectionEth} = await helper.eth.createCollection(callerEth, new CreateCollectionData(name, description, tokenPrefix, testCase.mode)).send(); await collectionEth.methods.mint(callerEth).send({from: callerEth}); const {collectionId} = await helper[testCase.mode].mintCollection(callerSub, {name, description, tokenPrefix}); const collectionSub = await helper.ethNativeContract.collectionById(collectionId, testCase.mode, callerEth); @@ -60,7 +61,7 @@ itEth.ifWithPallets(`${testCase.mode.toUpperCase()}: totalSupply`, testCase.requiredPallets, async ({helper}) => { const caller = await helper.eth.createAccountWithBalance(donor); - const {collection} = await helper.eth.createCollection(testCase.mode, caller, 'TotalSupply', '6', '6'); + const {collection} = await helper.eth.createCollection(caller, new CreateCollectionData('TotalSupply', '6', '6', testCase.mode)).send(); await collection.methods.mint(caller).send({from: caller}); const totalSupply = await collection.methods.totalSupply().call(); @@ -75,7 +76,7 @@ itEth.ifWithPallets(`${testCase.mode.toUpperCase()}: balanceOf`, testCase.requiredPallets, async ({helper}) => { const caller = await helper.eth.createAccountWithBalance(donor); - const {collection} = await helper.eth.createCollection(testCase.mode, caller, 'BalanceOf', 'Descroption', 'Prefix'); + const {collection} = await helper.eth.createCollection(caller, new CreateCollectionData('BalanceOf', 'Descroption', 'Prefix', testCase.mode)).send(); await collection.methods.mint(caller).send({from: caller}); await collection.methods.mint(caller).send({from: caller}); await collection.methods.mint(caller).send({from: caller}); @@ -91,7 +92,7 @@ ].map(testCase => { itEth.ifWithPallets(`${testCase.mode.toUpperCase()}: ownerOf`, testCase.requiredPallets, async ({helper}) => { const caller = await helper.eth.createAccountWithBalance(donor); - const {collection} = await helper.eth.createCollection(testCase.mode, caller, 'OwnerOf', '6', '6'); + const {collection} = await helper.eth.createCollection(caller, new CreateCollectionData('OwnerOf', '6', '6', testCase.mode)).send(); const result = await collection.methods.mint(caller).send(); const tokenId = result.events.Transfer.returnValues.tokenId; @@ -108,7 +109,7 @@ itEth.ifWithPallets(`${testCase.mode.toUpperCase()}: ownerOf after burn`, testCase.requiredPallets, async ({helper}) => { const caller = await helper.eth.createAccountWithBalance(donor); const receiver = helper.eth.createAccount(); - const {collection, collectionId} = await helper.eth.createCollection(testCase.mode, caller, 'OwnerOf-AfterBurn', '6', '6'); + const {collection, collectionId} = await helper.eth.createCollection(caller, new CreateCollectionData('OwnerOf-AfterBurn', '6', '6', testCase.mode)).send(); const result = await collection.methods.mint(caller).send(); const tokenId = result.events.Transfer.returnValues.tokenId; --- a/tests/src/eth/tokens/minting.test.ts +++ b/tests/src/eth/tokens/minting.test.ts @@ -17,6 +17,7 @@ import {IKeyringPair} from '@polkadot/types/types'; import {Pallets} from '../../util'; import {expect, itEth, usingEthPlaygrounds} from '../util'; +import {CreateCollectionData} from '../util/playgrounds/types'; describe('Minting tokens', () => { @@ -78,7 +79,7 @@ const receiver = helper.eth.createAccount(); const mintingParams = testCase.mode === 'ft' ? [receiver, 100] : [receiver]; - const {collection, collectionAddress, collectionId} = await helper.eth.createCollection(testCase.mode, owner, 'Name', 'Desc', 'Prefix'); + const {collection, collectionAddress, collectionId} = await helper.eth.createCollection(owner, new CreateCollectionData('Name', 'Desc', 'Prefix', testCase.mode)).send(); const result = await collection.methods.mint(...mintingParams).send({from: owner}); @@ -112,7 +113,7 @@ const receiver = helper.eth.createAccount(); const mintingParams = testCase.mode === 'ft' ? [receiver, 100] : [receiver]; - const {collection, collectionAddress, collectionId} = await helper.eth.createCollection(testCase.mode, owner, 'Name', 'Desc', 'Prefix'); + const {collection, collectionAddress, collectionId} = await helper.eth.createCollection(owner, new CreateCollectionData('Name', 'Desc', 'Prefix', testCase.mode)).send(); const result = await collection.methods.mint(...mintingParams).send({from: owner}); --- a/tests/src/eth/util/playgrounds/types.ts +++ b/tests/src/eth/util/playgrounds/types.ts @@ -1,3 +1,5 @@ +import {CollectionFlag, TCollectionMode} from '../../../util/playgrounds/types'; + export interface ContractImports { solPath: string; fsPath: string; @@ -19,8 +21,10 @@ value: bigint, } +export type EthAddress = string; + export interface CrossAddress { - readonly eth: string, + readonly eth: EthAddress, readonly sub: string | Uint8Array, } @@ -48,3 +52,81 @@ field: CollectionLimitField, value: OptionUint, } + +export interface CollectionLimitValue { + field: CollectionLimitField, + value: bigint, +} + +export enum CollectionMode { + Fungible, + Nonfungible, + Refungible, +} + +export interface PropertyPermission { + code: TokenPermissionField, + value: boolean, +} +export interface TokenPropertyPermission { + key: string, + permissions: PropertyPermission[], +} +export interface CollectionNestingAndPermission { + token_owner: boolean, + collection_admin: boolean, + restricted: string[], +} + +export const emptyAddress: [string, string] = [ + '0x0000000000000000000000000000000000000000', + '0', +]; + +export const CREATE_COLLECTION_DATA_DEFAULTS = { + decimals: 0, + properties: [], + tokenPropertyPermissions: [], + adminList: [], + nestingSettings: {token_owner: false, collection_admin: false, restricted: []}, + limits: [], + pendingSponsor: emptyAddress, + flags: 0, +}; + +export interface Property { + key: string; + value: Buffer; +} + +export class CreateCollectionData { + name: string; + description: string; + tokenPrefix: string; + collectionMode: TCollectionMode; + decimals? = 0; + properties?: Property[] = []; + tokenPropertyPermissions?: TokenPropertyPermission[] = []; + adminList?: CrossAddress[] = []; + nestingSettings?: CollectionNestingAndPermission = {token_owner: false, collection_admin: false, restricted: []}; + limits?: CollectionLimitValue[] = []; + pendingSponsor?: [string, string] = emptyAddress; + flags?: number | CollectionFlag[] = [0]; + + constructor( + name: string, + description: string, + tokenPrefix: string, + collectionMode: TCollectionMode, + decimals = 18, + ) { + this.name = name; + this.description = description; + this.tokenPrefix = tokenPrefix; + this.collectionMode = collectionMode; + if(collectionMode == 'ft') + this.decimals = decimals; + else + this.decimals = 0; + } +} \ No newline at end of file --- a/tests/src/eth/util/playgrounds/unique.dev.ts +++ b/tests/src/eth/util/playgrounds/unique.dev.ts @@ -18,7 +18,7 @@ import {ArrangeGroup, DevUniqueHelper} from '../../../util/playgrounds/unique.dev'; -import {ContractImports, CompiledContract, CrossAddress, NormalizedEvent, EthProperty} from './types'; +import {ContractImports, CompiledContract, CrossAddress, NormalizedEvent, EthProperty, CollectionMode, CreateCollectionData} from './types'; // Native contracts ABI import collectionHelpersAbi from '../../abi/collectionHelpers.json' assert {type: 'json'}; @@ -50,7 +50,6 @@ } } - class ContractGroup extends EthGroupBase { async findImports(imports?: ContractImports[]) { if(!imports) return function(path: string) { @@ -181,7 +180,84 @@ } } +class CreateCollectionTransaction { + signer: string; + data: CreateCollectionData; + mergeDeprecated: boolean; + helper: EthUniqueHelper; + + constructor(helper: EthUniqueHelper, signer: string, data: CreateCollectionData, mergeDeprecated = false) { + this.helper = helper; + this.signer = signer; + + let flags = 0; + // convert CollectionFlags to number and join them in one number + if(!data.flags || typeof data.flags == 'number') { + flags = data.flags ?? 0; + } else { + for(let i = 0; i < data.flags.length; i++){ + const flag = data.flags[i]; + flags = flags | flag; + } + } + data.flags = flags; + + this.data = data; + this.mergeDeprecated = mergeDeprecated; + } + + private async createTransaction() { + const collectionHelper = await this.helper.ethNativeContract.collectionHelpers(this.signer); + let collectionMode; + switch (this.data.collectionMode) { + case 'nft': collectionMode = CollectionMode.Nonfungible; break; + case 'rft': collectionMode = CollectionMode.Refungible; break; + case 'ft': collectionMode = CollectionMode.Fungible; break; + } + + const tx = collectionHelper.methods.createCollection([ + this.data.pendingSponsor, + this.data.name, + this.data.description, + this.data.tokenPrefix, + collectionMode, + this.data.decimals, + this.data.properties, + this.data.tokenPropertyPermissions, + this.data.adminList, + this.data.nestingSettings, + this.data.limits, + this.data.flags, + ]); + return tx; + } + + async send(options?: any): Promise<{ collectionId: number, collectionAddress: string, events: NormalizedEvent[], collection: Contract }> { + const collectionCreationPrice = { + value: Number(this.helper.balance.getCollectionCreationPrice()), + }; + const tx = await this.createTransaction(); + const result = await tx.send({...options, ...collectionCreationPrice}); + const collectionAddress = this.helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId); + const collectionId = this.helper.ethAddress.extractCollectionId(collectionAddress); + const events = this.helper.eth.normalizeEvents(result.events); + const collection = await this.helper.ethNativeContract.collectionById(collectionId, this.data.collectionMode, this.signer, this.mergeDeprecated); + + return {collectionId, collectionAddress, events, collection}; + } + + async call(options?: any) { + const collectionCreationPrice = { + value: Number(this.helper.balance.getCollectionCreationPrice()), + }; + const tx = await this.createTransaction(); + + return await tx.call({...options, ...collectionCreationPrice}); + } +} + + class EthGroup extends EthGroupBase { DEFAULT_GAS = 2_500_000; @@ -236,30 +312,28 @@ } } - async createCollection(mode: TCollectionMode, signer: string, name: string, description: string, tokenPrefix: string, decimals = 18, mergeDeprecated = false): Promise<{ collectionId: number, collectionAddress: string, events: NormalizedEvent[], collection: Contract }> { - const collectionCreationPrice = this.helper.balance.getCollectionCreationPrice(); + createCollection(signer: string, data: CreateCollectionData, mergeDeprecated = false): CreateCollectionTransaction { + return new CreateCollectionTransaction(this.helper, signer, data, mergeDeprecated); + } + + createNFTCollection(signer: string, name: string, description: string, tokenPrefix: string): Promise<{ collectionId: number, collectionAddress: string, events: NormalizedEvent[] }> { + return this.createCollection(signer, new CreateCollectionData(name, description, tokenPrefix, 'nft')).send(); + } + + async createERC721MetadataCompatibleCollection(signer: string, data: CreateCollectionData, baseUri: string): Promise<{ collectionId: number, collectionAddress: string, events: NormalizedEvent[] }> { const collectionHelper = await this.helper.ethNativeContract.collectionHelpers(signer); - const functionName: string = this.createCollectionMethodName(mode); - - const functionParams = mode === 'ft' ? [name, decimals, description, tokenPrefix] : [name, description, tokenPrefix]; - const result = await collectionHelper.methods[functionName](...functionParams).send({value: Number(collectionCreationPrice)}); - const collectionAddress = this.helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId); - const collectionId = this.helper.ethAddress.extractCollectionId(collectionAddress); - const events = this.helper.eth.normalizeEvents(result.events); - const collection = await this.helper.ethNativeContract.collectionById(collectionId, mode, signer, mergeDeprecated); + const {collectionId, collectionAddress, events} = await this.createCollection(signer, data).send(); - return {collectionId, collectionAddress, events, collection}; - } + await collectionHelper.methods.makeCollectionERC721MetadataCompatible(collectionAddress, baseUri).send(); - createNFTCollection(signer: string, name: string, description: string, tokenPrefix: string): Promise<{ collectionId: number, collectionAddress: string, events: NormalizedEvent[] }> { - return this.createCollection('nft', signer, name, description, tokenPrefix); + return {collectionId, collectionAddress, events}; } async createERC721MetadataCompatibleNFTCollection(signer: string, name: string, description: string, tokenPrefix: string, baseUri: string): Promise<{ collectionId: number, collectionAddress: string, events: NormalizedEvent[] }> { const collectionHelper = await this.helper.ethNativeContract.collectionHelpers(signer); - const {collectionId, collectionAddress, events} = await this.createCollection('nft', signer, name, description, tokenPrefix); + const {collectionId, collectionAddress, events} = await this.createCollection(signer, new CreateCollectionData(name, description, tokenPrefix, 'nft')).send(); await collectionHelper.methods.makeCollectionERC721MetadataCompatible(collectionAddress, baseUri).send(); @@ -267,17 +341,17 @@ } createRFTCollection(signer: string, name: string, description: string, tokenPrefix: string): Promise<{ collectionId: number, collectionAddress: string, events: NormalizedEvent[] }> { - return this.createCollection('rft', signer, name, description, tokenPrefix); + return this.createCollection(signer, new CreateCollectionData(name, description, tokenPrefix, 'rft')).send(); } createFungibleCollection(signer: string, name: string, decimals: number, description: string, tokenPrefix: string): Promise<{ collectionId: number, collectionAddress: string, events: NormalizedEvent[] }> { - return this.createCollection('ft', signer, name, description, tokenPrefix, decimals); + return this.createCollection(signer, new CreateCollectionData(name, description, tokenPrefix, 'ft')).send(); } async createERC721MetadataCompatibleRFTCollection(signer: string, name: string, description: string, tokenPrefix: string, baseUri: string): Promise<{ collectionId: number, collectionAddress: string, events: NormalizedEvent[] }> { const collectionHelper = await this.helper.ethNativeContract.collectionHelpers(signer); - const {collectionId, collectionAddress, events} = await this.createCollection('rft', signer, name, description, tokenPrefix); + const {collectionId, collectionAddress, events} = await this.createCollection(signer, new CreateCollectionData(name, description, tokenPrefix, 'rft')).send(); await collectionHelper.methods.makeCollectionERC721MetadataCompatible(collectionAddress, baseUri).send(); @@ -432,6 +506,13 @@ }; } + fromAddr(address: TEthereumAccount): [string, string] { + return [ + address, + '0', + ]; + } + fromKeyringPair(keyring: IKeyringPair): CrossAddress { return { eth: '0x0000000000000000000000000000000000000000', --- a/tests/src/interfaces/augment-api-tx.ts +++ b/tests/src/interfaces/augment-api-tx.ts @@ -1499,7 +1499,7 @@ * * * `data`: Explicit data of a collection used for its creation. **/ - createCollectionEx: AugmentedSubmittable<(data: UpDataStructsCreateCollectionData | { mode?: any; access?: any; name?: any; description?: any; tokenPrefix?: any; pendingSponsor?: any; limits?: any; permissions?: any; tokenPropertyPermissions?: any; properties?: any } | string | Uint8Array) => SubmittableExtrinsic, [UpDataStructsCreateCollectionData]>; + createCollectionEx: AugmentedSubmittable<(data: UpDataStructsCreateCollectionData | { mode?: any; access?: any; name?: any; description?: any; tokenPrefix?: any; limits?: any; permissions?: any; tokenPropertyPermissions?: any; properties?: any; adminList?: any; pendingSponsor?: any; flags?: any } | string | Uint8Array) => SubmittableExtrinsic, [UpDataStructsCreateCollectionData]>; /** * Mint an item within a collection. * --- a/tests/src/interfaces/augment-types.ts +++ b/tests/src/interfaces/augment-types.ts @@ -25,7 +25,7 @@ import type { CollectiveOrigin, MemberCount, ProposalIndex, Votes, VotesTo230 } from '@polkadot/types/interfaces/collective'; import type { AuthorityId, RawVRFOutput } from '@polkadot/types/interfaces/consensus'; import type { AliveContractInfo, CodeHash, CodeSource, CodeUploadRequest, CodeUploadResult, CodeUploadResultValue, ContractCallFlags, ContractCallRequest, ContractExecResult, ContractExecResultOk, ContractExecResultResult, ContractExecResultSuccessTo255, ContractExecResultSuccessTo260, ContractExecResultTo255, ContractExecResultTo260, ContractExecResultTo267, ContractExecResultU64, ContractInfo, ContractInstantiateResult, ContractInstantiateResultTo267, ContractInstantiateResultTo299, ContractInstantiateResultU64, ContractReturnFlags, ContractStorageKey, DeletedContract, ExecReturnValue, Gas, HostFnWeights, HostFnWeightsTo264, InstantiateRequest, InstantiateRequestV1, InstantiateRequestV2, InstantiateReturnValue, InstantiateReturnValueOk, InstantiateReturnValueTo267, InstructionWeights, Limits, LimitsTo264, PrefabWasmModule, RentProjection, Schedule, ScheduleTo212, ScheduleTo258, ScheduleTo264, SeedOf, StorageDeposit, TombstoneContractInfo, TrieId } from '@polkadot/types/interfaces/contracts'; -import type { ContractConstructorSpecLatest, ContractConstructorSpecV0, ContractConstructorSpecV1, ContractConstructorSpecV2, ContractConstructorSpecV3, ContractConstructorSpecV4, ContractContractSpecV0, ContractContractSpecV1, ContractContractSpecV2, ContractContractSpecV3, ContractContractSpecV4, ContractCryptoHasher, ContractDiscriminant, ContractDisplayName, ContractEnvironmentV4, ContractEventParamSpecLatest, ContractEventParamSpecV0, ContractEventParamSpecV2, ContractEventSpecLatest, ContractEventSpecV0, ContractEventSpecV1, ContractEventSpecV2, ContractLayoutArray, ContractLayoutCell, ContractLayoutEnum, ContractLayoutHash, ContractLayoutHashingStrategy, ContractLayoutKey, ContractLayoutStruct, ContractLayoutStructField, ContractMessageParamSpecLatest, ContractMessageParamSpecV0, ContractMessageParamSpecV2, ContractMessageSpecLatest, ContractMessageSpecV0, ContractMessageSpecV1, ContractMessageSpecV2, ContractMessageSpecV3, ContractMetadata, ContractMetadataLatest, ContractMetadataV0, ContractMetadataV1, ContractMetadataV2, ContractMetadataV3, ContractMetadataV4, ContractProject, ContractProjectContract, ContractProjectInfo, ContractProjectSource, ContractProjectV0, ContractSelector, ContractStorageLayout, ContractTypeSpec } from '@polkadot/types/interfaces/contractsAbi'; +import type { ContractConstructorSpecLatest, ContractConstructorSpecV0, ContractConstructorSpecV1, ContractConstructorSpecV2, ContractConstructorSpecV3, ContractContractSpecV0, ContractContractSpecV1, ContractContractSpecV2, ContractContractSpecV3, ContractContractSpecV4, ContractCryptoHasher, ContractDiscriminant, ContractDisplayName, ContractEventParamSpecLatest, ContractEventParamSpecV0, ContractEventParamSpecV2, ContractEventSpecLatest, ContractEventSpecV0, ContractEventSpecV1, ContractEventSpecV2, ContractLayoutArray, ContractLayoutCell, ContractLayoutEnum, ContractLayoutHash, ContractLayoutHashingStrategy, ContractLayoutKey, ContractLayoutStruct, ContractLayoutStructField, ContractMessageParamSpecLatest, ContractMessageParamSpecV0, ContractMessageParamSpecV2, ContractMessageSpecLatest, ContractMessageSpecV0, ContractMessageSpecV1, ContractMessageSpecV2, ContractMetadata, ContractMetadataLatest, ContractMetadataV0, ContractMetadataV1, ContractMetadataV2, ContractMetadataV3, ContractMetadataV4, ContractProject, ContractProjectContract, ContractProjectInfo, ContractProjectSource, ContractProjectV0, ContractSelector, ContractStorageLayout, ContractTypeSpec } from '@polkadot/types/interfaces/contractsAbi'; import type { FundIndex, FundInfo, LastContribution, TrieIndex } from '@polkadot/types/interfaces/crowdloan'; import type { CollationInfo, CollationInfoV1, ConfigData, MessageId, OverweightIndex, PageCounter, PageIndexData } from '@polkadot/types/interfaces/cumulus'; import type { AccountVote, AccountVoteSplit, AccountVoteStandard, Conviction, Delegations, PreimageStatus, PreimageStatusAvailable, PriorLock, PropIndex, Proposal, ProxyState, ReferendumIndex, ReferendumInfo, ReferendumInfoFinished, ReferendumInfoTo239, ReferendumStatus, Tally, Voting, VotingDelegating, VotingDirect, VotingDirectVote } from '@polkadot/types/interfaces/democracy'; @@ -254,7 +254,6 @@ ContractConstructorSpecV1: ContractConstructorSpecV1; ContractConstructorSpecV2: ContractConstructorSpecV2; ContractConstructorSpecV3: ContractConstructorSpecV3; - ContractConstructorSpecV4: ContractConstructorSpecV4; ContractContractSpecV0: ContractContractSpecV0; ContractContractSpecV1: ContractContractSpecV1; ContractContractSpecV2: ContractContractSpecV2; @@ -263,7 +262,6 @@ ContractCryptoHasher: ContractCryptoHasher; ContractDiscriminant: ContractDiscriminant; ContractDisplayName: ContractDisplayName; - ContractEnvironmentV4: ContractEnvironmentV4; ContractEventParamSpecLatest: ContractEventParamSpecLatest; ContractEventParamSpecV0: ContractEventParamSpecV0; ContractEventParamSpecV2: ContractEventParamSpecV2; @@ -300,7 +298,6 @@ ContractMessageSpecV0: ContractMessageSpecV0; ContractMessageSpecV1: ContractMessageSpecV1; ContractMessageSpecV2: ContractMessageSpecV2; - ContractMessageSpecV3: ContractMessageSpecV3; ContractMetadata: ContractMetadata; ContractMetadataLatest: ContractMetadataLatest; ContractMetadataV0: ContractMetadataV0; --- a/tests/src/interfaces/default/types.ts +++ b/tests/src/interfaces/default/types.ts @@ -3221,11 +3221,13 @@ readonly name: Vec; readonly description: Vec; readonly tokenPrefix: Bytes; - readonly pendingSponsor: Option; readonly limits: Option; readonly permissions: Option; readonly tokenPropertyPermissions: Vec; readonly properties: Vec; + readonly adminList: Vec; + readonly pendingSponsor: Option; + readonly flags: U8aFixed; } /** @name UpDataStructsCreateFungibleData */ --- a/tests/src/interfaces/lookup.ts +++ b/tests/src/interfaces/lookup.ts @@ -2904,7 +2904,7 @@ } }, /** - * Lookup335: up_data_structs::CreateCollectionData + * Lookup335: up_data_structs::CreateCollectionData> **/ UpDataStructsCreateCollectionData: { mode: 'UpDataStructsCollectionMode', @@ -2912,11 +2912,13 @@ name: 'Vec', description: 'Vec', tokenPrefix: 'Bytes', - pendingSponsor: 'Option', limits: 'Option', permissions: 'Option', tokenPropertyPermissions: 'Vec', - properties: 'Vec' + properties: 'Vec', + adminList: 'Vec', + pendingSponsor: 'Option', + flags: '[u8;1]' }, /** * Lookup337: up_data_structs::AccessMode @@ -2990,7 +2992,7 @@ value: 'Bytes' }, /** - * Lookup360: up_data_structs::CreateItemData + * Lookup362: up_data_structs::CreateItemData **/ UpDataStructsCreateItemData: { _enum: { @@ -3000,26 +3002,26 @@ } }, /** - * Lookup361: up_data_structs::CreateNftData + * Lookup363: up_data_structs::CreateNftData **/ UpDataStructsCreateNftData: { properties: 'Vec' }, /** - * Lookup362: up_data_structs::CreateFungibleData + * Lookup364: up_data_structs::CreateFungibleData **/ UpDataStructsCreateFungibleData: { value: 'u128' }, /** - * Lookup363: up_data_structs::CreateReFungibleData + * Lookup365: up_data_structs::CreateReFungibleData **/ UpDataStructsCreateReFungibleData: { pieces: 'u128', properties: 'Vec' }, /** - * Lookup366: up_data_structs::CreateItemExData> + * Lookup368: up_data_structs::CreateItemExData> **/ UpDataStructsCreateItemExData: { _enum: { @@ -3030,14 +3032,14 @@ } }, /** - * Lookup368: up_data_structs::CreateNftExData> + * Lookup370: up_data_structs::CreateNftExData> **/ UpDataStructsCreateNftExData: { properties: 'Vec', owner: 'PalletEvmAccountBasicCrossAccountIdRepr' }, /** - * Lookup375: up_data_structs::CreateRefungibleExSingleOwner> + * Lookup377: up_data_structs::CreateRefungibleExSingleOwner> **/ UpDataStructsCreateRefungibleExSingleOwner: { user: 'PalletEvmAccountBasicCrossAccountIdRepr', @@ -3045,14 +3047,14 @@ properties: 'Vec' }, /** - * Lookup377: up_data_structs::CreateRefungibleExMultipleOwners> + * Lookup379: up_data_structs::CreateRefungibleExMultipleOwners> **/ UpDataStructsCreateRefungibleExMultipleOwners: { users: 'BTreeMap', properties: 'Vec' }, /** - * Lookup378: pallet_configuration::pallet::Call + * Lookup380: pallet_configuration::pallet::Call **/ PalletConfigurationCall: { _enum: { @@ -3078,7 +3080,7 @@ } }, /** - * Lookup380: pallet_configuration::AppPromotionConfiguration + * Lookup382: pallet_configuration::AppPromotionConfiguration **/ PalletConfigurationAppPromotionConfiguration: { recalculationInterval: 'Option', @@ -3087,11 +3089,11 @@ maxStakersPerCalculation: 'Option' }, /** - * Lookup384: pallet_structure::pallet::Call + * Lookup386: pallet_structure::pallet::Call **/ PalletStructureCall: 'Null', /** - * Lookup385: pallet_app_promotion::pallet::Call + * Lookup387: pallet_app_promotion::pallet::Call **/ PalletAppPromotionCall: { _enum: { @@ -3126,7 +3128,7 @@ } }, /** - * Lookup386: pallet_foreign_assets::module::Call + * Lookup388: pallet_foreign_assets::module::Call **/ PalletForeignAssetsModuleCall: { _enum: { @@ -3143,7 +3145,7 @@ } }, /** - * Lookup387: pallet_evm::pallet::Call + * Lookup389: pallet_evm::pallet::Call **/ PalletEvmCall: { _enum: { @@ -3186,7 +3188,7 @@ } }, /** - * Lookup393: pallet_ethereum::pallet::Call + * Lookup395: pallet_ethereum::pallet::Call **/ PalletEthereumCall: { _enum: { @@ -3196,7 +3198,7 @@ } }, /** - * Lookup394: ethereum::transaction::TransactionV2 + * Lookup396: ethereum::transaction::TransactionV2 **/ EthereumTransactionTransactionV2: { _enum: { @@ -3206,7 +3208,7 @@ } }, /** - * Lookup395: ethereum::transaction::LegacyTransaction + * Lookup397: ethereum::transaction::LegacyTransaction **/ EthereumTransactionLegacyTransaction: { nonce: 'U256', @@ -3218,7 +3220,7 @@ signature: 'EthereumTransactionTransactionSignature' }, /** - * Lookup396: ethereum::transaction::TransactionAction + * Lookup398: ethereum::transaction::TransactionAction **/ EthereumTransactionTransactionAction: { _enum: { @@ -3227,7 +3229,7 @@ } }, /** - * Lookup397: ethereum::transaction::TransactionSignature + * Lookup399: ethereum::transaction::TransactionSignature **/ EthereumTransactionTransactionSignature: { v: 'u64', @@ -3235,7 +3237,7 @@ s: 'H256' }, /** - * Lookup399: ethereum::transaction::EIP2930Transaction + * Lookup401: ethereum::transaction::EIP2930Transaction **/ EthereumTransactionEip2930Transaction: { chainId: 'u64', @@ -3251,14 +3253,14 @@ s: 'H256' }, /** - * Lookup401: ethereum::transaction::AccessListItem + * Lookup403: ethereum::transaction::AccessListItem **/ EthereumTransactionAccessListItem: { address: 'H160', storageKeys: 'Vec' }, /** - * Lookup402: ethereum::transaction::EIP1559Transaction + * Lookup404: ethereum::transaction::EIP1559Transaction **/ EthereumTransactionEip1559Transaction: { chainId: 'u64', @@ -3275,7 +3277,7 @@ s: 'H256' }, /** - * Lookup403: pallet_evm_contract_helpers::pallet::Call + * Lookup405: pallet_evm_contract_helpers::pallet::Call **/ PalletEvmContractHelpersCall: { _enum: { @@ -3285,7 +3287,7 @@ } }, /** - * Lookup405: pallet_evm_migration::pallet::Call + * Lookup407: pallet_evm_migration::pallet::Call **/ PalletEvmMigrationCall: { _enum: { @@ -3310,7 +3312,7 @@ } }, /** - * Lookup409: pallet_maintenance::pallet::Call + * Lookup411: pallet_maintenance::pallet::Call **/ PalletMaintenanceCall: { _enum: { @@ -3326,7 +3328,7 @@ } }, /** - * Lookup410: pallet_test_utils::pallet::Call + * Lookup412: pallet_test_utils::pallet::Call **/ PalletTestUtilsCall: { _enum: { @@ -3345,32 +3347,32 @@ } }, /** - * Lookup412: pallet_sudo::pallet::Error + * Lookup414: pallet_sudo::pallet::Error **/ PalletSudoError: { _enum: ['RequireSudo'] }, /** - * Lookup414: orml_vesting::module::Error + * Lookup416: orml_vesting::module::Error **/ OrmlVestingModuleError: { _enum: ['ZeroVestingPeriod', 'ZeroVestingPeriodCount', 'InsufficientBalanceToLock', 'TooManyVestingSchedules', 'AmountLow', 'MaxVestingSchedulesExceeded'] }, /** - * Lookup415: orml_xtokens::module::Error + * Lookup417: orml_xtokens::module::Error **/ OrmlXtokensModuleError: { _enum: ['AssetHasNoReserve', 'NotCrossChainTransfer', 'InvalidDest', 'NotCrossChainTransferableCurrency', 'UnweighableMessage', 'XcmExecutionFailed', 'CannotReanchor', 'InvalidAncestry', 'InvalidAsset', 'DestinationNotInvertible', 'BadVersion', 'DistinctReserveForAssetAndFee', 'ZeroFee', 'ZeroAmount', 'TooManyAssetsBeingSent', 'AssetIndexNonExistent', 'FeeNotEnough', 'NotSupportedMultiLocation', 'MinXcmFeeNotDefined'] }, /** - * Lookup418: orml_tokens::BalanceLock + * Lookup420: orml_tokens::BalanceLock **/ OrmlTokensBalanceLock: { id: '[u8;8]', amount: 'u128' }, /** - * Lookup420: orml_tokens::AccountData + * Lookup422: orml_tokens::AccountData **/ OrmlTokensAccountData: { free: 'u128', @@ -3378,20 +3380,20 @@ frozen: 'u128' }, /** - * Lookup422: orml_tokens::ReserveData + * Lookup424: orml_tokens::ReserveData **/ OrmlTokensReserveData: { id: 'Null', amount: 'u128' }, /** - * Lookup424: orml_tokens::module::Error + * Lookup426: orml_tokens::module::Error **/ OrmlTokensModuleError: { _enum: ['BalanceTooLow', 'AmountIntoBalanceFailed', 'LiquidityRestrictions', 'MaxLocksExceeded', 'KeepAlive', 'ExistentialDeposit', 'DeadAccount', 'TooManyReserves'] }, /** - * Lookup429: pallet_identity::types::RegistrarInfo + * Lookup431: pallet_identity::types::RegistrarInfo **/ PalletIdentityRegistrarInfo: { account: 'AccountId32', @@ -3399,13 +3401,13 @@ fields: 'PalletIdentityBitFlags' }, /** - * Lookup431: pallet_identity::pallet::Error + * Lookup433: pallet_identity::pallet::Error **/ PalletIdentityError: { _enum: ['TooManySubAccounts', 'NotFound', 'NotNamed', 'EmptyIndex', 'FeeChanged', 'NoIdentity', 'StickyJudgement', 'JudgementGiven', 'InvalidJudgement', 'InvalidIndex', 'InvalidTarget', 'TooManyFields', 'TooManyRegistrars', 'AlreadyClaimed', 'NotSub', 'NotOwned', 'JudgementForDifferentIdentity', 'JudgementPaymentFailed'] }, /** - * Lookup432: pallet_preimage::RequestStatus + * Lookup434: pallet_preimage::RequestStatus **/ PalletPreimageRequestStatus: { _enum: { @@ -3421,13 +3423,13 @@ } }, /** - * Lookup437: pallet_preimage::pallet::Error + * Lookup439: pallet_preimage::pallet::Error **/ PalletPreimageError: { _enum: ['TooBig', 'AlreadyNoted', 'NotAuthorized', 'NotNoted', 'Requested', 'NotRequested'] }, /** - * Lookup439: cumulus_pallet_xcmp_queue::InboundChannelDetails + * Lookup441: cumulus_pallet_xcmp_queue::InboundChannelDetails **/ CumulusPalletXcmpQueueInboundChannelDetails: { sender: 'u32', @@ -3435,19 +3437,19 @@ messageMetadata: 'Vec<(u32,PolkadotParachainPrimitivesXcmpMessageFormat)>' }, /** - * Lookup440: cumulus_pallet_xcmp_queue::InboundState + * Lookup442: cumulus_pallet_xcmp_queue::InboundState **/ CumulusPalletXcmpQueueInboundState: { _enum: ['Ok', 'Suspended'] }, /** - * Lookup443: polkadot_parachain::primitives::XcmpMessageFormat + * Lookup445: polkadot_parachain::primitives::XcmpMessageFormat **/ PolkadotParachainPrimitivesXcmpMessageFormat: { _enum: ['ConcatenatedVersionedXcm', 'ConcatenatedEncodedBlob', 'Signals'] }, /** - * Lookup446: cumulus_pallet_xcmp_queue::OutboundChannelDetails + * Lookup448: cumulus_pallet_xcmp_queue::OutboundChannelDetails **/ CumulusPalletXcmpQueueOutboundChannelDetails: { recipient: 'u32', @@ -3457,13 +3459,13 @@ lastIndex: 'u16' }, /** - * Lookup447: cumulus_pallet_xcmp_queue::OutboundState + * Lookup449: cumulus_pallet_xcmp_queue::OutboundState **/ CumulusPalletXcmpQueueOutboundState: { _enum: ['Ok', 'Suspended'] }, /** - * Lookup449: cumulus_pallet_xcmp_queue::QueueConfigData + * Lookup451: cumulus_pallet_xcmp_queue::QueueConfigData **/ CumulusPalletXcmpQueueQueueConfigData: { suspendThreshold: 'u32', @@ -3474,13 +3476,13 @@ xcmpMaxIndividualWeight: 'SpWeightsWeightV2Weight' }, /** - * Lookup451: cumulus_pallet_xcmp_queue::pallet::Error + * Lookup453: cumulus_pallet_xcmp_queue::pallet::Error **/ CumulusPalletXcmpQueueError: { _enum: ['FailedToSend', 'BadXcmOrigin', 'BadXcm', 'BadOverweightIndex', 'WeightOverLimit'] }, /** - * Lookup452: pallet_xcm::pallet::QueryStatus + * Lookup454: pallet_xcm::pallet::QueryStatus **/ PalletXcmQueryStatus: { _enum: { @@ -3501,7 +3503,7 @@ } }, /** - * Lookup456: xcm::VersionedResponse + * Lookup458: xcm::VersionedResponse **/ XcmVersionedResponse: { _enum: { @@ -3512,7 +3514,7 @@ } }, /** - * Lookup462: pallet_xcm::pallet::VersionMigrationStage + * Lookup464: pallet_xcm::pallet::VersionMigrationStage **/ PalletXcmVersionMigrationStage: { _enum: { @@ -3523,7 +3525,7 @@ } }, /** - * Lookup465: xcm::VersionedAssetId + * Lookup467: xcm::VersionedAssetId **/ XcmVersionedAssetId: { _enum: { @@ -3534,7 +3536,7 @@ } }, /** - * Lookup466: pallet_xcm::pallet::RemoteLockedFungibleRecord + * Lookup468: pallet_xcm::pallet::RemoteLockedFungibleRecord **/ PalletXcmRemoteLockedFungibleRecord: { amount: 'u128', @@ -3543,23 +3545,23 @@ consumers: 'Vec<(Null,u128)>' }, /** - * Lookup473: pallet_xcm::pallet::Error + * Lookup475: pallet_xcm::pallet::Error **/ PalletXcmError: { _enum: ['Unreachable', 'SendFailure', 'Filtered', 'UnweighableMessage', 'DestinationNotInvertible', 'Empty', 'CannotReanchor', 'TooManyAssets', 'InvalidOrigin', 'BadVersion', 'BadLocation', 'NoSubscription', 'AlreadySubscribed', 'InvalidAsset', 'LowBalance', 'TooManyLocks', 'AccountNotSovereign', 'FeesNotMet', 'LockNotFound', 'InUse'] }, /** - * Lookup474: cumulus_pallet_xcm::pallet::Error + * Lookup476: cumulus_pallet_xcm::pallet::Error **/ CumulusPalletXcmError: 'Null', /** - * Lookup475: cumulus_pallet_dmp_queue::ConfigData + * Lookup477: cumulus_pallet_dmp_queue::ConfigData **/ CumulusPalletDmpQueueConfigData: { maxIndividual: 'SpWeightsWeightV2Weight' }, /** - * Lookup476: cumulus_pallet_dmp_queue::PageIndexData + * Lookup478: cumulus_pallet_dmp_queue::PageIndexData **/ CumulusPalletDmpQueuePageIndexData: { beginUsed: 'u32', @@ -3567,25 +3569,25 @@ overweightCount: 'u64' }, /** - * Lookup479: cumulus_pallet_dmp_queue::pallet::Error + * Lookup481: cumulus_pallet_dmp_queue::pallet::Error **/ CumulusPalletDmpQueueError: { _enum: ['Unknown', 'OverLimit'] }, /** - * Lookup483: pallet_unique::pallet::Error + * Lookup485: pallet_unique::pallet::Error **/ PalletUniqueError: { _enum: ['CollectionDecimalPointLimitExceeded', 'EmptyArgument', 'RepartitionCalledOnNonRefungibleCollection'] }, /** - * Lookup484: pallet_configuration::pallet::Error + * Lookup486: pallet_configuration::pallet::Error **/ PalletConfigurationError: { _enum: ['InconsistentConfiguration'] }, /** - * Lookup485: up_data_structs::Collection + * Lookup487: up_data_structs::Collection **/ UpDataStructsCollection: { owner: 'AccountId32', @@ -3599,7 +3601,7 @@ flags: '[u8;1]' }, /** - * Lookup486: up_data_structs::SponsorshipState + * Lookup488: up_data_structs::SponsorshipState **/ UpDataStructsSponsorshipStateAccountId32: { _enum: { @@ -3609,7 +3611,7 @@ } }, /** - * Lookup487: up_data_structs::Properties + * Lookup489: up_data_structs::Properties **/ UpDataStructsProperties: { map: 'UpDataStructsPropertiesMapBoundedVec', @@ -3617,15 +3619,15 @@ reserved: 'u32' }, /** - * Lookup488: up_data_structs::PropertiesMap> + * Lookup490: up_data_structs::PropertiesMap> **/ UpDataStructsPropertiesMapBoundedVec: 'BTreeMap', /** - * Lookup493: up_data_structs::PropertiesMap + * Lookup495: up_data_structs::PropertiesMap **/ UpDataStructsPropertiesMapPropertyPermission: 'BTreeMap', /** - * Lookup500: up_data_structs::CollectionStats + * Lookup502: up_data_structs::CollectionStats **/ UpDataStructsCollectionStats: { created: 'u32', @@ -3633,18 +3635,18 @@ alive: 'u32' }, /** - * Lookup501: up_data_structs::TokenChild + * Lookup503: up_data_structs::TokenChild **/ UpDataStructsTokenChild: { token: 'u32', collection: 'u32' }, /** - * Lookup502: PhantomType::up_data_structs + * Lookup504: PhantomType::up_data_structs **/ PhantomTypeUpDataStructs: '[(UpDataStructsTokenData,UpDataStructsRpcCollection,UpPovEstimateRpcPovInfo);0]', /** - * Lookup504: up_data_structs::TokenData> + * Lookup506: up_data_structs::TokenData> **/ UpDataStructsTokenData: { properties: 'Vec', @@ -3652,7 +3654,7 @@ pieces: 'u128' }, /** - * Lookup506: up_data_structs::RpcCollection + * Lookup507: up_data_structs::RpcCollection **/ UpDataStructsRpcCollection: { owner: 'AccountId32', @@ -3669,14 +3671,14 @@ flags: 'UpDataStructsRpcCollectionFlags' }, /** - * Lookup507: up_data_structs::RpcCollectionFlags + * Lookup508: up_data_structs::RpcCollectionFlags **/ UpDataStructsRpcCollectionFlags: { foreign: 'bool', erc721metadata: 'bool' }, /** - * Lookup508: up_pov_estimate_rpc::PovInfo + * Lookup509: up_pov_estimate_rpc::PovInfo **/ UpPovEstimateRpcPovInfo: { proofSize: 'u64', @@ -3686,7 +3688,7 @@ keyValues: 'Vec' }, /** - * Lookup511: sp_runtime::transaction_validity::TransactionValidityError + * Lookup512: sp_runtime::transaction_validity::TransactionValidityError **/ SpRuntimeTransactionValidityTransactionValidityError: { _enum: { @@ -3695,7 +3697,7 @@ } }, /** - * Lookup512: sp_runtime::transaction_validity::InvalidTransaction + * Lookup513: sp_runtime::transaction_validity::InvalidTransaction **/ SpRuntimeTransactionValidityInvalidTransaction: { _enum: { @@ -3713,7 +3715,7 @@ } }, /** - * Lookup513: sp_runtime::transaction_validity::UnknownTransaction + * Lookup514: sp_runtime::transaction_validity::UnknownTransaction **/ SpRuntimeTransactionValidityUnknownTransaction: { _enum: { @@ -3723,68 +3725,68 @@ } }, /** - * Lookup515: up_pov_estimate_rpc::TrieKeyValue + * Lookup516: up_pov_estimate_rpc::TrieKeyValue **/ UpPovEstimateRpcTrieKeyValue: { key: 'Bytes', value: 'Bytes' }, /** - * Lookup517: pallet_common::pallet::Error + * Lookup518: pallet_common::pallet::Error **/ PalletCommonError: { _enum: ['CollectionNotFound', 'MustBeTokenOwner', 'NoPermission', 'CantDestroyNotEmptyCollection', 'PublicMintingNotAllowed', 'AddressNotInAllowlist', 'CollectionNameLimitExceeded', 'CollectionDescriptionLimitExceeded', 'CollectionTokenPrefixLimitExceeded', 'TotalCollectionsLimitExceeded', 'CollectionAdminCountExceeded', 'CollectionLimitBoundsExceeded', 'OwnerPermissionsCantBeReverted', 'TransferNotAllowed', 'AccountTokenLimitExceeded', 'CollectionTokenLimitExceeded', 'MetadataFlagFrozen', 'TokenNotFound', 'TokenValueTooLow', 'ApprovedValueTooLow', 'CantApproveMoreThanOwned', 'AddressIsNotEthMirror', 'AddressIsZero', 'UnsupportedOperation', 'NotSufficientFounds', 'UserIsNotAllowedToNest', 'SourceCollectionIsNotAllowedToNest', 'CollectionFieldSizeExceeded', 'NoSpaceForProperty', 'PropertyLimitReached', 'PropertyKeyIsTooLong', 'InvalidCharacterInPropertyKey', 'EmptyPropertyKey', 'CollectionIsExternal', 'CollectionIsInternal', 'ConfirmSponsorshipFail', 'UserIsNotCollectionAdmin'] }, /** - * Lookup519: pallet_fungible::pallet::Error + * Lookup520: pallet_fungible::pallet::Error **/ PalletFungibleError: { _enum: ['NotFungibleDataUsedToMintFungibleCollectionToken', 'FungibleItemsHaveNoId', 'FungibleItemsDontHaveData', 'FungibleDisallowsNesting', 'SettingPropertiesNotAllowed', 'SettingAllowanceForAllNotAllowed', 'FungibleTokensAreAlwaysValid'] }, /** - * Lookup524: pallet_refungible::pallet::Error + * Lookup525: pallet_refungible::pallet::Error **/ PalletRefungibleError: { _enum: ['NotRefungibleDataUsedToMintFungibleCollectionToken', 'WrongRefungiblePieces', 'RepartitionWhileNotOwningAllPieces', 'RefungibleDisallowsNesting', 'SettingPropertiesNotAllowed'] }, /** - * Lookup525: pallet_nonfungible::ItemData> + * Lookup526: pallet_nonfungible::ItemData> **/ PalletNonfungibleItemData: { owner: 'PalletEvmAccountBasicCrossAccountIdRepr' }, /** - * Lookup527: up_data_structs::PropertyScope + * Lookup528: up_data_structs::PropertyScope **/ UpDataStructsPropertyScope: { _enum: ['None', 'Rmrk'] }, /** - * Lookup530: pallet_nonfungible::pallet::Error + * Lookup531: pallet_nonfungible::pallet::Error **/ PalletNonfungibleError: { _enum: ['NotNonfungibleDataUsedToMintFungibleCollectionToken', 'NonfungibleItemsHaveNoAmount', 'CantBurnNftWithChildren'] }, /** - * Lookup531: pallet_structure::pallet::Error + * Lookup532: pallet_structure::pallet::Error **/ PalletStructureError: { _enum: ['OuroborosDetected', 'DepthLimit', 'BreadthLimit', 'TokenNotFound', 'CantNestTokenUnderCollection'] }, /** - * Lookup536: pallet_app_promotion::pallet::Error + * Lookup537: pallet_app_promotion::pallet::Error **/ PalletAppPromotionError: { _enum: ['AdminNotSet', 'NoPermission', 'NotSufficientFunds', 'PendingForBlockOverflow', 'SponsorNotSet', 'InsufficientStakedBalance', 'InconsistencyState'] }, /** - * Lookup537: pallet_foreign_assets::module::Error + * Lookup538: pallet_foreign_assets::module::Error **/ PalletForeignAssetsModuleError: { _enum: ['BadLocation', 'MultiLocationExisted', 'AssetIdNotExists', 'AssetIdExisted'] }, /** - * Lookup538: pallet_evm::CodeMetadata + * Lookup539: pallet_evm::CodeMetadata **/ PalletEvmCodeMetadata: { _alias: { @@ -3795,13 +3797,13 @@ hash_: 'H256' }, /** - * Lookup540: pallet_evm::pallet::Error + * Lookup541: pallet_evm::pallet::Error **/ PalletEvmError: { _enum: ['BalanceLow', 'FeeOverflow', 'PaymentOverflow', 'WithdrawFailed', 'GasPriceTooLow', 'InvalidNonce', 'GasLimitTooLow', 'GasLimitTooHigh', 'Undefined', 'Reentrancy', 'TransactionMustComeFromEOA'] }, /** - * Lookup543: fp_rpc::TransactionStatus + * Lookup544: fp_rpc::TransactionStatus **/ FpRpcTransactionStatus: { transactionHash: 'H256', @@ -3813,11 +3815,11 @@ logsBloom: 'EthbloomBloom' }, /** - * Lookup545: ethbloom::Bloom + * Lookup546: ethbloom::Bloom **/ EthbloomBloom: '[u8;256]', /** - * Lookup547: ethereum::receipt::ReceiptV3 + * Lookup548: ethereum::receipt::ReceiptV3 **/ EthereumReceiptReceiptV3: { _enum: { @@ -3827,7 +3829,7 @@ } }, /** - * Lookup548: ethereum::receipt::EIP658ReceiptData + * Lookup549: ethereum::receipt::EIP658ReceiptData **/ EthereumReceiptEip658ReceiptData: { statusCode: 'u8', @@ -3836,7 +3838,7 @@ logs: 'Vec' }, /** - * Lookup549: ethereum::block::Block + * Lookup550: ethereum::block::Block **/ EthereumBlock: { header: 'EthereumHeader', @@ -3844,7 +3846,7 @@ ommers: 'Vec' }, /** - * Lookup550: ethereum::header::Header + * Lookup551: ethereum::header::Header **/ EthereumHeader: { parentHash: 'H256', @@ -3864,23 +3866,23 @@ nonce: 'EthereumTypesHashH64' }, /** - * Lookup551: ethereum_types::hash::H64 + * Lookup552: ethereum_types::hash::H64 **/ EthereumTypesHashH64: '[u8;8]', /** - * Lookup556: pallet_ethereum::pallet::Error + * Lookup557: pallet_ethereum::pallet::Error **/ PalletEthereumError: { _enum: ['InvalidSignature', 'PreLogExists'] }, /** - * Lookup557: pallet_evm_coder_substrate::pallet::Error + * Lookup558: pallet_evm_coder_substrate::pallet::Error **/ PalletEvmCoderSubstrateError: { _enum: ['OutOfGas', 'OutOfFund'] }, /** - * Lookup558: up_data_structs::SponsorshipState> + * Lookup559: up_data_structs::SponsorshipState> **/ UpDataStructsSponsorshipStateBasicCrossAccountIdRepr: { _enum: { @@ -3890,35 +3892,35 @@ } }, /** - * Lookup559: pallet_evm_contract_helpers::SponsoringModeT + * Lookup560: pallet_evm_contract_helpers::SponsoringModeT **/ PalletEvmContractHelpersSponsoringModeT: { _enum: ['Disabled', 'Allowlisted', 'Generous'] }, /** - * Lookup565: pallet_evm_contract_helpers::pallet::Error + * Lookup566: pallet_evm_contract_helpers::pallet::Error **/ PalletEvmContractHelpersError: { _enum: ['NoPermission', 'NoPendingSponsor', 'TooManyMethodsHaveSponsoredLimit'] }, /** - * Lookup566: pallet_evm_migration::pallet::Error + * Lookup567: pallet_evm_migration::pallet::Error **/ PalletEvmMigrationError: { _enum: ['AccountNotEmpty', 'AccountIsNotMigrating', 'BadEvent'] }, /** - * Lookup567: pallet_maintenance::pallet::Error + * Lookup568: pallet_maintenance::pallet::Error **/ PalletMaintenanceError: 'Null', /** - * Lookup568: pallet_test_utils::pallet::Error + * Lookup569: pallet_test_utils::pallet::Error **/ PalletTestUtilsError: { _enum: ['TestPalletDisabled', 'TriggerRollback'] }, /** - * Lookup570: sp_runtime::MultiSignature + * Lookup571: sp_runtime::MultiSignature **/ SpRuntimeMultiSignature: { _enum: { @@ -3928,55 +3930,55 @@ } }, /** - * Lookup571: sp_core::ed25519::Signature + * Lookup572: sp_core::ed25519::Signature **/ SpCoreEd25519Signature: '[u8;64]', /** - * Lookup573: sp_core::sr25519::Signature + * Lookup574: sp_core::sr25519::Signature **/ SpCoreSr25519Signature: '[u8;64]', /** - * Lookup574: sp_core::ecdsa::Signature + * Lookup575: sp_core::ecdsa::Signature **/ SpCoreEcdsaSignature: '[u8;65]', /** - * Lookup577: frame_system::extensions::check_spec_version::CheckSpecVersion + * Lookup578: frame_system::extensions::check_spec_version::CheckSpecVersion **/ FrameSystemExtensionsCheckSpecVersion: 'Null', /** - * Lookup578: frame_system::extensions::check_tx_version::CheckTxVersion + * Lookup579: frame_system::extensions::check_tx_version::CheckTxVersion **/ FrameSystemExtensionsCheckTxVersion: 'Null', /** - * Lookup579: frame_system::extensions::check_genesis::CheckGenesis + * Lookup580: frame_system::extensions::check_genesis::CheckGenesis **/ FrameSystemExtensionsCheckGenesis: 'Null', /** - * Lookup582: frame_system::extensions::check_nonce::CheckNonce + * Lookup583: frame_system::extensions::check_nonce::CheckNonce **/ FrameSystemExtensionsCheckNonce: 'Compact', /** - * Lookup583: frame_system::extensions::check_weight::CheckWeight + * Lookup584: frame_system::extensions::check_weight::CheckWeight **/ FrameSystemExtensionsCheckWeight: 'Null', /** - * Lookup584: opal_runtime::runtime_common::maintenance::CheckMaintenance + * Lookup585: opal_runtime::runtime_common::maintenance::CheckMaintenance **/ OpalRuntimeRuntimeCommonMaintenanceCheckMaintenance: 'Null', /** - * Lookup585: opal_runtime::runtime_common::identity::DisableIdentityCalls + * Lookup586: opal_runtime::runtime_common::identity::DisableIdentityCalls **/ OpalRuntimeRuntimeCommonIdentityDisableIdentityCalls: 'Null', /** - * Lookup586: pallet_template_transaction_payment::ChargeTransactionPayment + * Lookup587: pallet_template_transaction_payment::ChargeTransactionPayment **/ PalletTemplateTransactionPaymentChargeTransactionPayment: 'Compact', /** - * Lookup587: opal_runtime::Runtime + * Lookup588: opal_runtime::Runtime **/ OpalRuntimeRuntime: 'Null', /** - * Lookup588: pallet_ethereum::FakeTransactionFinalizer + * Lookup589: pallet_ethereum::FakeTransactionFinalizer **/ PalletEthereumFakeTransactionFinalizer: 'Null' }; --- a/tests/src/interfaces/types-lookup.ts +++ b/tests/src/interfaces/types-lookup.ts @@ -3181,11 +3181,13 @@ readonly name: Vec; readonly description: Vec; readonly tokenPrefix: Bytes; - readonly pendingSponsor: Option; readonly limits: Option; readonly permissions: Option; readonly tokenPropertyPermissions: Vec; readonly properties: Vec; + readonly adminList: Vec; + readonly pendingSponsor: Option; + readonly flags: U8aFixed; } /** @name UpDataStructsAccessMode (337) */ @@ -3252,7 +3254,7 @@ readonly value: Bytes; } - /** @name UpDataStructsCreateItemData (360) */ + /** @name UpDataStructsCreateItemData (362) */ interface UpDataStructsCreateItemData extends Enum { readonly isNft: boolean; readonly asNft: UpDataStructsCreateNftData; @@ -3263,23 +3265,23 @@ readonly type: 'Nft' | 'Fungible' | 'ReFungible'; } - /** @name UpDataStructsCreateNftData (361) */ + /** @name UpDataStructsCreateNftData (363) */ interface UpDataStructsCreateNftData extends Struct { readonly properties: Vec; } - /** @name UpDataStructsCreateFungibleData (362) */ + /** @name UpDataStructsCreateFungibleData (364) */ interface UpDataStructsCreateFungibleData extends Struct { readonly value: u128; } - /** @name UpDataStructsCreateReFungibleData (363) */ + /** @name UpDataStructsCreateReFungibleData (365) */ interface UpDataStructsCreateReFungibleData extends Struct { readonly pieces: u128; readonly properties: Vec; } - /** @name UpDataStructsCreateItemExData (366) */ + /** @name UpDataStructsCreateItemExData (368) */ interface UpDataStructsCreateItemExData extends Enum { readonly isNft: boolean; readonly asNft: Vec; @@ -3292,26 +3294,26 @@ readonly type: 'Nft' | 'Fungible' | 'RefungibleMultipleItems' | 'RefungibleMultipleOwners'; } - /** @name UpDataStructsCreateNftExData (368) */ + /** @name UpDataStructsCreateNftExData (370) */ interface UpDataStructsCreateNftExData extends Struct { readonly properties: Vec; readonly owner: PalletEvmAccountBasicCrossAccountIdRepr; } - /** @name UpDataStructsCreateRefungibleExSingleOwner (375) */ + /** @name UpDataStructsCreateRefungibleExSingleOwner (377) */ interface UpDataStructsCreateRefungibleExSingleOwner extends Struct { readonly user: PalletEvmAccountBasicCrossAccountIdRepr; readonly pieces: u128; readonly properties: Vec; } - /** @name UpDataStructsCreateRefungibleExMultipleOwners (377) */ + /** @name UpDataStructsCreateRefungibleExMultipleOwners (379) */ interface UpDataStructsCreateRefungibleExMultipleOwners extends Struct { readonly users: BTreeMap; readonly properties: Vec; } - /** @name PalletConfigurationCall (378) */ + /** @name PalletConfigurationCall (380) */ interface PalletConfigurationCall extends Enum { readonly isSetWeightToFeeCoefficientOverride: boolean; readonly asSetWeightToFeeCoefficientOverride: { @@ -3340,7 +3342,7 @@ readonly type: 'SetWeightToFeeCoefficientOverride' | 'SetMinGasPriceOverride' | 'SetAppPromotionConfigurationOverride' | 'SetCollatorSelectionDesiredCollators' | 'SetCollatorSelectionLicenseBond' | 'SetCollatorSelectionKickThreshold'; } - /** @name PalletConfigurationAppPromotionConfiguration (380) */ + /** @name PalletConfigurationAppPromotionConfiguration (382) */ interface PalletConfigurationAppPromotionConfiguration extends Struct { readonly recalculationInterval: Option; readonly pendingInterval: Option; @@ -3348,10 +3350,10 @@ readonly maxStakersPerCalculation: Option; } - /** @name PalletStructureCall (384) */ + /** @name PalletStructureCall (386) */ type PalletStructureCall = Null; - /** @name PalletAppPromotionCall (385) */ + /** @name PalletAppPromotionCall (387) */ interface PalletAppPromotionCall extends Enum { readonly isSetAdminAddress: boolean; readonly asSetAdminAddress: { @@ -3393,7 +3395,7 @@ readonly type: 'SetAdminAddress' | 'Stake' | 'UnstakeAll' | 'SponsorCollection' | 'StopSponsoringCollection' | 'SponsorContract' | 'StopSponsoringContract' | 'PayoutStakers' | 'UnstakePartial' | 'ForceUnstake'; } - /** @name PalletForeignAssetsModuleCall (386) */ + /** @name PalletForeignAssetsModuleCall (388) */ interface PalletForeignAssetsModuleCall extends Enum { readonly isRegisterForeignAsset: boolean; readonly asRegisterForeignAsset: { @@ -3410,7 +3412,7 @@ readonly type: 'RegisterForeignAsset' | 'UpdateForeignAsset'; } - /** @name PalletEvmCall (387) */ + /** @name PalletEvmCall (389) */ interface PalletEvmCall extends Enum { readonly isWithdraw: boolean; readonly asWithdraw: { @@ -3455,7 +3457,7 @@ readonly type: 'Withdraw' | 'Call' | 'Create' | 'Create2'; } - /** @name PalletEthereumCall (393) */ + /** @name PalletEthereumCall (395) */ interface PalletEthereumCall extends Enum { readonly isTransact: boolean; readonly asTransact: { @@ -3464,7 +3466,7 @@ readonly type: 'Transact'; } - /** @name EthereumTransactionTransactionV2 (394) */ + /** @name EthereumTransactionTransactionV2 (396) */ interface EthereumTransactionTransactionV2 extends Enum { readonly isLegacy: boolean; readonly asLegacy: EthereumTransactionLegacyTransaction; @@ -3475,7 +3477,7 @@ readonly type: 'Legacy' | 'Eip2930' | 'Eip1559'; } - /** @name EthereumTransactionLegacyTransaction (395) */ + /** @name EthereumTransactionLegacyTransaction (397) */ interface EthereumTransactionLegacyTransaction extends Struct { readonly nonce: U256; readonly gasPrice: U256; @@ -3486,7 +3488,7 @@ readonly signature: EthereumTransactionTransactionSignature; } - /** @name EthereumTransactionTransactionAction (396) */ + /** @name EthereumTransactionTransactionAction (398) */ interface EthereumTransactionTransactionAction extends Enum { readonly isCall: boolean; readonly asCall: H160; @@ -3494,14 +3496,14 @@ readonly type: 'Call' | 'Create'; } - /** @name EthereumTransactionTransactionSignature (397) */ + /** @name EthereumTransactionTransactionSignature (399) */ interface EthereumTransactionTransactionSignature extends Struct { readonly v: u64; readonly r: H256; readonly s: H256; } - /** @name EthereumTransactionEip2930Transaction (399) */ + /** @name EthereumTransactionEip2930Transaction (401) */ interface EthereumTransactionEip2930Transaction extends Struct { readonly chainId: u64; readonly nonce: U256; @@ -3516,13 +3518,13 @@ readonly s: H256; } - /** @name EthereumTransactionAccessListItem (401) */ + /** @name EthereumTransactionAccessListItem (403) */ interface EthereumTransactionAccessListItem extends Struct { readonly address: H160; readonly storageKeys: Vec; } - /** @name EthereumTransactionEip1559Transaction (402) */ + /** @name EthereumTransactionEip1559Transaction (404) */ interface EthereumTransactionEip1559Transaction extends Struct { readonly chainId: u64; readonly nonce: U256; @@ -3538,7 +3540,7 @@ readonly s: H256; } - /** @name PalletEvmContractHelpersCall (403) */ + /** @name PalletEvmContractHelpersCall (405) */ interface PalletEvmContractHelpersCall extends Enum { readonly isMigrateFromSelfSponsoring: boolean; readonly asMigrateFromSelfSponsoring: { @@ -3547,7 +3549,7 @@ readonly type: 'MigrateFromSelfSponsoring'; } - /** @name PalletEvmMigrationCall (405) */ + /** @name PalletEvmMigrationCall (407) */ interface PalletEvmMigrationCall extends Enum { readonly isBegin: boolean; readonly asBegin: { @@ -3575,7 +3577,7 @@ readonly type: 'Begin' | 'SetData' | 'Finish' | 'InsertEthLogs' | 'InsertEvents' | 'RemoveRmrkData'; } - /** @name PalletMaintenanceCall (409) */ + /** @name PalletMaintenanceCall (411) */ interface PalletMaintenanceCall extends Enum { readonly isEnable: boolean; readonly isDisable: boolean; @@ -3587,7 +3589,7 @@ readonly type: 'Enable' | 'Disable' | 'ExecutePreimage'; } - /** @name PalletTestUtilsCall (410) */ + /** @name PalletTestUtilsCall (412) */ interface PalletTestUtilsCall extends Enum { readonly isEnable: boolean; readonly isSetTestValue: boolean; @@ -3607,13 +3609,13 @@ readonly type: 'Enable' | 'SetTestValue' | 'SetTestValueAndRollback' | 'IncTestValue' | 'JustTakeFee' | 'BatchAll'; } - /** @name PalletSudoError (412) */ + /** @name PalletSudoError (414) */ interface PalletSudoError extends Enum { readonly isRequireSudo: boolean; readonly type: 'RequireSudo'; } - /** @name OrmlVestingModuleError (414) */ + /** @name OrmlVestingModuleError (416) */ interface OrmlVestingModuleError extends Enum { readonly isZeroVestingPeriod: boolean; readonly isZeroVestingPeriodCount: boolean; @@ -3624,7 +3626,7 @@ readonly type: 'ZeroVestingPeriod' | 'ZeroVestingPeriodCount' | 'InsufficientBalanceToLock' | 'TooManyVestingSchedules' | 'AmountLow' | 'MaxVestingSchedulesExceeded'; } - /** @name OrmlXtokensModuleError (415) */ + /** @name OrmlXtokensModuleError (417) */ interface OrmlXtokensModuleError extends Enum { readonly isAssetHasNoReserve: boolean; readonly isNotCrossChainTransfer: boolean; @@ -3648,26 +3650,26 @@ readonly type: 'AssetHasNoReserve' | 'NotCrossChainTransfer' | 'InvalidDest' | 'NotCrossChainTransferableCurrency' | 'UnweighableMessage' | 'XcmExecutionFailed' | 'CannotReanchor' | 'InvalidAncestry' | 'InvalidAsset' | 'DestinationNotInvertible' | 'BadVersion' | 'DistinctReserveForAssetAndFee' | 'ZeroFee' | 'ZeroAmount' | 'TooManyAssetsBeingSent' | 'AssetIndexNonExistent' | 'FeeNotEnough' | 'NotSupportedMultiLocation' | 'MinXcmFeeNotDefined'; } - /** @name OrmlTokensBalanceLock (418) */ + /** @name OrmlTokensBalanceLock (420) */ interface OrmlTokensBalanceLock extends Struct { readonly id: U8aFixed; readonly amount: u128; } - /** @name OrmlTokensAccountData (420) */ + /** @name OrmlTokensAccountData (422) */ interface OrmlTokensAccountData extends Struct { readonly free: u128; readonly reserved: u128; readonly frozen: u128; } - /** @name OrmlTokensReserveData (422) */ + /** @name OrmlTokensReserveData (424) */ interface OrmlTokensReserveData extends Struct { readonly id: Null; readonly amount: u128; } - /** @name OrmlTokensModuleError (424) */ + /** @name OrmlTokensModuleError (426) */ interface OrmlTokensModuleError extends Enum { readonly isBalanceTooLow: boolean; readonly isAmountIntoBalanceFailed: boolean; @@ -3680,14 +3682,14 @@ readonly type: 'BalanceTooLow' | 'AmountIntoBalanceFailed' | 'LiquidityRestrictions' | 'MaxLocksExceeded' | 'KeepAlive' | 'ExistentialDeposit' | 'DeadAccount' | 'TooManyReserves'; } - /** @name PalletIdentityRegistrarInfo (429) */ + /** @name PalletIdentityRegistrarInfo (431) */ interface PalletIdentityRegistrarInfo extends Struct { readonly account: AccountId32; readonly fee: u128; readonly fields: PalletIdentityBitFlags; } - /** @name PalletIdentityError (431) */ + /** @name PalletIdentityError (433) */ interface PalletIdentityError extends Enum { readonly isTooManySubAccounts: boolean; readonly isNotFound: boolean; @@ -3710,7 +3712,7 @@ readonly type: 'TooManySubAccounts' | 'NotFound' | 'NotNamed' | 'EmptyIndex' | 'FeeChanged' | 'NoIdentity' | 'StickyJudgement' | 'JudgementGiven' | 'InvalidJudgement' | 'InvalidIndex' | 'InvalidTarget' | 'TooManyFields' | 'TooManyRegistrars' | 'AlreadyClaimed' | 'NotSub' | 'NotOwned' | 'JudgementForDifferentIdentity' | 'JudgementPaymentFailed'; } - /** @name PalletPreimageRequestStatus (432) */ + /** @name PalletPreimageRequestStatus (434) */ interface PalletPreimageRequestStatus extends Enum { readonly isUnrequested: boolean; readonly asUnrequested: { @@ -3726,7 +3728,7 @@ readonly type: 'Unrequested' | 'Requested'; } - /** @name PalletPreimageError (437) */ + /** @name PalletPreimageError (439) */ interface PalletPreimageError extends Enum { readonly isTooBig: boolean; readonly isAlreadyNoted: boolean; @@ -3737,21 +3739,21 @@ readonly type: 'TooBig' | 'AlreadyNoted' | 'NotAuthorized' | 'NotNoted' | 'Requested' | 'NotRequested'; } - /** @name CumulusPalletXcmpQueueInboundChannelDetails (439) */ + /** @name CumulusPalletXcmpQueueInboundChannelDetails (441) */ interface CumulusPalletXcmpQueueInboundChannelDetails extends Struct { readonly sender: u32; readonly state: CumulusPalletXcmpQueueInboundState; readonly messageMetadata: Vec>; } - /** @name CumulusPalletXcmpQueueInboundState (440) */ + /** @name CumulusPalletXcmpQueueInboundState (442) */ interface CumulusPalletXcmpQueueInboundState extends Enum { readonly isOk: boolean; readonly isSuspended: boolean; readonly type: 'Ok' | 'Suspended'; } - /** @name PolkadotParachainPrimitivesXcmpMessageFormat (443) */ + /** @name PolkadotParachainPrimitivesXcmpMessageFormat (445) */ interface PolkadotParachainPrimitivesXcmpMessageFormat extends Enum { readonly isConcatenatedVersionedXcm: boolean; readonly isConcatenatedEncodedBlob: boolean; @@ -3759,7 +3761,7 @@ readonly type: 'ConcatenatedVersionedXcm' | 'ConcatenatedEncodedBlob' | 'Signals'; } - /** @name CumulusPalletXcmpQueueOutboundChannelDetails (446) */ + /** @name CumulusPalletXcmpQueueOutboundChannelDetails (448) */ interface CumulusPalletXcmpQueueOutboundChannelDetails extends Struct { readonly recipient: u32; readonly state: CumulusPalletXcmpQueueOutboundState; @@ -3768,14 +3770,14 @@ readonly lastIndex: u16; } - /** @name CumulusPalletXcmpQueueOutboundState (447) */ + /** @name CumulusPalletXcmpQueueOutboundState (449) */ interface CumulusPalletXcmpQueueOutboundState extends Enum { readonly isOk: boolean; readonly isSuspended: boolean; readonly type: 'Ok' | 'Suspended'; } - /** @name CumulusPalletXcmpQueueQueueConfigData (449) */ + /** @name CumulusPalletXcmpQueueQueueConfigData (451) */ interface CumulusPalletXcmpQueueQueueConfigData extends Struct { readonly suspendThreshold: u32; readonly dropThreshold: u32; @@ -3785,7 +3787,7 @@ readonly xcmpMaxIndividualWeight: SpWeightsWeightV2Weight; } - /** @name CumulusPalletXcmpQueueError (451) */ + /** @name CumulusPalletXcmpQueueError (453) */ interface CumulusPalletXcmpQueueError extends Enum { readonly isFailedToSend: boolean; readonly isBadXcmOrigin: boolean; @@ -3795,7 +3797,7 @@ readonly type: 'FailedToSend' | 'BadXcmOrigin' | 'BadXcm' | 'BadOverweightIndex' | 'WeightOverLimit'; } - /** @name PalletXcmQueryStatus (452) */ + /** @name PalletXcmQueryStatus (454) */ interface PalletXcmQueryStatus extends Enum { readonly isPending: boolean; readonly asPending: { @@ -3817,7 +3819,7 @@ readonly type: 'Pending' | 'VersionNotifier' | 'Ready'; } - /** @name XcmVersionedResponse (456) */ + /** @name XcmVersionedResponse (458) */ interface XcmVersionedResponse extends Enum { readonly isV2: boolean; readonly asV2: XcmV2Response; @@ -3826,7 +3828,7 @@ readonly type: 'V2' | 'V3'; } - /** @name PalletXcmVersionMigrationStage (462) */ + /** @name PalletXcmVersionMigrationStage (464) */ interface PalletXcmVersionMigrationStage extends Enum { readonly isMigrateSupportedVersion: boolean; readonly isMigrateVersionNotifiers: boolean; @@ -3836,14 +3838,14 @@ readonly type: 'MigrateSupportedVersion' | 'MigrateVersionNotifiers' | 'NotifyCurrentTargets' | 'MigrateAndNotifyOldTargets'; } - /** @name XcmVersionedAssetId (465) */ + /** @name XcmVersionedAssetId (467) */ interface XcmVersionedAssetId extends Enum { readonly isV3: boolean; readonly asV3: XcmV3MultiassetAssetId; readonly type: 'V3'; } - /** @name PalletXcmRemoteLockedFungibleRecord (466) */ + /** @name PalletXcmRemoteLockedFungibleRecord (468) */ interface PalletXcmRemoteLockedFungibleRecord extends Struct { readonly amount: u128; readonly owner: XcmVersionedMultiLocation; @@ -3851,7 +3853,7 @@ readonly consumers: Vec>; } - /** @name PalletXcmError (473) */ + /** @name PalletXcmError (475) */ interface PalletXcmError extends Enum { readonly isUnreachable: boolean; readonly isSendFailure: boolean; @@ -3876,29 +3878,29 @@ readonly type: 'Unreachable' | 'SendFailure' | 'Filtered' | 'UnweighableMessage' | 'DestinationNotInvertible' | 'Empty' | 'CannotReanchor' | 'TooManyAssets' | 'InvalidOrigin' | 'BadVersion' | 'BadLocation' | 'NoSubscription' | 'AlreadySubscribed' | 'InvalidAsset' | 'LowBalance' | 'TooManyLocks' | 'AccountNotSovereign' | 'FeesNotMet' | 'LockNotFound' | 'InUse'; } - /** @name CumulusPalletXcmError (474) */ + /** @name CumulusPalletXcmError (476) */ type CumulusPalletXcmError = Null; - /** @name CumulusPalletDmpQueueConfigData (475) */ + /** @name CumulusPalletDmpQueueConfigData (477) */ interface CumulusPalletDmpQueueConfigData extends Struct { readonly maxIndividual: SpWeightsWeightV2Weight; } - /** @name CumulusPalletDmpQueuePageIndexData (476) */ + /** @name CumulusPalletDmpQueuePageIndexData (478) */ interface CumulusPalletDmpQueuePageIndexData extends Struct { readonly beginUsed: u32; readonly endUsed: u32; readonly overweightCount: u64; } - /** @name CumulusPalletDmpQueueError (479) */ + /** @name CumulusPalletDmpQueueError (481) */ interface CumulusPalletDmpQueueError extends Enum { readonly isUnknown: boolean; readonly isOverLimit: boolean; readonly type: 'Unknown' | 'OverLimit'; } - /** @name PalletUniqueError (483) */ + /** @name PalletUniqueError (485) */ interface PalletUniqueError extends Enum { readonly isCollectionDecimalPointLimitExceeded: boolean; readonly isEmptyArgument: boolean; @@ -3906,13 +3908,13 @@ readonly type: 'CollectionDecimalPointLimitExceeded' | 'EmptyArgument' | 'RepartitionCalledOnNonRefungibleCollection'; } - /** @name PalletConfigurationError (484) */ + /** @name PalletConfigurationError (486) */ interface PalletConfigurationError extends Enum { readonly isInconsistentConfiguration: boolean; readonly type: 'InconsistentConfiguration'; } - /** @name UpDataStructsCollection (485) */ + /** @name UpDataStructsCollection (487) */ interface UpDataStructsCollection extends Struct { readonly owner: AccountId32; readonly mode: UpDataStructsCollectionMode; @@ -3925,7 +3927,7 @@ readonly flags: U8aFixed; } - /** @name UpDataStructsSponsorshipStateAccountId32 (486) */ + /** @name UpDataStructsSponsorshipStateAccountId32 (488) */ interface UpDataStructsSponsorshipStateAccountId32 extends Enum { readonly isDisabled: boolean; readonly isUnconfirmed: boolean; @@ -3935,43 +3937,43 @@ readonly type: 'Disabled' | 'Unconfirmed' | 'Confirmed'; } - /** @name UpDataStructsProperties (487) */ + /** @name UpDataStructsProperties (489) */ interface UpDataStructsProperties extends Struct { readonly map: UpDataStructsPropertiesMapBoundedVec; readonly consumedSpace: u32; readonly reserved: u32; } - /** @name UpDataStructsPropertiesMapBoundedVec (488) */ + /** @name UpDataStructsPropertiesMapBoundedVec (490) */ interface UpDataStructsPropertiesMapBoundedVec extends BTreeMap {} - /** @name UpDataStructsPropertiesMapPropertyPermission (493) */ + /** @name UpDataStructsPropertiesMapPropertyPermission (495) */ interface UpDataStructsPropertiesMapPropertyPermission extends BTreeMap {} - /** @name UpDataStructsCollectionStats (500) */ + /** @name UpDataStructsCollectionStats (502) */ interface UpDataStructsCollectionStats extends Struct { readonly created: u32; readonly destroyed: u32; readonly alive: u32; } - /** @name UpDataStructsTokenChild (501) */ + /** @name UpDataStructsTokenChild (503) */ interface UpDataStructsTokenChild extends Struct { readonly token: u32; readonly collection: u32; } - /** @name PhantomTypeUpDataStructs (502) */ + /** @name PhantomTypeUpDataStructs (504) */ interface PhantomTypeUpDataStructs extends Vec> {} - /** @name UpDataStructsTokenData (504) */ + /** @name UpDataStructsTokenData (506) */ interface UpDataStructsTokenData extends Struct { readonly properties: Vec; readonly owner: Option; readonly pieces: u128; } - /** @name UpDataStructsRpcCollection (506) */ + /** @name UpDataStructsRpcCollection (507) */ interface UpDataStructsRpcCollection extends Struct { readonly owner: AccountId32; readonly mode: UpDataStructsCollectionMode; @@ -3987,13 +3989,13 @@ readonly flags: UpDataStructsRpcCollectionFlags; } - /** @name UpDataStructsRpcCollectionFlags (507) */ + /** @name UpDataStructsRpcCollectionFlags (508) */ interface UpDataStructsRpcCollectionFlags extends Struct { readonly foreign: bool; readonly erc721metadata: bool; } - /** @name UpPovEstimateRpcPovInfo (508) */ + /** @name UpPovEstimateRpcPovInfo (509) */ interface UpPovEstimateRpcPovInfo extends Struct { readonly proofSize: u64; readonly compactProofSize: u64; @@ -4002,7 +4004,7 @@ readonly keyValues: Vec; } - /** @name SpRuntimeTransactionValidityTransactionValidityError (511) */ + /** @name SpRuntimeTransactionValidityTransactionValidityError (512) */ interface SpRuntimeTransactionValidityTransactionValidityError extends Enum { readonly isInvalid: boolean; readonly asInvalid: SpRuntimeTransactionValidityInvalidTransaction; @@ -4011,7 +4013,7 @@ readonly type: 'Invalid' | 'Unknown'; } - /** @name SpRuntimeTransactionValidityInvalidTransaction (512) */ + /** @name SpRuntimeTransactionValidityInvalidTransaction (513) */ interface SpRuntimeTransactionValidityInvalidTransaction extends Enum { readonly isCall: boolean; readonly isPayment: boolean; @@ -4028,7 +4030,7 @@ readonly type: 'Call' | 'Payment' | 'Future' | 'Stale' | 'BadProof' | 'AncientBirthBlock' | 'ExhaustsResources' | 'Custom' | 'BadMandatory' | 'MandatoryValidation' | 'BadSigner'; } - /** @name SpRuntimeTransactionValidityUnknownTransaction (513) */ + /** @name SpRuntimeTransactionValidityUnknownTransaction (514) */ interface SpRuntimeTransactionValidityUnknownTransaction extends Enum { readonly isCannotLookup: boolean; readonly isNoUnsignedValidator: boolean; @@ -4037,13 +4039,13 @@ readonly type: 'CannotLookup' | 'NoUnsignedValidator' | 'Custom'; } - /** @name UpPovEstimateRpcTrieKeyValue (515) */ + /** @name UpPovEstimateRpcTrieKeyValue (516) */ interface UpPovEstimateRpcTrieKeyValue extends Struct { readonly key: Bytes; readonly value: Bytes; } - /** @name PalletCommonError (517) */ + /** @name PalletCommonError (518) */ interface PalletCommonError extends Enum { readonly isCollectionNotFound: boolean; readonly isMustBeTokenOwner: boolean; @@ -4085,7 +4087,7 @@ readonly type: 'CollectionNotFound' | 'MustBeTokenOwner' | 'NoPermission' | 'CantDestroyNotEmptyCollection' | 'PublicMintingNotAllowed' | 'AddressNotInAllowlist' | 'CollectionNameLimitExceeded' | 'CollectionDescriptionLimitExceeded' | 'CollectionTokenPrefixLimitExceeded' | 'TotalCollectionsLimitExceeded' | 'CollectionAdminCountExceeded' | 'CollectionLimitBoundsExceeded' | 'OwnerPermissionsCantBeReverted' | 'TransferNotAllowed' | 'AccountTokenLimitExceeded' | 'CollectionTokenLimitExceeded' | 'MetadataFlagFrozen' | 'TokenNotFound' | 'TokenValueTooLow' | 'ApprovedValueTooLow' | 'CantApproveMoreThanOwned' | 'AddressIsNotEthMirror' | 'AddressIsZero' | 'UnsupportedOperation' | 'NotSufficientFounds' | 'UserIsNotAllowedToNest' | 'SourceCollectionIsNotAllowedToNest' | 'CollectionFieldSizeExceeded' | 'NoSpaceForProperty' | 'PropertyLimitReached' | 'PropertyKeyIsTooLong' | 'InvalidCharacterInPropertyKey' | 'EmptyPropertyKey' | 'CollectionIsExternal' | 'CollectionIsInternal' | 'ConfirmSponsorshipFail' | 'UserIsNotCollectionAdmin'; } - /** @name PalletFungibleError (519) */ + /** @name PalletFungibleError (520) */ interface PalletFungibleError extends Enum { readonly isNotFungibleDataUsedToMintFungibleCollectionToken: boolean; readonly isFungibleItemsHaveNoId: boolean; @@ -4097,7 +4099,7 @@ readonly type: 'NotFungibleDataUsedToMintFungibleCollectionToken' | 'FungibleItemsHaveNoId' | 'FungibleItemsDontHaveData' | 'FungibleDisallowsNesting' | 'SettingPropertiesNotAllowed' | 'SettingAllowanceForAllNotAllowed' | 'FungibleTokensAreAlwaysValid'; } - /** @name PalletRefungibleError (524) */ + /** @name PalletRefungibleError (525) */ interface PalletRefungibleError extends Enum { readonly isNotRefungibleDataUsedToMintFungibleCollectionToken: boolean; readonly isWrongRefungiblePieces: boolean; @@ -4107,19 +4109,19 @@ readonly type: 'NotRefungibleDataUsedToMintFungibleCollectionToken' | 'WrongRefungiblePieces' | 'RepartitionWhileNotOwningAllPieces' | 'RefungibleDisallowsNesting' | 'SettingPropertiesNotAllowed'; } - /** @name PalletNonfungibleItemData (525) */ + /** @name PalletNonfungibleItemData (526) */ interface PalletNonfungibleItemData extends Struct { readonly owner: PalletEvmAccountBasicCrossAccountIdRepr; } - /** @name UpDataStructsPropertyScope (527) */ + /** @name UpDataStructsPropertyScope (528) */ interface UpDataStructsPropertyScope extends Enum { readonly isNone: boolean; readonly isRmrk: boolean; readonly type: 'None' | 'Rmrk'; } - /** @name PalletNonfungibleError (530) */ + /** @name PalletNonfungibleError (531) */ interface PalletNonfungibleError extends Enum { readonly isNotNonfungibleDataUsedToMintFungibleCollectionToken: boolean; readonly isNonfungibleItemsHaveNoAmount: boolean; @@ -4127,7 +4129,7 @@ readonly type: 'NotNonfungibleDataUsedToMintFungibleCollectionToken' | 'NonfungibleItemsHaveNoAmount' | 'CantBurnNftWithChildren'; } - /** @name PalletStructureError (531) */ + /** @name PalletStructureError (532) */ interface PalletStructureError extends Enum { readonly isOuroborosDetected: boolean; readonly isDepthLimit: boolean; @@ -4137,7 +4139,7 @@ readonly type: 'OuroborosDetected' | 'DepthLimit' | 'BreadthLimit' | 'TokenNotFound' | 'CantNestTokenUnderCollection'; } - /** @name PalletAppPromotionError (536) */ + /** @name PalletAppPromotionError (537) */ interface PalletAppPromotionError extends Enum { readonly isAdminNotSet: boolean; readonly isNoPermission: boolean; @@ -4149,7 +4151,7 @@ readonly type: 'AdminNotSet' | 'NoPermission' | 'NotSufficientFunds' | 'PendingForBlockOverflow' | 'SponsorNotSet' | 'InsufficientStakedBalance' | 'InconsistencyState'; } - /** @name PalletForeignAssetsModuleError (537) */ + /** @name PalletForeignAssetsModuleError (538) */ interface PalletForeignAssetsModuleError extends Enum { readonly isBadLocation: boolean; readonly isMultiLocationExisted: boolean; @@ -4158,13 +4160,13 @@ readonly type: 'BadLocation' | 'MultiLocationExisted' | 'AssetIdNotExists' | 'AssetIdExisted'; } - /** @name PalletEvmCodeMetadata (538) */ + /** @name PalletEvmCodeMetadata (539) */ interface PalletEvmCodeMetadata extends Struct { readonly size_: u64; readonly hash_: H256; } - /** @name PalletEvmError (540) */ + /** @name PalletEvmError (541) */ interface PalletEvmError extends Enum { readonly isBalanceLow: boolean; readonly isFeeOverflow: boolean; @@ -4180,7 +4182,7 @@ readonly type: 'BalanceLow' | 'FeeOverflow' | 'PaymentOverflow' | 'WithdrawFailed' | 'GasPriceTooLow' | 'InvalidNonce' | 'GasLimitTooLow' | 'GasLimitTooHigh' | 'Undefined' | 'Reentrancy' | 'TransactionMustComeFromEOA'; } - /** @name FpRpcTransactionStatus (543) */ + /** @name FpRpcTransactionStatus (544) */ interface FpRpcTransactionStatus extends Struct { readonly transactionHash: H256; readonly transactionIndex: u32; @@ -4191,10 +4193,10 @@ readonly logsBloom: EthbloomBloom; } - /** @name EthbloomBloom (545) */ + /** @name EthbloomBloom (546) */ interface EthbloomBloom extends U8aFixed {} - /** @name EthereumReceiptReceiptV3 (547) */ + /** @name EthereumReceiptReceiptV3 (548) */ interface EthereumReceiptReceiptV3 extends Enum { readonly isLegacy: boolean; readonly asLegacy: EthereumReceiptEip658ReceiptData; @@ -4205,7 +4207,7 @@ readonly type: 'Legacy' | 'Eip2930' | 'Eip1559'; } - /** @name EthereumReceiptEip658ReceiptData (548) */ + /** @name EthereumReceiptEip658ReceiptData (549) */ interface EthereumReceiptEip658ReceiptData extends Struct { readonly statusCode: u8; readonly usedGas: U256; @@ -4213,14 +4215,14 @@ readonly logs: Vec; } - /** @name EthereumBlock (549) */ + /** @name EthereumBlock (550) */ interface EthereumBlock extends Struct { readonly header: EthereumHeader; readonly transactions: Vec; readonly ommers: Vec; } - /** @name EthereumHeader (550) */ + /** @name EthereumHeader (551) */ interface EthereumHeader extends Struct { readonly parentHash: H256; readonly ommersHash: H256; @@ -4239,24 +4241,24 @@ readonly nonce: EthereumTypesHashH64; } - /** @name EthereumTypesHashH64 (551) */ + /** @name EthereumTypesHashH64 (552) */ interface EthereumTypesHashH64 extends U8aFixed {} - /** @name PalletEthereumError (556) */ + /** @name PalletEthereumError (557) */ interface PalletEthereumError extends Enum { readonly isInvalidSignature: boolean; readonly isPreLogExists: boolean; readonly type: 'InvalidSignature' | 'PreLogExists'; } - /** @name PalletEvmCoderSubstrateError (557) */ + /** @name PalletEvmCoderSubstrateError (558) */ interface PalletEvmCoderSubstrateError extends Enum { readonly isOutOfGas: boolean; readonly isOutOfFund: boolean; readonly type: 'OutOfGas' | 'OutOfFund'; } - /** @name UpDataStructsSponsorshipStateBasicCrossAccountIdRepr (558) */ + /** @name UpDataStructsSponsorshipStateBasicCrossAccountIdRepr (559) */ interface UpDataStructsSponsorshipStateBasicCrossAccountIdRepr extends Enum { readonly isDisabled: boolean; readonly isUnconfirmed: boolean; @@ -4266,7 +4268,7 @@ readonly type: 'Disabled' | 'Unconfirmed' | 'Confirmed'; } - /** @name PalletEvmContractHelpersSponsoringModeT (559) */ + /** @name PalletEvmContractHelpersSponsoringModeT (560) */ interface PalletEvmContractHelpersSponsoringModeT extends Enum { readonly isDisabled: boolean; readonly isAllowlisted: boolean; @@ -4274,7 +4276,7 @@ readonly type: 'Disabled' | 'Allowlisted' | 'Generous'; } - /** @name PalletEvmContractHelpersError (565) */ + /** @name PalletEvmContractHelpersError (566) */ interface PalletEvmContractHelpersError extends Enum { readonly isNoPermission: boolean; readonly isNoPendingSponsor: boolean; @@ -4282,7 +4284,7 @@ readonly type: 'NoPermission' | 'NoPendingSponsor' | 'TooManyMethodsHaveSponsoredLimit'; } - /** @name PalletEvmMigrationError (566) */ + /** @name PalletEvmMigrationError (567) */ interface PalletEvmMigrationError extends Enum { readonly isAccountNotEmpty: boolean; readonly isAccountIsNotMigrating: boolean; @@ -4290,17 +4292,17 @@ readonly type: 'AccountNotEmpty' | 'AccountIsNotMigrating' | 'BadEvent'; } - /** @name PalletMaintenanceError (567) */ + /** @name PalletMaintenanceError (568) */ type PalletMaintenanceError = Null; - /** @name PalletTestUtilsError (568) */ + /** @name PalletTestUtilsError (569) */ interface PalletTestUtilsError extends Enum { readonly isTestPalletDisabled: boolean; readonly isTriggerRollback: boolean; readonly type: 'TestPalletDisabled' | 'TriggerRollback'; } - /** @name SpRuntimeMultiSignature (570) */ + /** @name SpRuntimeMultiSignature (571) */ interface SpRuntimeMultiSignature extends Enum { readonly isEd25519: boolean; readonly asEd25519: SpCoreEd25519Signature; @@ -4311,43 +4313,43 @@ readonly type: 'Ed25519' | 'Sr25519' | 'Ecdsa'; } - /** @name SpCoreEd25519Signature (571) */ + /** @name SpCoreEd25519Signature (572) */ interface SpCoreEd25519Signature extends U8aFixed {} - /** @name SpCoreSr25519Signature (573) */ + /** @name SpCoreSr25519Signature (574) */ interface SpCoreSr25519Signature extends U8aFixed {} - /** @name SpCoreEcdsaSignature (574) */ + /** @name SpCoreEcdsaSignature (575) */ interface SpCoreEcdsaSignature extends U8aFixed {} - /** @name FrameSystemExtensionsCheckSpecVersion (577) */ + /** @name FrameSystemExtensionsCheckSpecVersion (578) */ type FrameSystemExtensionsCheckSpecVersion = Null; - /** @name FrameSystemExtensionsCheckTxVersion (578) */ + /** @name FrameSystemExtensionsCheckTxVersion (579) */ type FrameSystemExtensionsCheckTxVersion = Null; - /** @name FrameSystemExtensionsCheckGenesis (579) */ + /** @name FrameSystemExtensionsCheckGenesis (580) */ type FrameSystemExtensionsCheckGenesis = Null; - /** @name FrameSystemExtensionsCheckNonce (582) */ + /** @name FrameSystemExtensionsCheckNonce (583) */ interface FrameSystemExtensionsCheckNonce extends Compact {} - /** @name FrameSystemExtensionsCheckWeight (583) */ + /** @name FrameSystemExtensionsCheckWeight (584) */ type FrameSystemExtensionsCheckWeight = Null; - /** @name OpalRuntimeRuntimeCommonMaintenanceCheckMaintenance (584) */ + /** @name OpalRuntimeRuntimeCommonMaintenanceCheckMaintenance (585) */ type OpalRuntimeRuntimeCommonMaintenanceCheckMaintenance = Null; - /** @name OpalRuntimeRuntimeCommonIdentityDisableIdentityCalls (585) */ + /** @name OpalRuntimeRuntimeCommonIdentityDisableIdentityCalls (586) */ type OpalRuntimeRuntimeCommonIdentityDisableIdentityCalls = Null; - /** @name PalletTemplateTransactionPaymentChargeTransactionPayment (586) */ + /** @name PalletTemplateTransactionPaymentChargeTransactionPayment (587) */ interface PalletTemplateTransactionPaymentChargeTransactionPayment extends Compact {} - /** @name OpalRuntimeRuntime (587) */ + /** @name OpalRuntimeRuntime (588) */ type OpalRuntimeRuntime = Null; - /** @name PalletEthereumFakeTransactionFinalizer (588) */ + /** @name PalletEthereumFakeTransactionFinalizer (589) */ type PalletEthereumFakeTransactionFinalizer = Null; } // declare module --- a/tests/src/sub/appPromotion/appPromotion.test.ts +++ b/tests/src/sub/appPromotion/appPromotion.test.ts @@ -51,6 +51,7 @@ donor = await privateKey({url: import.meta.url}); palletAddress = helper.arrange.calculatePalletAddress('appstake'); palletAdmin = await privateKey('//PromotionAdmin'); + nominal = helper.balance.getOneTokenNominal(); const accountBalances = new Array(200).fill(1000n); @@ -96,7 +97,6 @@ expect(await helper.balance.getSubstrate(staker.address) / nominal).to.be.equal(999n); // it is potentially flaky test. Promotion can credited some tokens. Maybe we need to use closeTo? expect(await helper.staking.getTotalStaked()).to.be.equal(totalStakedBefore + 100n * nominal); // total tokens amount staked in app-promotion increased - await helper.staking.stake(staker, 200n * nominal); expect(await helper.staking.getTotalStaked({Substrate: staker.address})).to.be.equal(300n * nominal); @@ -497,13 +497,13 @@ expect((await collectionWithoutSponsor.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: palletAddress}); // Can set sponsoring for collection with unconfirmed sponsor - const collectionWithUnconfirmedSponsor = await helper.nft.mintCollection(collectionOwner, {name: 'Unconfirmed', description: 'New Collection', tokenPrefix: 'Promotion', pendingSponsor: oldSponsor.address}); + const collectionWithUnconfirmedSponsor = await helper.nft.mintCollection(collectionOwner, {name: 'Unconfirmed', description: 'New Collection', tokenPrefix: 'Promotion', pendingSponsor: {Substrate: oldSponsor.address}}); expect((await collectionWithUnconfirmedSponsor.getData())?.raw.sponsorship).to.be.deep.equal({Unconfirmed: oldSponsor.address}); await expect(helper.signTransaction(palletAdmin, api.tx.appPromotion.sponsorCollection(collectionWithUnconfirmedSponsor.collectionId))).to.be.fulfilled; expect((await collectionWithUnconfirmedSponsor.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: palletAddress}); // Can set sponsoring for collection with confirmed sponsor - const collectionWithConfirmedSponsor = await helper.nft.mintCollection(collectionOwner, {name: 'Confirmed', description: 'New Collection', tokenPrefix: 'Promotion', pendingSponsor: oldSponsor.address}); + const collectionWithConfirmedSponsor = await helper.nft.mintCollection(collectionOwner, {name: 'Confirmed', description: 'New Collection', tokenPrefix: 'Promotion', pendingSponsor: {Substrate: oldSponsor.address}}); await collectionWithConfirmedSponsor.confirmSponsorship(oldSponsor); await expect(helper.signTransaction(palletAdmin, api.tx.appPromotion.sponsorCollection(collectionWithConfirmedSponsor.collectionId))).to.be.fulfilled; expect((await collectionWithConfirmedSponsor.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: palletAddress}); @@ -584,7 +584,7 @@ itSub('should not affect collection which is not sponsored by pallete', async ({helper}) => { const api = helper.getApi(); const [collectionOwner] = await getAccounts(1); - const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion', pendingSponsor: collectionOwner.address}); + const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion', pendingSponsor: {Substrate: collectionOwner.address}}); await collection.confirmSponsorship(collectionOwner); await expect(helper.signTransaction(palletAdmin, api.tx.appPromotion.stopSponsoringCollection(collection.collectionId))).to.be.rejected; --- a/tests/src/util/playgrounds/types.ts +++ b/tests/src/util/playgrounds/types.ts @@ -142,6 +142,21 @@ } } +export interface ICollectionFlags { + foreign: boolean, + erc721metadata: boolean, +} + +export enum CollectionFlag { + None = 0, + /// External collections can't be managed using `unique` api + External = 1, + /// Supports ERC721Metadata + Erc721metadata = 64, + /// Tokens in foreign collections can be transferred, but not burnt + Foreign = 128, +} + export interface ICollectionCreationOptions { name?: string | number[]; description?: string | number[]; @@ -155,7 +170,9 @@ properties?: IProperty[]; tokenPropertyPermissions?: ITokenPropertyPermission[]; limits?: ICollectionLimits; - pendingSponsor?: TSubstrateAccount; + pendingSponsor?: ICrossAccountId; + adminList?: ICrossAccountId[]; + flags?: number[] | CollectionFlag[] , } export interface IChainProperties { --- a/tests/src/util/playgrounds/unique.ts +++ b/tests/src/util/playgrounds/unique.ts @@ -44,10 +44,12 @@ MoonbeamAssetInfo, DemocracyStandardAccountVote, IEthCrossAccountId, + CollectionFlag, } from './types'; import {RuntimeDispatchInfo} from '@polkadot/types/interfaces'; import type {Vec} from '@polkadot/types-codec'; import {FrameSystemEventRecord, PalletBalancesIdAmount} from '@polkadot/types/lookup'; +import {arrayUnzip} from '@polkadot/util'; export class CrossAccountId { Substrate!: TSubstrateAccount; @@ -1624,6 +1626,17 @@ for(const key of ['name', 'description', 'tokenPrefix']) { if(typeof collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] === 'string') collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] = this.helper.util.str2vec(collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] as string); } + + let flags = 0; + // convert CollectionFlags to number and join them in one number + if(collectionOptions.flags) { + for(let i = 0; i < collectionOptions.flags.length; i++){ + const flag = collectionOptions.flags[i]; + flags = flags | flag; + } + } + collectionOptions.flags = [flags]; + const creationResult = await this.helper.executeExtrinsic( signer, 'api.tx.unique.createCollectionEx', [collectionOptions],