--- a/pallets/nft/Cargo.toml +++ b/pallets/nft/Cargo.toml @@ -27,6 +27,10 @@ 'pallet-timestamp/std', 'pallet-randomness-collective-flip/std', 'pallet-transaction-payment/std', + 'pallet-common/std', + 'pallet-fungible/std', + 'pallet-nonfungible/std', + 'pallet-refungible/std', 'fp-evm/std', 'nft-data-structs/std', 'up-sponsorship/std', @@ -154,3 +158,8 @@ pallet-ethereum = { default-features = false, version = "4.0.0-dev", git = "https://github.com/uniquenetwork/frontier.git", branch = "unique-polkadot-v0.9.10" } fp-evm = { default-features = false, version = '3.0.0-dev', git = "https://github.com/uniquenetwork/frontier.git", branch = "unique-polkadot-v0.9.10" } hex-literal = "0.3.3" + +pallet-common = { default-features = false, path = "../common" } +pallet-fungible = { default-features = false, path = "../fungible" } +pallet-nonfungible = { default-features = false, path = "../nonfungible" } +pallet-refungible = { default-features = false, path = "../refungible" } --- /dev/null +++ b/pallets/nft/src/common.rs @@ -0,0 +1,48 @@ +use core::marker::PhantomData; +use frame_support::{weights::Weight}; +use pallet_common::{CommonWeightInfo}; + +use pallet_fungible::{common::CommonWeights as FungibleWeights}; +use pallet_nonfungible::{common::CommonWeights as NonfungibleWeights}; +use pallet_refungible::{common::CommonWeights as RefungibleWeights}; + +use crate::{Config, dispatch::dispatch_weight}; + +macro_rules! max_weight_of { + ($method:ident ( $($args:tt)* )) => { + >::$method($($args)*) + .max(>::$method($($args)*)) + .max(>::$method($($args)*)) + }; +} + +pub struct CommonWeights(PhantomData); +impl CommonWeightInfo for CommonWeights { + fn create_item() -> nft_data_structs::Weight { + dispatch_weight::() + max_weight_of!(create_item()) + } + + fn create_multiple_items(amount: u32) -> Weight { + dispatch_weight::() + max_weight_of!(create_multiple_items(amount)) + } + + fn burn_item() -> Weight { + dispatch_weight::() + max_weight_of!(burn_item()) + } + + fn transfer() -> nft_data_structs::Weight { + dispatch_weight::() + max_weight_of!(transfer()) + } + + fn approve() -> nft_data_structs::Weight { + dispatch_weight::() + max_weight_of!(approve()) + } + + fn transfer_from() -> nft_data_structs::Weight { + dispatch_weight::() + max_weight_of!(transfer_from()) + } + + fn set_variable_metadata(bytes: u32) -> nft_data_structs::Weight { + dispatch_weight::() + max_weight_of!(set_variable_metadata(bytes)) + } +} --- /dev/null +++ b/pallets/nft/src/dispatch.rs @@ -0,0 +1,92 @@ +use frame_support::{ + dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo}, + traits::Get, + weights::Weight, +}; +use nft_data_structs::{CollectionId, CollectionMode, Pays, PostDispatchInfo}; +use pallet_common::{CollectionHandle, CommonCollectionOperations}; +use pallet_fungible::FungibleHandle; +use pallet_nonfungible::NonfungibleHandle; +use pallet_refungible::RefungibleHandle; + +use crate::Config; + +// TODO: move to benchmarking +/// Price of [`dispatch_call`] call with noop `call` argument +pub fn dispatch_weight() -> Weight { + // Read collection + ::DbWeight::get().reads(1) + // Dynamic dispatch? + + 6_000_000 + // submit_logs is measured as part of collection pallets +} + +pub enum Dispatched { + Fungible(FungibleHandle), + Nonfungible(NonfungibleHandle), + Refungible(RefungibleHandle), +} +impl Dispatched { + pub fn dispatch(handle: CollectionHandle) -> Self { + match handle.mode { + CollectionMode::Fungible(_) => Self::Fungible(FungibleHandle::cast(handle)), + CollectionMode::NFT => Self::Nonfungible(NonfungibleHandle::cast(handle)), + CollectionMode::ReFungible => Self::Refungible(RefungibleHandle::cast(handle)), + } + } + fn into_inner(self) -> CollectionHandle { + match self { + Dispatched::Fungible(f) => f.into_inner(), + Dispatched::Nonfungible(f) => f.into_inner(), + Dispatched::Refungible(f) => f.into_inner(), + } + } + pub fn as_dyn(&self) -> &dyn CommonCollectionOperations { + match self { + Dispatched::Fungible(h) => h, + Dispatched::Nonfungible(h) => h, + Dispatched::Refungible(h) => h, + } + } +} + +/// Helper function to implement substrate calls for common collection methods +pub fn dispatch_call< + T: Config, + C: FnOnce(&dyn pallet_common::CommonCollectionOperations) -> DispatchResultWithPostInfo, +>( + collection: CollectionId, + call: C, +) -> DispatchResultWithPostInfo { + let handle = + CollectionHandle::try_get(collection).map_err(|error| DispatchErrorWithPostInfo { + post_info: PostDispatchInfo { + actual_weight: Some(dispatch_weight::()), + pays_fee: Pays::Yes, + }, + error, + })?; + let dispatched = Dispatched::dispatch(handle); + let mut result = call(dispatched.as_dyn()); + match &mut result { + Ok(PostDispatchInfo { + actual_weight: Some(weight), + .. + }) + | Err(DispatchErrorWithPostInfo { + post_info: PostDispatchInfo { + actual_weight: Some(weight), + .. + }, + .. + }) => *weight += dispatch_weight::(), + _ => {} + } + + // TODO: Make submit_logs infallible, but it shouldn't fail here anyway + dispatched + .into_inner() + .submit_logs() + .expect("should succeed"); + result +} --- a/pallets/nft/src/lib.rs +++ b/pallets/nft/src/lib.rs @@ -16,12 +16,12 @@ pub use serde::{Serialize, Deserialize}; pub use frame_support::{ - construct_runtime, decl_event, decl_module, decl_storage, decl_error, + construct_runtime, decl_module, decl_storage, decl_error, dispatch::DispatchResult, ensure, fail, parameter_types, traits::{ - Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced, - Randomness, IsSubType, WithdrawReasons, + ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced, Randomness, + IsSubType, WithdrawReasons, }, weights::{ constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND}, @@ -29,22 +29,23 @@ WeightToFeePolynomial, DispatchClass, }, StorageValue, transactional, + pallet_prelude::DispatchResultWithPostInfo, }; - use frame_system::{self as system, ensure_signed}; -use sp_core::H160; -use sp_std::vec; -use sp_runtime::{DispatchError, sp_std::prelude::Vec}; -use core::ops::{Deref, DerefMut}; +use sp_runtime::{sp_std::prelude::Vec}; use nft_data_structs::{ - MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, MAX_REFUNGIBLE_PIECES, - CUSTOM_DATA_LIMIT, COLLECTION_NUMBER_LIMIT, ACCOUNT_TOKEN_OWNERSHIP_LIMIT, + MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, CUSTOM_DATA_LIMIT, VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, COLLECTION_ADMINS_LIMIT, - OFFCHAIN_SCHEMA_LIMIT, MAX_TOKEN_PREFIX_LENGTH, MAX_COLLECTION_NAME_LENGTH, - MAX_COLLECTION_DESCRIPTION_LENGTH, AccessMode, Collection, CreateItemData, CollectionLimits, - CollectionId, CollectionMode, TokenId, SchemaVersion, SponsorshipState, Ownership, NftItemType, - MetaUpdatePermission, FungibleItemType, ReFungibleItemType, + OFFCHAIN_SCHEMA_LIMIT, AccessMode, Collection, CreateItemData, CollectionLimits, CollectionId, + CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission, +}; +use pallet_common::{ + account::CrossAccountId, CollectionHandle, IsAdmin, Pallet as PalletCommon, + Error as CommonError, CommonWeightInfo, }; +use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle}; +use pallet_fungible::{Pallet as PalletFungible, FungibleHandle}; +use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle}; #[cfg(test)] mod mock; @@ -58,9 +59,12 @@ pub use eth::sponsoring::NftEthSponsorshipHandler; pub use eth::NftErcSupport; -pub use eth::account::*; -use eth::erc::{ERC20Events, ERC721Events}; +pub mod common; +use common::CommonWeights; +pub mod dispatch; +use dispatch::dispatch_call; + #[cfg(feature = "runtime-benchmarks")] mod benchmarking; pub mod weights; @@ -125,10 +129,6 @@ .max(Self::create_multiple_items_fungible(amount)) .max(Self::create_multiple_items_refungible(amount)) } - fn burn_item() -> Weight { - // TODO: refungible, fungible - Self::burn_item_nft() - } } impl WeightInfoHelpers for T {} @@ -219,57 +219,19 @@ // Anyone can create a collection let who = ensure_signed(origin)?; - // Take a (non-refundable) deposit of collection creation - let mut imbalance = <<::Currency as Currency>::PositiveImbalance>::zero(); - imbalance.subsume(<::Currency as Currency>::deposit_creating( - &T::TreasuryAccountId::get(), - T::CollectionCreationPrice::get(), - )); - ::Currency::settle( - &who, - imbalance, - WithdrawReasons::TRANSFER, - ExistenceRequirement::KeepAlive, - ).map_err(|_| Error::::NoPermission)?; - - let decimal_points = match mode { - CollectionMode::Fungible(points) => points, - _ => 0 - }; - - let created_count = CreatedCollectionCount::get(); - let destroyed_count = DestroyedCollectionCount::get(); - - // bound Total number of collections - ensure!(created_count - destroyed_count < COLLECTION_NUMBER_LIMIT, Error::::TotalCollectionsLimitExceeded); - - // check params - ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::::CollectionDecimalPointLimitExceeded); - ensure!(collection_name.len() <= MAX_COLLECTION_NAME_LENGTH, Error::::CollectionNameLimitExceeded); - ensure!(collection_description.len() <= MAX_COLLECTION_DESCRIPTION_LENGTH, Error::::CollectionDescriptionLimitExceeded); - ensure!(token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH, Error::::CollectionTokenPrefixLimitExceeded); - - // Generate next collection ID - let next_id = created_count - .checked_add(1) - .ok_or(Error::::NumOverflow)?; - - CreatedCollectionCount::put(next_id); - - let limits = CollectionLimits { + let limits = CollectionLimits:: { sponsored_data_size: CUSTOM_DATA_LIMIT, ..Default::default() }; // Create new collection - let new_collection = Collection { + let new_collection = Collection:: { owner: who.clone(), name: collection_name, mode: mode.clone(), mint_mode: false, access: AccessMode::Normal, description: collection_description, - decimal_points, token_prefix, offchain_schema: Vec::new(), schema_version: SchemaVersion::ImageURL, @@ -277,15 +239,21 @@ variable_on_chain_schema: Vec::new(), const_on_chain_schema: Vec::new(), limits, - meta_update_permission: MetaUpdatePermission::default(), transfers_enabled: true, + meta_update_permission: Default::default(), }; - - // Add new collection to map - >::insert(next_id, new_collection); - // call event - Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.id(), who)); + let _id = match mode { + CollectionMode::NFT => {PalletNonfungible::init_collection(new_collection)?}, + CollectionMode::Fungible(decimal_points) => { + // check params + ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::::CollectionDecimalPointLimitExceeded); + PalletFungible::init_collection(new_collection)? + } + CollectionMode::ReFungible => { + PalletRefungible::init_collection(new_collection)? + } + }; Ok(()) } @@ -302,25 +270,18 @@ #[weight = >::destroy_collection()] #[transactional] pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult { + let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); - let sender = ensure_signed(origin)?; - let collection = Self::get_collection(collection_id)?; - Self::check_owner_permissions(&collection, &sender)?; - if !collection.limits.owner_can_destroy { - fail!(Error::::NoPermission); - } + let collection = >::try_get(collection_id)?; + collection.check_is_owner(&sender)?; - >::remove_prefix(collection_id, None); - >::remove_prefix(collection_id, None); - >::remove_prefix(collection_id, None); - ::remove(collection_id); - >::remove(collection_id); - >::remove(collection_id); - >::remove_prefix(collection_id, None); + // ========= - >::remove_prefix(collection_id, None); - >::remove_prefix(collection_id, None); - >::remove_prefix(collection_id, None); + match collection.mode { + CollectionMode::ReFungible => PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?, + CollectionMode::Fungible(_) => PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)?, + CollectionMode::NFT => PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)?, + } >::remove_prefix(collection_id, None); >::remove_prefix(collection_id, None); @@ -328,10 +289,6 @@ >::remove_prefix(collection_id, None); - DestroyedCollectionCount::put(DestroyedCollectionCount::get() - .checked_add(1) - .ok_or(Error::::NumOverflow)?); - Ok(()) } @@ -352,11 +309,11 @@ pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{ let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); - let collection = Self::get_collection(collection_id)?; + let collection = >::try_get(collection_id)?; - Self::toggle_white_list_internal( - &sender, + >::toggle_whitelist( &collection, + &sender, &address, true, )?; @@ -381,11 +338,11 @@ pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{ let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); - let collection = Self::get_collection(collection_id)?; + let collection = >::try_get(collection_id)?; - Self::toggle_white_list_internal( - &sender, + >::toggle_whitelist( &collection, + &sender, &address, false, )?; @@ -408,10 +365,11 @@ #[transactional] pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult { - let sender = ensure_signed(origin)?; + let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); + + let mut target_collection = >::try_get(collection_id)?; + target_collection.check_is_owner(&sender)?; - let mut target_collection = Self::get_collection(collection_id)?; - Self::check_owner_permissions(&target_collection, &sender)?; target_collection.access = mode; target_collection.save() } @@ -433,10 +391,11 @@ #[transactional] pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult { - let sender = ensure_signed(origin)?; + let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); + + let mut target_collection = >::try_get(collection_id)?; + target_collection.check_is_owner(&sender)?; - let mut target_collection = Self::get_collection(collection_id)?; - Self::check_owner_permissions(&target_collection, &sender)?; target_collection.mint_mode = mint_permission; target_collection.save() } @@ -456,9 +415,11 @@ #[transactional] pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult { - let sender = ensure_signed(origin)?; - let mut target_collection = Self::get_collection(collection_id)?; - Self::check_owner_permissions(&target_collection, &sender)?; + let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); + + let mut target_collection = >::try_get(collection_id)?; + target_collection.check_is_owner(&sender)?; + target_collection.owner = new_owner; target_collection.save() } @@ -480,18 +441,11 @@ #[transactional] pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult { let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); - let collection = Self::get_collection(collection_id)?; - Self::check_owner_or_admin_permissions(&collection, &sender)?; - let mut admin_arr = >::get(collection_id); - match admin_arr.binary_search(&new_admin_id) { - Ok(_) => {}, - Err(idx) => { - ensure!(admin_arr.len() < COLLECTION_ADMINS_LIMIT as usize, Error::::CollectionAdminsLimitExceeded); - admin_arr.insert(idx, new_admin_id); - >::insert(collection_id, admin_arr); - } - } + let collection = >::try_get(collection_id)?; + collection.check_is_owner_or_admin(&sender)?; + + >::insert((collection_id, new_admin_id.as_sub()), true); Ok(()) } @@ -511,14 +465,11 @@ #[transactional] pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult { let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); - let collection = Self::get_collection(collection_id)?; - Self::check_owner_or_admin_permissions(&collection, &sender)?; - let mut admin_arr = >::get(collection_id); - if let Ok(idx) = admin_arr.binary_search(&account_id) { - admin_arr.remove(idx); - >::insert(collection_id, admin_arr); - } + let collection = >::try_get(collection_id)?; + collection.check_is_owner_or_admin(&sender)?; + + >::remove((collection_id, account_id.as_sub())); Ok(()) } @@ -534,9 +485,10 @@ #[weight = >::set_collection_sponsor()] #[transactional] pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult { - let sender = ensure_signed(origin)?; - let mut target_collection = Self::get_collection(collection_id)?; - Self::check_owner_permissions(&target_collection, &sender)?; + let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); + + let mut target_collection = >::try_get(collection_id)?; + target_collection.check_is_owner_or_admin(&sender)?; target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor); target_collection.save() @@ -554,7 +506,7 @@ pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult { let sender = ensure_signed(origin)?; - let mut target_collection = Self::get_collection(collection_id)?; + let mut target_collection = >::try_get(collection_id)?; ensure!( target_collection.sponsorship.pending_sponsor() == Some(&sender), Error::::ConfirmUnsetSponsorFail @@ -576,10 +528,10 @@ #[weight = >::remove_collection_sponsor()] #[transactional] pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult { - let sender = ensure_signed(origin)?; + let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); - let mut target_collection = Self::get_collection(collection_id)?; - Self::check_owner_permissions(&target_collection, &sender)?; + let mut target_collection = >::try_get(collection_id)?; + target_collection.check_is_owner(&sender)?; target_collection.sponsorship = SponsorshipState::Disabled; target_collection.save() @@ -603,21 +555,12 @@ /// * owner: Address, initial owner of the NFT. /// /// * data: Token data to store on chain. - // #[weight = - // (130_000_000 as Weight) - // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight)) - // .saturating_add(RocksDbWeight::get().reads(10 as Weight)) - // .saturating_add(RocksDbWeight::get().writes(8 as Weight))] - - #[weight = >::create_item(data.data_size() as u32)] + #[weight = >::create_item()] #[transactional] - pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult { + pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo { let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); - let collection = Self::get_collection(collection_id)?; - Self::create_item_internal(&sender, &collection, &owner, data)?; - - collection.submit_logs() + dispatch_call::(collection_id, |d| d.create_item(sender, owner, data)) } /// This method creates multiple items in a collection created with CreateCollection method. @@ -638,17 +581,13 @@ /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item]. /// /// * owner: Address, initial owner of the NFT. - #[weight = >::create_multiple_items(items_data.len() as u32)] + #[weight = >::create_multiple_items(items_data.len() as u32)] #[transactional] - pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec) -> DispatchResult { - + pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec) -> DispatchResultWithPostInfo { ensure!(!items_data.is_empty(), Error::::EmptyArgument); let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); - let collection = Self::get_collection(collection_id)?; - Self::create_multiple_items_internal(&sender, &collection, &owner, items_data)?; - - collection.submit_logs() + dispatch_call::(collection_id, |d| d.create_multiple_items(sender, owner, items_data)) } // TODO! transaction weight @@ -667,43 +606,13 @@ #[weight = >::set_transfers_enabled_flag()] #[transactional] pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult { - - let sender = ensure_signed(origin)?; - let mut target_collection = Self::get_collection(collection_id)?; + let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); + let mut target_collection = >::try_get(collection_id)?; + target_collection.check_is_owner(&sender)?; - Self::check_owner_permissions(&target_collection, &sender)?; + // ========= target_collection.transfers_enabled = value; - target_collection.save() - } - - // TODO! transaction weight - /// Set meta_update_permission value for particular collection - /// - /// # Permissions - /// - /// * Collection Owner. - /// - /// # Arguments - /// - /// * collection_id: ID of the collection. - /// - /// * value: New flag value. - #[weight = ::WeightInfo::burn_item()] - #[transactional] - pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult { - - let sender = ensure_signed(origin)?; - let mut target_collection = Self::get_collection(collection_id)?; - - ensure!( - target_collection.meta_update_permission != MetaUpdatePermission::None, - Error::::MetadataFlagFrozen - ); - Self::check_owner_permissions(&target_collection, &sender)?; - - target_collection.meta_update_permission = value; - target_collection.save() } @@ -720,16 +629,12 @@ /// * collection_id: ID of the collection. /// /// * item_id: ID of NFT to burn. - #[weight = >::burn_item()] + #[weight = >::burn_item()] #[transactional] - pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult { - + pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo { let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); - let target_collection = Self::get_collection(collection_id)?; - Self::burn_item_internal(&sender, &target_collection, item_id, value)?; - - target_collection.submit_logs() + dispatch_call::(collection_id, |d| d.burn_item(sender, item_id, value)) } /// Change ownership of the token. @@ -755,15 +660,12 @@ /// * Non-Fungible Mode: Ignored /// * Fungible Mode: Must specify transferred amount /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1) - #[weight = >::transfer()] + #[weight = >::transfer()] #[transactional] - pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult { + pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo { let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); - let collection = Self::get_collection(collection_id)?; - Self::transfer_internal(&sender, &recipient, &collection, item_id, value)?; - - collection.submit_logs() + dispatch_call::(collection_id, |d| d.transfer(sender, recipient, item_id, value)) } /// Set, change, or remove approved address to transfer the ownership of the NFT. @@ -781,15 +683,12 @@ /// * collection_id. /// /// * item_id: ID of the item. - #[weight = >::approve()] + #[weight = >::approve()] #[transactional] - pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult { + pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo { let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); - let collection = Self::get_collection(collection_id)?; - Self::approve_internal(&sender, &spender, &collection, item_id, amount)?; - - collection.submit_logs() + dispatch_call::(collection_id, |d| d.approve(sender, spender, item_id, amount)) } /// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner. @@ -811,29 +710,14 @@ /// * item_id: ID of the item. /// /// * value: Amount to transfer. - #[weight = >::transfer_from()] + #[weight = >::transfer_from()] #[transactional] - pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult { + pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo { let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); - let collection = Self::get_collection(collection_id)?; - Self::transfer_from_internal(&sender, &from, &recipient, &collection, item_id, value)?; - - collection.submit_logs() + dispatch_call::(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value)) } - // #[weight = 0] - // // let no_perm_mes = "You do not have permissions to modify this collection"; - // // ensure!(>::contains_key((collection_id, item_id)), no_perm_mes); - // // let list_itm = >::get((collection_id, item_id)); - // // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes); - - // // // on_nft_received call - - // // Self::transfer(origin, collection_id, item_id, new_owner)?; - // Ok(()) - // } - /// Set off-chain data schema. /// /// # Permissions @@ -846,22 +730,45 @@ /// * collection_id. /// /// * schema: String representing the offchain data schema. - #[weight = >::set_variable_meta_data(data.len() as u32)] + #[weight = >::set_variable_metadata(data.len() as u32)] #[transactional] pub fn set_variable_meta_data ( origin, collection_id: CollectionId, item_id: TokenId, data: Vec - ) -> DispatchResult { + ) -> DispatchResultWithPostInfo { + let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); + + dispatch_call::(collection_id, |d| d.set_variable_metadata(sender, item_id, data)) + } + + /// Set meta_update_permission value for particular collection + /// + /// # Permissions + /// + /// * Collection Owner. + /// + /// # Arguments + /// + /// * collection_id: ID of the collection. + /// + /// * value: New flag value. + #[weight = >::set_variable_meta_data(0)] + #[transactional] + pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult { let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); + let mut target_collection = >::try_get(collection_id)?; - let collection = Self::get_collection(collection_id)?; - Self::meta_update_check(&sender, &collection, item_id)?; + ensure!( + target_collection.meta_update_permission != MetaUpdatePermission::None, + >::MetadataFlagFrozen, + ); + target_collection.check_is_owner(&sender)?; - Self::set_variable_meta_data_internal(&sender, &collection, item_id, data)?; + target_collection.meta_update_permission = value; - Ok(()) + target_collection.save() } /// Set schema standard @@ -886,8 +793,8 @@ version: SchemaVersion ) -> DispatchResult { let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); - let mut target_collection = Self::get_collection(collection_id)?; - Self::check_owner_or_admin_permissions(&target_collection, &sender)?; + let mut target_collection = >::try_get(collection_id)?; + target_collection.check_is_owner_or_admin(&sender)?; target_collection.schema_version = version; target_collection.save() } @@ -912,8 +819,8 @@ schema: Vec ) -> DispatchResult { let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); - let mut target_collection = Self::get_collection(collection_id)?; - Self::check_owner_or_admin_permissions(&target_collection, &sender)?; + let mut target_collection = >::try_get(collection_id)?; + target_collection.check_is_owner_or_admin(&sender)?; // check schema limit ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, ""); @@ -942,8 +849,8 @@ schema: Vec ) -> DispatchResult { let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); - let mut target_collection = Self::get_collection(collection_id)?; - Self::check_owner_or_admin_permissions(&target_collection, &sender)?; + let mut target_collection = >::try_get(collection_id)?; + target_collection.check_is_owner_or_admin(&sender)?; // check schema limit ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, ""); @@ -972,8 +879,8 @@ schema: Vec ) -> DispatchResult { let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); - let mut target_collection = Self::get_collection(collection_id)?; - Self::check_owner_or_admin_permissions(&target_collection, &sender)?; + let mut target_collection = >::try_get(collection_id)?; + target_collection.check_is_owner_or_admin(&sender)?; // check schema limit ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, ""); @@ -986,23 +893,23 @@ #[transactional] pub fn set_collection_limits( origin, - collection_id: u32, + collection_id: CollectionId, new_limits: CollectionLimits, ) -> DispatchResult { let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); - let mut target_collection = Self::get_collection(collection_id)?; - Self::check_owner_permissions(&target_collection, sender.as_sub())?; + let mut target_collection = >::try_get(collection_id)?; + target_collection.check_is_owner(&sender)?; let old_limits = &target_collection.limits; // collection bounds ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT && - new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP && + new_limits.account_token_ownership_limit.unwrap_or(0) <= MAX_TOKEN_OWNERSHIP && new_limits.sponsored_data_size <= CUSTOM_DATA_LIMIT, Error::::CollectionLimitBoundsExceeded); // token_limit check prev - ensure!(old_limits.token_limit >= new_limits.token_limit, Error::::CollectionTokenLimitExceeded); - ensure!(new_limits.token_limit > 0, Error::::CollectionTokenLimitExceeded); + ensure!(old_limits.token_limit >= new_limits.token_limit, >::CollectionTokenLimitExceeded); + ensure!(new_limits.token_limit > 0, >::CollectionTokenLimitExceeded); ensure!( (old_limits.owner_can_transfer || !new_limits.owner_can_transfer) && @@ -1013,1222 +920,6 @@ target_collection.limits = new_limits; target_collection.save() - } - } -} - -impl Module { - pub fn create_item_internal( - sender: &T::CrossAccountId, - collection: &CollectionHandle, - owner: &T::CrossAccountId, - data: CreateItemData, - ) -> DispatchResult { - ensure!( - owner != &T::CrossAccountId::from_eth(H160([0; 20])), - Error::::AddressIsZero - ); - - Self::can_create_items_in_collection(collection, sender, owner, 1)?; - Self::validate_create_item_args(collection, &data)?; - Self::create_item_no_validation(collection, owner, data)?; - - Ok(()) - } - - pub fn transfer_internal( - sender: &T::CrossAccountId, - recipient: &T::CrossAccountId, - target_collection: &CollectionHandle, - item_id: TokenId, - value: u128, - ) -> DispatchResult { - ensure!( - recipient != &T::CrossAccountId::from_eth(H160([0; 20])), - Error::::AddressIsZero - ); - - // Limits check - Self::is_correct_transfer(target_collection, recipient)?; - - // Transfer permissions check - ensure!( - Self::is_item_owner(sender, target_collection, item_id)? - || Self::is_owner_or_admin_permissions(target_collection, sender)?, - Error::::NoPermission - ); - - if target_collection.access == AccessMode::WhiteList { - Self::check_white_list(target_collection, sender)?; - Self::check_white_list(target_collection, recipient)?; - } - - match target_collection.mode { - CollectionMode::NFT => Self::transfer_nft( - target_collection, - item_id, - sender.clone(), - recipient.clone(), - )?, - CollectionMode::Fungible(_) => { - Self::transfer_fungible(target_collection, value, sender, recipient)? - } - CollectionMode::ReFungible => Self::transfer_refungible( - target_collection, - item_id, - value, - sender.clone(), - recipient.clone(), - )?, - }; - - Self::deposit_event(RawEvent::Transfer( - target_collection.id, - item_id, - sender.clone(), - recipient.clone(), - value, - )); - - Ok(()) - } - - pub fn approve_internal( - sender: &T::CrossAccountId, - spender: &T::CrossAccountId, - collection: &CollectionHandle, - item_id: TokenId, - amount: u128, - ) -> DispatchResult { - Self::token_exists(collection, item_id)?; - - // Transfer permissions check - let bypasses_limits = collection.limits.owner_can_transfer - && Self::is_owner_or_admin_permissions(collection, sender)?; - - let allowance_limit = if bypasses_limits { - None - } else if let Some(amount) = Self::owned_amount(sender, collection, item_id)? { - Some(amount) - } else { - fail!(Error::::NoPermission); - }; - - if collection.access == AccessMode::WhiteList { - Self::check_white_list(collection, sender)?; - Self::check_white_list(collection, spender)?; - } - - collection.consume_sload()?; - let allowance: u128 = amount - .checked_add(>::get( - collection.id, - (item_id, sender.as_sub(), spender.as_sub()), - )) - .ok_or(Error::::NumOverflow)?; - if let Some(limit) = allowance_limit { - ensure!(limit >= allowance, Error::::TokenValueTooLow); - } - collection.consume_sstore()?; - >::insert( - collection.id, - (item_id, sender.as_sub(), spender.as_sub()), - allowance, - ); - - if matches!(collection.mode, CollectionMode::NFT) { - // TODO: NFT: only one owner may exist for token in ERC721 - collection.log(ERC721Events::Approval { - owner: *sender.as_eth(), - approved: *spender.as_eth(), - token_id: item_id.into(), - })?; - } - - if matches!(collection.mode, CollectionMode::Fungible(_)) { - // TODO: NFT: only one owner may exist for token in ERC20 - collection.log(ERC20Events::Approval { - owner: *sender.as_eth(), - spender: *spender.as_eth(), - value: allowance.into(), - })?; - } - - Self::deposit_event(RawEvent::Approved( - collection.id, - item_id, - sender.clone(), - spender.clone(), - allowance, - )); - Ok(()) - } - - pub fn transfer_from_internal( - sender: &T::CrossAccountId, - from: &T::CrossAccountId, - recipient: &T::CrossAccountId, - collection: &CollectionHandle, - item_id: TokenId, - amount: u128, - ) -> DispatchResult { - if sender == from { - // Transfer by `from`, because it is either equal to sender, or derived from him - return Self::transfer_internal(from, recipient, collection, item_id, amount); - } - - // Check approval - collection.consume_sload()?; - let approval: u128 = - >::get(collection.id, (item_id, from.as_sub(), sender.as_sub())); - - // Limits check - Self::is_correct_transfer(collection, recipient)?; - - // Transfer permissions check - ensure!( - approval >= amount - || (collection.limits.owner_can_transfer - && Self::is_owner_or_admin_permissions(collection, sender)?), - Error::::NoPermission - ); - - if collection.access == AccessMode::WhiteList { - Self::check_white_list(collection, sender)?; - Self::check_white_list(collection, recipient)?; - } - - // Reduce approval by transferred amount or remove if remaining approval drops to 0 - let allowance = approval.saturating_sub(amount); - collection.consume_sstore()?; - if allowance > 0 { - >::insert( - collection.id, - (item_id, from.as_sub(), sender.as_sub()), - allowance, - ); - } else { - >::remove(collection.id, (item_id, from.as_sub(), sender.as_sub())); - } - - match collection.mode { - CollectionMode::NFT => { - Self::transfer_nft(collection, item_id, from.clone(), recipient.clone())? - } - CollectionMode::Fungible(_) => { - Self::transfer_fungible(collection, amount, from, recipient)? - } - CollectionMode::ReFungible => Self::transfer_refungible( - collection, - item_id, - amount, - from.clone(), - recipient.clone(), - )?, - }; - - if matches!(collection.mode, CollectionMode::Fungible(_)) { - collection.log(ERC20Events::Approval { - owner: *from.as_eth(), - spender: *sender.as_eth(), - value: allowance.into(), - })?; } - - Ok(()) - } - - pub fn set_variable_meta_data_internal( - sender: &T::CrossAccountId, - collection: &CollectionHandle, - item_id: TokenId, - data: Vec, - ) -> DispatchResult { - Self::token_exists(collection, item_id)?; - - ensure!( - CUSTOM_DATA_LIMIT >= data.len() as u32, - Error::::TokenVariableDataLimitExceeded - ); - - ensure!( - (Self::is_item_owner(sender, collection, item_id)? - && collection.meta_update_permission == MetaUpdatePermission::ItemOwner) - || (Self::is_owner_or_admin_permissions(collection, sender)? - && collection.meta_update_permission == MetaUpdatePermission::Admin), - Error::::NoPermission - ); - - match collection.mode { - CollectionMode::NFT => Self::set_nft_variable_data(collection, item_id, data)?, - CollectionMode::ReFungible => { - Self::set_re_fungible_variable_data(collection, item_id, data)? - } - CollectionMode::Fungible(_) => fail!(Error::::CantStoreMetadataInFungibleTokens), - }; - - Ok(()) - } - - pub fn meta_update_check( - sender: &T::CrossAccountId, - collection: &CollectionHandle, - item_id: TokenId, - ) -> DispatchResult { - match collection.meta_update_permission { - MetaUpdatePermission::ItemOwner => ensure!( - Self::is_item_owner(sender, collection, item_id)?, - Error::::NoPermission - ), - MetaUpdatePermission::Admin => ensure!( - Self::is_owner_or_admin_permissions(collection, sender)?, - Error::::NoPermission - ), - MetaUpdatePermission::None => fail!(Error::::MetadataUpdateDenied), - } - - Ok(()) - } - - pub fn get_variable_metadata( - collection: &CollectionHandle, - item_id: TokenId, - ) -> Result, DispatchError> { - Ok(match collection.mode { - CollectionMode::NFT => { - >::get(collection.id, item_id) - .ok_or(Error::::TokenNotFound)? - .variable_data - } - CollectionMode::ReFungible => { - >::get(collection.id, item_id) - .ok_or(Error::::TokenNotFound)? - .variable_data - } - _ => fail!(Error::::UnexpectedCollectionType), - }) - } - - pub fn create_multiple_items_internal( - sender: &T::CrossAccountId, - collection: &CollectionHandle, - owner: &T::CrossAccountId, - items_data: Vec, - ) -> DispatchResult { - Self::can_create_items_in_collection(collection, sender, owner, items_data.len() as u32)?; - - for data in &items_data { - Self::validate_create_item_args(collection, data)?; - } - for data in &items_data { - Self::create_item_no_validation(collection, owner, data.clone())?; - } - - Ok(()) - } - - pub fn burn_item_internal( - sender: &T::CrossAccountId, - collection: &CollectionHandle, - item_id: TokenId, - value: u128, - ) -> DispatchResult { - ensure!( - Self::is_item_owner(sender, collection, item_id)? - || (collection.limits.owner_can_transfer - && Self::is_owner_or_admin_permissions(collection, sender)?), - Error::::NoPermission - ); - - if collection.access == AccessMode::WhiteList { - Self::check_white_list(collection, sender)?; - } - - match collection.mode { - CollectionMode::NFT => Self::burn_nft_item(collection, item_id)?, - CollectionMode::Fungible(_) => Self::burn_fungible_item(sender, collection, value)?, - CollectionMode::ReFungible => Self::burn_refungible_item(collection, item_id, sender)?, - }; - - Ok(()) - } - - pub fn toggle_white_list_internal( - sender: &T::CrossAccountId, - collection: &CollectionHandle, - address: &T::CrossAccountId, - whitelisted: bool, - ) -> DispatchResult { - Self::check_owner_or_admin_permissions(collection, sender)?; - - if whitelisted { - >::insert(collection.id, address.as_sub(), true); - } else { - >::remove(collection.id, address.as_sub()); - } - - Ok(()) - } - - fn is_correct_transfer( - collection: &CollectionHandle, - recipient: &T::CrossAccountId, - ) -> DispatchResult { - let collection_id = collection.id; - - // check token limit and account token limit - collection.consume_sload()?; - let account_items: u32 = - >::get(collection_id, recipient.as_sub()).len() as u32; - - // zero limit means collection limit is disabled - // otherwise get lower value - let limit = if collection.limits.account_token_ownership_limit == 0 - || collection.limits.account_token_ownership_limit > ACCOUNT_TOKEN_OWNERSHIP_LIMIT - { - ACCOUNT_TOKEN_OWNERSHIP_LIMIT - } else { - collection.limits.account_token_ownership_limit - }; - - ensure!(limit > account_items, Error::::AccountTokenLimitExceeded); - - // preliminary transfer check - ensure!(collection.transfers_enabled, Error::::TransferNotAllowed); - - Ok(()) - } - - fn can_create_items_in_collection( - collection: &CollectionHandle, - sender: &T::CrossAccountId, - owner: &T::CrossAccountId, - amount: u32, - ) -> DispatchResult { - let collection_id = collection.id; - - // check token limit and account token limit - let total_items: u32 = ItemListIndex::get(collection_id) - .checked_add(amount) - .ok_or(Error::::CollectionTokenLimitExceeded)?; - let account_items: u32 = (>::get(collection_id, owner.as_sub()).len() - as u32) - .checked_add(amount) - .ok_or(Error::::AccountTokenLimitExceeded)?; - - // zero limit means collection limit is disabled - // otherwise get lower value - let account_token_limit = if collection.limits.account_token_ownership_limit == 0 - || collection.limits.account_token_ownership_limit > ACCOUNT_TOKEN_OWNERSHIP_LIMIT - { - ACCOUNT_TOKEN_OWNERSHIP_LIMIT - } else { - collection.limits.account_token_ownership_limit - }; - - ensure!( - collection.limits.token_limit >= total_items, - Error::::CollectionTokenLimitExceeded - ); - ensure!( - account_token_limit >= account_items, - Error::::AccountTokenLimitExceeded - ); - - if !Self::is_owner_or_admin_permissions(collection, sender)? { - ensure!(collection.mint_mode, Error::::PublicMintingNotAllowed); - Self::check_white_list(collection, owner)?; - Self::check_white_list(collection, sender)?; - } - - Ok(()) - } - - fn validate_create_item_args( - target_collection: &CollectionHandle, - data: &CreateItemData, - ) -> DispatchResult { - match target_collection.mode { - CollectionMode::NFT => { - if !matches!(data, CreateItemData::NFT(_)) { - fail!(Error::::NotNftDataUsedToMintNftCollectionToken); - } - } - CollectionMode::Fungible(_) => { - if !matches!(data, CreateItemData::Fungible(_)) { - fail!(Error::::NotFungibleDataUsedToMintFungibleCollectionToken); - } - } - CollectionMode::ReFungible => { - if let CreateItemData::ReFungible(data) = data { - // Check refungibility limits - ensure!( - data.pieces <= MAX_REFUNGIBLE_PIECES, - Error::::WrongRefungiblePieces - ); - ensure!(data.pieces > 0, Error::::WrongRefungiblePieces); - } else { - fail!(Error::::NotReFungibleDataUsedToMintReFungibleCollectionToken); - } - } - }; - - Ok(()) - } - - fn create_item_no_validation( - collection: &CollectionHandle, - owner: &T::CrossAccountId, - data: CreateItemData, - ) -> DispatchResult { - match data { - CreateItemData::NFT(data) => { - let item = NftItemType { - owner: owner.clone(), - const_data: data.const_data.into_inner(), - variable_data: data.variable_data.into_inner(), - }; - - Self::add_nft_item(collection, item)?; - } - CreateItemData::Fungible(data) => { - Self::add_fungible_item(collection, owner, data.value)?; - } - CreateItemData::ReFungible(data) => { - let owner_list = vec![Ownership { - owner: owner.clone(), - fraction: data.pieces, - }]; - - let item = ReFungibleItemType { - owner: owner_list, - const_data: data.const_data.into_inner(), - variable_data: data.variable_data.into_inner(), - }; - - Self::add_refungible_item(collection, item)?; - } - }; - - Ok(()) - } - - fn add_fungible_item( - collection: &CollectionHandle, - owner: &T::CrossAccountId, - value: u128, - ) -> DispatchResult { - let collection_id = collection.id; - - // Does new owner already have an account? - collection.consume_sload()?; - let balance: u128 = >::get(collection_id, owner.as_sub()).value; - - // Mint - let item = FungibleItemType { - value: balance.checked_add(value).ok_or(Error::::NumOverflow)?, - }; - collection.consume_sstore()?; - >::insert(collection_id, owner.as_sub(), item); - - // Update balance - collection.consume_sload()?; - let new_balance = >::get(collection_id, owner.as_sub()) - .checked_add(value) - .ok_or(Error::::NumOverflow)?; - collection.consume_sstore()?; - >::insert(collection_id, owner.as_sub(), new_balance); - - collection.log(ERC20Events::Transfer { - from: H160::default(), - to: *owner.as_eth(), - value: value.into(), - })?; - Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone())); - Ok(()) - } - - fn add_refungible_item( - collection: &CollectionHandle, - item: ReFungibleItemType, - ) -> DispatchResult { - let collection_id = collection.id; - - let current_index = ::get(collection_id) - .checked_add(1) - .ok_or(Error::::NumOverflow)?; - let itemcopy = item.clone(); - - ensure!(item.owner.len() == 1, Error::::BadCreateRefungibleCall,); - let item_owner = item.owner.first().expect("only one owner is defined"); - - let value = item_owner.fraction; - let owner = item_owner.owner.clone(); - - Self::add_token_index(collection, current_index, &owner)?; - - ::insert(collection_id, current_index); - >::insert(collection_id, current_index, itemcopy); - - // Update balance - let new_balance = >::get(collection_id, owner.as_sub()) - .checked_add(value) - .ok_or(Error::::NumOverflow)?; - >::insert(collection_id, owner.as_sub(), new_balance); - - Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner)); - Ok(()) - } - - fn add_nft_item( - collection: &CollectionHandle, - item: NftItemType, - ) -> DispatchResult { - let collection_id = collection.id; - - let current_index = ::get(collection_id) - .checked_add(1) - .ok_or(Error::::NumOverflow)?; - - let item_owner = item.owner.clone(); - Self::add_token_index(collection, current_index, &item.owner)?; - - ::insert(collection_id, current_index); - >::insert(collection_id, current_index, item); - - // Update balance - let new_balance = >::get(collection_id, item_owner.as_sub()) - .checked_add(1) - .ok_or(Error::::NumOverflow)?; - >::insert(collection_id, item_owner.as_sub(), new_balance); - - collection.log(ERC721Events::Transfer { - from: H160::default(), - to: *item_owner.as_eth(), - token_id: current_index.into(), - })?; - Self::deposit_event(RawEvent::ItemCreated( - collection_id, - current_index, - item_owner, - )); - Ok(()) - } - - fn burn_refungible_item( - collection: &CollectionHandle, - item_id: TokenId, - owner: &T::CrossAccountId, - ) -> DispatchResult { - let collection_id = collection.id; - - let mut token = >::get(collection_id, item_id) - .ok_or(Error::::TokenNotFound)?; - let rft_balance = token - .owner - .iter() - .find(|&i| i.owner == *owner) - .ok_or(Error::::TokenNotFound)?; - Self::remove_token_index(collection, item_id, owner)?; - - // update balance - let new_balance = >::get(collection_id, rft_balance.owner.as_sub()) - .checked_sub(rft_balance.fraction) - .ok_or(Error::::NumOverflow)?; - >::insert(collection_id, rft_balance.owner.as_sub(), new_balance); - - // Re-create owners list with sender removed - let index = token - .owner - .iter() - .position(|i| i.owner == *owner) - .expect("owned item is exists"); - token.owner.remove(index); - let owner_count = token.owner.len(); - - // Burn the token completely if this was the last (only) owner - if owner_count == 0 { - >::remove(collection_id, item_id); - >::remove(collection_id, item_id); - } else { - >::insert(collection_id, item_id, token); - } - - Ok(()) - } - - fn burn_nft_item(collection: &CollectionHandle, item_id: TokenId) -> DispatchResult { - let collection_id = collection.id; - - let item = - >::get(collection_id, item_id).ok_or(Error::::TokenNotFound)?; - Self::remove_token_index(collection, item_id, &item.owner)?; - - // update balance - let new_balance = >::get(collection_id, item.owner.as_sub()) - .checked_sub(1) - .ok_or(Error::::NumOverflow)?; - >::insert(collection_id, item.owner.as_sub(), new_balance); - >::remove(collection_id, item_id); - >::remove(collection_id, item_id); - - collection.log(ERC721Events::Transfer { - from: *item.owner.as_eth(), - to: H160::default(), - token_id: item_id.into(), - })?; - Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id)); - Ok(()) - } - - fn burn_fungible_item( - owner: &T::CrossAccountId, - collection: &CollectionHandle, - value: u128, - ) -> DispatchResult { - let collection_id = collection.id; - - let mut balance = >::get(collection_id, owner.as_sub()); - ensure!(balance.value >= value, Error::::TokenValueNotEnough); - - // update balance - let new_balance = >::get(collection_id, owner.as_sub()) - .checked_sub(value) - .ok_or(Error::::NumOverflow)?; - >::insert(collection_id, owner.as_sub(), new_balance); - - if balance.value - value > 0 { - balance.value -= value; - >::insert(collection_id, owner.as_sub(), balance); - } else { - >::remove(collection_id, owner.as_sub()); - } - - collection.log(ERC20Events::Transfer { - from: *owner.as_eth(), - to: H160::default(), - value: value.into(), - })?; - Ok(()) - } - - pub fn get_collection( - collection_id: CollectionId, - ) -> Result, sp_runtime::DispatchError> { - Ok(>::get(collection_id).ok_or(Error::::CollectionNotFound)?) - } - - fn check_owner_permissions( - target_collection: &CollectionHandle, - subject: &T::AccountId, - ) -> DispatchResult { - ensure!( - *subject == target_collection.owner, - Error::::NoPermission - ); - - Ok(()) - } - - fn is_owner_or_admin_permissions( - collection: &CollectionHandle, - subject: &T::CrossAccountId, - ) -> Result { - collection.consume_sload()?; - Ok(*subject.as_sub() == collection.owner - || >::get(collection.id).contains(subject)) - } - - fn check_owner_or_admin_permissions( - collection: &CollectionHandle, - subject: &T::CrossAccountId, - ) -> DispatchResult { - ensure!( - Self::is_owner_or_admin_permissions(collection, subject)?, - Error::::NoPermission - ); - - Ok(()) - } - - fn owned_amount( - subject: &T::CrossAccountId, - collection: &CollectionHandle, - item_id: TokenId, - ) -> Result, DispatchError> { - collection.consume_sload()?; - Ok(Self::owned_amount_unchecked(subject, collection, item_id)) - } - - fn owned_amount_unchecked( - subject: &T::CrossAccountId, - target_collection: &CollectionHandle, - item_id: TokenId, - ) -> Option { - let collection_id = target_collection.id; - - match target_collection.mode { - CollectionMode::NFT => { - (>::get(collection_id, item_id)?.owner == *subject).then(|| 1) - } - CollectionMode::Fungible(_) => { - Some(>::get(collection_id, &subject.as_sub()).value) - } - CollectionMode::ReFungible => >::get(collection_id, item_id)? - .owner - .iter() - .find(|i| i.owner == *subject) - .map(|i| i.fraction), - } - } - - fn is_item_owner( - subject: &T::CrossAccountId, - target_collection: &CollectionHandle, - item_id: TokenId, - ) -> Result { - Ok(match target_collection.mode { - CollectionMode::Fungible(_) => true, - _ => Self::owned_amount(subject, target_collection, item_id)?.is_some(), - }) - } - - fn check_white_list( - collection: &CollectionHandle, - address: &T::CrossAccountId, - ) -> DispatchResult { - collection.consume_sload()?; - ensure!( - >::contains_key(collection.id, address.as_sub()), - Error::::AddresNotInWhiteList, - ); - Ok(()) - } - - /// Check if token exists. In case of Fungible, check if there is an entry for - /// the owner in fungible balances double map - fn token_exists(target_collection: &CollectionHandle, item_id: TokenId) -> DispatchResult { - let collection_id = target_collection.id; - let exists = match target_collection.mode { - CollectionMode::NFT => >::contains_key(collection_id, item_id), - CollectionMode::Fungible(_) => true, - CollectionMode::ReFungible => { - >::contains_key(collection_id, item_id) - } - }; - - ensure!(exists, Error::::TokenNotFound); - Ok(()) - } - - fn transfer_fungible( - collection: &CollectionHandle, - value: u128, - owner: &T::CrossAccountId, - recipient: &T::CrossAccountId, - ) -> DispatchResult { - let collection_id = collection.id; - - collection.consume_sload()?; - collection.consume_sload()?; - let mut recipient_balance = >::get(collection_id, recipient.as_sub()); - let mut balance = >::get(collection_id, owner.as_sub()); - - recipient_balance.value = recipient_balance - .value - .checked_add(value) - .ok_or(Error::::NumOverflow)?; - balance.value = balance - .value - .checked_sub(value) - .ok_or(Error::::TokenValueTooLow)?; - - // update balanceOf - collection.consume_sstore()?; - collection.consume_sstore()?; - if balance.value != 0 { - >::insert(collection_id, owner.as_sub(), balance.value); - } else { - >::remove(collection_id, owner.as_sub()); - } - >::insert(collection_id, recipient.as_sub(), recipient_balance.value); - - // Reduce or remove sender - collection.consume_sstore()?; - collection.consume_sstore()?; - if balance.value != 0 { - >::insert(collection_id, owner.as_sub(), balance); - } else { - >::remove(collection_id, owner.as_sub()); - } - >::insert(collection_id, recipient.as_sub(), recipient_balance); - - collection.log(ERC20Events::Transfer { - from: *owner.as_eth(), - to: *recipient.as_eth(), - value: value.into(), - })?; - Self::deposit_event(RawEvent::Transfer( - collection.id, - 1, - owner.clone(), - recipient.clone(), - value, - )); - - Ok(()) - } - - fn transfer_refungible( - collection: &CollectionHandle, - item_id: TokenId, - value: u128, - owner: T::CrossAccountId, - new_owner: T::CrossAccountId, - ) -> DispatchResult { - let collection_id = collection.id; - collection.consume_sload()?; - let full_item = >::get(collection_id, item_id) - .ok_or(Error::::TokenNotFound)?; - - let item = full_item - .owner - .iter() - .find(|i| i.owner == owner) - .ok_or(Error::::TokenNotFound)?; - let amount = item.fraction; - - ensure!(amount >= value, Error::::TokenValueTooLow); - - collection.consume_sload()?; - // update balance - let balance_old_owner = >::get(collection_id, item.owner.as_sub()) - .checked_sub(value) - .ok_or(Error::::NumOverflow)?; - collection.consume_sstore()?; - >::insert(collection_id, item.owner.as_sub(), balance_old_owner); - - collection.consume_sload()?; - let balance_new_owner = >::get(collection_id, new_owner.as_sub()) - .checked_add(value) - .ok_or(Error::::NumOverflow)?; - collection.consume_sstore()?; - >::insert(collection_id, new_owner.as_sub(), balance_new_owner); - - let old_owner = item.owner.clone(); - let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner); - - let mut new_full_item = full_item.clone(); - // transfer - if amount == value && !new_owner_has_account { - // change owner - // new owner do not have account - new_full_item - .owner - .iter_mut() - .find(|i| i.owner == owner) - .expect("old owner does present in refungible") - .owner = new_owner.clone(); - collection.consume_sstore()?; - >::insert(collection_id, item_id, new_full_item); - - // update index collection - Self::move_token_index(collection, item_id, &old_owner, &new_owner)?; - } else { - new_full_item - .owner - .iter_mut() - .find(|i| i.owner == owner) - .expect("old owner does present in refungible") - .fraction -= value; - - // separate amount - if new_owner_has_account { - // new owner has account - new_full_item - .owner - .iter_mut() - .find(|i| i.owner == new_owner) - .expect("new owner has account") - .fraction += value; - } else { - // new owner do not have account - new_full_item.owner.push(Ownership { - owner: new_owner.clone(), - fraction: value, - }); - Self::add_token_index(collection, item_id, &new_owner)?; - } - - collection.consume_sstore()?; - >::insert(collection_id, item_id, new_full_item); - } - - Self::deposit_event(RawEvent::Transfer( - collection.id, - item_id, - owner, - new_owner, - amount, - )); - - Ok(()) - } - - fn transfer_nft( - collection: &CollectionHandle, - item_id: TokenId, - sender: T::CrossAccountId, - new_owner: T::CrossAccountId, - ) -> DispatchResult { - let collection_id = collection.id; - collection.consume_sload()?; - let mut item = - >::get(collection_id, item_id).ok_or(Error::::TokenNotFound)?; - - ensure!(sender == item.owner, Error::::MustBeTokenOwner); - - collection.consume_sload()?; - // update balance - let balance_old_owner = >::get(collection_id, item.owner.as_sub()) - .checked_sub(1) - .ok_or(Error::::NumOverflow)?; - collection.consume_sstore()?; - >::insert(collection_id, item.owner.as_sub(), balance_old_owner); - - collection.consume_sload()?; - let balance_new_owner = >::get(collection_id, new_owner.as_sub()) - .checked_add(1) - .ok_or(Error::::NumOverflow)?; - collection.consume_sstore()?; - >::insert(collection_id, new_owner.as_sub(), balance_new_owner); - - // change owner - let old_owner = item.owner.clone(); - item.owner = new_owner.clone(); - collection.consume_sstore()?; - >::insert(collection_id, item_id, item); - - // update index collection - Self::move_token_index(collection, item_id, &old_owner, &new_owner)?; - - collection.log(ERC721Events::Transfer { - from: *sender.as_eth(), - to: *new_owner.as_eth(), - token_id: item_id.into(), - })?; - Self::deposit_event(RawEvent::Transfer( - collection.id, - item_id, - sender, - new_owner, - 1, - )); - - Ok(()) - } - - fn set_re_fungible_variable_data( - collection: &CollectionHandle, - item_id: TokenId, - data: Vec, - ) -> DispatchResult { - let collection_id = collection.id; - let mut item = >::get(collection_id, item_id) - .ok_or(Error::::TokenNotFound)?; - - item.variable_data = data; - - >::insert(collection_id, item_id, item); - - Ok(()) - } - - fn set_nft_variable_data( - collection: &CollectionHandle, - item_id: TokenId, - data: Vec, - ) -> DispatchResult { - let collection_id = collection.id; - let mut item = - >::get(collection_id, item_id).ok_or(Error::::TokenNotFound)?; - - item.variable_data = data; - - >::insert(collection_id, item_id, item); - - Ok(()) - } - - #[allow(dead_code)] - fn init_collection(item: &Collection) { - // check params - assert!( - item.decimal_points <= MAX_DECIMAL_POINTS, - "decimal_points parameter must be lower than MAX_DECIMAL_POINTS" - ); - assert!( - item.name.len() <= 64, - "Collection name can not be longer than 63 char" - ); - assert!( - item.name.len() <= 256, - "Collection description can not be longer than 255 char" - ); - assert!( - item.token_prefix.len() <= 16, - "Token prefix can not be longer than 15 char" - ); - - // Generate next collection ID - let next_id = CreatedCollectionCount::get().checked_add(1).unwrap(); - - CreatedCollectionCount::put(next_id); - } - - #[allow(dead_code)] - fn init_nft_token(collection_id: CollectionId, item: &NftItemType) { - let current_index = ::get(collection_id).checked_add(1).unwrap(); - - Self::add_token_index( - &CollectionHandle::get(collection_id).unwrap(), - current_index, - &item.owner, - ) - .unwrap(); - - ::insert(collection_id, current_index); - - // Update balance - let new_balance = >::get(collection_id, item.owner.as_sub()) - .checked_add(1) - .unwrap(); - >::insert(collection_id, item.owner.as_sub(), new_balance); - } - - #[allow(dead_code)] - fn init_fungible_token( - collection_id: CollectionId, - owner: &T::CrossAccountId, - item: &FungibleItemType, - ) { - let current_index = ::get(collection_id).checked_add(1).unwrap(); - - Self::add_token_index( - &CollectionHandle::get(collection_id).unwrap(), - current_index, - owner, - ) - .unwrap(); - - ::insert(collection_id, current_index); - - // Update balance - let new_balance = >::get(collection_id, owner.as_sub()) - .checked_add(item.value) - .unwrap(); - >::insert(collection_id, owner.as_sub(), new_balance); - } - - #[allow(dead_code)] - fn init_refungible_token( - collection_id: CollectionId, - item: &ReFungibleItemType, - ) { - let current_index = ::get(collection_id).checked_add(1).unwrap(); - - let value = item.owner.first().unwrap().fraction; - let owner = item.owner.first().unwrap().owner.clone(); - - Self::add_token_index( - &CollectionHandle::get(collection_id).unwrap(), - current_index, - &owner, - ) - .unwrap(); - - ::insert(collection_id, current_index); - - // Update balance - let new_balance = >::get(collection_id, &owner.as_sub()) - .checked_add(value) - .unwrap(); - >::insert(collection_id, owner.as_sub(), new_balance); - } - - fn add_token_index( - collection: &CollectionHandle, - item_index: TokenId, - owner: &T::CrossAccountId, - ) -> DispatchResult { - collection.consume_sload()?; - let list_exists = >::contains_key(collection.id, owner.as_sub()); - if list_exists { - collection.consume_sload()?; - let mut list = >::get(collection.id, owner.as_sub()); - - // bound Owned tokens by a single address in collection - let account_items: u32 = list.len() as u32; - ensure!( - account_items < ACCOUNT_TOKEN_OWNERSHIP_LIMIT, - Error::::AddressOwnershipLimitExceeded - ); - - let item_contains = list.contains(&item_index.clone()); - - if !item_contains { - list.push(item_index); - } - - collection.consume_sstore()?; - >::insert(collection.id, owner.as_sub(), list); - } else { - let itm = vec![item_index]; - collection.consume_sstore()?; - >::insert(collection.id, owner.as_sub(), itm); - } - - Ok(()) - } - - fn remove_token_index( - collection: &CollectionHandle, - item_index: TokenId, - owner: &T::CrossAccountId, - ) -> DispatchResult { - collection.consume_sload()?; - let list_exists = >::contains_key(collection.id, owner.as_sub()); - if list_exists { - collection.consume_sload()?; - let mut list = >::get(collection.id, owner.as_sub()); - let item_contains = list.contains(&item_index.clone()); - - if item_contains { - list.retain(|&item| item != item_index); - collection.consume_sstore()?; - >::insert(collection.id, owner.as_sub(), list); - } - } - - Ok(()) - } - - fn move_token_index( - collection: &CollectionHandle, - item_index: TokenId, - old_owner: &T::CrossAccountId, - new_owner: &T::CrossAccountId, - ) -> DispatchResult { - Self::remove_token_index(collection, item_index, old_owner)?; - Self::add_token_index(collection, item_index, new_owner)?; - - Ok(()) - } -} - -sp_api::decl_runtime_apis! { - pub trait NftApi { - /// Used for ethereum integration - fn eth_contract_code(account: H160) -> Option>; } } --- a/pallets/nft/src/sponsorship.rs +++ b/pallets/nft/src/sponsorship.rs @@ -1,7 +1,6 @@ use crate::{ - Config, Call, CollectionById, CreateItemBasket, VariableMetaDataBasket, - ReFungibleTransferBasket, FungibleTransferBasket, NftTransferBasket, CreateItemData, - CollectionMode, + Config, Call, CreateItemBasket, VariableMetaDataBasket, ReFungibleTransferBasket, + FungibleTransferBasket, NftTransferBasket, CreateItemData, CollectionMode, }; use core::marker::PhantomData; use up_sponsorship::SponsorshipHandler; @@ -13,6 +12,7 @@ TokenId, CollectionId, NFT_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, }; +use pallet_common::{CollectionById}; pub struct NftSponsorshipHandler(PhantomData); impl NftSponsorshipHandler { @@ -61,15 +61,10 @@ sponsor_transfer = match collection_mode { CollectionMode::NFT => { // get correct limit - let limit: u32 = if collection_limits.sponsor_transfer_timeout != 0 { - if collection_limits.sponsor_transfer_timeout > NFT_SPONSOR_TRANSFER_TIMEOUT - { - collection_limits.sponsor_transfer_timeout - } else { - NFT_SPONSOR_TRANSFER_TIMEOUT - } + let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 { + collection_limits.sponsor_transfer_timeout } else { - 0 + NFT_SPONSOR_TRANSFER_TIMEOUT }; let mut sponsored = true; @@ -88,18 +83,13 @@ } CollectionMode::Fungible(_) => { // get correct limit - let limit: u32 = if collection_limits.sponsor_transfer_timeout != 0 { - if collection_limits.sponsor_transfer_timeout - > FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT - { - collection_limits.sponsor_transfer_timeout - } else { - FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT - } + let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 { + collection_limits.sponsor_transfer_timeout } else { - 0 + FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT }; + let block_number = >::block_number() as T::BlockNumber; let mut sponsored = true; if FungibleTransferBasket::::contains_key(collection_id, who) { let last_tx_block = FungibleTransferBasket::::get(collection_id, who); @@ -116,16 +106,10 @@ } CollectionMode::ReFungible => { // get correct limit - let limit: u32 = if collection_limits.sponsor_transfer_timeout != 0 { - if collection_limits.sponsor_transfer_timeout - > REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT - { - collection_limits.sponsor_transfer_timeout - } else { - REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT - } + let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 { + collection_limits.sponsor_transfer_timeout } else { - 0 + REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT }; let mut sponsored = true;