difftreelog
fix use new weights
in: master
1 file changed
pallets/nft/src/lib.rsdiffbeforeafterboth1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]7#![cfg_attr(not(feature = "std"), no_std)]8#![allow(9 clippy::too_many_arguments,10 clippy::unnecessary_mut_passed,11 clippy::unused_unit12)]1314extern crate alloc;1516pub use serde::{Serialize, Deserialize};1718pub use frame_support::{19 construct_runtime, decl_event, decl_module, decl_storage, decl_error,20 dispatch::DispatchResult,21 ensure, fail, parameter_types,22 traits::{23 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,24 Randomness, IsSubType, WithdrawReasons,25 },26 weights::{27 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},28 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,29 WeightToFeePolynomial, DispatchClass,30 },31 StorageValue, transactional,32};3334use frame_system::{self as system, ensure_signed};35use sp_core::H160;36use sp_std::vec;37use sp_runtime::sp_std::prelude::Vec;38use core::ops::{Deref, DerefMut};39use nft_data_structs::{40 MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, MAX_REFUNGIBLE_PIECES,41 CUSTOM_DATA_LIMIT, COLLECTION_NUMBER_LIMIT, ACCOUNT_TOKEN_OWNERSHIP_LIMIT,42 VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, COLLECTION_ADMINS_LIMIT,43 OFFCHAIN_SCHEMA_LIMIT, MAX_TOKEN_PREFIX_LENGTH, MAX_COLLECTION_NAME_LENGTH,44 MAX_COLLECTION_DESCRIPTION_LENGTH, AccessMode, Collection, CreateItemData, CollectionLimits,45 CollectionId, CollectionMode, TokenId, SchemaVersion, SponsorshipState, Ownership, NftItemType,46 FungibleItemType, ReFungibleItemType,47};4849#[cfg(test)]50mod mock;5152#[cfg(test)]53mod tests;5455mod eth;56mod sponsorship;57pub use sponsorship::NftSponsorshipHandler;58pub use eth::sponsoring::NftEthSponsorshipHandler;5960pub use eth::NftErcSupport;61pub use eth::account::*;62use eth::erc::{ERC20Events, ERC721Events};6364#[cfg(feature = "runtime-benchmarks")]65mod benchmarking;66pub mod weights;67use weights::WeightInfo;6869decl_error! {70 /// Error for non-fungible-token module.71 pub enum Error for Module<T: Config> {72 /// Total collections bound exceeded.73 TotalCollectionsLimitExceeded,74 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.75 CollectionDecimalPointLimitExceeded,76 /// Collection name can not be longer than 63 char.77 CollectionNameLimitExceeded,78 /// Collection description can not be longer than 255 char.79 CollectionDescriptionLimitExceeded,80 /// Token prefix can not be longer than 15 char.81 CollectionTokenPrefixLimitExceeded,82 /// This collection does not exist.83 CollectionNotFound,84 /// Item not exists.85 TokenNotFound,86 /// Admin not found87 AdminNotFound,88 /// Arithmetic calculation overflow.89 NumOverflow,90 /// Account already has admin role.91 AlreadyAdmin,92 /// You do not own this collection.93 NoPermission,94 /// This address is not set as sponsor, use setCollectionSponsor first.95 ConfirmUnsetSponsorFail,96 /// Collection is not in mint mode.97 PublicMintingNotAllowed,98 /// Sender parameter and item owner must be equal.99 MustBeTokenOwner,100 /// Item balance not enough.101 TokenValueTooLow,102 /// Size of item is too large.103 NftSizeLimitExceeded,104 /// No approve found105 ApproveNotFound,106 /// Requested value more than approved.107 TokenValueNotEnough,108 /// Only approved addresses can call this method.109 ApproveRequired,110 /// Address is not in white list.111 AddresNotInWhiteList,112 /// Number of collection admins bound exceeded.113 CollectionAdminsLimitExceeded,114 /// Owned tokens by a single address bound exceeded.115 AddressOwnershipLimitExceeded,116 /// Length of items properties must be greater than 0.117 EmptyArgument,118 /// const_data exceeded data limit.119 TokenConstDataLimitExceeded,120 /// variable_data exceeded data limit.121 TokenVariableDataLimitExceeded,122 /// Not NFT item data used to mint in NFT collection.123 NotNftDataUsedToMintNftCollectionToken,124 /// Not Fungible item data used to mint in Fungible collection.125 NotFungibleDataUsedToMintFungibleCollectionToken,126 /// Not Re Fungible item data used to mint in Re Fungible collection.127 NotReFungibleDataUsedToMintReFungibleCollectionToken,128 /// Unexpected collection type.129 UnexpectedCollectionType,130 /// Can't store metadata in fungible tokens.131 CantStoreMetadataInFungibleTokens,132 /// Collection token limit exceeded133 CollectionTokenLimitExceeded,134 /// Account token limit exceeded per collection135 AccountTokenLimitExceeded,136 /// Collection limit bounds per collection exceeded137 CollectionLimitBoundsExceeded,138 /// Tried to enable permissions which are only permitted to be disabled139 OwnerPermissionsCantBeReverted,140 /// Schema data size limit bound exceeded141 SchemaDataLimitExceeded,142 /// Maximum refungibility exceeded143 WrongRefungiblePieces,144 /// createRefungible should be called with one owner145 BadCreateRefungibleCall,146 /// Gas limit exceeded147 OutOfGas,148 /// Collection settings not allowing items transferring149 TransferNotAllowed,150 }151}152153#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]154pub struct CollectionHandle<T: Config> {155 pub id: CollectionId,156 collection: Collection<T>,157 recorder: pallet_evm_coder_substrate::SubstrateRecorder<T>,158}159impl<T: Config> CollectionHandle<T> {160 pub fn get_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {161 <CollectionById<T>>::get(id).map(|collection| Self {162 id,163 collection,164 recorder: pallet_evm_coder_substrate::SubstrateRecorder::new(165 eth::collection_id_to_address(id),166 gas_limit,167 ),168 })169 }170 pub fn get(id: CollectionId) -> Option<Self> {171 Self::get_with_gas_limit(id, u64::MAX)172 }173 pub fn log(&self, log: impl evm_coder::ToLog) -> DispatchResult {174 self.recorder.log_sub(log)175 }176 fn consume_gas(&self, gas: u64) -> DispatchResult {177 self.recorder.consume_gas_sub(gas)178 }179 pub fn submit_logs(self) -> DispatchResult {180 self.recorder.submit_logs()181 }182 pub fn save(self) -> DispatchResult {183 self.recorder.submit_logs()?;184 <CollectionById<T>>::insert(self.id, self.collection);185 Ok(())186 }187}188impl<T: Config> Deref for CollectionHandle<T> {189 type Target = Collection<T>;190191 fn deref(&self) -> &Self::Target {192 &self.collection193 }194}195196impl<T: Config> DerefMut for CollectionHandle<T> {197 fn deref_mut(&mut self) -> &mut Self::Target {198 &mut self.collection199 }200}201202pub trait Config: system::Config + pallet_evm_coder_substrate::Config + Sized {203 type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;204205 /// Weight information for extrinsics in this pallet.206 type WeightInfo: WeightInfo;207208 type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;209 type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;210211 type CrossAccountId: CrossAccountId<Self::AccountId>;212 type Currency: Currency<Self::AccountId>;213 type CollectionCreationPrice: Get<214 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,215 >;216 type TreasuryAccountId: Get<Self::AccountId>;217}218219type SelfWeightOf<T> = <T as Config>::WeightInfo;220221trait WeightInfoHelpers: WeightInfo {222 fn transfer() -> Weight {223 Self::transfer_nft()224 .max(Self::transfer_fungible())225 .max(Self::transfer_refungible())226 }227 fn transfer_from() -> Weight {228 Self::transfer_from_nft()229 .max(Self::transfer_from_fungible())230 .max(Self::transfer_from_refungible())231 }232 fn approve() -> Weight {233 // TODO: refungible, fungible234 Self::approve_nft()235 }236 fn set_variable_meta_data(data: u32) -> Weight {237 // TODO: refungible238 Self::set_variable_meta_data_nft(data)239 }240 fn create_item(data: u32) -> Weight {241 Self::create_item_nft(data)242 .max(Self::create_item_fungible())243 .max(Self::create_item_refungible(data))244 }245 fn burn_item() -> Weight {246 // TODO: refungible, fungible247 Self::burn_item_nft()248 }249}250impl<T: WeightInfo> WeightInfoHelpers for T {}251252// # Used definitions253//254// ## User control levels255//256// chain-controlled - key is uncontrolled by user257// i.e autoincrementing index258// can use non-cryptographic hash259// real - key is controlled by user260// but it is hard to generate enough colliding values, i.e owner of signed txs261// can use non-cryptographic hash262// controlled - key is completly controlled by users263// i.e maps with mutable keys264// should use cryptographic hash265//266// ## User control level downgrade reasons267//268// ?1 - chain-controlled -> controlled269// collections/tokens can be destroyed, resulting in massive holes270// ?2 - chain-controlled -> controlled271// same as ?1, but can be only added, resulting in easier exploitation272// ?3 - real -> controlled273// no confirmation required, so addresses can be easily generated274decl_storage! {275 trait Store for Module<T: Config> as Nft {276277 //#region Private members278 /// Id of next collection279 CreatedCollectionCount: u32;280 /// Used for migrations281 ChainVersion: u64;282 /// Id of last collection token283 /// Collection id (controlled?1)284 ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;285 //#endregion286287 //#region Bound counters288 /// Amount of collections destroyed, used for total amount tracking with289 /// CreatedCollectionCount290 DestroyedCollectionCount: u32;291 /// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)292 /// Account id (real)293 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;294 //#endregion295296 //#region Basic collections297 /// Collection info298 /// Collection id (controlled?1)299 pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;300 /// List of collection admins301 /// Collection id (controlled?2)302 pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::CrossAccountId>;303 /// Whitelisted collection users304 /// Collection id (controlled?2), user id (controlled?3)305 pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;306 //#endregion307308 /// How many of collection items user have309 /// Collection id (controlled?2), account id (real)310 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;311312 /// Amount of items which spender can transfer out of owners account (via transferFrom)313 /// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))314 /// TODO: Off chain worker should remove from this map when token gets removed315 pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;316317 //#region Item collections318 /// Collection id (controlled?2), token id (controlled?1)319 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::CrossAccountId>>;320 /// Collection id (controlled?2), owner (controlled?2)321 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;322 /// Collection id (controlled?2), token id (controlled?1)323 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::CrossAccountId>>;324 //#endregion325326 //#region Index list327 /// Collection id (controlled?2), tokens owner (controlled?2)328 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;329 //#endregion330331 //#region Tokens transfer rate limit baskets332 /// (Collection id (controlled?2), who created (real))333 /// TODO: Off chain worker should remove from this map when collection gets removed334 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;335 /// Collection id (controlled?2), token id (controlled?2)336 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;337 /// Collection id (controlled?2), owning user (real)338 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;339 /// Collection id (controlled?2), token id (controlled?2)340 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;341 //#endregion342343 /// Variable metadata sponsoring344 /// Collection id (controlled?2), token id (controlled?2)345 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;346 }347 add_extra_genesis {348 build(|config: &GenesisConfig<T>| {349 // Modification of storage350 for (_num, _c) in &config.collection_id {351 <Module<T>>::init_collection(_c);352 }353354 for (_num, _c, _i) in &config.nft_item_id {355 <Module<T>>::init_nft_token(*_c, _i);356 }357358 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {359 <Module<T>>::init_fungible_token(*collection_id, &T::CrossAccountId::from_sub(account_id.clone()), fungible_item);360 }361362 for (_num, _c, _i) in &config.refungible_item_id {363 <Module<T>>::init_refungible_token(*_c, _i);364 }365 })366 }367}368369decl_event!(370 pub enum Event<T>371 where372 AccountId = <T as frame_system::Config>::AccountId,373 CrossAccountId = <T as Config>::CrossAccountId,374 {375 /// New collection was created376 ///377 /// # Arguments378 ///379 /// * collection_id: Globally unique identifier of newly created collection.380 ///381 /// * mode: [CollectionMode] converted into u8.382 ///383 /// * account_id: Collection owner.384 CollectionCreated(CollectionId, u8, AccountId),385386 /// New item was created.387 ///388 /// # Arguments389 ///390 /// * collection_id: Id of the collection where item was created.391 ///392 /// * item_id: Id of an item. Unique within the collection.393 ///394 /// * recipient: Owner of newly created item395 ItemCreated(CollectionId, TokenId, CrossAccountId),396397 /// Collection item was burned.398 ///399 /// # Arguments400 ///401 /// collection_id.402 ///403 /// item_id: Identifier of burned NFT.404 ItemDestroyed(CollectionId, TokenId),405406 /// Item was transferred407 ///408 /// * collection_id: Id of collection to which item is belong409 ///410 /// * item_id: Id of an item411 ///412 /// * sender: Original owner of item413 ///414 /// * recipient: New owner of item415 ///416 /// * amount: Always 1 for NFT417 Transfer(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),418419 /// * collection_id420 ///421 /// * item_id422 ///423 /// * sender424 ///425 /// * spender426 ///427 /// * amount428 Approved(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),429 }430);431432decl_module! {433 pub struct Module<T: Config> for enum Call434 where435 origin: T::Origin436 {437 fn deposit_event() = default;438 const CollectionAdminsLimit: u64 = COLLECTION_ADMINS_LIMIT;439 type Error = Error<T>;440441 fn on_initialize(_now: T::BlockNumber) -> Weight {442 0443 }444445 /// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.446 ///447 /// # Permissions448 ///449 /// * Anyone.450 ///451 /// # Arguments452 ///453 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.454 ///455 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.456 ///457 /// * token_prefix: UTF-8 string with token prefix.458 ///459 /// * mode: [CollectionMode] collection type and type dependent data.460 // returns collection ID461 #[weight = <SelfWeightOf<T>>::create_collection()]462 #[transactional]463 pub fn create_collection(origin,464 collection_name: Vec<u16>,465 collection_description: Vec<u16>,466 token_prefix: Vec<u8>,467 mode: CollectionMode) -> DispatchResult {468469 // Anyone can create a collection470 let who = ensure_signed(origin)?;471472 // Take a (non-refundable) deposit of collection creation473 let mut imbalance = <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();474 imbalance.subsume(<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(475 &T::TreasuryAccountId::get(),476 T::CollectionCreationPrice::get(),477 ));478 <T as Config>::Currency::settle(479 &who,480 imbalance,481 WithdrawReasons::TRANSFER,482 ExistenceRequirement::KeepAlive,483 ).map_err(|_| Error::<T>::NoPermission)?;484485 let decimal_points = match mode {486 CollectionMode::Fungible(points) => points,487 _ => 0488 };489490 let created_count = CreatedCollectionCount::get();491 let destroyed_count = DestroyedCollectionCount::get();492493 // bound Total number of collections494 ensure!(created_count - destroyed_count < COLLECTION_NUMBER_LIMIT, Error::<T>::TotalCollectionsLimitExceeded);495496 // check params497 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);498 ensure!(collection_name.len() <= MAX_COLLECTION_NAME_LENGTH, Error::<T>::CollectionNameLimitExceeded);499 ensure!(collection_description.len() <= MAX_COLLECTION_DESCRIPTION_LENGTH, Error::<T>::CollectionDescriptionLimitExceeded);500 ensure!(token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH, Error::<T>::CollectionTokenPrefixLimitExceeded);501502 // Generate next collection ID503 let next_id = created_count504 .checked_add(1)505 .ok_or(Error::<T>::NumOverflow)?;506507 CreatedCollectionCount::put(next_id);508509 let limits = CollectionLimits {510 sponsored_data_size: CUSTOM_DATA_LIMIT,511 ..Default::default()512 };513514 // Create new collection515 let new_collection = Collection {516 owner: who.clone(),517 name: collection_name,518 mode: mode.clone(),519 mint_mode: false,520 access: AccessMode::Normal,521 description: collection_description,522 decimal_points,523 token_prefix,524 offchain_schema: Vec::new(),525 schema_version: SchemaVersion::ImageURL,526 sponsorship: SponsorshipState::Disabled,527 variable_on_chain_schema: Vec::new(),528 const_on_chain_schema: Vec::new(),529 limits,530 transfers_enabled: true,531 };532533 // Add new collection to map534 <CollectionById<T>>::insert(next_id, new_collection);535536 // call event537 Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.id(), who));538539 Ok(())540 }541542 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.543 ///544 /// # Permissions545 ///546 /// * Collection Owner.547 ///548 /// # Arguments549 ///550 /// * collection_id: collection to destroy.551 #[weight = <SelfWeightOf<T>>::destroy_collection()]552 #[transactional]553 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {554555 let sender = ensure_signed(origin)?;556 let collection = Self::get_collection(collection_id)?;557 Self::check_owner_permissions(&collection, &sender)?;558 if !collection.limits.owner_can_destroy {559 fail!(Error::<T>::NoPermission);560 }561562 <AddressTokens<T>>::remove_prefix(collection_id, None);563 <Allowances<T>>::remove_prefix(collection_id, None);564 <Balance<T>>::remove_prefix(collection_id, None);565 <ItemListIndex>::remove(collection_id);566 <AdminList<T>>::remove(collection_id);567 <CollectionById<T>>::remove(collection_id);568 <WhiteList<T>>::remove_prefix(collection_id, None);569570 <NftItemList<T>>::remove_prefix(collection_id, None);571 <FungibleItemList<T>>::remove_prefix(collection_id, None);572 <ReFungibleItemList<T>>::remove_prefix(collection_id, None);573574 <NftTransferBasket<T>>::remove_prefix(collection_id, None);575 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);576 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id, None);577578 <VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);579580 DestroyedCollectionCount::put(DestroyedCollectionCount::get()581 .checked_add(1)582 .ok_or(Error::<T>::NumOverflow)?);583584 Ok(())585 }586587 /// Add an address to white list.588 ///589 /// # Permissions590 ///591 /// * Collection Owner592 /// * Collection Admin593 ///594 /// # Arguments595 ///596 /// * collection_id.597 ///598 /// * address.599 #[weight = <SelfWeightOf<T>>::add_to_white_list()]600 #[transactional]601 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{602603 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);604 let collection = Self::get_collection(collection_id)?;605606 Self::toggle_white_list_internal(607 &sender,608 &collection,609 &address,610 true,611 )?;612613 Ok(())614 }615616 /// Remove an address from white list.617 ///618 /// # Permissions619 ///620 /// * Collection Owner621 /// * Collection Admin622 ///623 /// # Arguments624 ///625 /// * collection_id.626 ///627 /// * address.628 #[weight = <SelfWeightOf<T>>::remove_from_white_list()]629 #[transactional]630 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{631632 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);633 let collection = Self::get_collection(collection_id)?;634635 Self::toggle_white_list_internal(636 &sender,637 &collection,638 &address,639 false,640 )?;641642 Ok(())643 }644645 /// Toggle between normal and white list access for the methods with access for `Anyone`.646 ///647 /// # Permissions648 ///649 /// * Collection Owner.650 ///651 /// # Arguments652 ///653 /// * collection_id.654 ///655 /// * mode: [AccessMode]656 #[weight = <SelfWeightOf<T>>::set_public_access_mode()]657 #[transactional]658 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult659 {660 let sender = ensure_signed(origin)?;661662 let mut target_collection = Self::get_collection(collection_id)?;663 Self::check_owner_permissions(&target_collection, &sender)?;664 target_collection.access = mode;665 target_collection.save()666 }667668 /// Allows Anyone to create tokens if:669 /// * White List is enabled, and670 /// * Address is added to white list, and671 /// * This method was called with True parameter672 ///673 /// # Permissions674 /// * Collection Owner675 ///676 /// # Arguments677 ///678 /// * collection_id.679 ///680 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.681 #[weight = <SelfWeightOf<T>>::set_mint_permission()]682 #[transactional]683 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult684 {685 let sender = ensure_signed(origin)?;686687 let mut target_collection = Self::get_collection(collection_id)?;688 Self::check_owner_permissions(&target_collection, &sender)?;689 target_collection.mint_mode = mint_permission;690 target_collection.save()691 }692693 /// Change the owner of the collection.694 ///695 /// # Permissions696 ///697 /// * Collection Owner.698 ///699 /// # Arguments700 ///701 /// * collection_id.702 ///703 /// * new_owner.704 #[weight = <SelfWeightOf<T>>::change_collection_owner()]705 #[transactional]706 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {707708 let sender = ensure_signed(origin)?;709 let mut target_collection = Self::get_collection(collection_id)?;710 Self::check_owner_permissions(&target_collection, &sender)?;711 target_collection.owner = new_owner;712 target_collection.save()713 }714715 /// Adds an admin of the Collection.716 /// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership.717 ///718 /// # Permissions719 ///720 /// * Collection Owner.721 /// * Collection Admin.722 ///723 /// # Arguments724 ///725 /// * collection_id: ID of the Collection to add admin for.726 ///727 /// * new_admin_id: Address of new admin to add.728 #[weight = <SelfWeightOf<T>>::add_collection_admin()]729 #[transactional]730 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {731 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);732 let collection = Self::get_collection(collection_id)?;733 Self::check_owner_or_admin_permissions(&collection, &sender)?;734 let mut admin_arr = <AdminList<T>>::get(collection_id);735736 match admin_arr.binary_search(&new_admin_id) {737 Ok(_) => {},738 Err(idx) => {739 ensure!(admin_arr.len() < COLLECTION_ADMINS_LIMIT as usize, Error::<T>::CollectionAdminsLimitExceeded);740 admin_arr.insert(idx, new_admin_id);741 <AdminList<T>>::insert(collection_id, admin_arr);742 }743 }744 Ok(())745 }746747 /// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.748 ///749 /// # Permissions750 ///751 /// * Collection Owner.752 /// * Collection Admin.753 ///754 /// # Arguments755 ///756 /// * collection_id: ID of the Collection to remove admin for.757 ///758 /// * account_id: Address of admin to remove.759 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]760 #[transactional]761 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {762 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);763 let collection = Self::get_collection(collection_id)?;764 Self::check_owner_or_admin_permissions(&collection, &sender)?;765 let mut admin_arr = <AdminList<T>>::get(collection_id);766767 if let Ok(idx) = admin_arr.binary_search(&account_id) {768 admin_arr.remove(idx);769 <AdminList<T>>::insert(collection_id, admin_arr);770 }771 Ok(())772 }773774 /// # Permissions775 ///776 /// * Collection Owner777 ///778 /// # Arguments779 ///780 /// * collection_id.781 ///782 /// * new_sponsor.783 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]784 #[transactional]785 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {786 let sender = ensure_signed(origin)?;787 let mut target_collection = Self::get_collection(collection_id)?;788 Self::check_owner_permissions(&target_collection, &sender)?;789790 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);791 target_collection.save()792 }793794 /// # Permissions795 ///796 /// * Sponsor.797 ///798 /// # Arguments799 ///800 /// * collection_id.801 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]802 #[transactional]803 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {804 let sender = ensure_signed(origin)?;805806 let mut target_collection = Self::get_collection(collection_id)?;807 ensure!(808 target_collection.sponsorship.pending_sponsor() == Some(&sender),809 Error::<T>::ConfirmUnsetSponsorFail810 );811812 target_collection.sponsorship = SponsorshipState::Confirmed(sender);813 target_collection.save()814 }815816 /// Switch back to pay-per-own-transaction model.817 ///818 /// # Permissions819 ///820 /// * Collection owner.821 ///822 /// # Arguments823 ///824 /// * collection_id.825 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]826 #[transactional]827 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {828 let sender = ensure_signed(origin)?;829830 let mut target_collection = Self::get_collection(collection_id)?;831 Self::check_owner_permissions(&target_collection, &sender)?;832833 target_collection.sponsorship = SponsorshipState::Disabled;834 target_collection.save()835 }836837 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.838 ///839 /// # Permissions840 ///841 /// * Collection Owner.842 /// * Collection Admin.843 /// * Anyone if844 /// * White List is enabled, and845 /// * Address is added to white list, and846 /// * MintPermission is enabled (see SetMintPermission method)847 ///848 /// # Arguments849 ///850 /// * collection_id: ID of the collection.851 ///852 /// * owner: Address, initial owner of the NFT.853 ///854 /// * data: Token data to store on chain.855 // #[weight =856 // (130_000_000 as Weight)857 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))858 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))859 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]860861 #[weight = <SelfWeightOf<T>>::create_item(data.data_size() as u32)]862 #[transactional]863 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult {864 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);865 let collection = Self::get_collection(collection_id)?;866867 Self::create_item_internal(&sender, &collection, &owner, data)?;868869 collection.submit_logs()870 }871872 /// This method creates multiple items in a collection created with CreateCollection method.873 ///874 /// # Permissions875 ///876 /// * Collection Owner.877 /// * Collection Admin.878 /// * Anyone if879 /// * White List is enabled, and880 /// * Address is added to white list, and881 /// * MintPermission is enabled (see SetMintPermission method)882 ///883 /// # Arguments884 ///885 /// * collection_id: ID of the collection.886 ///887 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].888 ///889 /// * owner: Address, initial owner of the NFT.890 #[weight = <SelfWeightOf<T>>::create_item(items_data.iter()891 .map(|data| { data.data_size() as u32 })892 .sum())]893 #[transactional]894 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResult {895896 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);897 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);898 let collection = Self::get_collection(collection_id)?;899900 Self::create_multiple_items_internal(&sender, &collection, &owner, items_data)?;901902 collection.submit_logs()903 }904905 // TODO! transaction weight906907 /// Set transfers_enabled value for particular collection908 ///909 /// # Permissions910 ///911 /// * Collection Owner.912 ///913 /// # Arguments914 ///915 /// * collection_id: ID of the collection.916 ///917 /// * value: New flag value.918 #[weight = <SelfWeightOf<T>>::burn_item()]919 #[transactional]920 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {921922 let sender = ensure_signed(origin)?;923 let mut target_collection = Self::get_collection(collection_id)?;924925 Self::check_owner_permissions(&target_collection, &sender)?;926927 target_collection.transfers_enabled = value;928 target_collection.save()929 }930931 /// Destroys a concrete instance of NFT.932 ///933 /// # Permissions934 ///935 /// * Collection Owner.936 /// * Collection Admin.937 /// * Current NFT Owner.938 ///939 /// # Arguments940 ///941 /// * collection_id: ID of the collection.942 ///943 /// * item_id: ID of NFT to burn.944 #[weight = <SelfWeightOf<T>>::burn_item()]945 #[transactional]946 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {947948 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);949 let target_collection = Self::get_collection(collection_id)?;950951 Self::burn_item_internal(&sender, &target_collection, item_id, value)?;952953 target_collection.submit_logs()954 }955956 /// Change ownership of the token.957 ///958 /// # Permissions959 ///960 /// * Collection Owner961 /// * Collection Admin962 /// * Current NFT owner963 ///964 /// # Arguments965 ///966 /// * recipient: Address of token recipient.967 ///968 /// * collection_id.969 ///970 /// * item_id: ID of the item971 /// * Non-Fungible Mode: Required.972 /// * Fungible Mode: Ignored.973 /// * Re-Fungible Mode: Required.974 ///975 /// * value: Amount to transfer.976 /// * Non-Fungible Mode: Ignored977 /// * Fungible Mode: Must specify transferred amount978 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)979 #[weight = <SelfWeightOf<T>>::transfer()]980 #[transactional]981 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {982 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);983 let collection = Self::get_collection(collection_id)?;984985 Self::transfer_internal(&sender, &recipient, &collection, item_id, value)?;986987 collection.submit_logs()988 }989990 /// Set, change, or remove approved address to transfer the ownership of the NFT.991 ///992 /// # Permissions993 ///994 /// * Collection Owner995 /// * Collection Admin996 /// * Current NFT owner997 ///998 /// # Arguments999 ///1000 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1001 ///1002 /// * collection_id.1003 ///1004 /// * item_id: ID of the item.1005 #[weight = <SelfWeightOf<T>>::approve()]1006 #[transactional]1007 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {1008 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1009 let collection = Self::get_collection(collection_id)?;10101011 Self::approve_internal(&sender, &spender, &collection, item_id, amount)?;10121013 collection.submit_logs()1014 }10151016 /// 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.1017 ///1018 /// # Permissions1019 /// * Collection Owner1020 /// * Collection Admin1021 /// * Current NFT owner1022 /// * Address approved by current NFT owner1023 ///1024 /// # Arguments1025 ///1026 /// * from: Address that owns token.1027 ///1028 /// * recipient: Address of token recipient.1029 ///1030 /// * collection_id.1031 ///1032 /// * item_id: ID of the item.1033 ///1034 /// * value: Amount to transfer.1035 #[weight = <SelfWeightOf<T>>::transfer_from()]1036 #[transactional]1037 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {1038 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1039 let collection = Self::get_collection(collection_id)?;10401041 Self::transfer_from_internal(&sender, &from, &recipient, &collection, item_id, value)?;10421043 collection.submit_logs()1044 }1045 // #[weight = 0]1046 // // let no_perm_mes = "You do not have permissions to modify this collection";1047 // // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1048 // // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1049 // // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);10501051 // // // on_nft_received call10521053 // // Self::transfer(origin, collection_id, item_id, new_owner)?;10541055 // Ok(())1056 // }10571058 /// Set off-chain data schema.1059 ///1060 /// # Permissions1061 ///1062 /// * Collection Owner1063 /// * Collection Admin1064 ///1065 /// # Arguments1066 ///1067 /// * collection_id.1068 ///1069 /// * schema: String representing the offchain data schema.1070 #[weight = <SelfWeightOf<T>>::set_variable_meta_data(data.len() as u32)]1071 #[transactional]1072 pub fn set_variable_meta_data (1073 origin,1074 collection_id: CollectionId,1075 item_id: TokenId,1076 data: Vec<u8>1077 ) -> DispatchResult {1078 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);10791080 let collection = Self::get_collection(collection_id)?;10811082 Self::set_variable_meta_data_internal(&sender, &collection, item_id, data)?;10831084 Ok(())1085 }10861087 /// Set schema standard1088 /// ImageURL1089 /// Unique1090 ///1091 /// # Permissions1092 ///1093 /// * Collection Owner1094 /// * Collection Admin1095 ///1096 /// # Arguments1097 ///1098 /// * collection_id.1099 ///1100 /// * schema: SchemaVersion: enum1101 #[weight = <SelfWeightOf<T>>::set_schema_version()]1102 #[transactional]1103 pub fn set_schema_version(1104 origin,1105 collection_id: CollectionId,1106 version: SchemaVersion1107 ) -> DispatchResult {1108 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1109 let mut target_collection = Self::get_collection(collection_id)?;1110 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;1111 target_collection.schema_version = version;1112 target_collection.save()1113 }11141115 /// Set off-chain data schema.1116 ///1117 /// # Permissions1118 ///1119 /// * Collection Owner1120 /// * Collection Admin1121 ///1122 /// # Arguments1123 ///1124 /// * collection_id.1125 ///1126 /// * schema: String representing the offchain data schema.1127 #[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]1128 #[transactional]1129 pub fn set_offchain_schema(1130 origin,1131 collection_id: CollectionId,1132 schema: Vec<u8>1133 ) -> DispatchResult {1134 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1135 let mut target_collection = Self::get_collection(collection_id)?;1136 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11371138 // check schema limit1139 ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");11401141 target_collection.offchain_schema = schema;1142 target_collection.save()1143 }11441145 /// Set const on-chain data schema.1146 ///1147 /// # Permissions1148 ///1149 /// * Collection Owner1150 /// * Collection Admin1151 ///1152 /// # Arguments1153 ///1154 /// * collection_id.1155 ///1156 /// * schema: String representing the const on-chain data schema.1157 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1158 #[transactional]1159 pub fn set_const_on_chain_schema (1160 origin,1161 collection_id: CollectionId,1162 schema: Vec<u8>1163 ) -> DispatchResult {1164 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1165 let mut target_collection = Self::get_collection(collection_id)?;1166 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11671168 // check schema limit1169 ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");11701171 target_collection.const_on_chain_schema = schema;1172 target_collection.save()1173 }11741175 /// Set variable on-chain data schema.1176 ///1177 /// # Permissions1178 ///1179 /// * Collection Owner1180 /// * Collection Admin1181 ///1182 /// # Arguments1183 ///1184 /// * collection_id.1185 ///1186 /// * schema: String representing the variable on-chain data schema.1187 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1188 #[transactional]1189 pub fn set_variable_on_chain_schema (1190 origin,1191 collection_id: CollectionId,1192 schema: Vec<u8>1193 ) -> DispatchResult {1194 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1195 let mut target_collection = Self::get_collection(collection_id)?;1196 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11971198 // check schema limit1199 ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");12001201 target_collection.variable_on_chain_schema = schema;1202 target_collection.save()1203 }12041205 #[weight = <SelfWeightOf<T>>::set_collection_limits()]1206 #[transactional]1207 pub fn set_collection_limits(1208 origin,1209 collection_id: u32,1210 new_limits: CollectionLimits<T::BlockNumber>,1211 ) -> DispatchResult {1212 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1213 let mut target_collection = Self::get_collection(collection_id)?;1214 Self::check_owner_permissions(&target_collection, sender.as_sub())?;1215 let old_limits = &target_collection.limits;12161217 // collection bounds1218 ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1219 new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP &&1220 new_limits.sponsored_data_size <= CUSTOM_DATA_LIMIT,1221 Error::<T>::CollectionLimitBoundsExceeded);12221223 // token_limit check prev1224 ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1225 ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);12261227 ensure!(1228 (old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1229 (old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1230 Error::<T>::OwnerPermissionsCantBeReverted,1231 );12321233 target_collection.limits = new_limits;12341235 target_collection.save()1236 }1237 }1238}12391240impl<T: Config> Module<T> {1241 pub fn create_item_internal(1242 sender: &T::CrossAccountId,1243 collection: &CollectionHandle<T>,1244 owner: &T::CrossAccountId,1245 data: CreateItemData,1246 ) -> DispatchResult {1247 Self::can_create_items_in_collection(collection, sender, owner, 1)?;1248 Self::validate_create_item_args(collection, &data)?;1249 Self::create_item_no_validation(collection, owner, data)?;12501251 Ok(())1252 }12531254 pub fn transfer_internal(1255 sender: &T::CrossAccountId,1256 recipient: &T::CrossAccountId,1257 target_collection: &CollectionHandle<T>,1258 item_id: TokenId,1259 value: u128,1260 ) -> DispatchResult {1261 target_collection.consume_gas(2000000)?;1262 // Limits check1263 Self::is_correct_transfer(target_collection, recipient)?;12641265 // Transfer permissions check1266 ensure!(1267 Self::is_item_owner(sender, target_collection, item_id)1268 || Self::is_owner_or_admin_permissions(target_collection, sender),1269 Error::<T>::NoPermission1270 );12711272 if target_collection.access == AccessMode::WhiteList {1273 Self::check_white_list(target_collection, sender)?;1274 Self::check_white_list(target_collection, recipient)?;1275 }12761277 match target_collection.mode {1278 CollectionMode::NFT => Self::transfer_nft(1279 target_collection,1280 item_id,1281 sender.clone(),1282 recipient.clone(),1283 )?,1284 CollectionMode::Fungible(_) => {1285 Self::transfer_fungible(target_collection, value, sender, recipient)?1286 }1287 CollectionMode::ReFungible => Self::transfer_refungible(1288 target_collection,1289 item_id,1290 value,1291 sender.clone(),1292 recipient.clone(),1293 )?,1294 _ => (),1295 };12961297 Self::deposit_event(RawEvent::Transfer(1298 target_collection.id,1299 item_id,1300 sender.clone(),1301 recipient.clone(),1302 value,1303 ));13041305 Ok(())1306 }13071308 pub fn approve_internal(1309 sender: &T::CrossAccountId,1310 spender: &T::CrossAccountId,1311 collection: &CollectionHandle<T>,1312 item_id: TokenId,1313 amount: u128,1314 ) -> DispatchResult {1315 collection.consume_gas(2000000)?;1316 Self::token_exists(collection, item_id)?;13171318 // Transfer permissions check1319 let bypasses_limits = collection.limits.owner_can_transfer1320 && Self::is_owner_or_admin_permissions(collection, sender);13211322 let allowance_limit = if bypasses_limits {1323 None1324 } else if let Some(amount) = Self::owned_amount(sender, collection, item_id) {1325 Some(amount)1326 } else {1327 fail!(Error::<T>::NoPermission);1328 };13291330 if collection.access == AccessMode::WhiteList {1331 Self::check_white_list(collection, sender)?;1332 Self::check_white_list(collection, spender)?;1333 }13341335 let allowance: u128 = amount1336 .checked_add(<Allowances<T>>::get(1337 collection.id,1338 (item_id, sender.as_sub(), spender.as_sub()),1339 ))1340 .ok_or(Error::<T>::NumOverflow)?;1341 if let Some(limit) = allowance_limit {1342 ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1343 }1344 <Allowances<T>>::insert(1345 collection.id,1346 (item_id, sender.as_sub(), spender.as_sub()),1347 allowance,1348 );13491350 if matches!(collection.mode, CollectionMode::NFT) {1351 // TODO: NFT: only one owner may exist for token in ERC7211352 collection.log(ERC721Events::Approval {1353 owner: *sender.as_eth(),1354 approved: *spender.as_eth(),1355 token_id: item_id.into(),1356 })?;1357 }13581359 if matches!(collection.mode, CollectionMode::Fungible(_)) {1360 // TODO: NFT: only one owner may exist for token in ERC201361 collection.log(ERC20Events::Approval {1362 owner: *sender.as_eth(),1363 spender: *spender.as_eth(),1364 value: allowance.into(),1365 })?;1366 }13671368 Self::deposit_event(RawEvent::Approved(1369 collection.id,1370 item_id,1371 sender.clone(),1372 spender.clone(),1373 allowance,1374 ));1375 Ok(())1376 }13771378 pub fn transfer_from_internal(1379 sender: &T::CrossAccountId,1380 from: &T::CrossAccountId,1381 recipient: &T::CrossAccountId,1382 collection: &CollectionHandle<T>,1383 item_id: TokenId,1384 amount: u128,1385 ) -> DispatchResult {1386 collection.consume_gas(2000000)?;1387 // Check approval1388 let approval: u128 =1389 <Allowances<T>>::get(collection.id, (item_id, from.as_sub(), sender.as_sub()));13901391 // Limits check1392 Self::is_correct_transfer(collection, recipient)?;13931394 // Transfer permissions check1395 ensure!(1396 approval >= amount1397 || (collection.limits.owner_can_transfer1398 && Self::is_owner_or_admin_permissions(collection, sender)),1399 Error::<T>::NoPermission1400 );14011402 if collection.access == AccessMode::WhiteList {1403 Self::check_white_list(collection, sender)?;1404 Self::check_white_list(collection, recipient)?;1405 }14061407 // Reduce approval by transferred amount or remove if remaining approval drops to 01408 let allowance = approval.saturating_sub(amount);1409 if allowance > 0 {1410 <Allowances<T>>::insert(1411 collection.id,1412 (item_id, from.as_sub(), sender.as_sub()),1413 allowance,1414 );1415 } else {1416 <Allowances<T>>::remove(collection.id, (item_id, from.as_sub(), sender.as_sub()));1417 }14181419 match collection.mode {1420 CollectionMode::NFT => {1421 Self::transfer_nft(collection, item_id, from.clone(), recipient.clone())?1422 }1423 CollectionMode::Fungible(_) => {1424 Self::transfer_fungible(collection, amount, from, recipient)?1425 }1426 CollectionMode::ReFungible => Self::transfer_refungible(1427 collection,1428 item_id,1429 amount,1430 from.clone(),1431 recipient.clone(),1432 )?,1433 _ => (),1434 };14351436 if matches!(collection.mode, CollectionMode::Fungible(_)) {1437 collection.log(ERC20Events::Approval {1438 owner: *from.as_eth(),1439 spender: *sender.as_eth(),1440 value: allowance.into(),1441 })?;1442 }14431444 Ok(())1445 }14461447 pub fn set_variable_meta_data_internal(1448 sender: &T::CrossAccountId,1449 collection: &CollectionHandle<T>,1450 item_id: TokenId,1451 data: Vec<u8>,1452 ) -> DispatchResult {1453 Self::token_exists(collection, item_id)?;14541455 ensure!(1456 CUSTOM_DATA_LIMIT >= data.len() as u32,1457 Error::<T>::TokenVariableDataLimitExceeded1458 );14591460 // Modify permissions check1461 ensure!(1462 Self::is_item_owner(sender, collection, item_id)1463 || Self::is_owner_or_admin_permissions(collection, sender),1464 Error::<T>::NoPermission1465 );14661467 match collection.mode {1468 CollectionMode::NFT => Self::set_nft_variable_data(collection, item_id, data)?,1469 CollectionMode::ReFungible => {1470 Self::set_re_fungible_variable_data(collection, item_id, data)?1471 }1472 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1473 _ => fail!(Error::<T>::UnexpectedCollectionType),1474 };14751476 Ok(())1477 }14781479 pub fn create_multiple_items_internal(1480 sender: &T::CrossAccountId,1481 collection: &CollectionHandle<T>,1482 owner: &T::CrossAccountId,1483 items_data: Vec<CreateItemData>,1484 ) -> DispatchResult {1485 Self::can_create_items_in_collection(collection, sender, owner, items_data.len() as u32)?;14861487 for data in &items_data {1488 Self::validate_create_item_args(collection, data)?;1489 }1490 for data in &items_data {1491 Self::create_item_no_validation(collection, owner, data.clone())?;1492 }14931494 Ok(())1495 }14961497 pub fn burn_item_internal(1498 sender: &T::CrossAccountId,1499 collection: &CollectionHandle<T>,1500 item_id: TokenId,1501 value: u128,1502 ) -> DispatchResult {1503 ensure!(1504 Self::is_item_owner(sender, collection, item_id)1505 || (collection.limits.owner_can_transfer1506 && Self::is_owner_or_admin_permissions(collection, sender)),1507 Error::<T>::NoPermission1508 );15091510 if collection.access == AccessMode::WhiteList {1511 Self::check_white_list(collection, sender)?;1512 }15131514 match collection.mode {1515 CollectionMode::NFT => Self::burn_nft_item(collection, item_id)?,1516 CollectionMode::Fungible(_) => Self::burn_fungible_item(sender, collection, value)?,1517 CollectionMode::ReFungible => Self::burn_refungible_item(collection, item_id, sender)?,1518 _ => (),1519 };15201521 Ok(())1522 }15231524 pub fn toggle_white_list_internal(1525 sender: &T::CrossAccountId,1526 collection: &CollectionHandle<T>,1527 address: &T::CrossAccountId,1528 whitelisted: bool,1529 ) -> DispatchResult {1530 Self::check_owner_or_admin_permissions(collection, sender)?;15311532 if whitelisted {1533 <WhiteList<T>>::insert(collection.id, address.as_sub(), true);1534 } else {1535 <WhiteList<T>>::remove(collection.id, address.as_sub());1536 }15371538 Ok(())1539 }15401541 fn is_correct_transfer(1542 collection: &CollectionHandle<T>,1543 recipient: &T::CrossAccountId,1544 ) -> DispatchResult {1545 let collection_id = collection.id;15461547 // check token limit and account token limit1548 let account_items: u32 =1549 <AddressTokens<T>>::get(collection_id, recipient.as_sub()).len() as u32;1550 ensure!(1551 collection.limits.account_token_ownership_limit > account_items,1552 Error::<T>::AccountTokenLimitExceeded1553 );15541555 // preliminary transfer check1556 ensure!(collection.transfers_enabled, Error::<T>::TransferNotAllowed);15571558 Ok(())1559 }15601561 fn can_create_items_in_collection(1562 collection: &CollectionHandle<T>,1563 sender: &T::CrossAccountId,1564 owner: &T::CrossAccountId,1565 amount: u32,1566 ) -> DispatchResult {1567 let collection_id = collection.id;15681569 // check token limit and account token limit1570 let total_items: u32 = ItemListIndex::get(collection_id)1571 .checked_add(amount)1572 .ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1573 let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner.as_sub()).len()1574 as u32)1575 .checked_add(amount)1576 .ok_or(Error::<T>::AccountTokenLimitExceeded)?;1577 ensure!(1578 collection.limits.token_limit >= total_items,1579 Error::<T>::CollectionTokenLimitExceeded1580 );1581 ensure!(1582 collection.limits.account_token_ownership_limit >= account_items,1583 Error::<T>::AccountTokenLimitExceeded1584 );15851586 if !Self::is_owner_or_admin_permissions(collection, sender) {1587 ensure!(collection.mint_mode, Error::<T>::PublicMintingNotAllowed);1588 Self::check_white_list(collection, owner)?;1589 Self::check_white_list(collection, sender)?;1590 }15911592 Ok(())1593 }15941595 fn validate_create_item_args(1596 target_collection: &CollectionHandle<T>,1597 data: &CreateItemData,1598 ) -> DispatchResult {1599 match target_collection.mode {1600 CollectionMode::NFT => {1601 if !matches!(data, CreateItemData::NFT(_)) {1602 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1603 }1604 }1605 CollectionMode::Fungible(_) => {1606 if !matches!(data, CreateItemData::Fungible(_)) {1607 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1608 }1609 }1610 CollectionMode::ReFungible => {1611 if let CreateItemData::ReFungible(data) = data {1612 // Check refungibility limits1613 ensure!(1614 data.pieces <= MAX_REFUNGIBLE_PIECES,1615 Error::<T>::WrongRefungiblePieces1616 );1617 ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1618 } else {1619 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1620 }1621 }1622 _ => {1623 fail!(Error::<T>::UnexpectedCollectionType);1624 }1625 };16261627 Ok(())1628 }16291630 fn create_item_no_validation(1631 collection: &CollectionHandle<T>,1632 owner: &T::CrossAccountId,1633 data: CreateItemData,1634 ) -> DispatchResult {1635 match data {1636 CreateItemData::NFT(data) => {1637 let item = NftItemType {1638 owner: owner.clone(),1639 const_data: data.const_data.into_inner(),1640 variable_data: data.variable_data.into_inner(),1641 };16421643 Self::add_nft_item(collection, item)?;1644 }1645 CreateItemData::Fungible(data) => {1646 Self::add_fungible_item(collection, owner, data.value)?;1647 }1648 CreateItemData::ReFungible(data) => {1649 let owner_list = vec![Ownership {1650 owner: owner.clone(),1651 fraction: data.pieces,1652 }];16531654 let item = ReFungibleItemType {1655 owner: owner_list,1656 const_data: data.const_data.into_inner(),1657 variable_data: data.variable_data.into_inner(),1658 };16591660 Self::add_refungible_item(collection, item)?;1661 }1662 };16631664 Ok(())1665 }16661667 fn add_fungible_item(1668 collection: &CollectionHandle<T>,1669 owner: &T::CrossAccountId,1670 value: u128,1671 ) -> DispatchResult {1672 let collection_id = collection.id;16731674 // Does new owner already have an account?1675 let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner.as_sub()).value;16761677 // Mint1678 let item = FungibleItemType {1679 value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,1680 };1681 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), item);16821683 // Update balance1684 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1685 .checked_add(value)1686 .ok_or(Error::<T>::NumOverflow)?;1687 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);16881689 Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));1690 Ok(())1691 }16921693 fn add_refungible_item(1694 collection: &CollectionHandle<T>,1695 item: ReFungibleItemType<T::CrossAccountId>,1696 ) -> DispatchResult {1697 let collection_id = collection.id;16981699 let current_index = <ItemListIndex>::get(collection_id)1700 .checked_add(1)1701 .ok_or(Error::<T>::NumOverflow)?;1702 let itemcopy = item.clone();17031704 ensure!(item.owner.len() == 1, Error::<T>::BadCreateRefungibleCall,);1705 let item_owner = item.owner.first().expect("only one owner is defined");17061707 let value = item_owner.fraction;1708 let owner = item_owner.owner.clone();17091710 Self::add_token_index(collection_id, current_index, &owner)?;17111712 <ItemListIndex>::insert(collection_id, current_index);1713 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);17141715 // Update balance1716 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1717 .checked_add(value)1718 .ok_or(Error::<T>::NumOverflow)?;1719 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);17201721 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));1722 Ok(())1723 }17241725 fn add_nft_item(1726 collection: &CollectionHandle<T>,1727 item: NftItemType<T::CrossAccountId>,1728 ) -> DispatchResult {1729 let collection_id = collection.id;17301731 let current_index = <ItemListIndex>::get(collection_id)1732 .checked_add(1)1733 .ok_or(Error::<T>::NumOverflow)?;17341735 let item_owner = item.owner.clone();1736 Self::add_token_index(collection_id, current_index, &item.owner)?;17371738 <ItemListIndex>::insert(collection_id, current_index);1739 <NftItemList<T>>::insert(collection_id, current_index, item);17401741 // Update balance1742 let new_balance = <Balance<T>>::get(collection_id, item_owner.as_sub())1743 .checked_add(1)1744 .ok_or(Error::<T>::NumOverflow)?;1745 <Balance<T>>::insert(collection_id, item_owner.as_sub(), new_balance);17461747 collection.log(ERC721Events::Transfer {1748 from: H160::default(),1749 to: *item_owner.as_eth(),1750 token_id: current_index.into(),1751 })?;1752 Self::deposit_event(RawEvent::ItemCreated(1753 collection_id,1754 current_index,1755 item_owner,1756 ));1757 Ok(())1758 }17591760 fn burn_refungible_item(1761 collection: &CollectionHandle<T>,1762 item_id: TokenId,1763 owner: &T::CrossAccountId,1764 ) -> DispatchResult {1765 let collection_id = collection.id;17661767 let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)1768 .ok_or(Error::<T>::TokenNotFound)?;1769 let rft_balance = token1770 .owner1771 .iter()1772 .find(|&i| i.owner == *owner)1773 .ok_or(Error::<T>::TokenNotFound)?;1774 Self::remove_token_index(collection_id, item_id, owner)?;17751776 // update balance1777 let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.as_sub())1778 .checked_sub(rft_balance.fraction)1779 .ok_or(Error::<T>::NumOverflow)?;1780 <Balance<T>>::insert(collection_id, rft_balance.owner.as_sub(), new_balance);17811782 // Re-create owners list with sender removed1783 let index = token1784 .owner1785 .iter()1786 .position(|i| i.owner == *owner)1787 .expect("owned item is exists");1788 token.owner.remove(index);1789 let owner_count = token.owner.len();17901791 // Burn the token completely if this was the last (only) owner1792 if owner_count == 0 {1793 <ReFungibleItemList<T>>::remove(collection_id, item_id);1794 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);1795 } else {1796 <ReFungibleItemList<T>>::insert(collection_id, item_id, token);1797 }17981799 Ok(())1800 }18011802 fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1803 let collection_id = collection.id;18041805 let item =1806 <NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;1807 Self::remove_token_index(collection_id, item_id, &item.owner)?;18081809 // update balance1810 let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())1811 .checked_sub(1)1812 .ok_or(Error::<T>::NumOverflow)?;1813 <Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);1814 <NftItemList<T>>::remove(collection_id, item_id);1815 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);18161817 Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id));1818 Ok(())1819 }18201821 fn burn_fungible_item(1822 owner: &T::CrossAccountId,1823 collection: &CollectionHandle<T>,1824 value: u128,1825 ) -> DispatchResult {1826 let collection_id = collection.id;18271828 let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());1829 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);18301831 // update balance1832 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1833 .checked_sub(value)1834 .ok_or(Error::<T>::NumOverflow)?;1835 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);18361837 if balance.value - value > 0 {1838 balance.value -= value;1839 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);1840 } else {1841 <FungibleItemList<T>>::remove(collection_id, owner.as_sub());1842 }18431844 collection.log(ERC20Events::Transfer {1845 from: *owner.as_eth(),1846 to: H160::default(),1847 value: value.into(),1848 })?;1849 Ok(())1850 }18511852 pub fn get_collection(1853 collection_id: CollectionId,1854 ) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {1855 Ok(<CollectionHandle<T>>::get(collection_id).ok_or(Error::<T>::CollectionNotFound)?)1856 }18571858 fn check_owner_permissions(1859 target_collection: &CollectionHandle<T>,1860 subject: &T::AccountId,1861 ) -> DispatchResult {1862 ensure!(1863 *subject == target_collection.owner,1864 Error::<T>::NoPermission1865 );18661867 Ok(())1868 }18691870 fn is_owner_or_admin_permissions(1871 collection: &CollectionHandle<T>,1872 subject: &T::CrossAccountId,1873 ) -> bool {1874 *subject.as_sub() == collection.owner1875 || <AdminList<T>>::get(collection.id).contains(subject)1876 }18771878 fn check_owner_or_admin_permissions(1879 collection: &CollectionHandle<T>,1880 subject: &T::CrossAccountId,1881 ) -> DispatchResult {1882 ensure!(1883 Self::is_owner_or_admin_permissions(collection, subject),1884 Error::<T>::NoPermission1885 );18861887 Ok(())1888 }18891890 fn owned_amount(1891 subject: &T::CrossAccountId,1892 target_collection: &CollectionHandle<T>,1893 item_id: TokenId,1894 ) -> Option<u128> {1895 let collection_id = target_collection.id;18961897 match target_collection.mode {1898 CollectionMode::NFT => {1899 (<NftItemList<T>>::get(collection_id, item_id)?.owner == *subject).then(|| 1)1900 }1901 CollectionMode::Fungible(_) => {1902 Some(<FungibleItemList<T>>::get(collection_id, &subject.as_sub()).value)1903 }1904 CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?1905 .owner1906 .iter()1907 .find(|i| i.owner == *subject)1908 .map(|i| i.fraction),1909 CollectionMode::Invalid => None,1910 }1911 }19121913 fn is_item_owner(1914 subject: &T::CrossAccountId,1915 target_collection: &CollectionHandle<T>,1916 item_id: TokenId,1917 ) -> bool {1918 match target_collection.mode {1919 CollectionMode::Fungible(_) => true,1920 _ => Self::owned_amount(subject, target_collection, item_id).is_some(),1921 }1922 }19231924 fn check_white_list(1925 collection: &CollectionHandle<T>,1926 address: &T::CrossAccountId,1927 ) -> DispatchResult {1928 let collection_id = collection.id;19291930 let mes = Error::<T>::AddresNotInWhiteList;1931 ensure!(1932 <WhiteList<T>>::contains_key(collection_id, address.as_sub()),1933 mes1934 );19351936 Ok(())1937 }19381939 /// Check if token exists. In case of Fungible, check if there is an entry for1940 /// the owner in fungible balances double map1941 fn token_exists(target_collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1942 let collection_id = target_collection.id;1943 let exists = match target_collection.mode {1944 CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),1945 CollectionMode::Fungible(_) => true,1946 CollectionMode::ReFungible => {1947 <ReFungibleItemList<T>>::contains_key(collection_id, item_id)1948 }1949 _ => false,1950 };19511952 ensure!(exists, Error::<T>::TokenNotFound);1953 Ok(())1954 }19551956 fn transfer_fungible(1957 collection: &CollectionHandle<T>,1958 value: u128,1959 owner: &T::CrossAccountId,1960 recipient: &T::CrossAccountId,1961 ) -> DispatchResult {1962 let collection_id = collection.id;19631964 let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());1965 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);19661967 // Send balance to recipient (updates balanceOf of recipient)1968 Self::add_fungible_item(collection, recipient, value)?;19691970 // update balanceOf of sender1971 <Balance<T>>::insert(collection_id, owner.as_sub(), balance.value - value);19721973 // Reduce or remove sender1974 if balance.value == value {1975 <FungibleItemList<T>>::remove(collection_id, owner.as_sub());1976 } else {1977 balance.value -= value;1978 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);1979 }19801981 collection.log(ERC20Events::Transfer {1982 from: *owner.as_eth(),1983 to: *recipient.as_eth(),1984 value: value.into(),1985 })?;1986 Self::deposit_event(RawEvent::Transfer(1987 collection.id,1988 1,1989 owner.clone(),1990 recipient.clone(),1991 value,1992 ));19931994 Ok(())1995 }19961997 fn transfer_refungible(1998 collection: &CollectionHandle<T>,1999 item_id: TokenId,2000 value: u128,2001 owner: T::CrossAccountId,2002 new_owner: T::CrossAccountId,2003 ) -> DispatchResult {2004 let collection_id = collection.id;2005 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)2006 .ok_or(Error::<T>::TokenNotFound)?;20072008 let item = full_item2009 .owner2010 .iter()2011 .find(|i| i.owner == owner)2012 .ok_or(Error::<T>::TokenNotFound)?;2013 let amount = item.fraction;20142015 ensure!(amount >= value, Error::<T>::TokenValueTooLow);20162017 // update balance2018 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2019 .checked_sub(value)2020 .ok_or(Error::<T>::NumOverflow)?;2021 <Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);20222023 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2024 .checked_add(value)2025 .ok_or(Error::<T>::NumOverflow)?;2026 <Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);20272028 let old_owner = item.owner.clone();2029 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);20302031 let mut new_full_item = full_item.clone();2032 // transfer2033 if amount == value && !new_owner_has_account {2034 // change owner2035 // new owner do not have account2036 new_full_item2037 .owner2038 .iter_mut()2039 .find(|i| i.owner == owner)2040 .expect("old owner does present in refungible")2041 .owner = new_owner.clone();2042 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);20432044 // update index collection2045 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;2046 } else {2047 new_full_item2048 .owner2049 .iter_mut()2050 .find(|i| i.owner == owner)2051 .expect("old owner does present in refungible")2052 .fraction -= value;20532054 // separate amount2055 if new_owner_has_account {2056 // new owner has account2057 new_full_item2058 .owner2059 .iter_mut()2060 .find(|i| i.owner == new_owner)2061 .expect("new owner has account")2062 .fraction += value;2063 } else {2064 // new owner do not have account2065 new_full_item.owner.push(Ownership {2066 owner: new_owner.clone(),2067 fraction: value,2068 });2069 Self::add_token_index(collection_id, item_id, &new_owner)?;2070 }20712072 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2073 }20742075 Self::deposit_event(RawEvent::Transfer(2076 collection.id,2077 item_id,2078 owner,2079 new_owner,2080 amount,2081 ));20822083 Ok(())2084 }20852086 fn transfer_nft(2087 collection: &CollectionHandle<T>,2088 item_id: TokenId,2089 sender: T::CrossAccountId,2090 new_owner: T::CrossAccountId,2091 ) -> DispatchResult {2092 let collection_id = collection.id;2093 let mut item =2094 <NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;20952096 ensure!(sender == item.owner, Error::<T>::MustBeTokenOwner);20972098 // update balance2099 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2100 .checked_sub(1)2101 .ok_or(Error::<T>::NumOverflow)?;2102 <Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);21032104 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2105 .checked_add(1)2106 .ok_or(Error::<T>::NumOverflow)?;2107 <Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);21082109 // change owner2110 let old_owner = item.owner.clone();2111 item.owner = new_owner.clone();2112 <NftItemList<T>>::insert(collection_id, item_id, item);21132114 // update index collection2115 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;21162117 collection.log(ERC721Events::Transfer {2118 from: *sender.as_eth(),2119 to: *new_owner.as_eth(),2120 token_id: item_id.into(),2121 })?;2122 Self::deposit_event(RawEvent::Transfer(2123 collection.id,2124 item_id,2125 sender,2126 new_owner,2127 1,2128 ));21292130 Ok(())2131 }21322133 fn set_re_fungible_variable_data(2134 collection: &CollectionHandle<T>,2135 item_id: TokenId,2136 data: Vec<u8>,2137 ) -> DispatchResult {2138 let collection_id = collection.id;2139 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2140 .ok_or(Error::<T>::TokenNotFound)?;21412142 item.variable_data = data;21432144 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);21452146 Ok(())2147 }21482149 fn set_nft_variable_data(2150 collection: &CollectionHandle<T>,2151 item_id: TokenId,2152 data: Vec<u8>,2153 ) -> DispatchResult {2154 let collection_id = collection.id;2155 let mut item =2156 <NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;21572158 item.variable_data = data;21592160 <NftItemList<T>>::insert(collection_id, item_id, item);21612162 Ok(())2163 }21642165 #[allow(dead_code)]2166 fn init_collection(item: &Collection<T>) {2167 // check params2168 assert!(2169 item.decimal_points <= MAX_DECIMAL_POINTS,2170 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2171 );2172 assert!(2173 item.name.len() <= 64,2174 "Collection name can not be longer than 63 char"2175 );2176 assert!(2177 item.name.len() <= 256,2178 "Collection description can not be longer than 255 char"2179 );2180 assert!(2181 item.token_prefix.len() <= 16,2182 "Token prefix can not be longer than 15 char"2183 );21842185 // Generate next collection ID2186 let next_id = CreatedCollectionCount::get().checked_add(1).unwrap();21872188 CreatedCollectionCount::put(next_id);2189 }21902191 #[allow(dead_code)]2192 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::CrossAccountId>) {2193 let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();21942195 Self::add_token_index(collection_id, current_index, &item.owner).unwrap();21962197 <ItemListIndex>::insert(collection_id, current_index);21982199 // Update balance2200 let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2201 .checked_add(1)2202 .unwrap();2203 <Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2204 }22052206 #[allow(dead_code)]2207 fn init_fungible_token(2208 collection_id: CollectionId,2209 owner: &T::CrossAccountId,2210 item: &FungibleItemType,2211 ) {2212 let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();22132214 Self::add_token_index(collection_id, current_index, owner).unwrap();22152216 <ItemListIndex>::insert(collection_id, current_index);22172218 // Update balance2219 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2220 .checked_add(item.value)2221 .unwrap();2222 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2223 }22242225 #[allow(dead_code)]2226 fn init_refungible_token(2227 collection_id: CollectionId,2228 item: &ReFungibleItemType<T::CrossAccountId>,2229 ) {2230 let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();22312232 let value = item.owner.first().unwrap().fraction;2233 let owner = item.owner.first().unwrap().owner.clone();22342235 Self::add_token_index(collection_id, current_index, &owner).unwrap();22362237 <ItemListIndex>::insert(collection_id, current_index);22382239 // Update balance2240 let new_balance = <Balance<T>>::get(collection_id, &owner.as_sub())2241 .checked_add(value)2242 .unwrap();2243 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2244 }22452246 fn add_token_index(2247 collection_id: CollectionId,2248 item_index: TokenId,2249 owner: &T::CrossAccountId,2250 ) -> DispatchResult {2251 // add to account limit2252 if <AccountItemCount<T>>::contains_key(owner.as_sub()) {2253 // bound Owned tokens by a single address2254 let count = <AccountItemCount<T>>::get(owner.as_sub());2255 ensure!(2256 count < ACCOUNT_TOKEN_OWNERSHIP_LIMIT,2257 Error::<T>::AddressOwnershipLimitExceeded2258 );22592260 <AccountItemCount<T>>::insert(2261 owner.as_sub(),2262 count.checked_add(1).ok_or(Error::<T>::NumOverflow)?,2263 );2264 } else {2265 <AccountItemCount<T>>::insert(owner.as_sub(), 1);2266 }22672268 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2269 if list_exists {2270 let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2271 let item_contains = list.contains(&item_index.clone());22722273 if !item_contains {2274 list.push(item_index);2275 }22762277 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2278 } else {2279 let itm = vec![item_index];2280 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), itm);2281 }22822283 Ok(())2284 }22852286 fn remove_token_index(2287 collection_id: CollectionId,2288 item_index: TokenId,2289 owner: &T::CrossAccountId,2290 ) -> DispatchResult {2291 // update counter2292 <AccountItemCount<T>>::insert(2293 owner.as_sub(),2294 <AccountItemCount<T>>::get(owner.as_sub())2295 .checked_sub(1)2296 .ok_or(Error::<T>::NumOverflow)?,2297 );22982299 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2300 if list_exists {2301 let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2302 let item_contains = list.contains(&item_index.clone());23032304 if item_contains {2305 list.retain(|&item| item != item_index);2306 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2307 }2308 }23092310 Ok(())2311 }23122313 fn move_token_index(2314 collection_id: CollectionId,2315 item_index: TokenId,2316 old_owner: &T::CrossAccountId,2317 new_owner: &T::CrossAccountId,2318 ) -> DispatchResult {2319 Self::remove_token_index(collection_id, item_index, old_owner)?;2320 Self::add_token_index(collection_id, item_index, new_owner)?;23212322 Ok(())2323 }2324}23252326sp_api::decl_runtime_apis! {2327 pub trait NftApi {2328 /// Used for ethereum integration2329 fn eth_contract_code(account: H160) -> Option<Vec<u8>>;2330 }2331}1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]7#![cfg_attr(not(feature = "std"), no_std)]8#![allow(9 clippy::too_many_arguments,10 clippy::unnecessary_mut_passed,11 clippy::unused_unit12)]1314extern crate alloc;1516pub use serde::{Serialize, Deserialize};1718pub use frame_support::{19 construct_runtime, decl_event, decl_module, decl_storage, decl_error,20 dispatch::DispatchResult,21 ensure, fail, parameter_types,22 traits::{23 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,24 Randomness, IsSubType, WithdrawReasons,25 },26 weights::{27 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},28 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,29 WeightToFeePolynomial, DispatchClass,30 },31 StorageValue, transactional,32};3334use frame_system::{self as system, ensure_signed};35use sp_core::H160;36use sp_std::vec;37use sp_runtime::sp_std::prelude::Vec;38use core::ops::{Deref, DerefMut};39use nft_data_structs::{40 MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, MAX_REFUNGIBLE_PIECES,41 CUSTOM_DATA_LIMIT, COLLECTION_NUMBER_LIMIT, ACCOUNT_TOKEN_OWNERSHIP_LIMIT,42 VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, COLLECTION_ADMINS_LIMIT,43 OFFCHAIN_SCHEMA_LIMIT, MAX_TOKEN_PREFIX_LENGTH, MAX_COLLECTION_NAME_LENGTH,44 MAX_COLLECTION_DESCRIPTION_LENGTH, AccessMode, Collection, CreateItemData, CollectionLimits,45 CollectionId, CollectionMode, TokenId, SchemaVersion, SponsorshipState, Ownership, NftItemType,46 FungibleItemType, ReFungibleItemType,47};4849#[cfg(test)]50mod mock;5152#[cfg(test)]53mod tests;5455mod eth;56mod sponsorship;57pub use sponsorship::NftSponsorshipHandler;58pub use eth::sponsoring::NftEthSponsorshipHandler;5960pub use eth::NftErcSupport;61pub use eth::account::*;62use eth::erc::{ERC20Events, ERC721Events};6364#[cfg(feature = "runtime-benchmarks")]65mod benchmarking;66pub mod weights;67use weights::WeightInfo;6869decl_error! {70 /// Error for non-fungible-token module.71 pub enum Error for Module<T: Config> {72 /// Total collections bound exceeded.73 TotalCollectionsLimitExceeded,74 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.75 CollectionDecimalPointLimitExceeded,76 /// Collection name can not be longer than 63 char.77 CollectionNameLimitExceeded,78 /// Collection description can not be longer than 255 char.79 CollectionDescriptionLimitExceeded,80 /// Token prefix can not be longer than 15 char.81 CollectionTokenPrefixLimitExceeded,82 /// This collection does not exist.83 CollectionNotFound,84 /// Item not exists.85 TokenNotFound,86 /// Admin not found87 AdminNotFound,88 /// Arithmetic calculation overflow.89 NumOverflow,90 /// Account already has admin role.91 AlreadyAdmin,92 /// You do not own this collection.93 NoPermission,94 /// This address is not set as sponsor, use setCollectionSponsor first.95 ConfirmUnsetSponsorFail,96 /// Collection is not in mint mode.97 PublicMintingNotAllowed,98 /// Sender parameter and item owner must be equal.99 MustBeTokenOwner,100 /// Item balance not enough.101 TokenValueTooLow,102 /// Size of item is too large.103 NftSizeLimitExceeded,104 /// No approve found105 ApproveNotFound,106 /// Requested value more than approved.107 TokenValueNotEnough,108 /// Only approved addresses can call this method.109 ApproveRequired,110 /// Address is not in white list.111 AddresNotInWhiteList,112 /// Number of collection admins bound exceeded.113 CollectionAdminsLimitExceeded,114 /// Owned tokens by a single address bound exceeded.115 AddressOwnershipLimitExceeded,116 /// Length of items properties must be greater than 0.117 EmptyArgument,118 /// const_data exceeded data limit.119 TokenConstDataLimitExceeded,120 /// variable_data exceeded data limit.121 TokenVariableDataLimitExceeded,122 /// Not NFT item data used to mint in NFT collection.123 NotNftDataUsedToMintNftCollectionToken,124 /// Not Fungible item data used to mint in Fungible collection.125 NotFungibleDataUsedToMintFungibleCollectionToken,126 /// Not Re Fungible item data used to mint in Re Fungible collection.127 NotReFungibleDataUsedToMintReFungibleCollectionToken,128 /// Unexpected collection type.129 UnexpectedCollectionType,130 /// Can't store metadata in fungible tokens.131 CantStoreMetadataInFungibleTokens,132 /// Collection token limit exceeded133 CollectionTokenLimitExceeded,134 /// Account token limit exceeded per collection135 AccountTokenLimitExceeded,136 /// Collection limit bounds per collection exceeded137 CollectionLimitBoundsExceeded,138 /// Tried to enable permissions which are only permitted to be disabled139 OwnerPermissionsCantBeReverted,140 /// Schema data size limit bound exceeded141 SchemaDataLimitExceeded,142 /// Maximum refungibility exceeded143 WrongRefungiblePieces,144 /// createRefungible should be called with one owner145 BadCreateRefungibleCall,146 /// Gas limit exceeded147 OutOfGas,148 /// Collection settings not allowing items transferring149 TransferNotAllowed,150 }151}152153#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]154pub struct CollectionHandle<T: Config> {155 pub id: CollectionId,156 collection: Collection<T>,157 recorder: pallet_evm_coder_substrate::SubstrateRecorder<T>,158}159impl<T: Config> CollectionHandle<T> {160 pub fn get_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {161 <CollectionById<T>>::get(id).map(|collection| Self {162 id,163 collection,164 recorder: pallet_evm_coder_substrate::SubstrateRecorder::new(165 eth::collection_id_to_address(id),166 gas_limit,167 ),168 })169 }170 pub fn get(id: CollectionId) -> Option<Self> {171 Self::get_with_gas_limit(id, u64::MAX)172 }173 pub fn log(&self, log: impl evm_coder::ToLog) -> DispatchResult {174 self.recorder.log_sub(log)175 }176 fn consume_gas(&self, gas: u64) -> DispatchResult {177 self.recorder.consume_gas_sub(gas)178 }179 pub fn submit_logs(self) -> DispatchResult {180 self.recorder.submit_logs()181 }182 pub fn save(self) -> DispatchResult {183 self.recorder.submit_logs()?;184 <CollectionById<T>>::insert(self.id, self.collection);185 Ok(())186 }187}188impl<T: Config> Deref for CollectionHandle<T> {189 type Target = Collection<T>;190191 fn deref(&self) -> &Self::Target {192 &self.collection193 }194}195196impl<T: Config> DerefMut for CollectionHandle<T> {197 fn deref_mut(&mut self) -> &mut Self::Target {198 &mut self.collection199 }200}201202pub trait Config: system::Config + pallet_evm_coder_substrate::Config + Sized {203 type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;204205 /// Weight information for extrinsics in this pallet.206 type WeightInfo: WeightInfo;207208 type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;209 type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;210211 type CrossAccountId: CrossAccountId<Self::AccountId>;212 type Currency: Currency<Self::AccountId>;213 type CollectionCreationPrice: Get<214 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,215 >;216 type TreasuryAccountId: Get<Self::AccountId>;217}218219type SelfWeightOf<T> = <T as Config>::WeightInfo;220221trait WeightInfoHelpers: WeightInfo {222 fn transfer() -> Weight {223 Self::transfer_nft()224 .max(Self::transfer_fungible())225 .max(Self::transfer_refungible())226 }227 fn transfer_from() -> Weight {228 Self::transfer_from_nft()229 .max(Self::transfer_from_fungible())230 .max(Self::transfer_from_refungible())231 }232 fn approve() -> Weight {233 // TODO: refungible, fungible234 Self::approve_nft()235 }236 fn set_variable_meta_data(data: u32) -> Weight {237 // TODO: refungible238 Self::set_variable_meta_data_nft(data)239 }240 fn create_item(data: u32) -> Weight {241 Self::create_item_nft(data)242 .max(Self::create_item_fungible())243 .max(Self::create_item_refungible(data))244 }245 fn create_multiple_items(amount: u32) -> Weight {246 Self::create_multiple_items_nft(amount)247 .max(Self::create_multiple_items_fungible(amount))248 .max(Self::create_multiple_items_refungible(amount))249 }250 fn burn_item() -> Weight {251 // TODO: refungible, fungible252 Self::burn_item_nft()253 }254}255impl<T: WeightInfo> WeightInfoHelpers for T {}256257// # Used definitions258//259// ## User control levels260//261// chain-controlled - key is uncontrolled by user262// i.e autoincrementing index263// can use non-cryptographic hash264// real - key is controlled by user265// but it is hard to generate enough colliding values, i.e owner of signed txs266// can use non-cryptographic hash267// controlled - key is completly controlled by users268// i.e maps with mutable keys269// should use cryptographic hash270//271// ## User control level downgrade reasons272//273// ?1 - chain-controlled -> controlled274// collections/tokens can be destroyed, resulting in massive holes275// ?2 - chain-controlled -> controlled276// same as ?1, but can be only added, resulting in easier exploitation277// ?3 - real -> controlled278// no confirmation required, so addresses can be easily generated279decl_storage! {280 trait Store for Module<T: Config> as Nft {281282 //#region Private members283 /// Id of next collection284 CreatedCollectionCount: u32;285 /// Used for migrations286 ChainVersion: u64;287 /// Id of last collection token288 /// Collection id (controlled?1)289 ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;290 //#endregion291292 //#region Bound counters293 /// Amount of collections destroyed, used for total amount tracking with294 /// CreatedCollectionCount295 DestroyedCollectionCount: u32;296 /// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)297 /// Account id (real)298 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;299 //#endregion300301 //#region Basic collections302 /// Collection info303 /// Collection id (controlled?1)304 pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;305 /// List of collection admins306 /// Collection id (controlled?2)307 pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::CrossAccountId>;308 /// Whitelisted collection users309 /// Collection id (controlled?2), user id (controlled?3)310 pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;311 //#endregion312313 /// How many of collection items user have314 /// Collection id (controlled?2), account id (real)315 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;316317 /// Amount of items which spender can transfer out of owners account (via transferFrom)318 /// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))319 /// TODO: Off chain worker should remove from this map when token gets removed320 pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;321322 //#region Item collections323 /// Collection id (controlled?2), token id (controlled?1)324 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::CrossAccountId>>;325 /// Collection id (controlled?2), owner (controlled?2)326 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;327 /// Collection id (controlled?2), token id (controlled?1)328 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::CrossAccountId>>;329 //#endregion330331 //#region Index list332 /// Collection id (controlled?2), tokens owner (controlled?2)333 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;334 //#endregion335336 //#region Tokens transfer rate limit baskets337 /// (Collection id (controlled?2), who created (real))338 /// TODO: Off chain worker should remove from this map when collection gets removed339 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;340 /// Collection id (controlled?2), token id (controlled?2)341 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;342 /// Collection id (controlled?2), owning user (real)343 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;344 /// Collection id (controlled?2), token id (controlled?2)345 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;346 //#endregion347348 /// Variable metadata sponsoring349 /// Collection id (controlled?2), token id (controlled?2)350 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;351 }352 add_extra_genesis {353 build(|config: &GenesisConfig<T>| {354 // Modification of storage355 for (_num, _c) in &config.collection_id {356 <Module<T>>::init_collection(_c);357 }358359 for (_num, _c, _i) in &config.nft_item_id {360 <Module<T>>::init_nft_token(*_c, _i);361 }362363 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {364 <Module<T>>::init_fungible_token(*collection_id, &T::CrossAccountId::from_sub(account_id.clone()), fungible_item);365 }366367 for (_num, _c, _i) in &config.refungible_item_id {368 <Module<T>>::init_refungible_token(*_c, _i);369 }370 })371 }372}373374decl_event!(375 pub enum Event<T>376 where377 AccountId = <T as frame_system::Config>::AccountId,378 CrossAccountId = <T as Config>::CrossAccountId,379 {380 /// New collection was created381 ///382 /// # Arguments383 ///384 /// * collection_id: Globally unique identifier of newly created collection.385 ///386 /// * mode: [CollectionMode] converted into u8.387 ///388 /// * account_id: Collection owner.389 CollectionCreated(CollectionId, u8, AccountId),390391 /// New item was created.392 ///393 /// # Arguments394 ///395 /// * collection_id: Id of the collection where item was created.396 ///397 /// * item_id: Id of an item. Unique within the collection.398 ///399 /// * recipient: Owner of newly created item400 ItemCreated(CollectionId, TokenId, CrossAccountId),401402 /// Collection item was burned.403 ///404 /// # Arguments405 ///406 /// collection_id.407 ///408 /// item_id: Identifier of burned NFT.409 ItemDestroyed(CollectionId, TokenId),410411 /// Item was transferred412 ///413 /// * collection_id: Id of collection to which item is belong414 ///415 /// * item_id: Id of an item416 ///417 /// * sender: Original owner of item418 ///419 /// * recipient: New owner of item420 ///421 /// * amount: Always 1 for NFT422 Transfer(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),423424 /// * collection_id425 ///426 /// * item_id427 ///428 /// * sender429 ///430 /// * spender431 ///432 /// * amount433 Approved(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),434 }435);436437decl_module! {438 pub struct Module<T: Config> for enum Call439 where440 origin: T::Origin441 {442 fn deposit_event() = default;443 const CollectionAdminsLimit: u64 = COLLECTION_ADMINS_LIMIT;444 type Error = Error<T>;445446 fn on_initialize(_now: T::BlockNumber) -> Weight {447 0448 }449450 /// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.451 ///452 /// # Permissions453 ///454 /// * Anyone.455 ///456 /// # Arguments457 ///458 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.459 ///460 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.461 ///462 /// * token_prefix: UTF-8 string with token prefix.463 ///464 /// * mode: [CollectionMode] collection type and type dependent data.465 // returns collection ID466 #[weight = <SelfWeightOf<T>>::create_collection()]467 #[transactional]468 pub fn create_collection(origin,469 collection_name: Vec<u16>,470 collection_description: Vec<u16>,471 token_prefix: Vec<u8>,472 mode: CollectionMode) -> DispatchResult {473474 // Anyone can create a collection475 let who = ensure_signed(origin)?;476477 // Take a (non-refundable) deposit of collection creation478 let mut imbalance = <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();479 imbalance.subsume(<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(480 &T::TreasuryAccountId::get(),481 T::CollectionCreationPrice::get(),482 ));483 <T as Config>::Currency::settle(484 &who,485 imbalance,486 WithdrawReasons::TRANSFER,487 ExistenceRequirement::KeepAlive,488 ).map_err(|_| Error::<T>::NoPermission)?;489490 let decimal_points = match mode {491 CollectionMode::Fungible(points) => points,492 _ => 0493 };494495 let created_count = CreatedCollectionCount::get();496 let destroyed_count = DestroyedCollectionCount::get();497498 // bound Total number of collections499 ensure!(created_count - destroyed_count < COLLECTION_NUMBER_LIMIT, Error::<T>::TotalCollectionsLimitExceeded);500501 // check params502 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);503 ensure!(collection_name.len() <= MAX_COLLECTION_NAME_LENGTH, Error::<T>::CollectionNameLimitExceeded);504 ensure!(collection_description.len() <= MAX_COLLECTION_DESCRIPTION_LENGTH, Error::<T>::CollectionDescriptionLimitExceeded);505 ensure!(token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH, Error::<T>::CollectionTokenPrefixLimitExceeded);506507 // Generate next collection ID508 let next_id = created_count509 .checked_add(1)510 .ok_or(Error::<T>::NumOverflow)?;511512 CreatedCollectionCount::put(next_id);513514 let limits = CollectionLimits {515 sponsored_data_size: CUSTOM_DATA_LIMIT,516 ..Default::default()517 };518519 // Create new collection520 let new_collection = Collection {521 owner: who.clone(),522 name: collection_name,523 mode: mode.clone(),524 mint_mode: false,525 access: AccessMode::Normal,526 description: collection_description,527 decimal_points,528 token_prefix,529 offchain_schema: Vec::new(),530 schema_version: SchemaVersion::ImageURL,531 sponsorship: SponsorshipState::Disabled,532 variable_on_chain_schema: Vec::new(),533 const_on_chain_schema: Vec::new(),534 limits,535 transfers_enabled: true,536 };537538 // Add new collection to map539 <CollectionById<T>>::insert(next_id, new_collection);540541 // call event542 Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.id(), who));543544 Ok(())545 }546547 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.548 ///549 /// # Permissions550 ///551 /// * Collection Owner.552 ///553 /// # Arguments554 ///555 /// * collection_id: collection to destroy.556 #[weight = <SelfWeightOf<T>>::destroy_collection()]557 #[transactional]558 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {559560 let sender = ensure_signed(origin)?;561 let collection = Self::get_collection(collection_id)?;562 Self::check_owner_permissions(&collection, &sender)?;563 if !collection.limits.owner_can_destroy {564 fail!(Error::<T>::NoPermission);565 }566567 <AddressTokens<T>>::remove_prefix(collection_id, None);568 <Allowances<T>>::remove_prefix(collection_id, None);569 <Balance<T>>::remove_prefix(collection_id, None);570 <ItemListIndex>::remove(collection_id);571 <AdminList<T>>::remove(collection_id);572 <CollectionById<T>>::remove(collection_id);573 <WhiteList<T>>::remove_prefix(collection_id, None);574575 <NftItemList<T>>::remove_prefix(collection_id, None);576 <FungibleItemList<T>>::remove_prefix(collection_id, None);577 <ReFungibleItemList<T>>::remove_prefix(collection_id, None);578579 <NftTransferBasket<T>>::remove_prefix(collection_id, None);580 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);581 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id, None);582583 <VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);584585 DestroyedCollectionCount::put(DestroyedCollectionCount::get()586 .checked_add(1)587 .ok_or(Error::<T>::NumOverflow)?);588589 Ok(())590 }591592 /// Add an address to white list.593 ///594 /// # Permissions595 ///596 /// * Collection Owner597 /// * Collection Admin598 ///599 /// # Arguments600 ///601 /// * collection_id.602 ///603 /// * address.604 #[weight = <SelfWeightOf<T>>::add_to_white_list()]605 #[transactional]606 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{607608 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);609 let collection = Self::get_collection(collection_id)?;610611 Self::toggle_white_list_internal(612 &sender,613 &collection,614 &address,615 true,616 )?;617618 Ok(())619 }620621 /// Remove an address from white list.622 ///623 /// # Permissions624 ///625 /// * Collection Owner626 /// * Collection Admin627 ///628 /// # Arguments629 ///630 /// * collection_id.631 ///632 /// * address.633 #[weight = <SelfWeightOf<T>>::remove_from_white_list()]634 #[transactional]635 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{636637 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);638 let collection = Self::get_collection(collection_id)?;639640 Self::toggle_white_list_internal(641 &sender,642 &collection,643 &address,644 false,645 )?;646647 Ok(())648 }649650 /// Toggle between normal and white list access for the methods with access for `Anyone`.651 ///652 /// # Permissions653 ///654 /// * Collection Owner.655 ///656 /// # Arguments657 ///658 /// * collection_id.659 ///660 /// * mode: [AccessMode]661 #[weight = <SelfWeightOf<T>>::set_public_access_mode()]662 #[transactional]663 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult664 {665 let sender = ensure_signed(origin)?;666667 let mut target_collection = Self::get_collection(collection_id)?;668 Self::check_owner_permissions(&target_collection, &sender)?;669 target_collection.access = mode;670 target_collection.save()671 }672673 /// Allows Anyone to create tokens if:674 /// * White List is enabled, and675 /// * Address is added to white list, and676 /// * This method was called with True parameter677 ///678 /// # Permissions679 /// * Collection Owner680 ///681 /// # Arguments682 ///683 /// * collection_id.684 ///685 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.686 #[weight = <SelfWeightOf<T>>::set_mint_permission()]687 #[transactional]688 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult689 {690 let sender = ensure_signed(origin)?;691692 let mut target_collection = Self::get_collection(collection_id)?;693 Self::check_owner_permissions(&target_collection, &sender)?;694 target_collection.mint_mode = mint_permission;695 target_collection.save()696 }697698 /// Change the owner of the collection.699 ///700 /// # Permissions701 ///702 /// * Collection Owner.703 ///704 /// # Arguments705 ///706 /// * collection_id.707 ///708 /// * new_owner.709 #[weight = <SelfWeightOf<T>>::change_collection_owner()]710 #[transactional]711 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {712713 let sender = ensure_signed(origin)?;714 let mut target_collection = Self::get_collection(collection_id)?;715 Self::check_owner_permissions(&target_collection, &sender)?;716 target_collection.owner = new_owner;717 target_collection.save()718 }719720 /// Adds an admin of the Collection.721 /// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership.722 ///723 /// # Permissions724 ///725 /// * Collection Owner.726 /// * Collection Admin.727 ///728 /// # Arguments729 ///730 /// * collection_id: ID of the Collection to add admin for.731 ///732 /// * new_admin_id: Address of new admin to add.733 #[weight = <SelfWeightOf<T>>::add_collection_admin()]734 #[transactional]735 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {736 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);737 let collection = Self::get_collection(collection_id)?;738 Self::check_owner_or_admin_permissions(&collection, &sender)?;739 let mut admin_arr = <AdminList<T>>::get(collection_id);740741 match admin_arr.binary_search(&new_admin_id) {742 Ok(_) => {},743 Err(idx) => {744 ensure!(admin_arr.len() < COLLECTION_ADMINS_LIMIT as usize, Error::<T>::CollectionAdminsLimitExceeded);745 admin_arr.insert(idx, new_admin_id);746 <AdminList<T>>::insert(collection_id, admin_arr);747 }748 }749 Ok(())750 }751752 /// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.753 ///754 /// # Permissions755 ///756 /// * Collection Owner.757 /// * Collection Admin.758 ///759 /// # Arguments760 ///761 /// * collection_id: ID of the Collection to remove admin for.762 ///763 /// * account_id: Address of admin to remove.764 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]765 #[transactional]766 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {767 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);768 let collection = Self::get_collection(collection_id)?;769 Self::check_owner_or_admin_permissions(&collection, &sender)?;770 let mut admin_arr = <AdminList<T>>::get(collection_id);771772 if let Ok(idx) = admin_arr.binary_search(&account_id) {773 admin_arr.remove(idx);774 <AdminList<T>>::insert(collection_id, admin_arr);775 }776 Ok(())777 }778779 /// # Permissions780 ///781 /// * Collection Owner782 ///783 /// # Arguments784 ///785 /// * collection_id.786 ///787 /// * new_sponsor.788 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]789 #[transactional]790 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {791 let sender = ensure_signed(origin)?;792 let mut target_collection = Self::get_collection(collection_id)?;793 Self::check_owner_permissions(&target_collection, &sender)?;794795 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);796 target_collection.save()797 }798799 /// # Permissions800 ///801 /// * Sponsor.802 ///803 /// # Arguments804 ///805 /// * collection_id.806 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]807 #[transactional]808 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {809 let sender = ensure_signed(origin)?;810811 let mut target_collection = Self::get_collection(collection_id)?;812 ensure!(813 target_collection.sponsorship.pending_sponsor() == Some(&sender),814 Error::<T>::ConfirmUnsetSponsorFail815 );816817 target_collection.sponsorship = SponsorshipState::Confirmed(sender);818 target_collection.save()819 }820821 /// Switch back to pay-per-own-transaction model.822 ///823 /// # Permissions824 ///825 /// * Collection owner.826 ///827 /// # Arguments828 ///829 /// * collection_id.830 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]831 #[transactional]832 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {833 let sender = ensure_signed(origin)?;834835 let mut target_collection = Self::get_collection(collection_id)?;836 Self::check_owner_permissions(&target_collection, &sender)?;837838 target_collection.sponsorship = SponsorshipState::Disabled;839 target_collection.save()840 }841842 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.843 ///844 /// # Permissions845 ///846 /// * Collection Owner.847 /// * Collection Admin.848 /// * Anyone if849 /// * White List is enabled, and850 /// * Address is added to white list, and851 /// * MintPermission is enabled (see SetMintPermission method)852 ///853 /// # Arguments854 ///855 /// * collection_id: ID of the collection.856 ///857 /// * owner: Address, initial owner of the NFT.858 ///859 /// * data: Token data to store on chain.860 // #[weight =861 // (130_000_000 as Weight)862 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))863 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))864 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]865866 #[weight = <SelfWeightOf<T>>::create_item(data.data_size() as u32)]867 #[transactional]868 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult {869 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);870 let collection = Self::get_collection(collection_id)?;871872 Self::create_item_internal(&sender, &collection, &owner, data)?;873874 collection.submit_logs()875 }876877 /// This method creates multiple items in a collection created with CreateCollection method.878 ///879 /// # Permissions880 ///881 /// * Collection Owner.882 /// * Collection Admin.883 /// * Anyone if884 /// * White List is enabled, and885 /// * Address is added to white list, and886 /// * MintPermission is enabled (see SetMintPermission method)887 ///888 /// # Arguments889 ///890 /// * collection_id: ID of the collection.891 ///892 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].893 ///894 /// * owner: Address, initial owner of the NFT.895 #[weight = <SelfWeightOf<T>>::create_multiple_items(items_data.len() as u32)]896 #[transactional]897 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResult {898899 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);900 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);901 let collection = Self::get_collection(collection_id)?;902903 Self::create_multiple_items_internal(&sender, &collection, &owner, items_data)?;904905 collection.submit_logs()906 }907908 // TODO! transaction weight909910 /// Set transfers_enabled value for particular collection911 ///912 /// # Permissions913 ///914 /// * Collection Owner.915 ///916 /// # Arguments917 ///918 /// * collection_id: ID of the collection.919 ///920 /// * value: New flag value.921 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]922 #[transactional]923 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {924925 let sender = ensure_signed(origin)?;926 let mut target_collection = Self::get_collection(collection_id)?;927928 Self::check_owner_permissions(&target_collection, &sender)?;929930 target_collection.transfers_enabled = value;931 target_collection.save()932 }933934 /// Destroys a concrete instance of NFT.935 ///936 /// # Permissions937 ///938 /// * Collection Owner.939 /// * Collection Admin.940 /// * Current NFT Owner.941 ///942 /// # Arguments943 ///944 /// * collection_id: ID of the collection.945 ///946 /// * item_id: ID of NFT to burn.947 #[weight = <SelfWeightOf<T>>::burn_item()]948 #[transactional]949 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {950951 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);952 let target_collection = Self::get_collection(collection_id)?;953954 Self::burn_item_internal(&sender, &target_collection, item_id, value)?;955956 target_collection.submit_logs()957 }958959 /// Change ownership of the token.960 ///961 /// # Permissions962 ///963 /// * Collection Owner964 /// * Collection Admin965 /// * Current NFT owner966 ///967 /// # Arguments968 ///969 /// * recipient: Address of token recipient.970 ///971 /// * collection_id.972 ///973 /// * item_id: ID of the item974 /// * Non-Fungible Mode: Required.975 /// * Fungible Mode: Ignored.976 /// * Re-Fungible Mode: Required.977 ///978 /// * value: Amount to transfer.979 /// * Non-Fungible Mode: Ignored980 /// * Fungible Mode: Must specify transferred amount981 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)982 #[weight = <SelfWeightOf<T>>::transfer()]983 #[transactional]984 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {985 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);986 let collection = Self::get_collection(collection_id)?;987988 Self::transfer_internal(&sender, &recipient, &collection, item_id, value)?;989990 collection.submit_logs()991 }992993 /// Set, change, or remove approved address to transfer the ownership of the NFT.994 ///995 /// # Permissions996 ///997 /// * Collection Owner998 /// * Collection Admin999 /// * Current NFT owner1000 ///1001 /// # Arguments1002 ///1003 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1004 ///1005 /// * collection_id.1006 ///1007 /// * item_id: ID of the item.1008 #[weight = <SelfWeightOf<T>>::approve()]1009 #[transactional]1010 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {1011 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1012 let collection = Self::get_collection(collection_id)?;10131014 Self::approve_internal(&sender, &spender, &collection, item_id, amount)?;10151016 collection.submit_logs()1017 }10181019 /// 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.1020 ///1021 /// # Permissions1022 /// * Collection Owner1023 /// * Collection Admin1024 /// * Current NFT owner1025 /// * Address approved by current NFT owner1026 ///1027 /// # Arguments1028 ///1029 /// * from: Address that owns token.1030 ///1031 /// * recipient: Address of token recipient.1032 ///1033 /// * collection_id.1034 ///1035 /// * item_id: ID of the item.1036 ///1037 /// * value: Amount to transfer.1038 #[weight = <SelfWeightOf<T>>::transfer_from()]1039 #[transactional]1040 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {1041 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1042 let collection = Self::get_collection(collection_id)?;10431044 Self::transfer_from_internal(&sender, &from, &recipient, &collection, item_id, value)?;10451046 collection.submit_logs()1047 }1048 // #[weight = 0]1049 // // let no_perm_mes = "You do not have permissions to modify this collection";1050 // // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1051 // // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1052 // // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);10531054 // // // on_nft_received call10551056 // // Self::transfer(origin, collection_id, item_id, new_owner)?;10571058 // Ok(())1059 // }10601061 /// Set off-chain data schema.1062 ///1063 /// # Permissions1064 ///1065 /// * Collection Owner1066 /// * Collection Admin1067 ///1068 /// # Arguments1069 ///1070 /// * collection_id.1071 ///1072 /// * schema: String representing the offchain data schema.1073 #[weight = <SelfWeightOf<T>>::set_variable_meta_data(data.len() as u32)]1074 #[transactional]1075 pub fn set_variable_meta_data (1076 origin,1077 collection_id: CollectionId,1078 item_id: TokenId,1079 data: Vec<u8>1080 ) -> DispatchResult {1081 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);10821083 let collection = Self::get_collection(collection_id)?;10841085 Self::set_variable_meta_data_internal(&sender, &collection, item_id, data)?;10861087 Ok(())1088 }10891090 /// Set schema standard1091 /// ImageURL1092 /// Unique1093 ///1094 /// # Permissions1095 ///1096 /// * Collection Owner1097 /// * Collection Admin1098 ///1099 /// # Arguments1100 ///1101 /// * collection_id.1102 ///1103 /// * schema: SchemaVersion: enum1104 #[weight = <SelfWeightOf<T>>::set_schema_version()]1105 #[transactional]1106 pub fn set_schema_version(1107 origin,1108 collection_id: CollectionId,1109 version: SchemaVersion1110 ) -> DispatchResult {1111 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1112 let mut target_collection = Self::get_collection(collection_id)?;1113 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;1114 target_collection.schema_version = version;1115 target_collection.save()1116 }11171118 /// Set off-chain data schema.1119 ///1120 /// # Permissions1121 ///1122 /// * Collection Owner1123 /// * Collection Admin1124 ///1125 /// # Arguments1126 ///1127 /// * collection_id.1128 ///1129 /// * schema: String representing the offchain data schema.1130 #[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]1131 #[transactional]1132 pub fn set_offchain_schema(1133 origin,1134 collection_id: CollectionId,1135 schema: Vec<u8>1136 ) -> DispatchResult {1137 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1138 let mut target_collection = Self::get_collection(collection_id)?;1139 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11401141 // check schema limit1142 ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");11431144 target_collection.offchain_schema = schema;1145 target_collection.save()1146 }11471148 /// Set const on-chain data schema.1149 ///1150 /// # Permissions1151 ///1152 /// * Collection Owner1153 /// * Collection Admin1154 ///1155 /// # Arguments1156 ///1157 /// * collection_id.1158 ///1159 /// * schema: String representing the const on-chain data schema.1160 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1161 #[transactional]1162 pub fn set_const_on_chain_schema (1163 origin,1164 collection_id: CollectionId,1165 schema: Vec<u8>1166 ) -> DispatchResult {1167 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1168 let mut target_collection = Self::get_collection(collection_id)?;1169 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11701171 // check schema limit1172 ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");11731174 target_collection.const_on_chain_schema = schema;1175 target_collection.save()1176 }11771178 /// Set variable on-chain data schema.1179 ///1180 /// # Permissions1181 ///1182 /// * Collection Owner1183 /// * Collection Admin1184 ///1185 /// # Arguments1186 ///1187 /// * collection_id.1188 ///1189 /// * schema: String representing the variable on-chain data schema.1190 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1191 #[transactional]1192 pub fn set_variable_on_chain_schema (1193 origin,1194 collection_id: CollectionId,1195 schema: Vec<u8>1196 ) -> DispatchResult {1197 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1198 let mut target_collection = Self::get_collection(collection_id)?;1199 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;12001201 // check schema limit1202 ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");12031204 target_collection.variable_on_chain_schema = schema;1205 target_collection.save()1206 }12071208 #[weight = <SelfWeightOf<T>>::set_collection_limits()]1209 #[transactional]1210 pub fn set_collection_limits(1211 origin,1212 collection_id: u32,1213 new_limits: CollectionLimits<T::BlockNumber>,1214 ) -> DispatchResult {1215 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1216 let mut target_collection = Self::get_collection(collection_id)?;1217 Self::check_owner_permissions(&target_collection, sender.as_sub())?;1218 let old_limits = &target_collection.limits;12191220 // collection bounds1221 ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1222 new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP &&1223 new_limits.sponsored_data_size <= CUSTOM_DATA_LIMIT,1224 Error::<T>::CollectionLimitBoundsExceeded);12251226 // token_limit check prev1227 ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1228 ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);12291230 ensure!(1231 (old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1232 (old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1233 Error::<T>::OwnerPermissionsCantBeReverted,1234 );12351236 target_collection.limits = new_limits;12371238 target_collection.save()1239 }1240 }1241}12421243impl<T: Config> Module<T> {1244 pub fn create_item_internal(1245 sender: &T::CrossAccountId,1246 collection: &CollectionHandle<T>,1247 owner: &T::CrossAccountId,1248 data: CreateItemData,1249 ) -> DispatchResult {1250 Self::can_create_items_in_collection(collection, sender, owner, 1)?;1251 Self::validate_create_item_args(collection, &data)?;1252 Self::create_item_no_validation(collection, owner, data)?;12531254 Ok(())1255 }12561257 pub fn transfer_internal(1258 sender: &T::CrossAccountId,1259 recipient: &T::CrossAccountId,1260 target_collection: &CollectionHandle<T>,1261 item_id: TokenId,1262 value: u128,1263 ) -> DispatchResult {1264 target_collection.consume_gas(2000000)?;1265 // Limits check1266 Self::is_correct_transfer(target_collection, recipient)?;12671268 // Transfer permissions check1269 ensure!(1270 Self::is_item_owner(sender, target_collection, item_id)1271 || Self::is_owner_or_admin_permissions(target_collection, sender),1272 Error::<T>::NoPermission1273 );12741275 if target_collection.access == AccessMode::WhiteList {1276 Self::check_white_list(target_collection, sender)?;1277 Self::check_white_list(target_collection, recipient)?;1278 }12791280 match target_collection.mode {1281 CollectionMode::NFT => Self::transfer_nft(1282 target_collection,1283 item_id,1284 sender.clone(),1285 recipient.clone(),1286 )?,1287 CollectionMode::Fungible(_) => {1288 Self::transfer_fungible(target_collection, value, sender, recipient)?1289 }1290 CollectionMode::ReFungible => Self::transfer_refungible(1291 target_collection,1292 item_id,1293 value,1294 sender.clone(),1295 recipient.clone(),1296 )?,1297 _ => (),1298 };12991300 Self::deposit_event(RawEvent::Transfer(1301 target_collection.id,1302 item_id,1303 sender.clone(),1304 recipient.clone(),1305 value,1306 ));13071308 Ok(())1309 }13101311 pub fn approve_internal(1312 sender: &T::CrossAccountId,1313 spender: &T::CrossAccountId,1314 collection: &CollectionHandle<T>,1315 item_id: TokenId,1316 amount: u128,1317 ) -> DispatchResult {1318 collection.consume_gas(2000000)?;1319 Self::token_exists(collection, item_id)?;13201321 // Transfer permissions check1322 let bypasses_limits = collection.limits.owner_can_transfer1323 && Self::is_owner_or_admin_permissions(collection, sender);13241325 let allowance_limit = if bypasses_limits {1326 None1327 } else if let Some(amount) = Self::owned_amount(sender, collection, item_id) {1328 Some(amount)1329 } else {1330 fail!(Error::<T>::NoPermission);1331 };13321333 if collection.access == AccessMode::WhiteList {1334 Self::check_white_list(collection, sender)?;1335 Self::check_white_list(collection, spender)?;1336 }13371338 let allowance: u128 = amount1339 .checked_add(<Allowances<T>>::get(1340 collection.id,1341 (item_id, sender.as_sub(), spender.as_sub()),1342 ))1343 .ok_or(Error::<T>::NumOverflow)?;1344 if let Some(limit) = allowance_limit {1345 ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1346 }1347 <Allowances<T>>::insert(1348 collection.id,1349 (item_id, sender.as_sub(), spender.as_sub()),1350 allowance,1351 );13521353 if matches!(collection.mode, CollectionMode::NFT) {1354 // TODO: NFT: only one owner may exist for token in ERC7211355 collection.log(ERC721Events::Approval {1356 owner: *sender.as_eth(),1357 approved: *spender.as_eth(),1358 token_id: item_id.into(),1359 })?;1360 }13611362 if matches!(collection.mode, CollectionMode::Fungible(_)) {1363 // TODO: NFT: only one owner may exist for token in ERC201364 collection.log(ERC20Events::Approval {1365 owner: *sender.as_eth(),1366 spender: *spender.as_eth(),1367 value: allowance.into(),1368 })?;1369 }13701371 Self::deposit_event(RawEvent::Approved(1372 collection.id,1373 item_id,1374 sender.clone(),1375 spender.clone(),1376 allowance,1377 ));1378 Ok(())1379 }13801381 pub fn transfer_from_internal(1382 sender: &T::CrossAccountId,1383 from: &T::CrossAccountId,1384 recipient: &T::CrossAccountId,1385 collection: &CollectionHandle<T>,1386 item_id: TokenId,1387 amount: u128,1388 ) -> DispatchResult {1389 collection.consume_gas(2000000)?;1390 // Check approval1391 let approval: u128 =1392 <Allowances<T>>::get(collection.id, (item_id, from.as_sub(), sender.as_sub()));13931394 // Limits check1395 Self::is_correct_transfer(collection, recipient)?;13961397 // Transfer permissions check1398 ensure!(1399 approval >= amount1400 || (collection.limits.owner_can_transfer1401 && Self::is_owner_or_admin_permissions(collection, sender)),1402 Error::<T>::NoPermission1403 );14041405 if collection.access == AccessMode::WhiteList {1406 Self::check_white_list(collection, sender)?;1407 Self::check_white_list(collection, recipient)?;1408 }14091410 // Reduce approval by transferred amount or remove if remaining approval drops to 01411 let allowance = approval.saturating_sub(amount);1412 if allowance > 0 {1413 <Allowances<T>>::insert(1414 collection.id,1415 (item_id, from.as_sub(), sender.as_sub()),1416 allowance,1417 );1418 } else {1419 <Allowances<T>>::remove(collection.id, (item_id, from.as_sub(), sender.as_sub()));1420 }14211422 match collection.mode {1423 CollectionMode::NFT => {1424 Self::transfer_nft(collection, item_id, from.clone(), recipient.clone())?1425 }1426 CollectionMode::Fungible(_) => {1427 Self::transfer_fungible(collection, amount, from, recipient)?1428 }1429 CollectionMode::ReFungible => Self::transfer_refungible(1430 collection,1431 item_id,1432 amount,1433 from.clone(),1434 recipient.clone(),1435 )?,1436 _ => (),1437 };14381439 if matches!(collection.mode, CollectionMode::Fungible(_)) {1440 collection.log(ERC20Events::Approval {1441 owner: *from.as_eth(),1442 spender: *sender.as_eth(),1443 value: allowance.into(),1444 })?;1445 }14461447 Ok(())1448 }14491450 pub fn set_variable_meta_data_internal(1451 sender: &T::CrossAccountId,1452 collection: &CollectionHandle<T>,1453 item_id: TokenId,1454 data: Vec<u8>,1455 ) -> DispatchResult {1456 Self::token_exists(collection, item_id)?;14571458 ensure!(1459 CUSTOM_DATA_LIMIT >= data.len() as u32,1460 Error::<T>::TokenVariableDataLimitExceeded1461 );14621463 // Modify permissions check1464 ensure!(1465 Self::is_item_owner(sender, collection, item_id)1466 || Self::is_owner_or_admin_permissions(collection, sender),1467 Error::<T>::NoPermission1468 );14691470 match collection.mode {1471 CollectionMode::NFT => Self::set_nft_variable_data(collection, item_id, data)?,1472 CollectionMode::ReFungible => {1473 Self::set_re_fungible_variable_data(collection, item_id, data)?1474 }1475 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1476 _ => fail!(Error::<T>::UnexpectedCollectionType),1477 };14781479 Ok(())1480 }14811482 pub fn create_multiple_items_internal(1483 sender: &T::CrossAccountId,1484 collection: &CollectionHandle<T>,1485 owner: &T::CrossAccountId,1486 items_data: Vec<CreateItemData>,1487 ) -> DispatchResult {1488 Self::can_create_items_in_collection(collection, sender, owner, items_data.len() as u32)?;14891490 for data in &items_data {1491 Self::validate_create_item_args(collection, data)?;1492 }1493 for data in &items_data {1494 Self::create_item_no_validation(collection, owner, data.clone())?;1495 }14961497 Ok(())1498 }14991500 pub fn burn_item_internal(1501 sender: &T::CrossAccountId,1502 collection: &CollectionHandle<T>,1503 item_id: TokenId,1504 value: u128,1505 ) -> DispatchResult {1506 ensure!(1507 Self::is_item_owner(sender, collection, item_id)1508 || (collection.limits.owner_can_transfer1509 && Self::is_owner_or_admin_permissions(collection, sender)),1510 Error::<T>::NoPermission1511 );15121513 if collection.access == AccessMode::WhiteList {1514 Self::check_white_list(collection, sender)?;1515 }15161517 match collection.mode {1518 CollectionMode::NFT => Self::burn_nft_item(collection, item_id)?,1519 CollectionMode::Fungible(_) => Self::burn_fungible_item(sender, collection, value)?,1520 CollectionMode::ReFungible => Self::burn_refungible_item(collection, item_id, sender)?,1521 _ => (),1522 };15231524 Ok(())1525 }15261527 pub fn toggle_white_list_internal(1528 sender: &T::CrossAccountId,1529 collection: &CollectionHandle<T>,1530 address: &T::CrossAccountId,1531 whitelisted: bool,1532 ) -> DispatchResult {1533 Self::check_owner_or_admin_permissions(collection, sender)?;15341535 if whitelisted {1536 <WhiteList<T>>::insert(collection.id, address.as_sub(), true);1537 } else {1538 <WhiteList<T>>::remove(collection.id, address.as_sub());1539 }15401541 Ok(())1542 }15431544 fn is_correct_transfer(1545 collection: &CollectionHandle<T>,1546 recipient: &T::CrossAccountId,1547 ) -> DispatchResult {1548 let collection_id = collection.id;15491550 // check token limit and account token limit1551 let account_items: u32 =1552 <AddressTokens<T>>::get(collection_id, recipient.as_sub()).len() as u32;1553 ensure!(1554 collection.limits.account_token_ownership_limit > account_items,1555 Error::<T>::AccountTokenLimitExceeded1556 );15571558 // preliminary transfer check1559 ensure!(collection.transfers_enabled, Error::<T>::TransferNotAllowed);15601561 Ok(())1562 }15631564 fn can_create_items_in_collection(1565 collection: &CollectionHandle<T>,1566 sender: &T::CrossAccountId,1567 owner: &T::CrossAccountId,1568 amount: u32,1569 ) -> DispatchResult {1570 let collection_id = collection.id;15711572 // check token limit and account token limit1573 let total_items: u32 = ItemListIndex::get(collection_id)1574 .checked_add(amount)1575 .ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1576 let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner.as_sub()).len()1577 as u32)1578 .checked_add(amount)1579 .ok_or(Error::<T>::AccountTokenLimitExceeded)?;1580 ensure!(1581 collection.limits.token_limit >= total_items,1582 Error::<T>::CollectionTokenLimitExceeded1583 );1584 ensure!(1585 collection.limits.account_token_ownership_limit >= account_items,1586 Error::<T>::AccountTokenLimitExceeded1587 );15881589 if !Self::is_owner_or_admin_permissions(collection, sender) {1590 ensure!(collection.mint_mode, Error::<T>::PublicMintingNotAllowed);1591 Self::check_white_list(collection, owner)?;1592 Self::check_white_list(collection, sender)?;1593 }15941595 Ok(())1596 }15971598 fn validate_create_item_args(1599 target_collection: &CollectionHandle<T>,1600 data: &CreateItemData,1601 ) -> DispatchResult {1602 match target_collection.mode {1603 CollectionMode::NFT => {1604 if !matches!(data, CreateItemData::NFT(_)) {1605 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1606 }1607 }1608 CollectionMode::Fungible(_) => {1609 if !matches!(data, CreateItemData::Fungible(_)) {1610 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1611 }1612 }1613 CollectionMode::ReFungible => {1614 if let CreateItemData::ReFungible(data) = data {1615 // Check refungibility limits1616 ensure!(1617 data.pieces <= MAX_REFUNGIBLE_PIECES,1618 Error::<T>::WrongRefungiblePieces1619 );1620 ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1621 } else {1622 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1623 }1624 }1625 _ => {1626 fail!(Error::<T>::UnexpectedCollectionType);1627 }1628 };16291630 Ok(())1631 }16321633 fn create_item_no_validation(1634 collection: &CollectionHandle<T>,1635 owner: &T::CrossAccountId,1636 data: CreateItemData,1637 ) -> DispatchResult {1638 match data {1639 CreateItemData::NFT(data) => {1640 let item = NftItemType {1641 owner: owner.clone(),1642 const_data: data.const_data.into_inner(),1643 variable_data: data.variable_data.into_inner(),1644 };16451646 Self::add_nft_item(collection, item)?;1647 }1648 CreateItemData::Fungible(data) => {1649 Self::add_fungible_item(collection, owner, data.value)?;1650 }1651 CreateItemData::ReFungible(data) => {1652 let owner_list = vec![Ownership {1653 owner: owner.clone(),1654 fraction: data.pieces,1655 }];16561657 let item = ReFungibleItemType {1658 owner: owner_list,1659 const_data: data.const_data.into_inner(),1660 variable_data: data.variable_data.into_inner(),1661 };16621663 Self::add_refungible_item(collection, item)?;1664 }1665 };16661667 Ok(())1668 }16691670 fn add_fungible_item(1671 collection: &CollectionHandle<T>,1672 owner: &T::CrossAccountId,1673 value: u128,1674 ) -> DispatchResult {1675 let collection_id = collection.id;16761677 // Does new owner already have an account?1678 let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner.as_sub()).value;16791680 // Mint1681 let item = FungibleItemType {1682 value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,1683 };1684 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), item);16851686 // Update balance1687 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1688 .checked_add(value)1689 .ok_or(Error::<T>::NumOverflow)?;1690 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);16911692 Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));1693 Ok(())1694 }16951696 fn add_refungible_item(1697 collection: &CollectionHandle<T>,1698 item: ReFungibleItemType<T::CrossAccountId>,1699 ) -> DispatchResult {1700 let collection_id = collection.id;17011702 let current_index = <ItemListIndex>::get(collection_id)1703 .checked_add(1)1704 .ok_or(Error::<T>::NumOverflow)?;1705 let itemcopy = item.clone();17061707 ensure!(item.owner.len() == 1, Error::<T>::BadCreateRefungibleCall,);1708 let item_owner = item.owner.first().expect("only one owner is defined");17091710 let value = item_owner.fraction;1711 let owner = item_owner.owner.clone();17121713 Self::add_token_index(collection_id, current_index, &owner)?;17141715 <ItemListIndex>::insert(collection_id, current_index);1716 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);17171718 // Update balance1719 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1720 .checked_add(value)1721 .ok_or(Error::<T>::NumOverflow)?;1722 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);17231724 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));1725 Ok(())1726 }17271728 fn add_nft_item(1729 collection: &CollectionHandle<T>,1730 item: NftItemType<T::CrossAccountId>,1731 ) -> DispatchResult {1732 let collection_id = collection.id;17331734 let current_index = <ItemListIndex>::get(collection_id)1735 .checked_add(1)1736 .ok_or(Error::<T>::NumOverflow)?;17371738 let item_owner = item.owner.clone();1739 Self::add_token_index(collection_id, current_index, &item.owner)?;17401741 <ItemListIndex>::insert(collection_id, current_index);1742 <NftItemList<T>>::insert(collection_id, current_index, item);17431744 // Update balance1745 let new_balance = <Balance<T>>::get(collection_id, item_owner.as_sub())1746 .checked_add(1)1747 .ok_or(Error::<T>::NumOverflow)?;1748 <Balance<T>>::insert(collection_id, item_owner.as_sub(), new_balance);17491750 collection.log(ERC721Events::Transfer {1751 from: H160::default(),1752 to: *item_owner.as_eth(),1753 token_id: current_index.into(),1754 })?;1755 Self::deposit_event(RawEvent::ItemCreated(1756 collection_id,1757 current_index,1758 item_owner,1759 ));1760 Ok(())1761 }17621763 fn burn_refungible_item(1764 collection: &CollectionHandle<T>,1765 item_id: TokenId,1766 owner: &T::CrossAccountId,1767 ) -> DispatchResult {1768 let collection_id = collection.id;17691770 let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)1771 .ok_or(Error::<T>::TokenNotFound)?;1772 let rft_balance = token1773 .owner1774 .iter()1775 .find(|&i| i.owner == *owner)1776 .ok_or(Error::<T>::TokenNotFound)?;1777 Self::remove_token_index(collection_id, item_id, owner)?;17781779 // update balance1780 let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.as_sub())1781 .checked_sub(rft_balance.fraction)1782 .ok_or(Error::<T>::NumOverflow)?;1783 <Balance<T>>::insert(collection_id, rft_balance.owner.as_sub(), new_balance);17841785 // Re-create owners list with sender removed1786 let index = token1787 .owner1788 .iter()1789 .position(|i| i.owner == *owner)1790 .expect("owned item is exists");1791 token.owner.remove(index);1792 let owner_count = token.owner.len();17931794 // Burn the token completely if this was the last (only) owner1795 if owner_count == 0 {1796 <ReFungibleItemList<T>>::remove(collection_id, item_id);1797 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);1798 } else {1799 <ReFungibleItemList<T>>::insert(collection_id, item_id, token);1800 }18011802 Ok(())1803 }18041805 fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1806 let collection_id = collection.id;18071808 let item =1809 <NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;1810 Self::remove_token_index(collection_id, item_id, &item.owner)?;18111812 // update balance1813 let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())1814 .checked_sub(1)1815 .ok_or(Error::<T>::NumOverflow)?;1816 <Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);1817 <NftItemList<T>>::remove(collection_id, item_id);1818 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);18191820 Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id));1821 Ok(())1822 }18231824 fn burn_fungible_item(1825 owner: &T::CrossAccountId,1826 collection: &CollectionHandle<T>,1827 value: u128,1828 ) -> DispatchResult {1829 let collection_id = collection.id;18301831 let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());1832 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);18331834 // update balance1835 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1836 .checked_sub(value)1837 .ok_or(Error::<T>::NumOverflow)?;1838 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);18391840 if balance.value - value > 0 {1841 balance.value -= value;1842 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);1843 } else {1844 <FungibleItemList<T>>::remove(collection_id, owner.as_sub());1845 }18461847 collection.log(ERC20Events::Transfer {1848 from: *owner.as_eth(),1849 to: H160::default(),1850 value: value.into(),1851 })?;1852 Ok(())1853 }18541855 pub fn get_collection(1856 collection_id: CollectionId,1857 ) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {1858 Ok(<CollectionHandle<T>>::get(collection_id).ok_or(Error::<T>::CollectionNotFound)?)1859 }18601861 fn check_owner_permissions(1862 target_collection: &CollectionHandle<T>,1863 subject: &T::AccountId,1864 ) -> DispatchResult {1865 ensure!(1866 *subject == target_collection.owner,1867 Error::<T>::NoPermission1868 );18691870 Ok(())1871 }18721873 fn is_owner_or_admin_permissions(1874 collection: &CollectionHandle<T>,1875 subject: &T::CrossAccountId,1876 ) -> bool {1877 *subject.as_sub() == collection.owner1878 || <AdminList<T>>::get(collection.id).contains(subject)1879 }18801881 fn check_owner_or_admin_permissions(1882 collection: &CollectionHandle<T>,1883 subject: &T::CrossAccountId,1884 ) -> DispatchResult {1885 ensure!(1886 Self::is_owner_or_admin_permissions(collection, subject),1887 Error::<T>::NoPermission1888 );18891890 Ok(())1891 }18921893 fn owned_amount(1894 subject: &T::CrossAccountId,1895 target_collection: &CollectionHandle<T>,1896 item_id: TokenId,1897 ) -> Option<u128> {1898 let collection_id = target_collection.id;18991900 match target_collection.mode {1901 CollectionMode::NFT => {1902 (<NftItemList<T>>::get(collection_id, item_id)?.owner == *subject).then(|| 1)1903 }1904 CollectionMode::Fungible(_) => {1905 Some(<FungibleItemList<T>>::get(collection_id, &subject.as_sub()).value)1906 }1907 CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?1908 .owner1909 .iter()1910 .find(|i| i.owner == *subject)1911 .map(|i| i.fraction),1912 CollectionMode::Invalid => None,1913 }1914 }19151916 fn is_item_owner(1917 subject: &T::CrossAccountId,1918 target_collection: &CollectionHandle<T>,1919 item_id: TokenId,1920 ) -> bool {1921 match target_collection.mode {1922 CollectionMode::Fungible(_) => true,1923 _ => Self::owned_amount(subject, target_collection, item_id).is_some(),1924 }1925 }19261927 fn check_white_list(1928 collection: &CollectionHandle<T>,1929 address: &T::CrossAccountId,1930 ) -> DispatchResult {1931 let collection_id = collection.id;19321933 let mes = Error::<T>::AddresNotInWhiteList;1934 ensure!(1935 <WhiteList<T>>::contains_key(collection_id, address.as_sub()),1936 mes1937 );19381939 Ok(())1940 }19411942 /// Check if token exists. In case of Fungible, check if there is an entry for1943 /// the owner in fungible balances double map1944 fn token_exists(target_collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1945 let collection_id = target_collection.id;1946 let exists = match target_collection.mode {1947 CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),1948 CollectionMode::Fungible(_) => true,1949 CollectionMode::ReFungible => {1950 <ReFungibleItemList<T>>::contains_key(collection_id, item_id)1951 }1952 _ => false,1953 };19541955 ensure!(exists, Error::<T>::TokenNotFound);1956 Ok(())1957 }19581959 fn transfer_fungible(1960 collection: &CollectionHandle<T>,1961 value: u128,1962 owner: &T::CrossAccountId,1963 recipient: &T::CrossAccountId,1964 ) -> DispatchResult {1965 let collection_id = collection.id;19661967 let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());1968 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);19691970 // Send balance to recipient (updates balanceOf of recipient)1971 Self::add_fungible_item(collection, recipient, value)?;19721973 // update balanceOf of sender1974 <Balance<T>>::insert(collection_id, owner.as_sub(), balance.value - value);19751976 // Reduce or remove sender1977 if balance.value == value {1978 <FungibleItemList<T>>::remove(collection_id, owner.as_sub());1979 } else {1980 balance.value -= value;1981 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);1982 }19831984 collection.log(ERC20Events::Transfer {1985 from: *owner.as_eth(),1986 to: *recipient.as_eth(),1987 value: value.into(),1988 })?;1989 Self::deposit_event(RawEvent::Transfer(1990 collection.id,1991 1,1992 owner.clone(),1993 recipient.clone(),1994 value,1995 ));19961997 Ok(())1998 }19992000 fn transfer_refungible(2001 collection: &CollectionHandle<T>,2002 item_id: TokenId,2003 value: u128,2004 owner: T::CrossAccountId,2005 new_owner: T::CrossAccountId,2006 ) -> DispatchResult {2007 let collection_id = collection.id;2008 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)2009 .ok_or(Error::<T>::TokenNotFound)?;20102011 let item = full_item2012 .owner2013 .iter()2014 .find(|i| i.owner == owner)2015 .ok_or(Error::<T>::TokenNotFound)?;2016 let amount = item.fraction;20172018 ensure!(amount >= value, Error::<T>::TokenValueTooLow);20192020 // update balance2021 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2022 .checked_sub(value)2023 .ok_or(Error::<T>::NumOverflow)?;2024 <Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);20252026 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2027 .checked_add(value)2028 .ok_or(Error::<T>::NumOverflow)?;2029 <Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);20302031 let old_owner = item.owner.clone();2032 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);20332034 let mut new_full_item = full_item.clone();2035 // transfer2036 if amount == value && !new_owner_has_account {2037 // change owner2038 // new owner do not have account2039 new_full_item2040 .owner2041 .iter_mut()2042 .find(|i| i.owner == owner)2043 .expect("old owner does present in refungible")2044 .owner = new_owner.clone();2045 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);20462047 // update index collection2048 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;2049 } else {2050 new_full_item2051 .owner2052 .iter_mut()2053 .find(|i| i.owner == owner)2054 .expect("old owner does present in refungible")2055 .fraction -= value;20562057 // separate amount2058 if new_owner_has_account {2059 // new owner has account2060 new_full_item2061 .owner2062 .iter_mut()2063 .find(|i| i.owner == new_owner)2064 .expect("new owner has account")2065 .fraction += value;2066 } else {2067 // new owner do not have account2068 new_full_item.owner.push(Ownership {2069 owner: new_owner.clone(),2070 fraction: value,2071 });2072 Self::add_token_index(collection_id, item_id, &new_owner)?;2073 }20742075 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2076 }20772078 Self::deposit_event(RawEvent::Transfer(2079 collection.id,2080 item_id,2081 owner,2082 new_owner,2083 amount,2084 ));20852086 Ok(())2087 }20882089 fn transfer_nft(2090 collection: &CollectionHandle<T>,2091 item_id: TokenId,2092 sender: T::CrossAccountId,2093 new_owner: T::CrossAccountId,2094 ) -> DispatchResult {2095 let collection_id = collection.id;2096 let mut item =2097 <NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;20982099 ensure!(sender == item.owner, Error::<T>::MustBeTokenOwner);21002101 // update balance2102 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2103 .checked_sub(1)2104 .ok_or(Error::<T>::NumOverflow)?;2105 <Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);21062107 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2108 .checked_add(1)2109 .ok_or(Error::<T>::NumOverflow)?;2110 <Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);21112112 // change owner2113 let old_owner = item.owner.clone();2114 item.owner = new_owner.clone();2115 <NftItemList<T>>::insert(collection_id, item_id, item);21162117 // update index collection2118 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;21192120 collection.log(ERC721Events::Transfer {2121 from: *sender.as_eth(),2122 to: *new_owner.as_eth(),2123 token_id: item_id.into(),2124 })?;2125 Self::deposit_event(RawEvent::Transfer(2126 collection.id,2127 item_id,2128 sender,2129 new_owner,2130 1,2131 ));21322133 Ok(())2134 }21352136 fn set_re_fungible_variable_data(2137 collection: &CollectionHandle<T>,2138 item_id: TokenId,2139 data: Vec<u8>,2140 ) -> DispatchResult {2141 let collection_id = collection.id;2142 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2143 .ok_or(Error::<T>::TokenNotFound)?;21442145 item.variable_data = data;21462147 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);21482149 Ok(())2150 }21512152 fn set_nft_variable_data(2153 collection: &CollectionHandle<T>,2154 item_id: TokenId,2155 data: Vec<u8>,2156 ) -> DispatchResult {2157 let collection_id = collection.id;2158 let mut item =2159 <NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;21602161 item.variable_data = data;21622163 <NftItemList<T>>::insert(collection_id, item_id, item);21642165 Ok(())2166 }21672168 #[allow(dead_code)]2169 fn init_collection(item: &Collection<T>) {2170 // check params2171 assert!(2172 item.decimal_points <= MAX_DECIMAL_POINTS,2173 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2174 );2175 assert!(2176 item.name.len() <= 64,2177 "Collection name can not be longer than 63 char"2178 );2179 assert!(2180 item.name.len() <= 256,2181 "Collection description can not be longer than 255 char"2182 );2183 assert!(2184 item.token_prefix.len() <= 16,2185 "Token prefix can not be longer than 15 char"2186 );21872188 // Generate next collection ID2189 let next_id = CreatedCollectionCount::get().checked_add(1).unwrap();21902191 CreatedCollectionCount::put(next_id);2192 }21932194 #[allow(dead_code)]2195 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::CrossAccountId>) {2196 let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();21972198 Self::add_token_index(collection_id, current_index, &item.owner).unwrap();21992200 <ItemListIndex>::insert(collection_id, current_index);22012202 // Update balance2203 let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2204 .checked_add(1)2205 .unwrap();2206 <Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2207 }22082209 #[allow(dead_code)]2210 fn init_fungible_token(2211 collection_id: CollectionId,2212 owner: &T::CrossAccountId,2213 item: &FungibleItemType,2214 ) {2215 let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();22162217 Self::add_token_index(collection_id, current_index, owner).unwrap();22182219 <ItemListIndex>::insert(collection_id, current_index);22202221 // Update balance2222 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2223 .checked_add(item.value)2224 .unwrap();2225 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2226 }22272228 #[allow(dead_code)]2229 fn init_refungible_token(2230 collection_id: CollectionId,2231 item: &ReFungibleItemType<T::CrossAccountId>,2232 ) {2233 let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();22342235 let value = item.owner.first().unwrap().fraction;2236 let owner = item.owner.first().unwrap().owner.clone();22372238 Self::add_token_index(collection_id, current_index, &owner).unwrap();22392240 <ItemListIndex>::insert(collection_id, current_index);22412242 // Update balance2243 let new_balance = <Balance<T>>::get(collection_id, &owner.as_sub())2244 .checked_add(value)2245 .unwrap();2246 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2247 }22482249 fn add_token_index(2250 collection_id: CollectionId,2251 item_index: TokenId,2252 owner: &T::CrossAccountId,2253 ) -> DispatchResult {2254 // add to account limit2255 if <AccountItemCount<T>>::contains_key(owner.as_sub()) {2256 // bound Owned tokens by a single address2257 let count = <AccountItemCount<T>>::get(owner.as_sub());2258 ensure!(2259 count < ACCOUNT_TOKEN_OWNERSHIP_LIMIT,2260 Error::<T>::AddressOwnershipLimitExceeded2261 );22622263 <AccountItemCount<T>>::insert(2264 owner.as_sub(),2265 count.checked_add(1).ok_or(Error::<T>::NumOverflow)?,2266 );2267 } else {2268 <AccountItemCount<T>>::insert(owner.as_sub(), 1);2269 }22702271 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2272 if list_exists {2273 let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2274 let item_contains = list.contains(&item_index.clone());22752276 if !item_contains {2277 list.push(item_index);2278 }22792280 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2281 } else {2282 let itm = vec![item_index];2283 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), itm);2284 }22852286 Ok(())2287 }22882289 fn remove_token_index(2290 collection_id: CollectionId,2291 item_index: TokenId,2292 owner: &T::CrossAccountId,2293 ) -> DispatchResult {2294 // update counter2295 <AccountItemCount<T>>::insert(2296 owner.as_sub(),2297 <AccountItemCount<T>>::get(owner.as_sub())2298 .checked_sub(1)2299 .ok_or(Error::<T>::NumOverflow)?,2300 );23012302 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2303 if list_exists {2304 let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2305 let item_contains = list.contains(&item_index.clone());23062307 if item_contains {2308 list.retain(|&item| item != item_index);2309 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2310 }2311 }23122313 Ok(())2314 }23152316 fn move_token_index(2317 collection_id: CollectionId,2318 item_index: TokenId,2319 old_owner: &T::CrossAccountId,2320 new_owner: &T::CrossAccountId,2321 ) -> DispatchResult {2322 Self::remove_token_index(collection_id, item_index, old_owner)?;2323 Self::add_token_index(collection_id, item_index, new_owner)?;23242325 Ok(())2326 }2327}23282329sp_api::decl_runtime_apis! {2330 pub trait NftApi {2331 /// Used for ethereum integration2332 fn eth_contract_code(account: H160) -> Option<Vec<u8>>;2333 }2334}