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 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}