difftreelog
refactor remove "invalid" collection mode
in: master
7 files changed
pallets/nft/src/eth/mod.rsdiffbeforeafterboth--- a/pallets/nft/src/eth/mod.rs
+++ b/pallets/nft/src/eth/mod.rs
@@ -103,7 +103,6 @@
CollectionMode::ReFungible => {
include_bytes!("stubs/UniqueRefungible.raw") as &[u8]
}
- CollectionMode::Invalid => include_bytes!("stubs/UniqueInvalid.raw") as &[u8],
}
.to_owned()
})
pallets/nft/src/eth/stubs/UniqueInvalid.rawdiffbeforeafterboth--- a/pallets/nft/src/eth/stubs/UniqueInvalid.raw
+++ /dev/null
@@ -1 +0,0 @@
-TODO
\ No newline at end of file
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::{DispatchError, 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 MetaUpdatePermission, 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 /// Metadata update denied by collection settings149 MetadataUpdateDenied,150 /// Metadata update flag become unmutable with None option151 MetadataFlagFrozen,152 /// Collection settings not allowing items transferring153 TransferNotAllowed,154 /// Can't transfer tokens to ethereum zero address155 AddressIsZero,156 }157}158159#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]160pub struct CollectionHandle<T: Config> {161 pub id: CollectionId,162 collection: Collection<T>,163 recorder: pallet_evm_coder_substrate::SubstrateRecorder<T>,164}165impl<T: Config> CollectionHandle<T> {166 pub fn get_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {167 <CollectionById<T>>::get(id).map(|collection| Self {168 id,169 collection,170 recorder: pallet_evm_coder_substrate::SubstrateRecorder::new(171 eth::collection_id_to_address(id),172 gas_limit,173 ),174 })175 }176 pub fn get(id: CollectionId) -> Option<Self> {177 Self::get_with_gas_limit(id, u64::MAX)178 }179 pub fn log(&self, log: impl evm_coder::ToLog) -> DispatchResult {180 self.recorder.log_sub(log)181 }182 #[allow(dead_code)]183 fn consume_gas(&self, gas: u64) -> DispatchResult {184 self.recorder.consume_gas_sub(gas)185 }186 fn consume_sload(&self) -> DispatchResult {187 self.recorder.consume_sload_sub()188 }189 fn consume_sstore(&self) -> DispatchResult {190 self.recorder.consume_sstore_sub()191 }192 pub fn submit_logs(self) -> DispatchResult {193 self.recorder.submit_logs()194 }195 pub fn save(self) -> DispatchResult {196 self.recorder.submit_logs()?;197 <CollectionById<T>>::insert(self.id, self.collection);198 Ok(())199 }200}201impl<T: Config> Deref for CollectionHandle<T> {202 type Target = Collection<T>;203204 fn deref(&self) -> &Self::Target {205 &self.collection206 }207}208209impl<T: Config> DerefMut for CollectionHandle<T> {210 fn deref_mut(&mut self) -> &mut Self::Target {211 &mut self.collection212 }213}214215pub trait Config: system::Config + pallet_evm_coder_substrate::Config + Sized {216 type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;217218 /// Weight information for extrinsics in this pallet.219 type WeightInfo: WeightInfo;220221 type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;222 type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;223224 type CrossAccountId: CrossAccountId<Self::AccountId>;225 type Currency: Currency<Self::AccountId>;226 type CollectionCreationPrice: Get<227 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,228 >;229 type TreasuryAccountId: Get<Self::AccountId>;230}231232type SelfWeightOf<T> = <T as Config>::WeightInfo;233234trait WeightInfoHelpers: WeightInfo {235 fn transfer() -> Weight {236 Self::transfer_nft()237 .max(Self::transfer_fungible())238 .max(Self::transfer_refungible())239 }240 fn transfer_from() -> Weight {241 Self::transfer_from_nft()242 .max(Self::transfer_from_fungible())243 .max(Self::transfer_from_refungible())244 }245 fn approve() -> Weight {246 // TODO: refungible, fungible247 Self::approve_nft()248 }249 fn set_variable_meta_data(data: u32) -> Weight {250 // TODO: refungible251 Self::set_variable_meta_data_nft(data)252 }253 fn create_item(data: u32) -> Weight {254 Self::create_item_nft(data)255 .max(Self::create_item_fungible())256 .max(Self::create_item_refungible(data))257 }258 fn create_multiple_items(amount: u32) -> Weight {259 Self::create_multiple_items_nft(amount)260 .max(Self::create_multiple_items_fungible(amount))261 .max(Self::create_multiple_items_refungible(amount))262 }263 fn burn_item() -> Weight {264 // TODO: refungible, fungible265 Self::burn_item_nft()266 }267}268impl<T: WeightInfo> WeightInfoHelpers for T {}269270// # Used definitions271//272// ## User control levels273//274// chain-controlled - key is uncontrolled by user275// i.e autoincrementing index276// can use non-cryptographic hash277// real - key is controlled by user278// but it is hard to generate enough colliding values, i.e owner of signed txs279// can use non-cryptographic hash280// controlled - key is completly controlled by users281// i.e maps with mutable keys282// should use cryptographic hash283//284// ## User control level downgrade reasons285//286// ?1 - chain-controlled -> controlled287// collections/tokens can be destroyed, resulting in massive holes288// ?2 - chain-controlled -> controlled289// same as ?1, but can be only added, resulting in easier exploitation290// ?3 - real -> controlled291// no confirmation required, so addresses can be easily generated292decl_storage! {293 trait Store for Module<T: Config> as Nft {294295 //#region Private members296 /// Id of next collection297 CreatedCollectionCount: u32;298 /// Used for migrations299 ChainVersion: u64;300 /// Id of last collection token301 /// Collection id (controlled?1)302 ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;303 //#endregion304305 //#region Bound counters306 /// Amount of collections destroyed, used for total amount tracking with307 /// CreatedCollectionCount308 DestroyedCollectionCount: u32;309 /// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)310 /// Account id (real)311 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;312 //#endregion313314 //#region Basic collections315 /// Collection info316 /// Collection id (controlled?1)317 pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;318 /// List of collection admins319 /// Collection id (controlled?2)320 pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::CrossAccountId>;321 /// Whitelisted collection users322 /// Collection id (controlled?2), user id (controlled?3)323 pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;324 //#endregion325326 /// How many of collection items user have327 /// Collection id (controlled?2), account id (real)328 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;329330 /// Amount of items which spender can transfer out of owners account (via transferFrom)331 /// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))332 /// TODO: Off chain worker should remove from this map when token gets removed333 pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;334335 //#region Item collections336 /// Collection id (controlled?2), token id (controlled?1)337 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::CrossAccountId>>;338 /// Collection id (controlled?2), owner (controlled?2)339 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;340 /// Collection id (controlled?2), token id (controlled?1)341 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::CrossAccountId>>;342 //#endregion343344 //#region Index list345 /// Collection id (controlled?2), tokens owner (controlled?2)346 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;347 //#endregion348349 //#region Tokens transfer rate limit baskets350 /// (Collection id (controlled?2), who created (real))351 /// TODO: Off chain worker should remove from this map when collection gets removed352 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;353 /// Collection id (controlled?2), token id (controlled?2)354 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;355 /// Collection id (controlled?2), owning user (real)356 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;357 /// Collection id (controlled?2), token id (controlled?2)358 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;359 //#endregion360361 /// Variable metadata sponsoring362 /// Collection id (controlled?2), token id (controlled?2)363 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;364 }365 add_extra_genesis {366 build(|config: &GenesisConfig<T>| {367 // Modification of storage368 for (_num, _c) in &config.collection_id {369 <Module<T>>::init_collection(_c);370 }371372 for (_num, _c, _i) in &config.nft_item_id {373 <Module<T>>::init_nft_token(*_c, _i);374 }375376 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {377 <Module<T>>::init_fungible_token(*collection_id, &T::CrossAccountId::from_sub(account_id.clone()), fungible_item);378 }379380 for (_num, _c, _i) in &config.refungible_item_id {381 <Module<T>>::init_refungible_token(*_c, _i);382 }383 })384 }385}386387decl_event!(388 pub enum Event<T>389 where390 AccountId = <T as frame_system::Config>::AccountId,391 CrossAccountId = <T as Config>::CrossAccountId,392 {393 /// New collection was created394 ///395 /// # Arguments396 ///397 /// * collection_id: Globally unique identifier of newly created collection.398 ///399 /// * mode: [CollectionMode] converted into u8.400 ///401 /// * account_id: Collection owner.402 CollectionCreated(CollectionId, u8, AccountId),403404 /// New item was created.405 ///406 /// # Arguments407 ///408 /// * collection_id: Id of the collection where item was created.409 ///410 /// * item_id: Id of an item. Unique within the collection.411 ///412 /// * recipient: Owner of newly created item413 ItemCreated(CollectionId, TokenId, CrossAccountId),414415 /// Collection item was burned.416 ///417 /// # Arguments418 ///419 /// collection_id.420 ///421 /// item_id: Identifier of burned NFT.422 ItemDestroyed(CollectionId, TokenId),423424 /// Item was transferred425 ///426 /// * collection_id: Id of collection to which item is belong427 ///428 /// * item_id: Id of an item429 ///430 /// * sender: Original owner of item431 ///432 /// * recipient: New owner of item433 ///434 /// * amount: Always 1 for NFT435 Transfer(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),436437 /// * collection_id438 ///439 /// * item_id440 ///441 /// * sender442 ///443 /// * spender444 ///445 /// * amount446 Approved(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),447 }448);449450decl_module! {451 pub struct Module<T: Config> for enum Call452 where453 origin: T::Origin454 {455 fn deposit_event() = default;456 const CollectionAdminsLimit: u64 = COLLECTION_ADMINS_LIMIT;457 type Error = Error<T>;458459 fn on_initialize(_now: T::BlockNumber) -> Weight {460 0461 }462463 /// 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.464 ///465 /// # Permissions466 ///467 /// * Anyone.468 ///469 /// # Arguments470 ///471 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.472 ///473 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.474 ///475 /// * token_prefix: UTF-8 string with token prefix.476 ///477 /// * mode: [CollectionMode] collection type and type dependent data.478 // returns collection ID479 #[weight = <SelfWeightOf<T>>::create_collection()]480 #[transactional]481 pub fn create_collection(origin,482 collection_name: Vec<u16>,483 collection_description: Vec<u16>,484 token_prefix: Vec<u8>,485 mode: CollectionMode) -> DispatchResult {486487 // Anyone can create a collection488 let who = ensure_signed(origin)?;489490 // Take a (non-refundable) deposit of collection creation491 let mut imbalance = <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();492 imbalance.subsume(<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(493 &T::TreasuryAccountId::get(),494 T::CollectionCreationPrice::get(),495 ));496 <T as Config>::Currency::settle(497 &who,498 imbalance,499 WithdrawReasons::TRANSFER,500 ExistenceRequirement::KeepAlive,501 ).map_err(|_| Error::<T>::NoPermission)?;502503 let decimal_points = match mode {504 CollectionMode::Fungible(points) => points,505 _ => 0506 };507508 let created_count = CreatedCollectionCount::get();509 let destroyed_count = DestroyedCollectionCount::get();510511 // bound Total number of collections512 ensure!(created_count - destroyed_count < COLLECTION_NUMBER_LIMIT, Error::<T>::TotalCollectionsLimitExceeded);513514 // check params515 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);516 ensure!(collection_name.len() <= MAX_COLLECTION_NAME_LENGTH, Error::<T>::CollectionNameLimitExceeded);517 ensure!(collection_description.len() <= MAX_COLLECTION_DESCRIPTION_LENGTH, Error::<T>::CollectionDescriptionLimitExceeded);518 ensure!(token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH, Error::<T>::CollectionTokenPrefixLimitExceeded);519520 // Generate next collection ID521 let next_id = created_count522 .checked_add(1)523 .ok_or(Error::<T>::NumOverflow)?;524525 CreatedCollectionCount::put(next_id);526527 let limits = CollectionLimits {528 sponsored_data_size: CUSTOM_DATA_LIMIT,529 ..Default::default()530 };531532 // Create new collection533 let new_collection = Collection {534 owner: who.clone(),535 name: collection_name,536 mode: mode.clone(),537 mint_mode: false,538 access: AccessMode::Normal,539 description: collection_description,540 decimal_points,541 token_prefix,542 offchain_schema: Vec::new(),543 schema_version: SchemaVersion::ImageURL,544 sponsorship: SponsorshipState::Disabled,545 variable_on_chain_schema: Vec::new(),546 const_on_chain_schema: Vec::new(),547 limits,548 meta_update_permission: MetaUpdatePermission::default(),549 transfers_enabled: true,550 };551552 // Add new collection to map553 <CollectionById<T>>::insert(next_id, new_collection);554555 // call event556 Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.id(), who));557558 Ok(())559 }560561 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.562 ///563 /// # Permissions564 ///565 /// * Collection Owner.566 ///567 /// # Arguments568 ///569 /// * collection_id: collection to destroy.570 #[weight = <SelfWeightOf<T>>::destroy_collection()]571 #[transactional]572 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {573574 let sender = ensure_signed(origin)?;575 let collection = Self::get_collection(collection_id)?;576 Self::check_owner_permissions(&collection, &sender)?;577 if !collection.limits.owner_can_destroy {578 fail!(Error::<T>::NoPermission);579 }580581 <AddressTokens<T>>::remove_prefix(collection_id, None);582 <Allowances<T>>::remove_prefix(collection_id, None);583 <Balance<T>>::remove_prefix(collection_id, None);584 <ItemListIndex>::remove(collection_id);585 <AdminList<T>>::remove(collection_id);586 <CollectionById<T>>::remove(collection_id);587 <WhiteList<T>>::remove_prefix(collection_id, None);588589 <NftItemList<T>>::remove_prefix(collection_id, None);590 <FungibleItemList<T>>::remove_prefix(collection_id, None);591 <ReFungibleItemList<T>>::remove_prefix(collection_id, None);592593 <NftTransferBasket<T>>::remove_prefix(collection_id, None);594 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);595 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id, None);596597 <VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);598599 DestroyedCollectionCount::put(DestroyedCollectionCount::get()600 .checked_add(1)601 .ok_or(Error::<T>::NumOverflow)?);602603 Ok(())604 }605606 /// Add an address to white list.607 ///608 /// # Permissions609 ///610 /// * Collection Owner611 /// * Collection Admin612 ///613 /// # Arguments614 ///615 /// * collection_id.616 ///617 /// * address.618 #[weight = <SelfWeightOf<T>>::add_to_white_list()]619 #[transactional]620 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{621622 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);623 let collection = Self::get_collection(collection_id)?;624625 Self::toggle_white_list_internal(626 &sender,627 &collection,628 &address,629 true,630 )?;631632 Ok(())633 }634635 /// Remove an address from white list.636 ///637 /// # Permissions638 ///639 /// * Collection Owner640 /// * Collection Admin641 ///642 /// # Arguments643 ///644 /// * collection_id.645 ///646 /// * address.647 #[weight = <SelfWeightOf<T>>::remove_from_white_list()]648 #[transactional]649 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{650651 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);652 let collection = Self::get_collection(collection_id)?;653654 Self::toggle_white_list_internal(655 &sender,656 &collection,657 &address,658 false,659 )?;660661 Ok(())662 }663664 /// Toggle between normal and white list access for the methods with access for `Anyone`.665 ///666 /// # Permissions667 ///668 /// * Collection Owner.669 ///670 /// # Arguments671 ///672 /// * collection_id.673 ///674 /// * mode: [AccessMode]675 #[weight = <SelfWeightOf<T>>::set_public_access_mode()]676 #[transactional]677 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult678 {679 let sender = ensure_signed(origin)?;680681 let mut target_collection = Self::get_collection(collection_id)?;682 Self::check_owner_permissions(&target_collection, &sender)?;683 target_collection.access = mode;684 target_collection.save()685 }686687 /// Allows Anyone to create tokens if:688 /// * White List is enabled, and689 /// * Address is added to white list, and690 /// * This method was called with True parameter691 ///692 /// # Permissions693 /// * Collection Owner694 ///695 /// # Arguments696 ///697 /// * collection_id.698 ///699 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.700 #[weight = <SelfWeightOf<T>>::set_mint_permission()]701 #[transactional]702 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult703 {704 let sender = ensure_signed(origin)?;705706 let mut target_collection = Self::get_collection(collection_id)?;707 Self::check_owner_permissions(&target_collection, &sender)?;708 target_collection.mint_mode = mint_permission;709 target_collection.save()710 }711712 /// Change the owner of the collection.713 ///714 /// # Permissions715 ///716 /// * Collection Owner.717 ///718 /// # Arguments719 ///720 /// * collection_id.721 ///722 /// * new_owner.723 #[weight = <SelfWeightOf<T>>::change_collection_owner()]724 #[transactional]725 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {726727 let sender = ensure_signed(origin)?;728 let mut target_collection = Self::get_collection(collection_id)?;729 Self::check_owner_permissions(&target_collection, &sender)?;730 target_collection.owner = new_owner;731 target_collection.save()732 }733734 /// Adds an admin of the Collection.735 /// 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.736 ///737 /// # Permissions738 ///739 /// * Collection Owner.740 /// * Collection Admin.741 ///742 /// # Arguments743 ///744 /// * collection_id: ID of the Collection to add admin for.745 ///746 /// * new_admin_id: Address of new admin to add.747 #[weight = <SelfWeightOf<T>>::add_collection_admin()]748 #[transactional]749 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {750 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);751 let collection = Self::get_collection(collection_id)?;752 Self::check_owner_or_admin_permissions(&collection, &sender)?;753 let mut admin_arr = <AdminList<T>>::get(collection_id);754755 match admin_arr.binary_search(&new_admin_id) {756 Ok(_) => {},757 Err(idx) => {758 ensure!(admin_arr.len() < COLLECTION_ADMINS_LIMIT as usize, Error::<T>::CollectionAdminsLimitExceeded);759 admin_arr.insert(idx, new_admin_id);760 <AdminList<T>>::insert(collection_id, admin_arr);761 }762 }763 Ok(())764 }765766 /// 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.767 ///768 /// # Permissions769 ///770 /// * Collection Owner.771 /// * Collection Admin.772 ///773 /// # Arguments774 ///775 /// * collection_id: ID of the Collection to remove admin for.776 ///777 /// * account_id: Address of admin to remove.778 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]779 #[transactional]780 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {781 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);782 let collection = Self::get_collection(collection_id)?;783 Self::check_owner_or_admin_permissions(&collection, &sender)?;784 let mut admin_arr = <AdminList<T>>::get(collection_id);785786 if let Ok(idx) = admin_arr.binary_search(&account_id) {787 admin_arr.remove(idx);788 <AdminList<T>>::insert(collection_id, admin_arr);789 }790 Ok(())791 }792793 /// # Permissions794 ///795 /// * Collection Owner796 ///797 /// # Arguments798 ///799 /// * collection_id.800 ///801 /// * new_sponsor.802 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]803 #[transactional]804 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {805 let sender = ensure_signed(origin)?;806 let mut target_collection = Self::get_collection(collection_id)?;807 Self::check_owner_permissions(&target_collection, &sender)?;808809 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);810 target_collection.save()811 }812813 /// # Permissions814 ///815 /// * Sponsor.816 ///817 /// # Arguments818 ///819 /// * collection_id.820 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]821 #[transactional]822 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {823 let sender = ensure_signed(origin)?;824825 let mut target_collection = Self::get_collection(collection_id)?;826 ensure!(827 target_collection.sponsorship.pending_sponsor() == Some(&sender),828 Error::<T>::ConfirmUnsetSponsorFail829 );830831 target_collection.sponsorship = SponsorshipState::Confirmed(sender);832 target_collection.save()833 }834835 /// Switch back to pay-per-own-transaction model.836 ///837 /// # Permissions838 ///839 /// * Collection owner.840 ///841 /// # Arguments842 ///843 /// * collection_id.844 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]845 #[transactional]846 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {847 let sender = ensure_signed(origin)?;848849 let mut target_collection = Self::get_collection(collection_id)?;850 Self::check_owner_permissions(&target_collection, &sender)?;851852 target_collection.sponsorship = SponsorshipState::Disabled;853 target_collection.save()854 }855856 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.857 ///858 /// # Permissions859 ///860 /// * Collection Owner.861 /// * Collection Admin.862 /// * Anyone if863 /// * White List is enabled, and864 /// * Address is added to white list, and865 /// * MintPermission is enabled (see SetMintPermission method)866 ///867 /// # Arguments868 ///869 /// * collection_id: ID of the collection.870 ///871 /// * owner: Address, initial owner of the NFT.872 ///873 /// * data: Token data to store on chain.874 // #[weight =875 // (130_000_000 as Weight)876 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))877 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))878 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]879880 #[weight = <SelfWeightOf<T>>::create_item(data.data_size() as u32)]881 #[transactional]882 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult {883 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);884 let collection = Self::get_collection(collection_id)?;885886 Self::create_item_internal(&sender, &collection, &owner, data)?;887888 collection.submit_logs()889 }890891 /// This method creates multiple items in a collection created with CreateCollection method.892 ///893 /// # Permissions894 ///895 /// * Collection Owner.896 /// * Collection Admin.897 /// * Anyone if898 /// * White List is enabled, and899 /// * Address is added to white list, and900 /// * MintPermission is enabled (see SetMintPermission method)901 ///902 /// # Arguments903 ///904 /// * collection_id: ID of the collection.905 ///906 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].907 ///908 /// * owner: Address, initial owner of the NFT.909 #[weight = <SelfWeightOf<T>>::create_multiple_items(items_data.len() as u32)]910 #[transactional]911 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResult {912913 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);914 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);915 let collection = Self::get_collection(collection_id)?;916917 Self::create_multiple_items_internal(&sender, &collection, &owner, items_data)?;918919 collection.submit_logs()920 }921922 // TODO! transaction weight923924 /// Set transfers_enabled value for particular collection925 ///926 /// # Permissions927 ///928 /// * Collection Owner.929 ///930 /// # Arguments931 ///932 /// * collection_id: ID of the collection.933 ///934 /// * value: New flag value.935 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]936 #[transactional]937 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {938939 let sender = ensure_signed(origin)?;940 let mut target_collection = Self::get_collection(collection_id)?;941942 Self::check_owner_permissions(&target_collection, &sender)?;943944 target_collection.transfers_enabled = value;945 target_collection.save()946 }947948 // TODO! transaction weight949 /// Set meta_update_permission value for particular collection950 ///951 /// # Permissions952 ///953 /// * Collection Owner.954 ///955 /// # Arguments956 ///957 /// * collection_id: ID of the collection.958 ///959 /// * value: New flag value.960 #[weight = <T as Config>::WeightInfo::burn_item()]961 #[transactional]962 pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {963964 let sender = ensure_signed(origin)?;965 let mut target_collection = Self::get_collection(collection_id)?;966967 ensure!(968 target_collection.meta_update_permission != MetaUpdatePermission::None,969 Error::<T>::MetadataFlagFrozen970 );971 Self::check_owner_permissions(&target_collection, &sender)?;972973 target_collection.meta_update_permission = value;974975 target_collection.save()976 }977978 /// Destroys a concrete instance of NFT.979 ///980 /// # Permissions981 ///982 /// * Collection Owner.983 /// * Collection Admin.984 /// * Current NFT Owner.985 ///986 /// # Arguments987 ///988 /// * collection_id: ID of the collection.989 ///990 /// * item_id: ID of NFT to burn.991 #[weight = <SelfWeightOf<T>>::burn_item()]992 #[transactional]993 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {994995 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);996 let target_collection = Self::get_collection(collection_id)?;997998 Self::burn_item_internal(&sender, &target_collection, item_id, value)?;9991000 target_collection.submit_logs()1001 }10021003 /// Change ownership of the token.1004 ///1005 /// # Permissions1006 ///1007 /// * Collection Owner1008 /// * Collection Admin1009 /// * Current NFT owner1010 ///1011 /// # Arguments1012 ///1013 /// * recipient: Address of token recipient.1014 ///1015 /// * collection_id.1016 ///1017 /// * item_id: ID of the item1018 /// * Non-Fungible Mode: Required.1019 /// * Fungible Mode: Ignored.1020 /// * Re-Fungible Mode: Required.1021 ///1022 /// * value: Amount to transfer.1023 /// * Non-Fungible Mode: Ignored1024 /// * Fungible Mode: Must specify transferred amount1025 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1026 #[weight = <SelfWeightOf<T>>::transfer()]1027 #[transactional]1028 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {1029 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1030 let collection = Self::get_collection(collection_id)?;10311032 Self::transfer_internal(&sender, &recipient, &collection, item_id, value)?;10331034 collection.submit_logs()1035 }10361037 /// Set, change, or remove approved address to transfer the ownership of the NFT.1038 ///1039 /// # Permissions1040 ///1041 /// * Collection Owner1042 /// * Collection Admin1043 /// * Current NFT owner1044 ///1045 /// # Arguments1046 ///1047 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1048 ///1049 /// * collection_id.1050 ///1051 /// * item_id: ID of the item.1052 #[weight = <SelfWeightOf<T>>::approve()]1053 #[transactional]1054 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {1055 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1056 let collection = Self::get_collection(collection_id)?;10571058 Self::approve_internal(&sender, &spender, &collection, item_id, amount)?;10591060 collection.submit_logs()1061 }10621063 /// 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.1064 ///1065 /// # Permissions1066 /// * Collection Owner1067 /// * Collection Admin1068 /// * Current NFT owner1069 /// * Address approved by current NFT owner1070 ///1071 /// # Arguments1072 ///1073 /// * from: Address that owns token.1074 ///1075 /// * recipient: Address of token recipient.1076 ///1077 /// * collection_id.1078 ///1079 /// * item_id: ID of the item.1080 ///1081 /// * value: Amount to transfer.1082 #[weight = <SelfWeightOf<T>>::transfer_from()]1083 #[transactional]1084 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {1085 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1086 let collection = Self::get_collection(collection_id)?;10871088 Self::transfer_from_internal(&sender, &from, &recipient, &collection, item_id, value)?;10891090 collection.submit_logs()1091 }1092 // #[weight = 0]1093 // // let no_perm_mes = "You do not have permissions to modify this collection";1094 // // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1095 // // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1096 // // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);10971098 // // // on_nft_received call10991100 // // Self::transfer(origin, collection_id, item_id, new_owner)?;11011102 // Ok(())1103 // }11041105 /// Set off-chain data schema.1106 ///1107 /// # Permissions1108 ///1109 /// * Collection Owner1110 /// * Collection Admin1111 ///1112 /// # Arguments1113 ///1114 /// * collection_id.1115 ///1116 /// * schema: String representing the offchain data schema.1117 #[weight = <SelfWeightOf<T>>::set_variable_meta_data(data.len() as u32)]1118 #[transactional]1119 pub fn set_variable_meta_data (1120 origin,1121 collection_id: CollectionId,1122 item_id: TokenId,1123 data: Vec<u8>1124 ) -> DispatchResult {1125 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);11261127 let collection = Self::get_collection(collection_id)?;11281129 Self::set_variable_meta_data_internal(&sender, &collection, item_id, data)?;11301131 Ok(())1132 }11331134 /// Set schema standard1135 /// ImageURL1136 /// Unique1137 ///1138 /// # Permissions1139 ///1140 /// * Collection Owner1141 /// * Collection Admin1142 ///1143 /// # Arguments1144 ///1145 /// * collection_id.1146 ///1147 /// * schema: SchemaVersion: enum1148 #[weight = <SelfWeightOf<T>>::set_schema_version()]1149 #[transactional]1150 pub fn set_schema_version(1151 origin,1152 collection_id: CollectionId,1153 version: SchemaVersion1154 ) -> DispatchResult {1155 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1156 let mut target_collection = Self::get_collection(collection_id)?;1157 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;1158 target_collection.schema_version = version;1159 target_collection.save()1160 }11611162 /// Set off-chain data schema.1163 ///1164 /// # Permissions1165 ///1166 /// * Collection Owner1167 /// * Collection Admin1168 ///1169 /// # Arguments1170 ///1171 /// * collection_id.1172 ///1173 /// * schema: String representing the offchain data schema.1174 #[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]1175 #[transactional]1176 pub fn set_offchain_schema(1177 origin,1178 collection_id: CollectionId,1179 schema: Vec<u8>1180 ) -> DispatchResult {1181 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1182 let mut target_collection = Self::get_collection(collection_id)?;1183 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11841185 // check schema limit1186 ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");11871188 target_collection.offchain_schema = schema;1189 target_collection.save()1190 }11911192 /// Set const on-chain data schema.1193 ///1194 /// # Permissions1195 ///1196 /// * Collection Owner1197 /// * Collection Admin1198 ///1199 /// # Arguments1200 ///1201 /// * collection_id.1202 ///1203 /// * schema: String representing the const on-chain data schema.1204 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1205 #[transactional]1206 pub fn set_const_on_chain_schema (1207 origin,1208 collection_id: CollectionId,1209 schema: Vec<u8>1210 ) -> DispatchResult {1211 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1212 let mut target_collection = Self::get_collection(collection_id)?;1213 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;12141215 // check schema limit1216 ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");12171218 target_collection.const_on_chain_schema = schema;1219 target_collection.save()1220 }12211222 /// Set variable on-chain data schema.1223 ///1224 /// # Permissions1225 ///1226 /// * Collection Owner1227 /// * Collection Admin1228 ///1229 /// # Arguments1230 ///1231 /// * collection_id.1232 ///1233 /// * schema: String representing the variable on-chain data schema.1234 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1235 #[transactional]1236 pub fn set_variable_on_chain_schema (1237 origin,1238 collection_id: CollectionId,1239 schema: Vec<u8>1240 ) -> DispatchResult {1241 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1242 let mut target_collection = Self::get_collection(collection_id)?;1243 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;12441245 // check schema limit1246 ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");12471248 target_collection.variable_on_chain_schema = schema;1249 target_collection.save()1250 }12511252 #[weight = <SelfWeightOf<T>>::set_collection_limits()]1253 #[transactional]1254 pub fn set_collection_limits(1255 origin,1256 collection_id: u32,1257 new_limits: CollectionLimits<T::BlockNumber>,1258 ) -> DispatchResult {1259 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1260 let mut target_collection = Self::get_collection(collection_id)?;1261 Self::check_owner_permissions(&target_collection, sender.as_sub())?;1262 let old_limits = &target_collection.limits;12631264 // collection bounds1265 ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1266 new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP &&1267 new_limits.sponsored_data_size <= CUSTOM_DATA_LIMIT,1268 Error::<T>::CollectionLimitBoundsExceeded);12691270 // token_limit check prev1271 ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1272 ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);12731274 ensure!(1275 (old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1276 (old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1277 Error::<T>::OwnerPermissionsCantBeReverted,1278 );12791280 target_collection.limits = new_limits;12811282 target_collection.save()1283 }1284 }1285}12861287impl<T: Config> Module<T> {1288 pub fn create_item_internal(1289 sender: &T::CrossAccountId,1290 collection: &CollectionHandle<T>,1291 owner: &T::CrossAccountId,1292 data: CreateItemData,1293 ) -> DispatchResult {1294 ensure!(1295 owner != &T::CrossAccountId::from_eth(H160([0; 20])),1296 Error::<T>::AddressIsZero1297 );12981299 Self::can_create_items_in_collection(collection, sender, owner, 1)?;1300 Self::validate_create_item_args(collection, &data)?;1301 Self::create_item_no_validation(collection, owner, data)?;13021303 Ok(())1304 }13051306 pub fn transfer_internal(1307 sender: &T::CrossAccountId,1308 recipient: &T::CrossAccountId,1309 target_collection: &CollectionHandle<T>,1310 item_id: TokenId,1311 value: u128,1312 ) -> DispatchResult {1313 ensure!(1314 recipient != &T::CrossAccountId::from_eth(H160([0; 20])),1315 Error::<T>::AddressIsZero1316 );13171318 // Limits check1319 Self::is_correct_transfer(target_collection, recipient)?;13201321 // Transfer permissions check1322 ensure!(1323 Self::is_item_owner(sender, target_collection, item_id)?1324 || Self::is_owner_or_admin_permissions(target_collection, sender)?,1325 Error::<T>::NoPermission1326 );13271328 if target_collection.access == AccessMode::WhiteList {1329 Self::check_white_list(target_collection, sender)?;1330 Self::check_white_list(target_collection, recipient)?;1331 }13321333 match target_collection.mode {1334 CollectionMode::NFT => Self::transfer_nft(1335 target_collection,1336 item_id,1337 sender.clone(),1338 recipient.clone(),1339 )?,1340 CollectionMode::Fungible(_) => {1341 Self::transfer_fungible(target_collection, value, sender, recipient)?1342 }1343 CollectionMode::ReFungible => Self::transfer_refungible(1344 target_collection,1345 item_id,1346 value,1347 sender.clone(),1348 recipient.clone(),1349 )?,1350 _ => (),1351 };13521353 Self::deposit_event(RawEvent::Transfer(1354 target_collection.id,1355 item_id,1356 sender.clone(),1357 recipient.clone(),1358 value,1359 ));13601361 Ok(())1362 }13631364 pub fn approve_internal(1365 sender: &T::CrossAccountId,1366 spender: &T::CrossAccountId,1367 collection: &CollectionHandle<T>,1368 item_id: TokenId,1369 amount: u128,1370 ) -> DispatchResult {1371 Self::token_exists(collection, item_id)?;13721373 // Transfer permissions check1374 let bypasses_limits = collection.limits.owner_can_transfer1375 && Self::is_owner_or_admin_permissions(collection, sender)?;13761377 let allowance_limit = if bypasses_limits {1378 None1379 } else if let Some(amount) = Self::owned_amount(sender, collection, item_id)? {1380 Some(amount)1381 } else {1382 fail!(Error::<T>::NoPermission);1383 };13841385 if collection.access == AccessMode::WhiteList {1386 Self::check_white_list(collection, sender)?;1387 Self::check_white_list(collection, spender)?;1388 }13891390 collection.consume_sload()?;1391 let allowance: u128 = amount1392 .checked_add(<Allowances<T>>::get(1393 collection.id,1394 (item_id, sender.as_sub(), spender.as_sub()),1395 ))1396 .ok_or(Error::<T>::NumOverflow)?;1397 if let Some(limit) = allowance_limit {1398 ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1399 }1400 collection.consume_sstore()?;1401 <Allowances<T>>::insert(1402 collection.id,1403 (item_id, sender.as_sub(), spender.as_sub()),1404 allowance,1405 );14061407 if matches!(collection.mode, CollectionMode::NFT) {1408 // TODO: NFT: only one owner may exist for token in ERC7211409 collection.log(ERC721Events::Approval {1410 owner: *sender.as_eth(),1411 approved: *spender.as_eth(),1412 token_id: item_id.into(),1413 })?;1414 }14151416 if matches!(collection.mode, CollectionMode::Fungible(_)) {1417 // TODO: NFT: only one owner may exist for token in ERC201418 collection.log(ERC20Events::Approval {1419 owner: *sender.as_eth(),1420 spender: *spender.as_eth(),1421 value: allowance.into(),1422 })?;1423 }14241425 Self::deposit_event(RawEvent::Approved(1426 collection.id,1427 item_id,1428 sender.clone(),1429 spender.clone(),1430 allowance,1431 ));1432 Ok(())1433 }14341435 pub fn transfer_from_internal(1436 sender: &T::CrossAccountId,1437 from: &T::CrossAccountId,1438 recipient: &T::CrossAccountId,1439 collection: &CollectionHandle<T>,1440 item_id: TokenId,1441 amount: u128,1442 ) -> DispatchResult {1443 if sender == from {1444 // Transfer by `from`, because it is either equal to sender, or derived from him1445 return Self::transfer_internal(from, recipient, collection, item_id, amount);1446 }14471448 // Check approval1449 collection.consume_sload()?;1450 let approval: u128 =1451 <Allowances<T>>::get(collection.id, (item_id, from.as_sub(), sender.as_sub()));14521453 // Limits check1454 Self::is_correct_transfer(collection, recipient)?;14551456 // Transfer permissions check1457 ensure!(1458 approval >= amount1459 || (collection.limits.owner_can_transfer1460 && Self::is_owner_or_admin_permissions(collection, sender)?),1461 Error::<T>::NoPermission1462 );14631464 if collection.access == AccessMode::WhiteList {1465 Self::check_white_list(collection, sender)?;1466 Self::check_white_list(collection, recipient)?;1467 }14681469 // Reduce approval by transferred amount or remove if remaining approval drops to 01470 let allowance = approval.saturating_sub(amount);1471 collection.consume_sstore()?;1472 if allowance > 0 {1473 <Allowances<T>>::insert(1474 collection.id,1475 (item_id, from.as_sub(), sender.as_sub()),1476 allowance,1477 );1478 } else {1479 <Allowances<T>>::remove(collection.id, (item_id, from.as_sub(), sender.as_sub()));1480 }14811482 match collection.mode {1483 CollectionMode::NFT => {1484 Self::transfer_nft(collection, item_id, from.clone(), recipient.clone())?1485 }1486 CollectionMode::Fungible(_) => {1487 Self::transfer_fungible(collection, amount, from, recipient)?1488 }1489 CollectionMode::ReFungible => Self::transfer_refungible(1490 collection,1491 item_id,1492 amount,1493 from.clone(),1494 recipient.clone(),1495 )?,1496 _ => (),1497 };14981499 if matches!(collection.mode, CollectionMode::Fungible(_)) {1500 collection.log(ERC20Events::Approval {1501 owner: *from.as_eth(),1502 spender: *sender.as_eth(),1503 value: allowance.into(),1504 })?;1505 }15061507 Ok(())1508 }15091510 pub fn set_variable_meta_data_internal(1511 sender: &T::CrossAccountId,1512 collection: &CollectionHandle<T>,1513 item_id: TokenId,1514 data: Vec<u8>,1515 ) -> DispatchResult {1516 Self::token_exists(collection, item_id)?;15171518 ensure!(1519 CUSTOM_DATA_LIMIT >= data.len() as u32,1520 Error::<T>::TokenVariableDataLimitExceeded1521 );15221523 ensure!(1524 (Self::is_item_owner(sender, collection, item_id)?1525 && collection.meta_update_permission == MetaUpdatePermission::ItemOwner)1526 || (Self::is_owner_or_admin_permissions(collection, sender)?1527 && collection.meta_update_permission == MetaUpdatePermission::Admin),1528 Error::<T>::NoPermission1529 );15301531 match collection.mode {1532 CollectionMode::NFT => Self::set_nft_variable_data(collection, item_id, data)?,1533 CollectionMode::ReFungible => {1534 Self::set_re_fungible_variable_data(collection, item_id, data)?1535 }1536 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1537 _ => fail!(Error::<T>::UnexpectedCollectionType),1538 };15391540 Ok(())1541 }15421543 pub fn meta_update_check(1544 sender: &T::CrossAccountId,1545 collection: &CollectionHandle<T>,1546 item_id: TokenId,1547 ) -> DispatchResult {1548 match collection.meta_update_permission {1549 MetaUpdatePermission::ItemOwner => ensure!(1550 Self::is_item_owner(sender, collection, item_id)?,1551 Error::<T>::NoPermission1552 ),1553 MetaUpdatePermission::Admin => ensure!(1554 Self::is_owner_or_admin_permissions(collection, sender)?,1555 Error::<T>::NoPermission1556 ),1557 MetaUpdatePermission::None => fail!(Error::<T>::MetadataUpdateDenied),1558 }15591560 Ok(())1561 }15621563 pub fn get_variable_metadata(1564 collection: &CollectionHandle<T>,1565 item_id: TokenId,1566 ) -> Result<Vec<u8>, DispatchError> {1567 Ok(match collection.mode {1568 CollectionMode::NFT => {1569 <NftItemList<T>>::get(collection.id, item_id)1570 .ok_or(Error::<T>::TokenNotFound)?1571 .variable_data1572 }1573 CollectionMode::ReFungible => {1574 <ReFungibleItemList<T>>::get(collection.id, item_id)1575 .ok_or(Error::<T>::TokenNotFound)?1576 .variable_data1577 }1578 _ => fail!(Error::<T>::UnexpectedCollectionType),1579 })1580 }15811582 pub fn create_multiple_items_internal(1583 sender: &T::CrossAccountId,1584 collection: &CollectionHandle<T>,1585 owner: &T::CrossAccountId,1586 items_data: Vec<CreateItemData>,1587 ) -> DispatchResult {1588 Self::can_create_items_in_collection(collection, sender, owner, items_data.len() as u32)?;15891590 for data in &items_data {1591 Self::validate_create_item_args(collection, data)?;1592 }1593 for data in &items_data {1594 Self::create_item_no_validation(collection, owner, data.clone())?;1595 }15961597 Ok(())1598 }15991600 pub fn burn_item_internal(1601 sender: &T::CrossAccountId,1602 collection: &CollectionHandle<T>,1603 item_id: TokenId,1604 value: u128,1605 ) -> DispatchResult {1606 ensure!(1607 Self::is_item_owner(sender, collection, item_id)?1608 || (collection.limits.owner_can_transfer1609 && Self::is_owner_or_admin_permissions(collection, sender)?),1610 Error::<T>::NoPermission1611 );16121613 if collection.access == AccessMode::WhiteList {1614 Self::check_white_list(collection, sender)?;1615 }16161617 match collection.mode {1618 CollectionMode::NFT => Self::burn_nft_item(collection, item_id)?,1619 CollectionMode::Fungible(_) => Self::burn_fungible_item(sender, collection, value)?,1620 CollectionMode::ReFungible => Self::burn_refungible_item(collection, item_id, sender)?,1621 _ => (),1622 };16231624 Ok(())1625 }16261627 pub fn toggle_white_list_internal(1628 sender: &T::CrossAccountId,1629 collection: &CollectionHandle<T>,1630 address: &T::CrossAccountId,1631 whitelisted: bool,1632 ) -> DispatchResult {1633 Self::check_owner_or_admin_permissions(collection, sender)?;16341635 if whitelisted {1636 <WhiteList<T>>::insert(collection.id, address.as_sub(), true);1637 } else {1638 <WhiteList<T>>::remove(collection.id, address.as_sub());1639 }16401641 Ok(())1642 }16431644 fn is_correct_transfer(1645 collection: &CollectionHandle<T>,1646 recipient: &T::CrossAccountId,1647 ) -> DispatchResult {1648 let collection_id = collection.id;16491650 // check token limit and account token limit1651 collection.consume_sload()?;1652 let account_items: u32 =1653 <AddressTokens<T>>::get(collection_id, recipient.as_sub()).len() as u32;1654 ensure!(1655 collection.limits.account_token_ownership_limit > account_items,1656 Error::<T>::AccountTokenLimitExceeded1657 );16581659 // preliminary transfer check1660 ensure!(collection.transfers_enabled, Error::<T>::TransferNotAllowed);16611662 Ok(())1663 }16641665 fn can_create_items_in_collection(1666 collection: &CollectionHandle<T>,1667 sender: &T::CrossAccountId,1668 owner: &T::CrossAccountId,1669 amount: u32,1670 ) -> DispatchResult {1671 let collection_id = collection.id;16721673 // check token limit and account token limit1674 let total_items: u32 = ItemListIndex::get(collection_id)1675 .checked_add(amount)1676 .ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1677 let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner.as_sub()).len()1678 as u32)1679 .checked_add(amount)1680 .ok_or(Error::<T>::AccountTokenLimitExceeded)?;1681 ensure!(1682 collection.limits.token_limit >= total_items,1683 Error::<T>::CollectionTokenLimitExceeded1684 );1685 ensure!(1686 collection.limits.account_token_ownership_limit >= account_items,1687 Error::<T>::AccountTokenLimitExceeded1688 );16891690 if !Self::is_owner_or_admin_permissions(collection, sender)? {1691 ensure!(collection.mint_mode, Error::<T>::PublicMintingNotAllowed);1692 Self::check_white_list(collection, owner)?;1693 Self::check_white_list(collection, sender)?;1694 }16951696 Ok(())1697 }16981699 fn validate_create_item_args(1700 target_collection: &CollectionHandle<T>,1701 data: &CreateItemData,1702 ) -> DispatchResult {1703 match target_collection.mode {1704 CollectionMode::NFT => {1705 if !matches!(data, CreateItemData::NFT(_)) {1706 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1707 }1708 }1709 CollectionMode::Fungible(_) => {1710 if !matches!(data, CreateItemData::Fungible(_)) {1711 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1712 }1713 }1714 CollectionMode::ReFungible => {1715 if let CreateItemData::ReFungible(data) = data {1716 // Check refungibility limits1717 ensure!(1718 data.pieces <= MAX_REFUNGIBLE_PIECES,1719 Error::<T>::WrongRefungiblePieces1720 );1721 ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1722 } else {1723 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1724 }1725 }1726 _ => {1727 fail!(Error::<T>::UnexpectedCollectionType);1728 }1729 };17301731 Ok(())1732 }17331734 fn create_item_no_validation(1735 collection: &CollectionHandle<T>,1736 owner: &T::CrossAccountId,1737 data: CreateItemData,1738 ) -> DispatchResult {1739 match data {1740 CreateItemData::NFT(data) => {1741 let item = NftItemType {1742 owner: owner.clone(),1743 const_data: data.const_data.into_inner(),1744 variable_data: data.variable_data.into_inner(),1745 };17461747 Self::add_nft_item(collection, item)?;1748 }1749 CreateItemData::Fungible(data) => {1750 Self::add_fungible_item(collection, owner, data.value)?;1751 }1752 CreateItemData::ReFungible(data) => {1753 let owner_list = vec![Ownership {1754 owner: owner.clone(),1755 fraction: data.pieces,1756 }];17571758 let item = ReFungibleItemType {1759 owner: owner_list,1760 const_data: data.const_data.into_inner(),1761 variable_data: data.variable_data.into_inner(),1762 };17631764 Self::add_refungible_item(collection, item)?;1765 }1766 };17671768 Ok(())1769 }17701771 fn add_fungible_item(1772 collection: &CollectionHandle<T>,1773 owner: &T::CrossAccountId,1774 value: u128,1775 ) -> DispatchResult {1776 let collection_id = collection.id;17771778 // Does new owner already have an account?1779 collection.consume_sload()?;1780 let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner.as_sub()).value;17811782 // Mint1783 let item = FungibleItemType {1784 value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,1785 };1786 collection.consume_sstore()?;1787 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), item);17881789 // Update balance1790 collection.consume_sload()?;1791 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1792 .checked_add(value)1793 .ok_or(Error::<T>::NumOverflow)?;1794 collection.consume_sstore()?;1795 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);17961797 collection.log(ERC20Events::Transfer {1798 from: H160::default(),1799 to: *owner.as_eth(),1800 value: value.into(),1801 })?;1802 Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));1803 Ok(())1804 }18051806 fn add_refungible_item(1807 collection: &CollectionHandle<T>,1808 item: ReFungibleItemType<T::CrossAccountId>,1809 ) -> DispatchResult {1810 let collection_id = collection.id;18111812 let current_index = <ItemListIndex>::get(collection_id)1813 .checked_add(1)1814 .ok_or(Error::<T>::NumOverflow)?;1815 let itemcopy = item.clone();18161817 ensure!(item.owner.len() == 1, Error::<T>::BadCreateRefungibleCall,);1818 let item_owner = item.owner.first().expect("only one owner is defined");18191820 let value = item_owner.fraction;1821 let owner = item_owner.owner.clone();18221823 Self::add_token_index(collection, current_index, &owner)?;18241825 <ItemListIndex>::insert(collection_id, current_index);1826 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);18271828 // Update balance1829 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1830 .checked_add(value)1831 .ok_or(Error::<T>::NumOverflow)?;1832 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);18331834 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));1835 Ok(())1836 }18371838 fn add_nft_item(1839 collection: &CollectionHandle<T>,1840 item: NftItemType<T::CrossAccountId>,1841 ) -> DispatchResult {1842 let collection_id = collection.id;18431844 let current_index = <ItemListIndex>::get(collection_id)1845 .checked_add(1)1846 .ok_or(Error::<T>::NumOverflow)?;18471848 let item_owner = item.owner.clone();1849 Self::add_token_index(collection, current_index, &item.owner)?;18501851 <ItemListIndex>::insert(collection_id, current_index);1852 <NftItemList<T>>::insert(collection_id, current_index, item);18531854 // Update balance1855 let new_balance = <Balance<T>>::get(collection_id, item_owner.as_sub())1856 .checked_add(1)1857 .ok_or(Error::<T>::NumOverflow)?;1858 <Balance<T>>::insert(collection_id, item_owner.as_sub(), new_balance);18591860 collection.log(ERC721Events::Transfer {1861 from: H160::default(),1862 to: *item_owner.as_eth(),1863 token_id: current_index.into(),1864 })?;1865 Self::deposit_event(RawEvent::ItemCreated(1866 collection_id,1867 current_index,1868 item_owner,1869 ));1870 Ok(())1871 }18721873 fn burn_refungible_item(1874 collection: &CollectionHandle<T>,1875 item_id: TokenId,1876 owner: &T::CrossAccountId,1877 ) -> DispatchResult {1878 let collection_id = collection.id;18791880 let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)1881 .ok_or(Error::<T>::TokenNotFound)?;1882 let rft_balance = token1883 .owner1884 .iter()1885 .find(|&i| i.owner == *owner)1886 .ok_or(Error::<T>::TokenNotFound)?;1887 Self::remove_token_index(collection, item_id, owner)?;18881889 // update balance1890 let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.as_sub())1891 .checked_sub(rft_balance.fraction)1892 .ok_or(Error::<T>::NumOverflow)?;1893 <Balance<T>>::insert(collection_id, rft_balance.owner.as_sub(), new_balance);18941895 // Re-create owners list with sender removed1896 let index = token1897 .owner1898 .iter()1899 .position(|i| i.owner == *owner)1900 .expect("owned item is exists");1901 token.owner.remove(index);1902 let owner_count = token.owner.len();19031904 // Burn the token completely if this was the last (only) owner1905 if owner_count == 0 {1906 <ReFungibleItemList<T>>::remove(collection_id, item_id);1907 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);1908 } else {1909 <ReFungibleItemList<T>>::insert(collection_id, item_id, token);1910 }19111912 Ok(())1913 }19141915 fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1916 let collection_id = collection.id;19171918 let item =1919 <NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;1920 Self::remove_token_index(collection, item_id, &item.owner)?;19211922 // update balance1923 let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())1924 .checked_sub(1)1925 .ok_or(Error::<T>::NumOverflow)?;1926 <Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);1927 <NftItemList<T>>::remove(collection_id, item_id);1928 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);19291930 collection.log(ERC721Events::Transfer {1931 from: *item.owner.as_eth(),1932 to: H160::default(),1933 token_id: item_id.into(),1934 })?;1935 Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id));1936 Ok(())1937 }19381939 fn burn_fungible_item(1940 owner: &T::CrossAccountId,1941 collection: &CollectionHandle<T>,1942 value: u128,1943 ) -> DispatchResult {1944 let collection_id = collection.id;19451946 let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());1947 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);19481949 // update balance1950 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1951 .checked_sub(value)1952 .ok_or(Error::<T>::NumOverflow)?;1953 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);19541955 if balance.value - value > 0 {1956 balance.value -= value;1957 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);1958 } else {1959 <FungibleItemList<T>>::remove(collection_id, owner.as_sub());1960 }19611962 collection.log(ERC20Events::Transfer {1963 from: *owner.as_eth(),1964 to: H160::default(),1965 value: value.into(),1966 })?;1967 Ok(())1968 }19691970 pub fn get_collection(1971 collection_id: CollectionId,1972 ) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {1973 Ok(<CollectionHandle<T>>::get(collection_id).ok_or(Error::<T>::CollectionNotFound)?)1974 }19751976 fn check_owner_permissions(1977 target_collection: &CollectionHandle<T>,1978 subject: &T::AccountId,1979 ) -> DispatchResult {1980 ensure!(1981 *subject == target_collection.owner,1982 Error::<T>::NoPermission1983 );19841985 Ok(())1986 }19871988 fn is_owner_or_admin_permissions(1989 collection: &CollectionHandle<T>,1990 subject: &T::CrossAccountId,1991 ) -> Result<bool, DispatchError> {1992 collection.consume_sload()?;1993 Ok(*subject.as_sub() == collection.owner1994 || <AdminList<T>>::get(collection.id).contains(subject))1995 }19961997 fn check_owner_or_admin_permissions(1998 collection: &CollectionHandle<T>,1999 subject: &T::CrossAccountId,2000 ) -> DispatchResult {2001 ensure!(2002 Self::is_owner_or_admin_permissions(collection, subject)?,2003 Error::<T>::NoPermission2004 );20052006 Ok(())2007 }20082009 fn owned_amount(2010 subject: &T::CrossAccountId,2011 collection: &CollectionHandle<T>,2012 item_id: TokenId,2013 ) -> Result<Option<u128>, DispatchError> {2014 collection.consume_sload()?;2015 Ok(Self::owned_amount_unchecked(subject, collection, item_id))2016 }20172018 fn owned_amount_unchecked(2019 subject: &T::CrossAccountId,2020 target_collection: &CollectionHandle<T>,2021 item_id: TokenId,2022 ) -> Option<u128> {2023 let collection_id = target_collection.id;20242025 match target_collection.mode {2026 CollectionMode::NFT => {2027 (<NftItemList<T>>::get(collection_id, item_id)?.owner == *subject).then(|| 1)2028 }2029 CollectionMode::Fungible(_) => {2030 Some(<FungibleItemList<T>>::get(collection_id, &subject.as_sub()).value)2031 }2032 CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?2033 .owner2034 .iter()2035 .find(|i| i.owner == *subject)2036 .map(|i| i.fraction),2037 CollectionMode::Invalid => None,2038 }2039 }20402041 fn is_item_owner(2042 subject: &T::CrossAccountId,2043 target_collection: &CollectionHandle<T>,2044 item_id: TokenId,2045 ) -> Result<bool, DispatchError> {2046 Ok(match target_collection.mode {2047 CollectionMode::Fungible(_) => true,2048 _ => Self::owned_amount(subject, target_collection, item_id)?.is_some(),2049 })2050 }20512052 fn check_white_list(2053 collection: &CollectionHandle<T>,2054 address: &T::CrossAccountId,2055 ) -> DispatchResult {2056 collection.consume_sload()?;2057 ensure!(2058 <WhiteList<T>>::contains_key(collection.id, address.as_sub()),2059 Error::<T>::AddresNotInWhiteList,2060 );2061 Ok(())2062 }20632064 /// Check if token exists. In case of Fungible, check if there is an entry for2065 /// the owner in fungible balances double map2066 fn token_exists(target_collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {2067 let collection_id = target_collection.id;2068 let exists = match target_collection.mode {2069 CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),2070 CollectionMode::Fungible(_) => true,2071 CollectionMode::ReFungible => {2072 <ReFungibleItemList<T>>::contains_key(collection_id, item_id)2073 }2074 _ => false,2075 };20762077 ensure!(exists, Error::<T>::TokenNotFound);2078 Ok(())2079 }20802081 fn transfer_fungible(2082 collection: &CollectionHandle<T>,2083 value: u128,2084 owner: &T::CrossAccountId,2085 recipient: &T::CrossAccountId,2086 ) -> DispatchResult {2087 let collection_id = collection.id;20882089 collection.consume_sload()?;2090 collection.consume_sload()?;2091 let mut recipient_balance = <FungibleItemList<T>>::get(collection_id, recipient.as_sub());2092 let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());20932094 recipient_balance.value = recipient_balance2095 .value2096 .checked_add(value)2097 .ok_or(Error::<T>::NumOverflow)?;2098 balance.value = balance2099 .value2100 .checked_sub(value)2101 .ok_or(Error::<T>::TokenValueTooLow)?;21022103 // update balanceOf2104 collection.consume_sstore()?;2105 collection.consume_sstore()?;2106 if balance.value != 0 {2107 <Balance<T>>::insert(collection_id, owner.as_sub(), balance.value);2108 } else {2109 <Balance<T>>::remove(collection_id, owner.as_sub());2110 }2111 <Balance<T>>::insert(collection_id, recipient.as_sub(), recipient_balance.value);21122113 // Reduce or remove sender2114 collection.consume_sstore()?;2115 collection.consume_sstore()?;2116 if balance.value != 0 {2117 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);2118 } else {2119 <FungibleItemList<T>>::remove(collection_id, owner.as_sub());2120 }2121 <FungibleItemList<T>>::insert(collection_id, recipient.as_sub(), recipient_balance);21222123 collection.log(ERC20Events::Transfer {2124 from: *owner.as_eth(),2125 to: *recipient.as_eth(),2126 value: value.into(),2127 })?;2128 Self::deposit_event(RawEvent::Transfer(2129 collection.id,2130 1,2131 owner.clone(),2132 recipient.clone(),2133 value,2134 ));21352136 Ok(())2137 }21382139 fn transfer_refungible(2140 collection: &CollectionHandle<T>,2141 item_id: TokenId,2142 value: u128,2143 owner: T::CrossAccountId,2144 new_owner: T::CrossAccountId,2145 ) -> DispatchResult {2146 let collection_id = collection.id;2147 collection.consume_sload()?;2148 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)2149 .ok_or(Error::<T>::TokenNotFound)?;21502151 let item = full_item2152 .owner2153 .iter()2154 .find(|i| i.owner == owner)2155 .ok_or(Error::<T>::TokenNotFound)?;2156 let amount = item.fraction;21572158 ensure!(amount >= value, Error::<T>::TokenValueTooLow);21592160 collection.consume_sload()?;2161 // update balance2162 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2163 .checked_sub(value)2164 .ok_or(Error::<T>::NumOverflow)?;2165 collection.consume_sstore()?;2166 <Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);21672168 collection.consume_sload()?;2169 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2170 .checked_add(value)2171 .ok_or(Error::<T>::NumOverflow)?;2172 collection.consume_sstore()?;2173 <Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);21742175 let old_owner = item.owner.clone();2176 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);21772178 let mut new_full_item = full_item.clone();2179 // transfer2180 if amount == value && !new_owner_has_account {2181 // change owner2182 // new owner do not have account2183 new_full_item2184 .owner2185 .iter_mut()2186 .find(|i| i.owner == owner)2187 .expect("old owner does present in refungible")2188 .owner = new_owner.clone();2189 collection.consume_sstore()?;2190 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);21912192 // update index collection2193 Self::move_token_index(collection, item_id, &old_owner, &new_owner)?;2194 } else {2195 new_full_item2196 .owner2197 .iter_mut()2198 .find(|i| i.owner == owner)2199 .expect("old owner does present in refungible")2200 .fraction -= value;22012202 // separate amount2203 if new_owner_has_account {2204 // new owner has account2205 new_full_item2206 .owner2207 .iter_mut()2208 .find(|i| i.owner == new_owner)2209 .expect("new owner has account")2210 .fraction += value;2211 } else {2212 // new owner do not have account2213 new_full_item.owner.push(Ownership {2214 owner: new_owner.clone(),2215 fraction: value,2216 });2217 Self::add_token_index(collection, item_id, &new_owner)?;2218 }22192220 collection.consume_sstore()?;2221 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2222 }22232224 Self::deposit_event(RawEvent::Transfer(2225 collection.id,2226 item_id,2227 owner,2228 new_owner,2229 amount,2230 ));22312232 Ok(())2233 }22342235 fn transfer_nft(2236 collection: &CollectionHandle<T>,2237 item_id: TokenId,2238 sender: T::CrossAccountId,2239 new_owner: T::CrossAccountId,2240 ) -> DispatchResult {2241 let collection_id = collection.id;2242 collection.consume_sload()?;2243 let mut item =2244 <NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;22452246 ensure!(sender == item.owner, Error::<T>::MustBeTokenOwner);22472248 collection.consume_sload()?;2249 // update balance2250 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2251 .checked_sub(1)2252 .ok_or(Error::<T>::NumOverflow)?;2253 collection.consume_sstore()?;2254 <Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);22552256 collection.consume_sload()?;2257 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2258 .checked_add(1)2259 .ok_or(Error::<T>::NumOverflow)?;2260 collection.consume_sstore()?;2261 <Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);22622263 // change owner2264 let old_owner = item.owner.clone();2265 item.owner = new_owner.clone();2266 collection.consume_sstore()?;2267 <NftItemList<T>>::insert(collection_id, item_id, item);22682269 // update index collection2270 Self::move_token_index(collection, item_id, &old_owner, &new_owner)?;22712272 collection.log(ERC721Events::Transfer {2273 from: *sender.as_eth(),2274 to: *new_owner.as_eth(),2275 token_id: item_id.into(),2276 })?;2277 Self::deposit_event(RawEvent::Transfer(2278 collection.id,2279 item_id,2280 sender,2281 new_owner,2282 1,2283 ));22842285 Ok(())2286 }22872288 fn set_re_fungible_variable_data(2289 collection: &CollectionHandle<T>,2290 item_id: TokenId,2291 data: Vec<u8>,2292 ) -> DispatchResult {2293 let collection_id = collection.id;2294 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2295 .ok_or(Error::<T>::TokenNotFound)?;22962297 item.variable_data = data;22982299 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);23002301 Ok(())2302 }23032304 fn set_nft_variable_data(2305 collection: &CollectionHandle<T>,2306 item_id: TokenId,2307 data: Vec<u8>,2308 ) -> DispatchResult {2309 let collection_id = collection.id;2310 let mut item =2311 <NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;23122313 item.variable_data = data;23142315 <NftItemList<T>>::insert(collection_id, item_id, item);23162317 Ok(())2318 }23192320 #[allow(dead_code)]2321 fn init_collection(item: &Collection<T>) {2322 // check params2323 assert!(2324 item.decimal_points <= MAX_DECIMAL_POINTS,2325 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2326 );2327 assert!(2328 item.name.len() <= 64,2329 "Collection name can not be longer than 63 char"2330 );2331 assert!(2332 item.name.len() <= 256,2333 "Collection description can not be longer than 255 char"2334 );2335 assert!(2336 item.token_prefix.len() <= 16,2337 "Token prefix can not be longer than 15 char"2338 );23392340 // Generate next collection ID2341 let next_id = CreatedCollectionCount::get().checked_add(1).unwrap();23422343 CreatedCollectionCount::put(next_id);2344 }23452346 #[allow(dead_code)]2347 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::CrossAccountId>) {2348 let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();23492350 Self::add_token_index(2351 &CollectionHandle::get(collection_id).unwrap(),2352 current_index,2353 &item.owner,2354 )2355 .unwrap();23562357 <ItemListIndex>::insert(collection_id, current_index);23582359 // Update balance2360 let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2361 .checked_add(1)2362 .unwrap();2363 <Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2364 }23652366 #[allow(dead_code)]2367 fn init_fungible_token(2368 collection_id: CollectionId,2369 owner: &T::CrossAccountId,2370 item: &FungibleItemType,2371 ) {2372 let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();23732374 Self::add_token_index(2375 &CollectionHandle::get(collection_id).unwrap(),2376 current_index,2377 owner,2378 )2379 .unwrap();23802381 <ItemListIndex>::insert(collection_id, current_index);23822383 // Update balance2384 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2385 .checked_add(item.value)2386 .unwrap();2387 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2388 }23892390 #[allow(dead_code)]2391 fn init_refungible_token(2392 collection_id: CollectionId,2393 item: &ReFungibleItemType<T::CrossAccountId>,2394 ) {2395 let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();23962397 let value = item.owner.first().unwrap().fraction;2398 let owner = item.owner.first().unwrap().owner.clone();23992400 Self::add_token_index(2401 &CollectionHandle::get(collection_id).unwrap(),2402 current_index,2403 &owner,2404 )2405 .unwrap();24062407 <ItemListIndex>::insert(collection_id, current_index);24082409 // Update balance2410 let new_balance = <Balance<T>>::get(collection_id, &owner.as_sub())2411 .checked_add(value)2412 .unwrap();2413 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2414 }24152416 fn add_token_index(2417 collection: &CollectionHandle<T>,2418 item_index: TokenId,2419 owner: &T::CrossAccountId,2420 ) -> DispatchResult {2421 // add to account limit2422 collection.consume_sload()?;2423 if <AccountItemCount<T>>::contains_key(owner.as_sub()) {2424 // bound Owned tokens by a single address2425 collection.consume_sload()?;2426 let count = <AccountItemCount<T>>::get(owner.as_sub());2427 ensure!(2428 count < ACCOUNT_TOKEN_OWNERSHIP_LIMIT,2429 Error::<T>::AddressOwnershipLimitExceeded2430 );24312432 collection.consume_sstore()?;2433 <AccountItemCount<T>>::insert(2434 owner.as_sub(),2435 count.checked_add(1).ok_or(Error::<T>::NumOverflow)?,2436 );2437 } else {2438 collection.consume_sstore()?;2439 <AccountItemCount<T>>::insert(owner.as_sub(), 1);2440 }24412442 collection.consume_sload()?;2443 let list_exists = <AddressTokens<T>>::contains_key(collection.id, owner.as_sub());2444 if list_exists {2445 collection.consume_sload()?;2446 let mut list = <AddressTokens<T>>::get(collection.id, owner.as_sub());2447 let item_contains = list.contains(&item_index.clone());24482449 if !item_contains {2450 list.push(item_index);2451 }24522453 collection.consume_sstore()?;2454 <AddressTokens<T>>::insert(collection.id, owner.as_sub(), list);2455 } else {2456 let itm = vec![item_index];2457 collection.consume_sstore()?;2458 <AddressTokens<T>>::insert(collection.id, owner.as_sub(), itm);2459 }24602461 Ok(())2462 }24632464 fn remove_token_index(2465 collection: &CollectionHandle<T>,2466 item_index: TokenId,2467 owner: &T::CrossAccountId,2468 ) -> DispatchResult {2469 // update counter2470 collection.consume_sload()?;2471 collection.consume_sstore()?;2472 <AccountItemCount<T>>::insert(2473 owner.as_sub(),2474 <AccountItemCount<T>>::get(owner.as_sub())2475 .checked_sub(1)2476 .ok_or(Error::<T>::NumOverflow)?,2477 );24782479 collection.consume_sload()?;2480 let list_exists = <AddressTokens<T>>::contains_key(collection.id, owner.as_sub());2481 if list_exists {2482 collection.consume_sload()?;2483 let mut list = <AddressTokens<T>>::get(collection.id, owner.as_sub());2484 let item_contains = list.contains(&item_index.clone());24852486 if item_contains {2487 list.retain(|&item| item != item_index);2488 collection.consume_sstore()?;2489 <AddressTokens<T>>::insert(collection.id, owner.as_sub(), list);2490 }2491 }24922493 Ok(())2494 }24952496 fn move_token_index(2497 collection: &CollectionHandle<T>,2498 item_index: TokenId,2499 old_owner: &T::CrossAccountId,2500 new_owner: &T::CrossAccountId,2501 ) -> DispatchResult {2502 Self::remove_token_index(collection, item_index, old_owner)?;2503 Self::add_token_index(collection, item_index, new_owner)?;25042505 Ok(())2506 }2507}25082509sp_api::decl_runtime_apis! {2510 pub trait NftApi {2511 /// Used for ethereum integration2512 fn eth_contract_code(account: H160) -> Option<Vec<u8>>;2513 }2514}pallets/nft/src/sponsorship.rsdiffbeforeafterboth--- a/pallets/nft/src/sponsorship.rs
+++ b/pallets/nft/src/sponsorship.rs
@@ -127,7 +127,6 @@
sponsored
}
- _ => false,
};
}
primitives/nft/src/lib.rsdiffbeforeafterboth--- a/primitives/nft/src/lib.rs
+++ b/primitives/nft/src/lib.rs
@@ -73,23 +73,15 @@
#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub enum CollectionMode {
- Invalid,
NFT,
// decimal points
Fungible(DecimalPoints),
ReFungible,
-}
-
-impl Default for CollectionMode {
- fn default() -> Self {
- Self::Invalid
- }
}
impl CollectionMode {
pub fn id(&self) -> u8 {
match self {
- CollectionMode::Invalid => 0,
CollectionMode::NFT => 1,
CollectionMode::Fungible(_) => 2,
CollectionMode::ReFungible => 3,
tests/src/createCollection.test.tsdiffbeforeafterboth--- a/tests/src/createCollection.test.ts
+++ b/tests/src/createCollection.test.ts
@@ -33,19 +33,6 @@
});
describe('(!negative test!) integration test: ext. createCollection():', () => {
- it('(!negative test!) create new NFT collection whith incorrect data (mode)', async () => {
- await usingApi(async (api) => {
- const AcollectionCount = parseInt((await api.query.nft.createdCollectionCount()).toString(), 10);
-
- const badTransaction = async () => {
- await createCollectionExpectSuccess({mode: {type: 'Invalid'}});
- };
- await expect(badTransaction()).to.be.rejected;
-
- const BcollectionCount = parseInt((await api.query.nft.createdCollectionCount()).toString(), 10);
- expect(BcollectionCount).to.be.equal(AcollectionCount, 'Error: Incorrect collection created.');
- });
- });
it('(!negative test!) create new NFT collection whith incorrect data (collection_name)', async () => {
await createCollectionExpectFailure({ name: 'A'.repeat(65), mode: {type: 'NFT'}});
});
tests/src/util/helpers.tsdiffbeforeafterboth--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -224,10 +224,6 @@
return result;
}
-interface Invalid {
- type: 'Invalid';
-}
-
interface Nft {
type: 'NFT';
}
@@ -241,7 +237,7 @@
type: 'ReFungible';
}
-type CollectionMode = Nft | Fungible | ReFungible | Invalid;
+type CollectionMode = Nft | Fungible | ReFungible;
export type CreateCollectionParams = {
mode: CollectionMode,
@@ -275,8 +271,6 @@
modeprm = { fungible: mode.decimalPoints };
} else if (mode.type === 'ReFungible') {
modeprm = { refungible: null };
- } else if (mode.type === 'Invalid') {
- modeprm = { invalid: null };
}
const tx = api.tx.nft.createCollection(strToUTF16(name), strToUTF16(description), strToUTF16(tokenPrefix), modeprm);
@@ -317,8 +311,6 @@
modeprm = { fungible: mode.decimalPoints };
} else if (mode.type === 'ReFungible') {
modeprm = { refungible: null };
- } else if (mode.type === 'Invalid') {
- modeprm = { invalid: null };
}
await usingApi(async (api) => {
@@ -528,23 +520,23 @@
export async function setMetadataUpdatePermissionFlagExpectSuccess(sender: IKeyringPair, collectionId: number, flag: string) {
await usingApi(async (api) => {
- const tx = api.tx.nft.setMetaUpdatePermissionFlag(collectionId, flag);
+ const tx = api.tx.nft.setMetaUpdatePermissionFlag(collectionId, flag);
const events = await submitTransactionAsync(sender, tx);
const result = getGenericResult(events);
expect(result.success).to.be.true;
- });
+ });
}
export async function setMetadataUpdatePermissionFlagExpectFailure(sender: IKeyringPair, collectionId: number, flag: string) {
await usingApi(async (api) => {
- const tx = api.tx.nft.setMetaUpdatePermissionFlag(collectionId, flag);
+ const tx = api.tx.nft.setMetaUpdatePermissionFlag(collectionId, flag);
const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;
const result = getGenericResult(events);
expect(result.success).to.be.false;
- });
+ });
}
export async function enableContractSponsoringExpectSuccess(sender: IKeyringPair, contractAddress: AccountId | string, enable: boolean) {
@@ -576,7 +568,7 @@
const result = getGenericResult(events);
expect(result.success).to.be.true;
- });
+ });
}
export async function setTransferFlagExpectFailure(sender: IKeyringPair, collectionId: number, enabled: boolean) {
@@ -588,7 +580,7 @@
const result = getGenericResult(events);
expect(result.success).to.be.false;
- });
+ });
}
export async function setContractSponsoringRateLimitExpectSuccess(sender: IKeyringPair, contractAddress: AccountId | string, rateLimit: number) {
@@ -833,7 +825,7 @@
const expectedBlockNumber = blockNumber + blockSchedule;
expect(blockNumber).to.be.greaterThan(0);
- const transferTx = await api.tx.nft.transfer(normalizeAccountId(recipient.address), collectionId, tokenId, value);
+ const transferTx = await api.tx.nft.transfer(normalizeAccountId(recipient.address), collectionId, tokenId, value);
const scheduleTx = await api.tx.scheduler.schedule(expectedBlockNumber, null, 0, transferTx);
await submitTransactionAsync(sender, scheduleTx);
@@ -1004,7 +996,7 @@
export async function createItemExpectFailure(sender: IKeyringPair, collectionId: number, createMode: string, owner: string = sender.address) {
await usingApi(async (api) => {
const tx = api.tx.nft.createItem(collectionId, normalizeAccountId(owner), createMode);
-
+
const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;
const result = getCreateItemResult(events);