difftreelog
feat implement sponsoring primitive for nft
in: master
3 files changed
pallets/nft/Cargo.tomldiffbeforeafterboth--- a/pallets/nft/Cargo.toml
+++ b/pallets/nft/Cargo.toml
@@ -30,6 +30,7 @@
'pallet-transaction-payment/std',
'fp-evm/std',
'nft-data-structs/std',
+ 'up-sponsorship/std',
'sp-std/std',
'sp-api/std',
'sp-runtime/std',
@@ -135,9 +136,14 @@
[dependencies.nft-data-structs]
default-features = false
-path = '../../primitives'
+path = '../../primitives/nft'
version = '0.9.0'
+[dependencies.up-sponsorship]
+default-features = false
+path = '../../primitives/sponsorship'
+version = '0.1.0'
+
[dependencies]
ethereum-tx-sign = { version = "3.0.4", optional = true }
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"]78#![cfg_attr(not(feature = "std"), no_std)]910extern crate alloc;1112pub use serde::{Serialize, Deserialize};1314pub use frame_support::{15 construct_runtime, decl_event, decl_module, decl_storage, decl_error,16 dispatch::DispatchResult,17 ensure, fail, parameter_types,18 traits::{19 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,20 Randomness, IsSubType, WithdrawReasons,21 },22 weights::{23 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},24 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,25 WeightToFeePolynomial, DispatchClass,26 },27 StorageValue,28 transactional,29};3031use frame_system::{self as system, ensure_signed, ensure_root};32use sp_core::H160;33use sp_runtime::sp_std::prelude::Vec;34use core::ops::{Deref, DerefMut};35use core::cell::RefCell;36use nft_data_structs::{37 MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, MAX_REFUNGIBLE_PIECES,38 AccessMode, ChainLimits, Collection, CreateItemData, CollectionLimits,39 CollectionId, CollectionMode, TokenId, 40 SchemaVersion, SponsorshipState, Ownership,41 NftItemType, FungibleItemType, ReFungibleItemType42};43use pallet_ethereum::EthereumTransactionSender;4445#[cfg(test)]46mod mock;4748#[cfg(test)]49mod tests;5051mod default_weights;52mod eth;5354pub use eth::NftErcSupport;55pub use eth::account::*;56use eth::erc::{ERC20Events, ERC721Events};5758#[cfg(feature = "runtime-benchmarks")]59mod benchmarking;6061pub trait WeightInfo {62 fn create_collection() -> Weight;63 fn destroy_collection() -> Weight;64 fn add_to_white_list() -> Weight;65 fn remove_from_white_list() -> Weight;66 fn set_public_access_mode() -> Weight;67 fn set_mint_permission() -> Weight;68 fn change_collection_owner() -> Weight;69 fn add_collection_admin() -> Weight;70 fn remove_collection_admin() -> Weight;71 fn set_collection_sponsor() -> Weight;72 fn confirm_sponsorship() -> Weight;73 fn remove_collection_sponsor() -> Weight;74 fn create_item(s: usize) -> Weight;75 fn burn_item() -> Weight;76 fn transfer() -> Weight;77 fn approve() -> Weight;78 fn transfer_from() -> Weight;79 fn set_offchain_schema() -> Weight;80 fn set_const_on_chain_schema() -> Weight;81 fn set_variable_on_chain_schema() -> Weight;82 fn set_variable_meta_data() -> Weight;83 fn enable_contract_sponsoring() -> Weight;84 fn set_schema_version() -> Weight;85 fn set_chain_limits() -> Weight;86 fn set_contract_sponsoring_rate_limit() -> Weight;87 fn set_variable_meta_data_sponsoring_rate_limit() -> Weight;88 fn toggle_contract_white_list() -> Weight;89 fn add_to_contract_white_list() -> Weight;90 fn remove_from_contract_white_list() -> Weight;91 fn set_collection_limits() -> Weight;92}9394decl_error! {95 /// Error for non-fungible-token module.96 pub enum Error for Module<T: Config> {97 /// Total collections bound exceeded.98 TotalCollectionsLimitExceeded,99 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.100 CollectionDecimalPointLimitExceeded, 101 /// Collection name can not be longer than 63 char.102 CollectionNameLimitExceeded, 103 /// Collection description can not be longer than 255 char.104 CollectionDescriptionLimitExceeded, 105 /// Token prefix can not be longer than 15 char.106 CollectionTokenPrefixLimitExceeded,107 /// This collection does not exist.108 CollectionNotFound,109 /// Item not exists.110 TokenNotFound,111 /// Admin not found112 AdminNotFound,113 /// Arithmetic calculation overflow.114 NumOverflow, 115 /// Account already has admin role.116 AlreadyAdmin, 117 /// You do not own this collection.118 NoPermission,119 /// This address is not set as sponsor, use setCollectionSponsor first.120 ConfirmUnsetSponsorFail,121 /// Collection is not in mint mode.122 PublicMintingNotAllowed,123 /// Sender parameter and item owner must be equal.124 MustBeTokenOwner,125 /// Item balance not enough.126 TokenValueTooLow,127 /// Size of item is too large.128 NftSizeLimitExceeded,129 /// No approve found130 ApproveNotFound,131 /// Requested value more than approved.132 TokenValueNotEnough,133 /// Only approved addresses can call this method.134 ApproveRequired,135 /// Address is not in white list.136 AddresNotInWhiteList,137 /// Number of collection admins bound exceeded.138 CollectionAdminsLimitExceeded,139 /// Owned tokens by a single address bound exceeded.140 AddressOwnershipLimitExceeded,141 /// Length of items properties must be greater than 0.142 EmptyArgument,143 /// const_data exceeded data limit.144 TokenConstDataLimitExceeded,145 /// variable_data exceeded data limit.146 TokenVariableDataLimitExceeded,147 /// Not NFT item data used to mint in NFT collection.148 NotNftDataUsedToMintNftCollectionToken,149 /// Not Fungible item data used to mint in Fungible collection.150 NotFungibleDataUsedToMintFungibleCollectionToken,151 /// Not Re Fungible item data used to mint in Re Fungible collection.152 NotReFungibleDataUsedToMintReFungibleCollectionToken,153 /// Unexpected collection type.154 UnexpectedCollectionType,155 /// Can't store metadata in fungible tokens.156 CantStoreMetadataInFungibleTokens,157 /// Collection token limit exceeded158 CollectionTokenLimitExceeded,159 /// Account token limit exceeded per collection160 AccountTokenLimitExceeded,161 /// Collection limit bounds per collection exceeded162 CollectionLimitBoundsExceeded,163 /// Tried to enable permissions which are only permitted to be disabled164 OwnerPermissionsCantBeReverted,165 /// Schema data size limit bound exceeded166 SchemaDataLimitExceeded,167 /// Maximum refungibility exceeded168 WrongRefungiblePieces,169 /// createRefungible should be called with one owner170 BadCreateRefungibleCall,171 /// Gas limit exceeded172 OutOfGas,173 }174}175176pub struct CollectionHandle<T: Config> {177 pub id: CollectionId,178 collection: Collection<T>,179 logs: eth::log::LogRecorder,180 evm_address: H160,181 gas_limit: RefCell<u64>,182}183impl<T: Config> CollectionHandle<T> {184 pub fn get_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {185 <CollectionById<T>>::get(id)186 .map(|collection| Self {187 id,188 collection,189 logs: eth::log::LogRecorder::default(),190 evm_address: eth::collection_id_to_address(id),191 gas_limit: RefCell::new(gas_limit),192 })193 }194 pub fn get(id: CollectionId) -> Option<Self> {195 Self::get_with_gas_limit(id, u64::MAX)196 }197 pub fn gas_left(&self) -> u64 {198 *self.gas_limit.borrow()199 }200 pub fn consume_gas(&self, gas: u64) -> DispatchResult {201 let mut gas_limit = self.gas_limit.borrow_mut();202 if *gas_limit < gas {203 fail!(Error::<T>::OutOfGas);204 }205 *gas_limit -= gas;206 Ok(())207 }208 pub fn log(&self, log: impl evm_coder::ToLog) {209 self.logs.log(log.to_log(self.evm_address))210 }211 pub fn into_inner(self) -> Collection<T> {212 self.collection.clone()213 }214}215impl<T: Config> Deref for CollectionHandle<T> {216 type Target = Collection<T>;217218 fn deref(&self) -> &Self::Target {219 &self.collection220 }221}222223impl<T: Config> DerefMut for CollectionHandle<T> {224 fn deref_mut(&mut self) -> &mut Self::Target {225 &mut self.collection226 }227}228229pub trait Config: system::Config + Sized {230 type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;231232 /// Weight information for extrinsics in this pallet.233 type WeightInfo: WeightInfo;234235 type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;236 type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;237 type EvmWithdrawOrigin: pallet_evm::EnsureAddressOrigin<Self::Origin, Success = Self::AccountId>;238239 type CrossAccountId: CrossAccountId<Self::AccountId>;240 type Currency: Currency<Self::AccountId>;241 type CollectionCreationPrice: Get<<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance>;242 type TreasuryAccountId: Get<Self::AccountId>;243244 type EthereumChainId: Get<u64>;245 type EthereumTransactionSender: pallet_ethereum::EthereumTransactionSender;246}247248// # Used definitions249//250// ## User control levels251//252// chain-controlled - key is uncontrolled by user253// i.e autoincrementing index254// can use non-cryptographic hash255// real - key is controlled by user256// but it is hard to generate enough colliding values, i.e owner of signed txs257// can use non-cryptographic hash258// controlled - key is completly controlled by users259// i.e maps with mutable keys260// should use cryptographic hash261//262// ## User control level downgrade reasons263//264// ?1 - chain-controlled -> controlled265// collections/tokens can be destroyed, resulting in massive holes266// ?2 - chain-controlled -> controlled267// same as ?1, but can be only added, resulting in easier exploitation268// ?3 - real -> controlled269// no confirmation required, so addresses can be easily generated270decl_storage! {271 trait Store for Module<T: Config> as Nft {272273 //#region Private members274 /// Id of next collection275 CreatedCollectionCount: u32;276 /// Used for migrations277 ChainVersion: u64;278 /// Id of last collection token279 /// Collection id (controlled?1)280 ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;281 //#endregion282283 //#region Chain limits struct284 pub ChainLimit get(fn chain_limit) config(): ChainLimits;285 //#endregion286287 //#region Bound counters288 /// Amount of collections destroyed, used for total amount tracking with289 /// CreatedCollectionCount290 DestroyedCollectionCount: u32;291 /// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)292 /// Account id (real)293 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;294 //#endregion295296 //#region Basic collections297 /// Collection info298 /// Collection id (controlled?1)299 pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;300 /// List of collection admins301 /// Collection id (controlled?2)302 pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::CrossAccountId>;303 /// Whitelisted collection users304 /// Collection id (controlled?2), user id (controlled?3)305 pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;306 //#endregion307308 /// How many of collection items user have309 /// Collection id (controlled?2), account id (real)310 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;311312 /// Amount of items which spender can transfer out of owners account (via transferFrom)313 /// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))314 /// TODO: Off chain worker should remove from this map when token gets removed315 pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;316317 //#region Item collections318 /// Collection id (controlled?2), token id (controlled?1)319 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::CrossAccountId>>;320 /// Collection id (controlled?2), owner (controlled?2)321 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;322 /// Collection id (controlled?2), token id (controlled?1)323 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::CrossAccountId>>;324 //#endregion325326 //#region Index list327 /// Collection id (controlled?2), tokens owner (controlled?2)328 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;329 //#endregion330331 //#region Tokens transfer rate limit baskets332 /// (Collection id (controlled?2), who created (real))333 /// TODO: Off chain worker should remove from this map when collection gets removed334 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;335 /// Collection id (controlled?2), token id (controlled?2)336 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;337 /// Collection id (controlled?2), owning user (real)338 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;339 /// Collection id (controlled?2), token id (controlled?2)340 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;341 //#endregion342343 /// Variable metadata sponsoring344 /// Collection id (controlled?2), token id (controlled?2)345 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;346 }347 add_extra_genesis {348 build(|config: &GenesisConfig<T>| {349 // Modification of storage350 for (_num, _c) in &config.collection_id {351 <Module<T>>::init_collection(_c);352 }353354 for (_num, _c, _i) in &config.nft_item_id {355 <Module<T>>::init_nft_token(*_c, _i);356 }357358 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {359 <Module<T>>::init_fungible_token(*collection_id, &T::CrossAccountId::from_sub(account_id.clone()), fungible_item);360 }361362 for (_num, _c, _i) in &config.refungible_item_id {363 <Module<T>>::init_refungible_token(*_c, _i);364 }365 })366 }367}368369decl_event!(370 pub enum Event<T>371 where372 AccountId = <T as frame_system::Config>::AccountId,373 CrossAccountId = <T as Config>::CrossAccountId,374 {375 /// New collection was created376 /// 377 /// # Arguments378 /// 379 /// * collection_id: Globally unique identifier of newly created collection.380 /// 381 /// * mode: [CollectionMode] converted into u8.382 /// 383 /// * account_id: Collection owner.384 CollectionCreated(CollectionId, u8, AccountId),385386 /// New item was created.387 /// 388 /// # Arguments389 /// 390 /// * collection_id: Id of the collection where item was created.391 /// 392 /// * item_id: Id of an item. Unique within the collection.393 ///394 /// * recipient: Owner of newly created item 395 ItemCreated(CollectionId, TokenId, CrossAccountId),396397 /// Collection item was burned.398 /// 399 /// # Arguments400 /// 401 /// collection_id.402 /// 403 /// item_id: Identifier of burned NFT.404 ItemDestroyed(CollectionId, TokenId),405406 /// Item was transferred407 ///408 /// * collection_id: Id of collection to which item is belong409 ///410 /// * item_id: Id of an item411 ///412 /// * sender: Original owner of item413 ///414 /// * recipient: New owner of item415 ///416 /// * amount: Always 1 for NFT417 Transfer(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),418419 /// * collection_id420 ///421 /// * item_id422 ///423 /// * sender424 ///425 /// * spender426 ///427 /// * amount428 Approved(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),429 }430);431432decl_module! {433 pub struct Module<T: Config> for enum Call 434 where 435 origin: T::Origin436 {437 fn deposit_event() = default;438 type Error = Error<T>;439440 fn on_initialize(_now: T::BlockNumber) -> Weight {441 0442 }443444 /// 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.445 /// 446 /// # Permissions447 /// 448 /// * Anyone.449 /// 450 /// # Arguments451 /// 452 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.453 /// 454 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.455 /// 456 /// * token_prefix: UTF-8 string with token prefix.457 /// 458 /// * mode: [CollectionMode] collection type and type dependent data.459 // returns collection ID460 #[weight = <T as Config>::WeightInfo::create_collection()]461 #[transactional]462 pub fn create_collection(origin,463 collection_name: Vec<u16>,464 collection_description: Vec<u16>,465 token_prefix: Vec<u8>,466 mode: CollectionMode) -> DispatchResult {467468 // Anyone can create a collection469 let who = ensure_signed(origin)?;470471 // Take a (non-refundable) deposit of collection creation472 let mut imbalance = <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();473 imbalance.subsume(<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(474 &T::TreasuryAccountId::get(),475 T::CollectionCreationPrice::get(),476 ));477 <T as Config>::Currency::settle(478 &who,479 imbalance,480 WithdrawReasons::TRANSFER,481 ExistenceRequirement::KeepAlive,482 ).map_err(|_| Error::<T>::NoPermission)?;483484 let decimal_points = match mode {485 CollectionMode::Fungible(points) => points,486 _ => 0487 };488489 let chain_limit = ChainLimit::get();490491 let created_count = CreatedCollectionCount::get();492 let destroyed_count = DestroyedCollectionCount::get();493494 // bound Total number of collections495 ensure!(created_count - destroyed_count < chain_limit.collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);496497 // check params498 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);499 ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);500 ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);501 ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);502503 // Generate next collection ID504 let next_id = created_count505 .checked_add(1)506 .ok_or(Error::<T>::NumOverflow)?;507508 CreatedCollectionCount::put(next_id);509510 let limits = CollectionLimits {511 sponsored_data_size: chain_limit.custom_data_limit,512 ..Default::default()513 };514515 // Create new collection516 let new_collection = Collection {517 owner: who.clone(),518 name: collection_name,519 mode: mode.clone(),520 mint_mode: false,521 access: AccessMode::Normal,522 description: collection_description,523 decimal_points: decimal_points,524 token_prefix: token_prefix,525 offchain_schema: Vec::new(),526 schema_version: SchemaVersion::ImageURL,527 sponsorship: SponsorshipState::Disabled,528 variable_on_chain_schema: Vec::new(),529 const_on_chain_schema: Vec::new(),530 limits,531 };532533 // Add new collection to map534 <CollectionById<T>>::insert(next_id, new_collection);535536 // call event537 Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.into(), who));538539 Ok(())540 }541542 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.543 /// 544 /// # Permissions545 /// 546 /// * Collection Owner.547 /// 548 /// # Arguments549 /// 550 /// * collection_id: collection to destroy.551 #[weight = <T as Config>::WeightInfo::destroy_collection()]552 #[transactional]553 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {554555 let sender = ensure_signed(origin)?;556 let collection = Self::get_collection(collection_id)?;557 Self::check_owner_permissions(&collection, &sender)?;558 if !collection.limits.owner_can_destroy {559 fail!(Error::<T>::NoPermission);560 }561562 <AddressTokens<T>>::remove_prefix(collection_id);563 <Allowances<T>>::remove_prefix(collection_id);564 <Balance<T>>::remove_prefix(collection_id);565 <ItemListIndex>::remove(collection_id);566 <AdminList<T>>::remove(collection_id);567 <CollectionById<T>>::remove(collection_id);568 <WhiteList<T>>::remove_prefix(collection_id);569570 <NftItemList<T>>::remove_prefix(collection_id);571 <FungibleItemList<T>>::remove_prefix(collection_id);572 <ReFungibleItemList<T>>::remove_prefix(collection_id);573574 <NftTransferBasket<T>>::remove_prefix(collection_id);575 <FungibleTransferBasket<T>>::remove_prefix(collection_id);576 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);577578 <VariableMetaDataBasket<T>>::remove_prefix(collection_id);579580 DestroyedCollectionCount::put(DestroyedCollectionCount::get()581 .checked_add(1)582 .ok_or(Error::<T>::NumOverflow)?);583584 Ok(())585 }586587 /// Add an address to white list.588 /// 589 /// # Permissions590 /// 591 /// * Collection Owner592 /// * Collection Admin593 /// 594 /// # Arguments595 /// 596 /// * collection_id.597 /// 598 /// * address.599 #[weight = <T as Config>::WeightInfo::add_to_white_list()]600 #[transactional]601 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{602603 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);604 let collection = Self::get_collection(collection_id)?;605606 Self::toggle_white_list_internal(607 &sender,608 &collection,609 &address,610 true,611 )?;612613 Ok(())614 }615616 /// Remove an address from white list.617 /// 618 /// # Permissions619 /// 620 /// * Collection Owner621 /// * Collection Admin622 /// 623 /// # Arguments624 /// 625 /// * collection_id.626 /// 627 /// * address.628 #[weight = <T as Config>::WeightInfo::remove_from_white_list()]629 #[transactional]630 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{631632 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);633 let collection = Self::get_collection(collection_id)?;634635 Self::toggle_white_list_internal(636 &sender,637 &collection,638 &address,639 false,640 )?;641642 Ok(())643 }644645 /// Toggle between normal and white list access for the methods with access for `Anyone`.646 /// 647 /// # Permissions648 /// 649 /// * Collection Owner.650 /// 651 /// # Arguments652 /// 653 /// * collection_id.654 /// 655 /// * mode: [AccessMode]656 #[weight = <T as Config>::WeightInfo::set_public_access_mode()]657 #[transactional]658 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult659 {660 let sender = ensure_signed(origin)?;661662 let mut target_collection = Self::get_collection(collection_id)?;663 Self::check_owner_permissions(&target_collection, &sender)?;664 target_collection.access = mode;665 Self::save_collection(target_collection);666667 Ok(())668 }669670 /// Allows Anyone to create tokens if:671 /// * White List is enabled, and672 /// * Address is added to white list, and673 /// * This method was called with True parameter674 /// 675 /// # Permissions676 /// * Collection Owner677 ///678 /// # Arguments679 /// 680 /// * collection_id.681 /// 682 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.683 #[weight = <T as Config>::WeightInfo::set_mint_permission()]684 #[transactional]685 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult686 {687 let sender = ensure_signed(origin)?;688689 let mut target_collection = Self::get_collection(collection_id)?;690 Self::check_owner_permissions(&target_collection, &sender)?;691 target_collection.mint_mode = mint_permission;692 Self::save_collection(target_collection);693694 Ok(())695 }696697 /// Change the owner of the collection.698 /// 699 /// # Permissions700 /// 701 /// * Collection Owner.702 /// 703 /// # Arguments704 /// 705 /// * collection_id.706 /// 707 /// * new_owner.708 #[weight = <T as Config>::WeightInfo::change_collection_owner()]709 #[transactional]710 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {711712 let sender = ensure_signed(origin)?;713 let mut target_collection = Self::get_collection(collection_id)?;714 Self::check_owner_permissions(&target_collection, &sender)?;715 target_collection.owner = new_owner;716 Self::save_collection(target_collection);717718 Ok(())719 }720721 /// Adds an admin of the Collection.722 /// 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. 723 /// 724 /// # Permissions725 /// 726 /// * Collection Owner.727 /// * Collection Admin.728 /// 729 /// # Arguments730 /// 731 /// * collection_id: ID of the Collection to add admin for.732 /// 733 /// * new_admin_id: Address of new admin to add.734 #[weight = <T as Config>::WeightInfo::add_collection_admin()]735 #[transactional]736 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {737 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);738 let collection = Self::get_collection(collection_id)?;739 Self::check_owner_or_admin_permissions(&collection, &sender)?;740 let mut admin_arr = <AdminList<T>>::get(collection_id);741742 match admin_arr.binary_search(&new_admin_id) {743 Ok(_) => {},744 Err(idx) => {745 let limits = ChainLimit::get();746 ensure!(admin_arr.len() < limits.collections_admins_limit as usize, Error::<T>::CollectionAdminsLimitExceeded);747 admin_arr.insert(idx, new_admin_id);748 <AdminList<T>>::insert(collection_id, admin_arr);749 }750 }751 Ok(())752 }753754 /// 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.755 ///756 /// # Permissions757 /// 758 /// * Collection Owner.759 /// * Collection Admin.760 /// 761 /// # Arguments762 /// 763 /// * collection_id: ID of the Collection to remove admin for.764 /// 765 /// * account_id: Address of admin to remove.766 #[weight = <T as Config>::WeightInfo::remove_collection_admin()]767 #[transactional]768 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {769 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);770 let collection = Self::get_collection(collection_id)?;771 Self::check_owner_or_admin_permissions(&collection, &sender)?;772 let mut admin_arr = <AdminList<T>>::get(collection_id);773774 match admin_arr.binary_search(&account_id) {775 Ok(idx) => {776 admin_arr.remove(idx);777 <AdminList<T>>::insert(collection_id, admin_arr);778 },779 Err(_) => {}780 }781 Ok(())782 }783784 /// # Permissions785 /// 786 /// * Collection Owner787 /// 788 /// # Arguments789 /// 790 /// * collection_id.791 /// 792 /// * new_sponsor.793 #[weight = <T as Config>::WeightInfo::set_collection_sponsor()]794 #[transactional]795 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {796 let sender = ensure_signed(origin)?;797 let mut target_collection = Self::get_collection(collection_id)?;798 Self::check_owner_permissions(&target_collection, &sender)?;799800 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);801 Self::save_collection(target_collection);802803 Ok(())804 }805806 /// # Permissions807 /// 808 /// * Sponsor.809 /// 810 /// # Arguments811 /// 812 /// * collection_id.813 #[weight = <T as Config>::WeightInfo::confirm_sponsorship()]814 #[transactional]815 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {816 let sender = ensure_signed(origin)?;817818 let mut target_collection = Self::get_collection(collection_id)?;819 ensure!(820 target_collection.sponsorship.pending_sponsor() == Some(&sender),821 Error::<T>::ConfirmUnsetSponsorFail822 );823824 target_collection.sponsorship = SponsorshipState::Confirmed(sender);825 Self::save_collection(target_collection);826827 Ok(())828 }829830 /// Switch back to pay-per-own-transaction model.831 ///832 /// # Permissions833 ///834 /// * Collection owner.835 /// 836 /// # Arguments837 /// 838 /// * collection_id.839 #[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]840 #[transactional]841 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {842 let sender = ensure_signed(origin)?;843844 let mut target_collection = Self::get_collection(collection_id)?;845 Self::check_owner_permissions(&target_collection, &sender)?;846847 target_collection.sponsorship = SponsorshipState::Disabled;848 Self::save_collection(target_collection);849850 Ok(())851 }852853 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.854 /// 855 /// # Permissions856 /// 857 /// * Collection Owner.858 /// * Collection Admin.859 /// * Anyone if860 /// * White List is enabled, and861 /// * Address is added to white list, and862 /// * MintPermission is enabled (see SetMintPermission method)863 /// 864 /// # Arguments865 /// 866 /// * collection_id: ID of the collection.867 /// 868 /// * owner: Address, initial owner of the NFT.869 ///870 /// * data: Token data to store on chain.871 // #[weight =872 // (130_000_000 as Weight)873 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))874 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))875 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]876877 #[weight = <T as Config>::WeightInfo::create_item(data.len())]878 #[transactional]879 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult {880 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);881 let collection = Self::get_collection(collection_id)?;882883 Self::create_item_internal(&sender, &collection, &owner, data)?;884885 Self::submit_logs(collection)?;886 Ok(())887 }888889 /// This method creates multiple items in a collection created with CreateCollection method.890 /// 891 /// # Permissions892 /// 893 /// * Collection Owner.894 /// * Collection Admin.895 /// * Anyone if896 /// * White List is enabled, and897 /// * Address is added to white list, and898 /// * MintPermission is enabled (see SetMintPermission method)899 /// 900 /// # Arguments901 /// 902 /// * collection_id: ID of the collection.903 /// 904 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].905 /// 906 /// * owner: Address, initial owner of the NFT.907 #[weight = <T as Config>::WeightInfo::create_item(items_data.into_iter()908 .map(|data| { data.len() })909 .sum())]910 #[transactional]911 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResult {912913 ensure!(items_data.len() > 0, 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 Self::submit_logs(collection)?;920 Ok(())921 }922923 /// Destroys a concrete instance of NFT.924 /// 925 /// # Permissions926 /// 927 /// * Collection Owner.928 /// * Collection Admin.929 /// * Current NFT Owner.930 /// 931 /// # Arguments932 /// 933 /// * collection_id: ID of the collection.934 /// 935 /// * item_id: ID of NFT to burn.936 #[weight = <T as Config>::WeightInfo::burn_item()]937 #[transactional]938 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {939940 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);941 let target_collection = Self::get_collection(collection_id)?;942943 Self::burn_item_internal(&sender, &target_collection, item_id, value)?;944945 Self::submit_logs(target_collection)?;946 Ok(())947 }948949 /// Change ownership of the token.950 /// 951 /// # Permissions952 /// 953 /// * Collection Owner954 /// * Collection Admin955 /// * Current NFT owner956 ///957 /// # Arguments958 /// 959 /// * recipient: Address of token recipient.960 /// 961 /// * collection_id.962 /// 963 /// * item_id: ID of the item964 /// * Non-Fungible Mode: Required.965 /// * Fungible Mode: Ignored.966 /// * Re-Fungible Mode: Required.967 /// 968 /// * value: Amount to transfer.969 /// * Non-Fungible Mode: Ignored970 /// * Fungible Mode: Must specify transferred amount971 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)972 #[weight = <T as Config>::WeightInfo::transfer()]973 #[transactional]974 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {975 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);976 let collection = Self::get_collection(collection_id)?;977978 Self::transfer_internal(&sender, &recipient, &collection, item_id, value)?;979980 Self::submit_logs(collection)?;981 Ok(())982 }983984 /// Set, change, or remove approved address to transfer the ownership of the NFT.985 /// 986 /// # Permissions987 /// 988 /// * Collection Owner989 /// * Collection Admin990 /// * Current NFT owner991 /// 992 /// # Arguments993 /// 994 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).995 /// 996 /// * collection_id.997 /// 998 /// * item_id: ID of the item.999 #[weight = <T as Config>::WeightInfo::approve()]1000 #[transactional]1001 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {1002 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1003 let collection = Self::get_collection(collection_id)?;10041005 Self::approve_internal(&sender, &spender, &collection, item_id, amount)?;10061007 Self::submit_logs(collection)?;1008 Ok(())1009 }1010 1011 /// 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.1012 /// 1013 /// # Permissions1014 /// * Collection Owner1015 /// * Collection Admin1016 /// * Current NFT owner1017 /// * Address approved by current NFT owner1018 /// 1019 /// # Arguments1020 /// 1021 /// * from: Address that owns token.1022 /// 1023 /// * recipient: Address of token recipient.1024 /// 1025 /// * collection_id.1026 /// 1027 /// * item_id: ID of the item.1028 /// 1029 /// * value: Amount to transfer.1030 #[weight = <T as Config>::WeightInfo::transfer_from()]1031 #[transactional]1032 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {1033 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1034 let collection = Self::get_collection(collection_id)?;10351036 Self::transfer_from_internal(&sender, &from, &recipient, &collection, item_id, value)?;10371038 Self::submit_logs(collection)?;1039 Ok(())1040 }1041 // #[weight = 0]1042 // // let no_perm_mes = "You do not have permissions to modify this collection";1043 // // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1044 // // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1045 // // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);10461047 // // // on_nft_received call10481049 // // Self::transfer(origin, collection_id, item_id, new_owner)?;10501051 // Ok(())1052 // }10531054 /// Set off-chain data schema.1055 /// 1056 /// # Permissions1057 /// 1058 /// * Collection Owner1059 /// * Collection Admin1060 /// 1061 /// # Arguments1062 /// 1063 /// * collection_id.1064 /// 1065 /// * schema: String representing the offchain data schema.1066 #[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1067 #[transactional]1068 pub fn set_variable_meta_data (1069 origin,1070 collection_id: CollectionId,1071 item_id: TokenId,1072 data: Vec<u8>1073 ) -> DispatchResult {1074 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1075 1076 let collection = Self::get_collection(collection_id)?;10771078 Self::set_variable_meta_data_internal(&sender, &collection, item_id, data)?;10791080 Ok(())1081 }1082 1083 /// Set schema standard1084 /// ImageURL1085 /// Unique1086 /// 1087 /// # Permissions1088 /// 1089 /// * Collection Owner1090 /// * Collection Admin1091 /// 1092 /// # Arguments1093 /// 1094 /// * collection_id.1095 /// 1096 /// * schema: SchemaVersion: enum1097 #[weight = <T as Config>::WeightInfo::set_schema_version()]1098 #[transactional]1099 pub fn set_schema_version(1100 origin,1101 collection_id: CollectionId,1102 version: SchemaVersion1103 ) -> DispatchResult {1104 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1105 let mut target_collection = Self::get_collection(collection_id)?;1106 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;1107 target_collection.schema_version = version;1108 Self::save_collection(target_collection);11091110 Ok(())1111 }11121113 /// Set off-chain data schema.1114 /// 1115 /// # Permissions1116 /// 1117 /// * Collection Owner1118 /// * Collection Admin1119 /// 1120 /// # Arguments1121 /// 1122 /// * collection_id.1123 /// 1124 /// * schema: String representing the offchain data schema.1125 #[weight = <T as Config>::WeightInfo::set_offchain_schema()]1126 #[transactional]1127 pub fn set_offchain_schema(1128 origin,1129 collection_id: CollectionId,1130 schema: Vec<u8>1131 ) -> DispatchResult {1132 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1133 let mut target_collection = Self::get_collection(collection_id)?;1134 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11351136 // check schema limit1137 ensure!(schema.len() as u32 <= ChainLimit::get().offchain_schema_limit, "");11381139 target_collection.offchain_schema = schema;1140 Self::save_collection(target_collection);11411142 Ok(())1143 }11441145 /// Set const on-chain data schema.1146 /// 1147 /// # Permissions1148 /// 1149 /// * Collection Owner1150 /// * Collection Admin1151 /// 1152 /// # Arguments1153 /// 1154 /// * collection_id.1155 /// 1156 /// * schema: String representing the const on-chain data schema.1157 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1158 #[transactional]1159 pub fn set_const_on_chain_schema (1160 origin,1161 collection_id: CollectionId,1162 schema: Vec<u8>1163 ) -> DispatchResult {1164 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1165 let mut target_collection = Self::get_collection(collection_id)?;1166 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11671168 // check schema limit1169 ensure!(schema.len() as u32 <= ChainLimit::get().const_on_chain_schema_limit, "");11701171 target_collection.const_on_chain_schema = schema;1172 Self::save_collection(target_collection);11731174 Ok(())1175 }11761177 /// Set variable on-chain data schema.1178 /// 1179 /// # Permissions1180 /// 1181 /// * Collection Owner1182 /// * Collection Admin1183 /// 1184 /// # Arguments1185 /// 1186 /// * collection_id.1187 /// 1188 /// * schema: String representing the variable on-chain data schema.1189 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1190 #[transactional]1191 pub fn set_variable_on_chain_schema (1192 origin,1193 collection_id: CollectionId,1194 schema: Vec<u8>1195 ) -> DispatchResult {1196 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1197 let mut target_collection = Self::get_collection(collection_id)?;1198 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11991200 // check schema limit1201 ensure!(schema.len() as u32 <= ChainLimit::get().variable_on_chain_schema_limit, "");12021203 target_collection.variable_on_chain_schema = schema;1204 Self::save_collection(target_collection);12051206 Ok(())1207 }12081209 // Sudo permissions function1210 #[weight = <T as Config>::WeightInfo::set_chain_limits()]1211 #[transactional]1212 pub fn set_chain_limits(1213 origin,1214 limits: ChainLimits1215 ) -> DispatchResult {12161217 #[cfg(not(feature = "runtime-benchmarks"))]1218 ensure_root(origin)?;12191220 <ChainLimit>::put(limits);1221 Ok(())1222 }12231224 #[weight = <T as Config>::WeightInfo::set_collection_limits()]1225 #[transactional]1226 pub fn set_collection_limits(1227 origin,1228 collection_id: u32,1229 new_limits: CollectionLimits<T::BlockNumber>,1230 ) -> DispatchResult {1231 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1232 let mut target_collection = Self::get_collection(collection_id)?;1233 Self::check_owner_permissions(&target_collection, &sender.as_sub())?;1234 let old_limits = &target_collection.limits;1235 let chain_limits = ChainLimit::get();12361237 // collection bounds1238 ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1239 new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP && 1240 new_limits.sponsored_data_size <= chain_limits.custom_data_limit,1241 Error::<T>::CollectionLimitBoundsExceeded);12421243 // token_limit check prev1244 ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1245 ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);12461247 ensure!(1248 (old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1249 (old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1250 Error::<T>::OwnerPermissionsCantBeReverted,1251 );12521253 target_collection.limits = new_limits;1254 Self::save_collection(target_collection);12551256 Ok(())1257 } 1258 }1259}12601261impl<T: Config> Module<T> {1262 pub fn create_item_internal(sender: &T::CrossAccountId, collection: &CollectionHandle<T>, owner: &T::CrossAccountId, data: CreateItemData) -> DispatchResult {1263 Self::can_create_items_in_collection(&collection, &sender, &owner, 1)?;1264 Self::validate_create_item_args(&collection, &data)?;1265 Self::create_item_no_validation(&collection, owner, data)?;12661267 Ok(())1268 }12691270 pub fn transfer_internal(sender: &T::CrossAccountId, recipient: &T::CrossAccountId, target_collection: &CollectionHandle<T>, item_id: TokenId, value: u128) -> DispatchResult {1271 target_collection.consume_gas(2000000)?;1272 // Limits check1273 Self::is_correct_transfer(target_collection, &recipient)?;12741275 // Transfer permissions check1276 ensure!(Self::is_item_owner(&sender, target_collection, item_id) ||1277 Self::is_owner_or_admin_permissions(target_collection, &sender),1278 Error::<T>::NoPermission);12791280 if target_collection.access == AccessMode::WhiteList {1281 Self::check_white_list(target_collection, &sender)?;1282 Self::check_white_list(target_collection, &recipient)?;1283 }12841285 match target_collection.mode1286 {1287 CollectionMode::NFT => Self::transfer_nft(target_collection, item_id, sender.clone(), recipient.clone())?,1288 CollectionMode::Fungible(_) => Self::transfer_fungible(target_collection, value, &sender, &recipient)?,1289 CollectionMode::ReFungible => Self::transfer_refungible(target_collection, item_id, value, sender.clone(), recipient.clone())?,1290 _ => ()1291 };12921293 Self::deposit_event(RawEvent::Transfer(target_collection.id, item_id, sender.clone(), recipient.clone(), value));12941295 Ok(())1296 }12971298 pub fn approve_internal(1299 sender: &T::CrossAccountId,1300 spender: &T::CrossAccountId,1301 collection: &CollectionHandle<T>,1302 item_id: TokenId,1303 amount: u1281304 ) -> DispatchResult {1305 collection.consume_gas(2000000)?;1306 Self::token_exists(&collection, item_id)?;13071308 // Transfer permissions check1309 let bypasses_limits = collection.limits.owner_can_transfer &&1310 Self::is_owner_or_admin_permissions(1311 &collection,1312 &sender,1313 );13141315 let allowance_limit = if bypasses_limits {1316 None1317 } else if let Some(amount) = Self::owned_amount(1318 &sender,1319 &collection,1320 item_id,1321 ) {1322 Some(amount)1323 } else {1324 fail!(Error::<T>::NoPermission);1325 };13261327 if collection.access == AccessMode::WhiteList {1328 Self::check_white_list(&collection, &sender)?;1329 Self::check_white_list(&collection, &spender)?;1330 }13311332 let allowance: u128 = amount1333 .checked_add(<Allowances<T>>::get(collection.id, (item_id, sender.as_sub(), spender.as_sub())))1334 .ok_or(Error::<T>::NumOverflow)?;1335 if let Some(limit) = allowance_limit {1336 ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1337 }1338 <Allowances<T>>::insert(collection.id, (item_id, sender.as_sub(), spender.as_sub()), allowance);13391340 if matches!(collection.mode, CollectionMode::NFT) {1341 // TODO: NFT: only one owner may exist for token in ERC7211342 collection.log(ERC721Events::Approval {1343 owner: *sender.as_eth(),1344 approved: *spender.as_eth(),1345 token_id: item_id.into(),1346 });1347 }13481349 if matches!(collection.mode, CollectionMode::Fungible(_)) {1350 // TODO: NFT: only one owner may exist for token in ERC201351 collection.log(ERC20Events::Approval {1352 owner: *sender.as_eth(),1353 spender: *spender.as_eth(),1354 value: allowance.into()1355 });1356 }13571358 Self::deposit_event(RawEvent::Approved(collection.id, item_id, sender.clone(), spender.clone(), allowance));1359 Ok(())1360 }13611362 pub fn transfer_from_internal(1363 sender: &T::CrossAccountId,1364 from: &T::CrossAccountId,1365 recipient: &T::CrossAccountId,1366 collection: &CollectionHandle<T>,1367 item_id: TokenId,1368 amount: u128,1369 ) -> DispatchResult {1370 collection.consume_gas(2000000)?;1371 // Check approval1372 let approval: u128 = <Allowances<T>>::get(collection.id, (item_id, from.as_sub(), sender.as_sub()));13731374 // Limits check1375 Self::is_correct_transfer(&collection, &recipient)?;13761377 // Transfer permissions check1378 ensure!(1379 approval >= amount || 1380 (1381 collection.limits.owner_can_transfer &&1382 Self::is_owner_or_admin_permissions(&collection, &sender)1383 ),1384 Error::<T>::NoPermission1385 );13861387 if collection.access == AccessMode::WhiteList {1388 Self::check_white_list(&collection, &sender)?;1389 Self::check_white_list(&collection, &recipient)?;1390 }13911392 // Reduce approval by transferred amount or remove if remaining approval drops to 01393 let allowance = approval.saturating_sub(amount);1394 if allowance > 0 {1395 <Allowances<T>>::insert(collection.id, (item_id, from.as_sub(), sender.as_sub()), allowance);1396 } else {1397 <Allowances<T>>::remove(collection.id, (item_id, from.as_sub(), sender.as_sub()));1398 }13991400 match collection.mode {1401 CollectionMode::NFT => {1402 Self::transfer_nft(&collection, item_id, from.clone(), recipient.clone())?1403 }1404 CollectionMode::Fungible(_) => {1405 Self::transfer_fungible(&collection, amount, &from, &recipient)?1406 }1407 CollectionMode::ReFungible => {1408 Self::transfer_refungible(&collection, item_id, amount, from.clone(), recipient.clone())?1409 }1410 _ => ()1411 };14121413 if matches!(collection.mode, CollectionMode::Fungible(_)) {1414 collection.log(ERC20Events::Approval {1415 owner: *from.as_eth(),1416 spender: *sender.as_eth(),1417 value: allowance.into()1418 });1419 }14201421 Ok(())1422 }14231424 pub fn set_variable_meta_data_internal(1425 sender: &T::CrossAccountId,1426 collection: &CollectionHandle<T>, 1427 item_id: TokenId,1428 data: Vec<u8>,1429 ) -> DispatchResult {1430 Self::token_exists(&collection, item_id)?;14311432 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);14331434 // Modify permissions check1435 ensure!(Self::is_item_owner(&sender, &collection, item_id) ||1436 Self::is_owner_or_admin_permissions(&collection, &sender),1437 Error::<T>::NoPermission);14381439 match collection.mode1440 {1441 CollectionMode::NFT => Self::set_nft_variable_data(&collection, item_id, data)?,1442 CollectionMode::ReFungible => Self::set_re_fungible_variable_data(&collection, item_id, data)?,1443 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1444 _ => fail!(Error::<T>::UnexpectedCollectionType)1445 };14461447 Ok(())1448 }14491450 pub fn create_multiple_items_internal(1451 sender: &T::CrossAccountId,1452 collection: &CollectionHandle<T>,1453 owner: &T::CrossAccountId,1454 items_data: Vec<CreateItemData>,1455 ) -> DispatchResult {1456 Self::can_create_items_in_collection(&collection, &sender, &owner, items_data.len() as u32)?;14571458 for data in &items_data {1459 Self::validate_create_item_args(&collection, data)?;1460 }1461 for data in &items_data {1462 Self::create_item_no_validation(&collection, owner, data.clone())?;1463 }14641465 Ok(())1466 }14671468 pub fn burn_item_internal(1469 sender: &T::CrossAccountId,1470 collection: &CollectionHandle<T>,1471 item_id: TokenId,1472 value: u128,1473 ) -> DispatchResult {1474 ensure!(1475 Self::is_item_owner(&sender, &collection, item_id) ||1476 (1477 collection.limits.owner_can_transfer &&1478 Self::is_owner_or_admin_permissions(&collection, &sender)1479 ),1480 Error::<T>::NoPermission1481 );14821483 if collection.access == AccessMode::WhiteList {1484 Self::check_white_list(&collection, &sender)?;1485 }14861487 match collection.mode1488 {1489 CollectionMode::NFT => Self::burn_nft_item(&collection, item_id)?,1490 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, &collection, value)?,1491 CollectionMode::ReFungible => Self::burn_refungible_item(&collection, item_id, &sender)?,1492 _ => ()1493 };14941495 Ok(())1496 }14971498 pub fn toggle_white_list_internal(1499 sender: &T::CrossAccountId,1500 collection: &CollectionHandle<T>,1501 address: &T::CrossAccountId,1502 whitelisted: bool,1503 ) -> DispatchResult {1504 Self::check_owner_or_admin_permissions(&collection, &sender)?;15051506 if whitelisted {1507 <WhiteList<T>>::insert(collection.id, address.as_sub(), true);1508 } else {1509 <WhiteList<T>>::remove(collection.id, address.as_sub());1510 }15111512 Ok(())1513 }15141515 fn is_correct_transfer(collection: &CollectionHandle<T>, recipient: &T::CrossAccountId) -> DispatchResult {1516 let collection_id = collection.id;15171518 // check token limit and account token limit1519 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient.as_sub()).len() as u32;1520 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1521 1522 Ok(())1523 }15241525 fn can_create_items_in_collection(collection: &CollectionHandle<T>, sender: &T::CrossAccountId, owner: &T::CrossAccountId, amount: u32) -> DispatchResult {1526 let collection_id = collection.id;15271528 // check token limit and account token limit1529 let total_items: u32 = ItemListIndex::get(collection_id)1530 .checked_add(amount)1531 .ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1532 let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner.as_sub()).len() as u32)1533 .checked_add(amount)1534 .ok_or(Error::<T>::AccountTokenLimitExceeded)?;1535 ensure!(collection.limits.token_limit >= total_items, Error::<T>::CollectionTokenLimitExceeded);1536 ensure!(collection.limits.account_token_ownership_limit >= account_items, Error::<T>::AccountTokenLimitExceeded);15371538 if !Self::is_owner_or_admin_permissions(collection, &sender) {1539 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1540 Self::check_white_list(collection, owner)?;1541 Self::check_white_list(collection, sender)?;1542 }15431544 Ok(())1545 }15461547 fn validate_create_item_args(target_collection: &CollectionHandle<T>, data: &CreateItemData) -> DispatchResult {1548 match target_collection.mode1549 {1550 CollectionMode::NFT => {1551 if let CreateItemData::NFT(data) = data {1552 // check sizes1553 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1554 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1555 } else {1556 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1557 }1558 },1559 CollectionMode::Fungible(_) => {1560 if let CreateItemData::Fungible(_) = data {1561 } else {1562 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1563 }1564 },1565 CollectionMode::ReFungible => {1566 if let CreateItemData::ReFungible(data) = data {15671568 // check sizes1569 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1570 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);15711572 // Check refungibility limits1573 ensure!(data.pieces <= MAX_REFUNGIBLE_PIECES, Error::<T>::WrongRefungiblePieces);1574 ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1575 } else {1576 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1577 }1578 },1579 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1580 };15811582 Ok(())1583 }15841585 fn create_item_no_validation(collection: &CollectionHandle<T>, owner: &T::CrossAccountId, data: CreateItemData) -> DispatchResult {1586 match data1587 {1588 CreateItemData::NFT(data) => {1589 let item = NftItemType {1590 owner: owner.clone(),1591 const_data: data.const_data,1592 variable_data: data.variable_data1593 };15941595 Self::add_nft_item(collection, item)?;1596 },1597 CreateItemData::Fungible(data) => {1598 Self::add_fungible_item(collection, &owner, data.value)?;1599 },1600 CreateItemData::ReFungible(data) => {1601 let mut owner_list = Vec::new();1602 owner_list.push(Ownership {owner: owner.clone(), fraction: data.pieces});16031604 let item = ReFungibleItemType {1605 owner: owner_list,1606 const_data: data.const_data,1607 variable_data: data.variable_data1608 };16091610 Self::add_refungible_item(collection, item)?;1611 }1612 };16131614 Ok(())1615 }16161617 fn add_fungible_item(collection: &CollectionHandle<T>, owner: &T::CrossAccountId, value: u128) -> DispatchResult {1618 let collection_id = collection.id;16191620 // Does new owner already have an account?1621 let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner.as_sub()).value;16221623 // Mint 1624 let item = FungibleItemType {1625 value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,1626 };1627 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), item);16281629 // Update balance1630 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1631 .checked_add(value)1632 .ok_or(Error::<T>::NumOverflow)?;1633 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);16341635 Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));1636 Ok(())1637 }16381639 fn add_refungible_item(collection: &CollectionHandle<T>, item: ReFungibleItemType<T::CrossAccountId>) -> DispatchResult {1640 let collection_id = collection.id;16411642 let current_index = <ItemListIndex>::get(collection_id)1643 .checked_add(1)1644 .ok_or(Error::<T>::NumOverflow)?;1645 let itemcopy = item.clone();16461647 ensure!(1648 item.owner.len() == 1,1649 Error::<T>::BadCreateRefungibleCall,1650 );1651 let item_owner = item.owner.first().expect("only one owner is defined");16521653 let value = item_owner.fraction;1654 let owner = item_owner.owner.clone();16551656 Self::add_token_index(collection_id, current_index, &owner)?;16571658 <ItemListIndex>::insert(collection_id, current_index);1659 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);16601661 // Update balance1662 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1663 .checked_add(value)1664 .ok_or(Error::<T>::NumOverflow)?;1665 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);16661667 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));1668 Ok(())1669 }16701671 fn add_nft_item(collection: &CollectionHandle<T>, item: NftItemType<T::CrossAccountId>) -> DispatchResult {1672 let collection_id = collection.id;16731674 let current_index = <ItemListIndex>::get(collection_id)1675 .checked_add(1)1676 .ok_or(Error::<T>::NumOverflow)?;16771678 let item_owner = item.owner.clone();1679 Self::add_token_index(collection_id, current_index, &item.owner)?;16801681 <ItemListIndex>::insert(collection_id, current_index);1682 <NftItemList<T>>::insert(collection_id, current_index, item);16831684 // Update balance1685 let new_balance = <Balance<T>>::get(collection_id, item_owner.as_sub())1686 .checked_add(1)1687 .ok_or(Error::<T>::NumOverflow)?;1688 <Balance<T>>::insert(collection_id, item_owner.as_sub(), new_balance);16891690 collection.log(ERC721Events::Transfer {1691 from: H160::default(),1692 to: *item_owner.as_eth(),1693 token_id: current_index.into(),1694 });1695 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, item_owner));1696 Ok(())1697 }16981699 fn burn_refungible_item(1700 collection: &CollectionHandle<T>,1701 item_id: TokenId,1702 owner: &T::CrossAccountId,1703 ) -> DispatchResult {1704 let collection_id = collection.id;17051706 let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)1707 .ok_or(Error::<T>::TokenNotFound)?;1708 let rft_balance = token1709 .owner1710 .iter()1711 .find(|&i| i.owner == *owner)1712 .ok_or(Error::<T>::TokenNotFound)?;1713 Self::remove_token_index(collection_id, item_id, owner)?;17141715 // update balance1716 let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.as_sub())1717 .checked_sub(rft_balance.fraction)1718 .ok_or(Error::<T>::NumOverflow)?;1719 <Balance<T>>::insert(collection_id, rft_balance.owner.as_sub(), new_balance);17201721 // Re-create owners list with sender removed1722 let index = token1723 .owner1724 .iter()1725 .position(|i| i.owner == *owner)1726 .expect("owned item is exists");1727 token.owner.remove(index);1728 let owner_count = token.owner.len();17291730 // Burn the token completely if this was the last (only) owner1731 if owner_count == 0 {1732 <ReFungibleItemList<T>>::remove(collection_id, item_id);1733 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);1734 }1735 else {1736 <ReFungibleItemList<T>>::insert(collection_id, item_id, token);1737 }17381739 Ok(())1740 }17411742 fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1743 let collection_id = collection.id;17441745 let item = <NftItemList<T>>::get(collection_id, item_id)1746 .ok_or(Error::<T>::TokenNotFound)?;1747 Self::remove_token_index(collection_id, item_id, &item.owner)?;17481749 // update balance1750 let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())1751 .checked_sub(1)1752 .ok_or(Error::<T>::NumOverflow)?;1753 <Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);1754 <NftItemList<T>>::remove(collection_id, item_id);1755 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);17561757 Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id));1758 Ok(())1759 }17601761 fn burn_fungible_item(owner: &T::CrossAccountId, collection: &CollectionHandle<T>, value: u128) -> DispatchResult {1762 let collection_id = collection.id;17631764 let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());1765 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);17661767 // update balance1768 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1769 .checked_sub(value)1770 .ok_or(Error::<T>::NumOverflow)?;1771 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);17721773 if balance.value - value > 0 {1774 balance.value -= value;1775 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);1776 }1777 else {1778 <FungibleItemList<T>>::remove(collection_id, owner.as_sub());1779 }17801781 collection.log(ERC20Events::Transfer {1782 from: *owner.as_eth(),1783 to: H160::default(),1784 value: value.into(),1785 });1786 Ok(())1787 }17881789 pub fn get_collection(collection_id: CollectionId) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {1790 Ok(<CollectionHandle<T>>::get(collection_id)1791 .ok_or(Error::<T>::CollectionNotFound)?)1792 }17931794 fn save_collection(collection: CollectionHandle<T>) {1795 <CollectionById<T>>::insert(collection.id, collection.into_inner());1796 }17971798 pub fn submit_logs(collection: CollectionHandle<T>) -> DispatchResult {1799 if collection.logs.is_empty() {1800 return Ok(())1801 }1802 T::EthereumTransactionSender::submit_logs_transaction(1803 eth::generate_transaction(collection.id, T::EthereumChainId::get()),1804 collection.logs.retrieve_logs(),1805 )1806 }18071808 fn check_owner_permissions(target_collection: &CollectionHandle<T>, subject: &T::AccountId) -> DispatchResult {1809 ensure!(1810 *subject == target_collection.owner,1811 Error::<T>::NoPermission1812 );18131814 Ok(())1815 }18161817 fn is_owner_or_admin_permissions(collection: &CollectionHandle<T>, subject: &T::CrossAccountId) -> bool {1818 *subject.as_sub() == collection.owner || <AdminList<T>>::get(collection.id).contains(&subject)1819 }18201821 fn check_owner_or_admin_permissions(1822 collection: &CollectionHandle<T>,1823 subject: &T::CrossAccountId,1824 ) -> DispatchResult {1825 ensure!(Self::is_owner_or_admin_permissions(collection, subject), Error::<T>::NoPermission);18261827 Ok(())1828 }18291830 fn owned_amount(1831 subject: &T::CrossAccountId,1832 target_collection: &CollectionHandle<T>,1833 item_id: TokenId,1834 ) -> Option<u128> {1835 let collection_id = target_collection.id;18361837 match target_collection.mode {1838 CollectionMode::NFT => (<NftItemList<T>>::get(collection_id, item_id)?.owner == *subject)1839 .then(|| 1),1840 CollectionMode::Fungible(_) => Some(<FungibleItemList<T>>::get(collection_id, &subject.as_sub())1841 .value),1842 CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?1843 .owner1844 .iter()1845 .find(|i| i.owner == *subject)1846 .map(|i| i.fraction),1847 CollectionMode::Invalid => None,1848 }1849 }18501851 fn is_item_owner(subject: &T::CrossAccountId, target_collection: &CollectionHandle<T>, item_id: TokenId) -> bool {1852 match target_collection.mode {1853 CollectionMode::Fungible(_) => true,1854 _ => Self::owned_amount(&subject, target_collection, item_id).is_some(),1855 }1856 }18571858 fn check_white_list(collection: &CollectionHandle<T>, address: &T::CrossAccountId) -> DispatchResult {1859 let collection_id = collection.id;18601861 let mes = Error::<T>::AddresNotInWhiteList;1862 ensure!(<WhiteList<T>>::contains_key(collection_id, address.as_sub()), mes);18631864 Ok(())1865 }18661867 /// Check if token exists. In case of Fungible, check if there is an entry for 1868 /// the owner in fungible balances double map1869 fn token_exists(1870 target_collection: &CollectionHandle<T>,1871 item_id: TokenId,1872 ) -> DispatchResult {1873 let collection_id = target_collection.id;1874 let exists = match target_collection.mode1875 {1876 CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),1877 CollectionMode::Fungible(_) => true,1878 CollectionMode::ReFungible => <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1879 _ => false1880 };18811882 ensure!(exists == true, Error::<T>::TokenNotFound);1883 Ok(())1884 }18851886 fn transfer_fungible(1887 collection: &CollectionHandle<T>,1888 value: u128,1889 owner: &T::CrossAccountId,1890 recipient: &T::CrossAccountId,1891 ) -> DispatchResult {1892 let collection_id = collection.id;18931894 let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());1895 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);18961897 // Send balance to recipient (updates balanceOf of recipient)1898 Self::add_fungible_item(collection, recipient, value)?;18991900 // update balanceOf of sender1901 <Balance<T>>::insert(collection_id, owner.as_sub(), balance.value - value);19021903 // Reduce or remove sender1904 if balance.value == value {1905 <FungibleItemList<T>>::remove(collection_id, owner.as_sub());1906 }1907 else {1908 balance.value -= value;1909 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);1910 }19111912 collection.log(ERC20Events::Transfer {1913 from: *owner.as_eth(),1914 to: *recipient.as_eth(),1915 value: value.into(),1916 });1917 Self::deposit_event(RawEvent::Transfer(collection.id, 1, owner.clone(), recipient.clone(), value));19181919 Ok(())1920 }19211922 fn transfer_refungible(1923 collection: &CollectionHandle<T>,1924 item_id: TokenId,1925 value: u128,1926 owner: T::CrossAccountId,1927 new_owner: T::CrossAccountId,1928 ) -> DispatchResult {1929 let collection_id = collection.id;1930 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)1931 .ok_or(Error::<T>::TokenNotFound)?;19321933 let item = full_item1934 .owner1935 .iter()1936 .filter(|i| i.owner == owner)1937 .next()1938 .ok_or(Error::<T>::TokenNotFound)?;1939 let amount = item.fraction;19401941 ensure!(amount >= value, Error::<T>::TokenValueTooLow);19421943 // update balance1944 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())1945 .checked_sub(value)1946 .ok_or(Error::<T>::NumOverflow)?;1947 <Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);19481949 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())1950 .checked_add(value)1951 .ok_or(Error::<T>::NumOverflow)?;1952 <Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);19531954 let old_owner = item.owner.clone();1955 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);19561957 // transfer1958 if amount == value && !new_owner_has_account {1959 // change owner1960 // new owner do not have account1961 let mut new_full_item = full_item.clone();1962 new_full_item1963 .owner1964 .iter_mut()1965 .find(|i| i.owner == owner)1966 .expect("old owner does present in refungible")1967 .owner = new_owner.clone();1968 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);19691970 // update index collection1971 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;1972 } else {1973 let mut new_full_item = full_item.clone();1974 new_full_item1975 .owner1976 .iter_mut()1977 .find(|i| i.owner == owner)1978 .expect("old owner does present in refungible")1979 .fraction -= value;19801981 // separate amount1982 if new_owner_has_account {1983 // new owner has account1984 new_full_item1985 .owner1986 .iter_mut()1987 .find(|i| i.owner == new_owner)1988 .expect("new owner has account")1989 .fraction += value;1990 } else {1991 // new owner do not have account1992 new_full_item.owner.push(Ownership {1993 owner: new_owner.clone(),1994 fraction: value,1995 });1996 Self::add_token_index(collection_id, item_id, &new_owner)?;1997 }19981999 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2000 }20012002 Self::deposit_event(RawEvent::Transfer(collection.id, item_id, owner, new_owner, amount));20032004 Ok(())2005 }20062007 fn transfer_nft(2008 collection: &CollectionHandle<T>,2009 item_id: TokenId,2010 sender: T::CrossAccountId,2011 new_owner: T::CrossAccountId,2012 ) -> DispatchResult {2013 let collection_id = collection.id;2014 let mut item = <NftItemList<T>>::get(collection_id, item_id)2015 .ok_or(Error::<T>::TokenNotFound)?;20162017 ensure!(2018 sender == item.owner,2019 Error::<T>::MustBeTokenOwner2020 );20212022 // update balance2023 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2024 .checked_sub(1)2025 .ok_or(Error::<T>::NumOverflow)?;2026 <Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);20272028 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2029 .checked_add(1)2030 .ok_or(Error::<T>::NumOverflow)?;2031 <Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);20322033 // change owner2034 let old_owner = item.owner.clone();2035 item.owner = new_owner.clone();2036 <NftItemList<T>>::insert(collection_id, item_id, item);20372038 // update index collection2039 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;20402041 collection.log(ERC721Events::Transfer {2042 from: *sender.as_eth(),2043 to: *new_owner.as_eth(),2044 token_id: item_id.into(),2045 });2046 Self::deposit_event(RawEvent::Transfer(collection.id, item_id, sender, new_owner, 1));20472048 Ok(())2049 }2050 2051 fn set_re_fungible_variable_data(2052 collection: &CollectionHandle<T>,2053 item_id: TokenId,2054 data: Vec<u8>2055 ) -> DispatchResult {2056 let collection_id = collection.id;2057 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2058 .ok_or(Error::<T>::TokenNotFound)?;20592060 item.variable_data = data;20612062 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);20632064 Ok(())2065 }20662067 fn set_nft_variable_data(2068 collection: &CollectionHandle<T>,2069 item_id: TokenId,2070 data: Vec<u8>2071 ) -> DispatchResult {2072 let collection_id = collection.id;2073 let mut item = <NftItemList<T>>::get(collection_id, item_id)2074 .ok_or(Error::<T>::TokenNotFound)?;2075 2076 item.variable_data = data;20772078 <NftItemList<T>>::insert(collection_id, item_id, item);2079 2080 Ok(())2081 }20822083 #[allow(dead_code)]2084 fn init_collection(item: &Collection<T>) {2085 // check params2086 assert!(2087 item.decimal_points <= MAX_DECIMAL_POINTS,2088 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2089 );2090 assert!(2091 item.name.len() <= 64,2092 "Collection name can not be longer than 63 char"2093 );2094 assert!(2095 item.name.len() <= 256,2096 "Collection description can not be longer than 255 char"2097 );2098 assert!(2099 item.token_prefix.len() <= 16,2100 "Token prefix can not be longer than 15 char"2101 );21022103 // Generate next collection ID2104 let next_id = CreatedCollectionCount::get()2105 .checked_add(1)2106 .unwrap();21072108 CreatedCollectionCount::put(next_id);2109 }21102111 #[allow(dead_code)]2112 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::CrossAccountId>) {2113 let current_index = <ItemListIndex>::get(collection_id)2114 .checked_add(1)2115 .unwrap();21162117 Self::add_token_index(collection_id, current_index, &item.owner).unwrap();21182119 <ItemListIndex>::insert(collection_id, current_index);21202121 // Update balance2122 let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2123 .checked_add(1)2124 .unwrap();2125 <Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2126 }21272128 #[allow(dead_code)]2129 fn init_fungible_token(collection_id: CollectionId, owner: &T::CrossAccountId, item: &FungibleItemType) {2130 let current_index = <ItemListIndex>::get(collection_id)2131 .checked_add(1)2132 .unwrap();21332134 Self::add_token_index(collection_id, current_index, owner).unwrap();21352136 <ItemListIndex>::insert(collection_id, current_index);21372138 // Update balance2139 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2140 .checked_add(item.value)2141 .unwrap();2142 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2143 }21442145 #[allow(dead_code)]2146 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::CrossAccountId>) {2147 let current_index = <ItemListIndex>::get(collection_id)2148 .checked_add(1)2149 .unwrap();21502151 let value = item.owner.first().unwrap().fraction;2152 let owner = item.owner.first().unwrap().owner.clone();21532154 Self::add_token_index(collection_id, current_index, &owner).unwrap();21552156 <ItemListIndex>::insert(collection_id, current_index);21572158 // Update balance2159 let new_balance = <Balance<T>>::get(collection_id, &owner.as_sub())2160 .checked_add(value)2161 .unwrap();2162 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2163 }21642165 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: &T::CrossAccountId) -> DispatchResult {2166 // add to account limit2167 if <AccountItemCount<T>>::contains_key(owner.as_sub()) {21682169 // bound Owned tokens by a single address2170 let count = <AccountItemCount<T>>::get(owner.as_sub());2171 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);21722173 <AccountItemCount<T>>::insert(owner.as_sub(), count2174 .checked_add(1)2175 .ok_or(Error::<T>::NumOverflow)?);2176 }2177 else {2178 <AccountItemCount<T>>::insert(owner.as_sub(), 1);2179 }21802181 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2182 if list_exists {2183 let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2184 let item_contains = list.contains(&item_index.clone());21852186 if !item_contains {2187 list.push(item_index.clone());2188 }21892190 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2191 } else {2192 let mut itm = Vec::new();2193 itm.push(item_index.clone());2194 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), itm);2195 }21962197 Ok(())2198 }21992200 fn remove_token_index(2201 collection_id: CollectionId,2202 item_index: TokenId,2203 owner: &T::CrossAccountId,2204 ) -> DispatchResult {22052206 // update counter2207 <AccountItemCount<T>>::insert(owner.as_sub(), 2208 <AccountItemCount<T>>::get(owner.as_sub())2209 .checked_sub(1)2210 .ok_or(Error::<T>::NumOverflow)?);221122122213 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2214 if list_exists {2215 let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2216 let item_contains = list.contains(&item_index.clone());22172218 if item_contains {2219 list.retain(|&item| item != item_index);2220 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2221 }2222 }22232224 Ok(())2225 }22262227 fn move_token_index(2228 collection_id: CollectionId,2229 item_index: TokenId,2230 old_owner: &T::CrossAccountId,2231 new_owner: &T::CrossAccountId,2232 ) -> DispatchResult {2233 Self::remove_token_index(collection_id, item_index, old_owner)?;2234 Self::add_token_index(collection_id, item_index, new_owner)?;22352236 Ok(())2237 }2238 2239 fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2240 ensure!(<ContractOwner<T>>::get(contract) == Some(account), Error::<T>::NoPermission);22412242 Ok(())2243 }2244}22452246sp_api::decl_runtime_apis! {2247 pub trait NftApi {2248 /// Used for ethereum integration2249 fn eth_contract_code(account: H160) -> Option<Vec<u8>>;2250 }2251}1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]78#![cfg_attr(not(feature = "std"), no_std)]910extern crate alloc;1112pub use serde::{Serialize, Deserialize};1314pub use frame_support::{15 construct_runtime, decl_event, decl_module, decl_storage, decl_error,16 dispatch::DispatchResult,17 ensure, fail, parameter_types,18 traits::{19 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,20 Randomness, IsSubType, WithdrawReasons,21 },22 weights::{23 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},24 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,25 WeightToFeePolynomial, DispatchClass,26 },27 StorageValue,28 transactional,29};3031use frame_system::{self as system, ensure_signed, ensure_root};32use sp_core::H160;33use sp_runtime::sp_std::prelude::Vec;34use core::ops::{Deref, DerefMut};35use core::cell::RefCell;36use nft_data_structs::{37 MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, MAX_REFUNGIBLE_PIECES,38 AccessMode, ChainLimits, Collection, CreateItemData, CollectionLimits,39 CollectionId, CollectionMode, TokenId, 40 SchemaVersion, SponsorshipState, Ownership,41 NftItemType, FungibleItemType, ReFungibleItemType42};43use pallet_ethereum::EthereumTransactionSender;4445#[cfg(test)]46mod mock;4748#[cfg(test)]49mod tests;5051mod default_weights;52mod eth;53mod sponsorship;54pub use sponsorship::NftSponsorshipHandler;5556pub use eth::NftErcSupport;57pub use eth::account::*;58use eth::erc::{ERC20Events, ERC721Events};5960#[cfg(feature = "runtime-benchmarks")]61mod benchmarking;6263pub trait WeightInfo {64 fn create_collection() -> Weight;65 fn destroy_collection() -> Weight;66 fn add_to_white_list() -> Weight;67 fn remove_from_white_list() -> Weight;68 fn set_public_access_mode() -> Weight;69 fn set_mint_permission() -> Weight;70 fn change_collection_owner() -> Weight;71 fn add_collection_admin() -> Weight;72 fn remove_collection_admin() -> Weight;73 fn set_collection_sponsor() -> Weight;74 fn confirm_sponsorship() -> Weight;75 fn remove_collection_sponsor() -> Weight;76 fn create_item(s: usize) -> Weight;77 fn burn_item() -> Weight;78 fn transfer() -> Weight;79 fn approve() -> Weight;80 fn transfer_from() -> Weight;81 fn set_offchain_schema() -> Weight;82 fn set_const_on_chain_schema() -> Weight;83 fn set_variable_on_chain_schema() -> Weight;84 fn set_variable_meta_data() -> Weight;85 fn enable_contract_sponsoring() -> Weight;86 fn set_schema_version() -> Weight;87 fn set_chain_limits() -> Weight;88 fn set_contract_sponsoring_rate_limit() -> Weight;89 fn set_variable_meta_data_sponsoring_rate_limit() -> Weight;90 fn toggle_contract_white_list() -> Weight;91 fn add_to_contract_white_list() -> Weight;92 fn remove_from_contract_white_list() -> Weight;93 fn set_collection_limits() -> Weight;94}9596decl_error! {97 /// Error for non-fungible-token module.98 pub enum Error for Module<T: Config> {99 /// Total collections bound exceeded.100 TotalCollectionsLimitExceeded,101 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.102 CollectionDecimalPointLimitExceeded, 103 /// Collection name can not be longer than 63 char.104 CollectionNameLimitExceeded, 105 /// Collection description can not be longer than 255 char.106 CollectionDescriptionLimitExceeded, 107 /// Token prefix can not be longer than 15 char.108 CollectionTokenPrefixLimitExceeded,109 /// This collection does not exist.110 CollectionNotFound,111 /// Item not exists.112 TokenNotFound,113 /// Admin not found114 AdminNotFound,115 /// Arithmetic calculation overflow.116 NumOverflow, 117 /// Account already has admin role.118 AlreadyAdmin, 119 /// You do not own this collection.120 NoPermission,121 /// This address is not set as sponsor, use setCollectionSponsor first.122 ConfirmUnsetSponsorFail,123 /// Collection is not in mint mode.124 PublicMintingNotAllowed,125 /// Sender parameter and item owner must be equal.126 MustBeTokenOwner,127 /// Item balance not enough.128 TokenValueTooLow,129 /// Size of item is too large.130 NftSizeLimitExceeded,131 /// No approve found132 ApproveNotFound,133 /// Requested value more than approved.134 TokenValueNotEnough,135 /// Only approved addresses can call this method.136 ApproveRequired,137 /// Address is not in white list.138 AddresNotInWhiteList,139 /// Number of collection admins bound exceeded.140 CollectionAdminsLimitExceeded,141 /// Owned tokens by a single address bound exceeded.142 AddressOwnershipLimitExceeded,143 /// Length of items properties must be greater than 0.144 EmptyArgument,145 /// const_data exceeded data limit.146 TokenConstDataLimitExceeded,147 /// variable_data exceeded data limit.148 TokenVariableDataLimitExceeded,149 /// Not NFT item data used to mint in NFT collection.150 NotNftDataUsedToMintNftCollectionToken,151 /// Not Fungible item data used to mint in Fungible collection.152 NotFungibleDataUsedToMintFungibleCollectionToken,153 /// Not Re Fungible item data used to mint in Re Fungible collection.154 NotReFungibleDataUsedToMintReFungibleCollectionToken,155 /// Unexpected collection type.156 UnexpectedCollectionType,157 /// Can't store metadata in fungible tokens.158 CantStoreMetadataInFungibleTokens,159 /// Collection token limit exceeded160 CollectionTokenLimitExceeded,161 /// Account token limit exceeded per collection162 AccountTokenLimitExceeded,163 /// Collection limit bounds per collection exceeded164 CollectionLimitBoundsExceeded,165 /// Tried to enable permissions which are only permitted to be disabled166 OwnerPermissionsCantBeReverted,167 /// Schema data size limit bound exceeded168 SchemaDataLimitExceeded,169 /// Maximum refungibility exceeded170 WrongRefungiblePieces,171 /// createRefungible should be called with one owner172 BadCreateRefungibleCall,173 /// Gas limit exceeded174 OutOfGas,175 }176}177178pub struct CollectionHandle<T: Config> {179 pub id: CollectionId,180 collection: Collection<T>,181 logs: eth::log::LogRecorder,182 evm_address: H160,183 gas_limit: RefCell<u64>,184}185impl<T: Config> CollectionHandle<T> {186 pub fn get_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {187 <CollectionById<T>>::get(id)188 .map(|collection| Self {189 id,190 collection,191 logs: eth::log::LogRecorder::default(),192 evm_address: eth::collection_id_to_address(id),193 gas_limit: RefCell::new(gas_limit),194 })195 }196 pub fn get(id: CollectionId) -> Option<Self> {197 Self::get_with_gas_limit(id, u64::MAX)198 }199 pub fn gas_left(&self) -> u64 {200 *self.gas_limit.borrow()201 }202 pub fn consume_gas(&self, gas: u64) -> DispatchResult {203 let mut gas_limit = self.gas_limit.borrow_mut();204 if *gas_limit < gas {205 fail!(Error::<T>::OutOfGas);206 }207 *gas_limit -= gas;208 Ok(())209 }210 pub fn log(&self, log: impl evm_coder::ToLog) {211 self.logs.log(log.to_log(self.evm_address))212 }213 pub fn into_inner(self) -> Collection<T> {214 self.collection.clone()215 }216}217impl<T: Config> Deref for CollectionHandle<T> {218 type Target = Collection<T>;219220 fn deref(&self) -> &Self::Target {221 &self.collection222 }223}224225impl<T: Config> DerefMut for CollectionHandle<T> {226 fn deref_mut(&mut self) -> &mut Self::Target {227 &mut self.collection228 }229}230231pub trait Config: system::Config + Sized {232 type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;233234 /// Weight information for extrinsics in this pallet.235 type WeightInfo: WeightInfo;236237 type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;238 type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;239 type EvmWithdrawOrigin: pallet_evm::EnsureAddressOrigin<Self::Origin, Success = Self::AccountId>;240241 type CrossAccountId: CrossAccountId<Self::AccountId>;242 type Currency: Currency<Self::AccountId>;243 type CollectionCreationPrice: Get<<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance>;244 type TreasuryAccountId: Get<Self::AccountId>;245246 type EthereumChainId: Get<u64>;247 type EthereumTransactionSender: pallet_ethereum::EthereumTransactionSender;248}249250// # Used definitions251//252// ## User control levels253//254// chain-controlled - key is uncontrolled by user255// i.e autoincrementing index256// can use non-cryptographic hash257// real - key is controlled by user258// but it is hard to generate enough colliding values, i.e owner of signed txs259// can use non-cryptographic hash260// controlled - key is completly controlled by users261// i.e maps with mutable keys262// should use cryptographic hash263//264// ## User control level downgrade reasons265//266// ?1 - chain-controlled -> controlled267// collections/tokens can be destroyed, resulting in massive holes268// ?2 - chain-controlled -> controlled269// same as ?1, but can be only added, resulting in easier exploitation270// ?3 - real -> controlled271// no confirmation required, so addresses can be easily generated272decl_storage! {273 trait Store for Module<T: Config> as Nft {274275 //#region Private members276 /// Id of next collection277 CreatedCollectionCount: u32;278 /// Used for migrations279 ChainVersion: u64;280 /// Id of last collection token281 /// Collection id (controlled?1)282 ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;283 //#endregion284285 //#region Chain limits struct286 pub ChainLimit get(fn chain_limit) config(): ChainLimits;287 //#endregion288289 //#region Bound counters290 /// Amount of collections destroyed, used for total amount tracking with291 /// CreatedCollectionCount292 DestroyedCollectionCount: u32;293 /// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)294 /// Account id (real)295 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;296 //#endregion297298 //#region Basic collections299 /// Collection info300 /// Collection id (controlled?1)301 pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;302 /// List of collection admins303 /// Collection id (controlled?2)304 pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::CrossAccountId>;305 /// Whitelisted collection users306 /// Collection id (controlled?2), user id (controlled?3)307 pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;308 //#endregion309310 /// How many of collection items user have311 /// Collection id (controlled?2), account id (real)312 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;313314 /// Amount of items which spender can transfer out of owners account (via transferFrom)315 /// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))316 /// TODO: Off chain worker should remove from this map when token gets removed317 pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;318319 //#region Item collections320 /// Collection id (controlled?2), token id (controlled?1)321 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::CrossAccountId>>;322 /// Collection id (controlled?2), owner (controlled?2)323 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;324 /// Collection id (controlled?2), token id (controlled?1)325 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::CrossAccountId>>;326 //#endregion327328 //#region Index list329 /// Collection id (controlled?2), tokens owner (controlled?2)330 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;331 //#endregion332333 //#region Tokens transfer rate limit baskets334 /// (Collection id (controlled?2), who created (real))335 /// TODO: Off chain worker should remove from this map when collection gets removed336 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;337 /// Collection id (controlled?2), token id (controlled?2)338 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;339 /// Collection id (controlled?2), owning user (real)340 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;341 /// Collection id (controlled?2), token id (controlled?2)342 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;343 //#endregion344345 /// Variable metadata sponsoring346 /// Collection id (controlled?2), token id (controlled?2)347 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;348 }349 add_extra_genesis {350 build(|config: &GenesisConfig<T>| {351 // Modification of storage352 for (_num, _c) in &config.collection_id {353 <Module<T>>::init_collection(_c);354 }355356 for (_num, _c, _i) in &config.nft_item_id {357 <Module<T>>::init_nft_token(*_c, _i);358 }359360 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {361 <Module<T>>::init_fungible_token(*collection_id, &T::CrossAccountId::from_sub(account_id.clone()), fungible_item);362 }363364 for (_num, _c, _i) in &config.refungible_item_id {365 <Module<T>>::init_refungible_token(*_c, _i);366 }367 })368 }369}370371decl_event!(372 pub enum Event<T>373 where374 AccountId = <T as frame_system::Config>::AccountId,375 CrossAccountId = <T as Config>::CrossAccountId,376 {377 /// New collection was created378 /// 379 /// # Arguments380 /// 381 /// * collection_id: Globally unique identifier of newly created collection.382 /// 383 /// * mode: [CollectionMode] converted into u8.384 /// 385 /// * account_id: Collection owner.386 CollectionCreated(CollectionId, u8, AccountId),387388 /// New item was created.389 /// 390 /// # Arguments391 /// 392 /// * collection_id: Id of the collection where item was created.393 /// 394 /// * item_id: Id of an item. Unique within the collection.395 ///396 /// * recipient: Owner of newly created item 397 ItemCreated(CollectionId, TokenId, CrossAccountId),398399 /// Collection item was burned.400 /// 401 /// # Arguments402 /// 403 /// collection_id.404 /// 405 /// item_id: Identifier of burned NFT.406 ItemDestroyed(CollectionId, TokenId),407408 /// Item was transferred409 ///410 /// * collection_id: Id of collection to which item is belong411 ///412 /// * item_id: Id of an item413 ///414 /// * sender: Original owner of item415 ///416 /// * recipient: New owner of item417 ///418 /// * amount: Always 1 for NFT419 Transfer(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),420421 /// * collection_id422 ///423 /// * item_id424 ///425 /// * sender426 ///427 /// * spender428 ///429 /// * amount430 Approved(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),431 }432);433434decl_module! {435 pub struct Module<T: Config> for enum Call 436 where 437 origin: T::Origin438 {439 fn deposit_event() = default;440 type Error = Error<T>;441442 fn on_initialize(_now: T::BlockNumber) -> Weight {443 0444 }445446 /// 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.447 /// 448 /// # Permissions449 /// 450 /// * Anyone.451 /// 452 /// # Arguments453 /// 454 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.455 /// 456 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.457 /// 458 /// * token_prefix: UTF-8 string with token prefix.459 /// 460 /// * mode: [CollectionMode] collection type and type dependent data.461 // returns collection ID462 #[weight = <T as Config>::WeightInfo::create_collection()]463 #[transactional]464 pub fn create_collection(origin,465 collection_name: Vec<u16>,466 collection_description: Vec<u16>,467 token_prefix: Vec<u8>,468 mode: CollectionMode) -> DispatchResult {469470 // Anyone can create a collection471 let who = ensure_signed(origin)?;472473 // Take a (non-refundable) deposit of collection creation474 let mut imbalance = <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();475 imbalance.subsume(<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(476 &T::TreasuryAccountId::get(),477 T::CollectionCreationPrice::get(),478 ));479 <T as Config>::Currency::settle(480 &who,481 imbalance,482 WithdrawReasons::TRANSFER,483 ExistenceRequirement::KeepAlive,484 ).map_err(|_| Error::<T>::NoPermission)?;485486 let decimal_points = match mode {487 CollectionMode::Fungible(points) => points,488 _ => 0489 };490491 let chain_limit = ChainLimit::get();492493 let created_count = CreatedCollectionCount::get();494 let destroyed_count = DestroyedCollectionCount::get();495496 // bound Total number of collections497 ensure!(created_count - destroyed_count < chain_limit.collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);498499 // check params500 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);501 ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);502 ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);503 ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);504505 // Generate next collection ID506 let next_id = created_count507 .checked_add(1)508 .ok_or(Error::<T>::NumOverflow)?;509510 CreatedCollectionCount::put(next_id);511512 let limits = CollectionLimits {513 sponsored_data_size: chain_limit.custom_data_limit,514 ..Default::default()515 };516517 // Create new collection518 let new_collection = Collection {519 owner: who.clone(),520 name: collection_name,521 mode: mode.clone(),522 mint_mode: false,523 access: AccessMode::Normal,524 description: collection_description,525 decimal_points: decimal_points,526 token_prefix: token_prefix,527 offchain_schema: Vec::new(),528 schema_version: SchemaVersion::ImageURL,529 sponsorship: SponsorshipState::Disabled,530 variable_on_chain_schema: Vec::new(),531 const_on_chain_schema: Vec::new(),532 limits,533 };534535 // Add new collection to map536 <CollectionById<T>>::insert(next_id, new_collection);537538 // call event539 Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.into(), who));540541 Ok(())542 }543544 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.545 /// 546 /// # Permissions547 /// 548 /// * Collection Owner.549 /// 550 /// # Arguments551 /// 552 /// * collection_id: collection to destroy.553 #[weight = <T as Config>::WeightInfo::destroy_collection()]554 #[transactional]555 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {556557 let sender = ensure_signed(origin)?;558 let collection = Self::get_collection(collection_id)?;559 Self::check_owner_permissions(&collection, &sender)?;560 if !collection.limits.owner_can_destroy {561 fail!(Error::<T>::NoPermission);562 }563564 <AddressTokens<T>>::remove_prefix(collection_id);565 <Allowances<T>>::remove_prefix(collection_id);566 <Balance<T>>::remove_prefix(collection_id);567 <ItemListIndex>::remove(collection_id);568 <AdminList<T>>::remove(collection_id);569 <CollectionById<T>>::remove(collection_id);570 <WhiteList<T>>::remove_prefix(collection_id);571572 <NftItemList<T>>::remove_prefix(collection_id);573 <FungibleItemList<T>>::remove_prefix(collection_id);574 <ReFungibleItemList<T>>::remove_prefix(collection_id);575576 <NftTransferBasket<T>>::remove_prefix(collection_id);577 <FungibleTransferBasket<T>>::remove_prefix(collection_id);578 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);579580 <VariableMetaDataBasket<T>>::remove_prefix(collection_id);581582 DestroyedCollectionCount::put(DestroyedCollectionCount::get()583 .checked_add(1)584 .ok_or(Error::<T>::NumOverflow)?);585586 Ok(())587 }588589 /// Add an address to white list.590 /// 591 /// # Permissions592 /// 593 /// * Collection Owner594 /// * Collection Admin595 /// 596 /// # Arguments597 /// 598 /// * collection_id.599 /// 600 /// * address.601 #[weight = <T as Config>::WeightInfo::add_to_white_list()]602 #[transactional]603 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{604605 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);606 let collection = Self::get_collection(collection_id)?;607608 Self::toggle_white_list_internal(609 &sender,610 &collection,611 &address,612 true,613 )?;614615 Ok(())616 }617618 /// Remove an address from white list.619 /// 620 /// # Permissions621 /// 622 /// * Collection Owner623 /// * Collection Admin624 /// 625 /// # Arguments626 /// 627 /// * collection_id.628 /// 629 /// * address.630 #[weight = <T as Config>::WeightInfo::remove_from_white_list()]631 #[transactional]632 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{633634 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);635 let collection = Self::get_collection(collection_id)?;636637 Self::toggle_white_list_internal(638 &sender,639 &collection,640 &address,641 false,642 )?;643644 Ok(())645 }646647 /// Toggle between normal and white list access for the methods with access for `Anyone`.648 /// 649 /// # Permissions650 /// 651 /// * Collection Owner.652 /// 653 /// # Arguments654 /// 655 /// * collection_id.656 /// 657 /// * mode: [AccessMode]658 #[weight = <T as Config>::WeightInfo::set_public_access_mode()]659 #[transactional]660 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult661 {662 let sender = ensure_signed(origin)?;663664 let mut target_collection = Self::get_collection(collection_id)?;665 Self::check_owner_permissions(&target_collection, &sender)?;666 target_collection.access = mode;667 Self::save_collection(target_collection);668669 Ok(())670 }671672 /// Allows Anyone to create tokens if:673 /// * White List is enabled, and674 /// * Address is added to white list, and675 /// * This method was called with True parameter676 /// 677 /// # Permissions678 /// * Collection Owner679 ///680 /// # Arguments681 /// 682 /// * collection_id.683 /// 684 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.685 #[weight = <T as Config>::WeightInfo::set_mint_permission()]686 #[transactional]687 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult688 {689 let sender = ensure_signed(origin)?;690691 let mut target_collection = Self::get_collection(collection_id)?;692 Self::check_owner_permissions(&target_collection, &sender)?;693 target_collection.mint_mode = mint_permission;694 Self::save_collection(target_collection);695696 Ok(())697 }698699 /// Change the owner of the collection.700 /// 701 /// # Permissions702 /// 703 /// * Collection Owner.704 /// 705 /// # Arguments706 /// 707 /// * collection_id.708 /// 709 /// * new_owner.710 #[weight = <T as Config>::WeightInfo::change_collection_owner()]711 #[transactional]712 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {713714 let sender = ensure_signed(origin)?;715 let mut target_collection = Self::get_collection(collection_id)?;716 Self::check_owner_permissions(&target_collection, &sender)?;717 target_collection.owner = new_owner;718 Self::save_collection(target_collection);719720 Ok(())721 }722723 /// Adds an admin of the Collection.724 /// 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. 725 /// 726 /// # Permissions727 /// 728 /// * Collection Owner.729 /// * Collection Admin.730 /// 731 /// # Arguments732 /// 733 /// * collection_id: ID of the Collection to add admin for.734 /// 735 /// * new_admin_id: Address of new admin to add.736 #[weight = <T as Config>::WeightInfo::add_collection_admin()]737 #[transactional]738 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {739 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);740 let collection = Self::get_collection(collection_id)?;741 Self::check_owner_or_admin_permissions(&collection, &sender)?;742 let mut admin_arr = <AdminList<T>>::get(collection_id);743744 match admin_arr.binary_search(&new_admin_id) {745 Ok(_) => {},746 Err(idx) => {747 let limits = ChainLimit::get();748 ensure!(admin_arr.len() < limits.collections_admins_limit as usize, Error::<T>::CollectionAdminsLimitExceeded);749 admin_arr.insert(idx, new_admin_id);750 <AdminList<T>>::insert(collection_id, admin_arr);751 }752 }753 Ok(())754 }755756 /// 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.757 ///758 /// # Permissions759 /// 760 /// * Collection Owner.761 /// * Collection Admin.762 /// 763 /// # Arguments764 /// 765 /// * collection_id: ID of the Collection to remove admin for.766 /// 767 /// * account_id: Address of admin to remove.768 #[weight = <T as Config>::WeightInfo::remove_collection_admin()]769 #[transactional]770 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {771 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);772 let collection = Self::get_collection(collection_id)?;773 Self::check_owner_or_admin_permissions(&collection, &sender)?;774 let mut admin_arr = <AdminList<T>>::get(collection_id);775776 match admin_arr.binary_search(&account_id) {777 Ok(idx) => {778 admin_arr.remove(idx);779 <AdminList<T>>::insert(collection_id, admin_arr);780 },781 Err(_) => {}782 }783 Ok(())784 }785786 /// # Permissions787 /// 788 /// * Collection Owner789 /// 790 /// # Arguments791 /// 792 /// * collection_id.793 /// 794 /// * new_sponsor.795 #[weight = <T as Config>::WeightInfo::set_collection_sponsor()]796 #[transactional]797 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {798 let sender = ensure_signed(origin)?;799 let mut target_collection = Self::get_collection(collection_id)?;800 Self::check_owner_permissions(&target_collection, &sender)?;801802 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);803 Self::save_collection(target_collection);804805 Ok(())806 }807808 /// # Permissions809 /// 810 /// * Sponsor.811 /// 812 /// # Arguments813 /// 814 /// * collection_id.815 #[weight = <T as Config>::WeightInfo::confirm_sponsorship()]816 #[transactional]817 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {818 let sender = ensure_signed(origin)?;819820 let mut target_collection = Self::get_collection(collection_id)?;821 ensure!(822 target_collection.sponsorship.pending_sponsor() == Some(&sender),823 Error::<T>::ConfirmUnsetSponsorFail824 );825826 target_collection.sponsorship = SponsorshipState::Confirmed(sender);827 Self::save_collection(target_collection);828829 Ok(())830 }831832 /// Switch back to pay-per-own-transaction model.833 ///834 /// # Permissions835 ///836 /// * Collection owner.837 /// 838 /// # Arguments839 /// 840 /// * collection_id.841 #[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]842 #[transactional]843 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {844 let sender = ensure_signed(origin)?;845846 let mut target_collection = Self::get_collection(collection_id)?;847 Self::check_owner_permissions(&target_collection, &sender)?;848849 target_collection.sponsorship = SponsorshipState::Disabled;850 Self::save_collection(target_collection);851852 Ok(())853 }854855 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.856 /// 857 /// # Permissions858 /// 859 /// * Collection Owner.860 /// * Collection Admin.861 /// * Anyone if862 /// * White List is enabled, and863 /// * Address is added to white list, and864 /// * MintPermission is enabled (see SetMintPermission method)865 /// 866 /// # Arguments867 /// 868 /// * collection_id: ID of the collection.869 /// 870 /// * owner: Address, initial owner of the NFT.871 ///872 /// * data: Token data to store on chain.873 // #[weight =874 // (130_000_000 as Weight)875 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))876 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))877 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]878879 #[weight = <T as Config>::WeightInfo::create_item(data.len())]880 #[transactional]881 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult {882 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);883 let collection = Self::get_collection(collection_id)?;884885 Self::create_item_internal(&sender, &collection, &owner, data)?;886887 Self::submit_logs(collection)?;888 Ok(())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 = <T as Config>::WeightInfo::create_item(items_data.into_iter()910 .map(|data| { data.len() })911 .sum())]912 #[transactional]913 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResult {914915 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);916 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);917 let collection = Self::get_collection(collection_id)?;918919 Self::create_multiple_items_internal(&sender, &collection, &owner, items_data)?;920921 Self::submit_logs(collection)?;922 Ok(())923 }924925 /// Destroys a concrete instance of NFT.926 /// 927 /// # Permissions928 /// 929 /// * Collection Owner.930 /// * Collection Admin.931 /// * Current NFT Owner.932 /// 933 /// # Arguments934 /// 935 /// * collection_id: ID of the collection.936 /// 937 /// * item_id: ID of NFT to burn.938 #[weight = <T as Config>::WeightInfo::burn_item()]939 #[transactional]940 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {941942 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);943 let target_collection = Self::get_collection(collection_id)?;944945 Self::burn_item_internal(&sender, &target_collection, item_id, value)?;946947 Self::submit_logs(target_collection)?;948 Ok(())949 }950951 /// Change ownership of the token.952 /// 953 /// # Permissions954 /// 955 /// * Collection Owner956 /// * Collection Admin957 /// * Current NFT owner958 ///959 /// # Arguments960 /// 961 /// * recipient: Address of token recipient.962 /// 963 /// * collection_id.964 /// 965 /// * item_id: ID of the item966 /// * Non-Fungible Mode: Required.967 /// * Fungible Mode: Ignored.968 /// * Re-Fungible Mode: Required.969 /// 970 /// * value: Amount to transfer.971 /// * Non-Fungible Mode: Ignored972 /// * Fungible Mode: Must specify transferred amount973 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)974 #[weight = <T as Config>::WeightInfo::transfer()]975 #[transactional]976 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {977 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);978 let collection = Self::get_collection(collection_id)?;979980 Self::transfer_internal(&sender, &recipient, &collection, item_id, value)?;981982 Self::submit_logs(collection)?;983 Ok(())984 }985986 /// Set, change, or remove approved address to transfer the ownership of the NFT.987 /// 988 /// # Permissions989 /// 990 /// * Collection Owner991 /// * Collection Admin992 /// * Current NFT owner993 /// 994 /// # Arguments995 /// 996 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).997 /// 998 /// * collection_id.999 /// 1000 /// * item_id: ID of the item.1001 #[weight = <T as Config>::WeightInfo::approve()]1002 #[transactional]1003 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {1004 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1005 let collection = Self::get_collection(collection_id)?;10061007 Self::approve_internal(&sender, &spender, &collection, item_id, amount)?;10081009 Self::submit_logs(collection)?;1010 Ok(())1011 }1012 1013 /// 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.1014 /// 1015 /// # Permissions1016 /// * Collection Owner1017 /// * Collection Admin1018 /// * Current NFT owner1019 /// * Address approved by current NFT owner1020 /// 1021 /// # Arguments1022 /// 1023 /// * from: Address that owns token.1024 /// 1025 /// * recipient: Address of token recipient.1026 /// 1027 /// * collection_id.1028 /// 1029 /// * item_id: ID of the item.1030 /// 1031 /// * value: Amount to transfer.1032 #[weight = <T as Config>::WeightInfo::transfer_from()]1033 #[transactional]1034 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {1035 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1036 let collection = Self::get_collection(collection_id)?;10371038 Self::transfer_from_internal(&sender, &from, &recipient, &collection, item_id, value)?;10391040 Self::submit_logs(collection)?;1041 Ok(())1042 }1043 // #[weight = 0]1044 // // let no_perm_mes = "You do not have permissions to modify this collection";1045 // // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1046 // // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1047 // // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);10481049 // // // on_nft_received call10501051 // // Self::transfer(origin, collection_id, item_id, new_owner)?;10521053 // Ok(())1054 // }10551056 /// Set off-chain data schema.1057 /// 1058 /// # Permissions1059 /// 1060 /// * Collection Owner1061 /// * Collection Admin1062 /// 1063 /// # Arguments1064 /// 1065 /// * collection_id.1066 /// 1067 /// * schema: String representing the offchain data schema.1068 #[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1069 #[transactional]1070 pub fn set_variable_meta_data (1071 origin,1072 collection_id: CollectionId,1073 item_id: TokenId,1074 data: Vec<u8>1075 ) -> DispatchResult {1076 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1077 1078 let collection = Self::get_collection(collection_id)?;10791080 Self::set_variable_meta_data_internal(&sender, &collection, item_id, data)?;10811082 Ok(())1083 }1084 1085 /// Set schema standard1086 /// ImageURL1087 /// Unique1088 /// 1089 /// # Permissions1090 /// 1091 /// * Collection Owner1092 /// * Collection Admin1093 /// 1094 /// # Arguments1095 /// 1096 /// * collection_id.1097 /// 1098 /// * schema: SchemaVersion: enum1099 #[weight = <T as Config>::WeightInfo::set_schema_version()]1100 #[transactional]1101 pub fn set_schema_version(1102 origin,1103 collection_id: CollectionId,1104 version: SchemaVersion1105 ) -> DispatchResult {1106 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1107 let mut target_collection = Self::get_collection(collection_id)?;1108 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;1109 target_collection.schema_version = version;1110 Self::save_collection(target_collection);11111112 Ok(())1113 }11141115 /// Set off-chain data schema.1116 /// 1117 /// # Permissions1118 /// 1119 /// * Collection Owner1120 /// * Collection Admin1121 /// 1122 /// # Arguments1123 /// 1124 /// * collection_id.1125 /// 1126 /// * schema: String representing the offchain data schema.1127 #[weight = <T as Config>::WeightInfo::set_offchain_schema()]1128 #[transactional]1129 pub fn set_offchain_schema(1130 origin,1131 collection_id: CollectionId,1132 schema: Vec<u8>1133 ) -> DispatchResult {1134 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1135 let mut target_collection = Self::get_collection(collection_id)?;1136 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11371138 // check schema limit1139 ensure!(schema.len() as u32 <= ChainLimit::get().offchain_schema_limit, "");11401141 target_collection.offchain_schema = schema;1142 Self::save_collection(target_collection);11431144 Ok(())1145 }11461147 /// Set const on-chain data schema.1148 /// 1149 /// # Permissions1150 /// 1151 /// * Collection Owner1152 /// * Collection Admin1153 /// 1154 /// # Arguments1155 /// 1156 /// * collection_id.1157 /// 1158 /// * schema: String representing the const on-chain data schema.1159 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1160 #[transactional]1161 pub fn set_const_on_chain_schema (1162 origin,1163 collection_id: CollectionId,1164 schema: Vec<u8>1165 ) -> DispatchResult {1166 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1167 let mut target_collection = Self::get_collection(collection_id)?;1168 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11691170 // check schema limit1171 ensure!(schema.len() as u32 <= ChainLimit::get().const_on_chain_schema_limit, "");11721173 target_collection.const_on_chain_schema = schema;1174 Self::save_collection(target_collection);11751176 Ok(())1177 }11781179 /// Set variable on-chain data schema.1180 /// 1181 /// # Permissions1182 /// 1183 /// * Collection Owner1184 /// * Collection Admin1185 /// 1186 /// # Arguments1187 /// 1188 /// * collection_id.1189 /// 1190 /// * schema: String representing the variable on-chain data schema.1191 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1192 #[transactional]1193 pub fn set_variable_on_chain_schema (1194 origin,1195 collection_id: CollectionId,1196 schema: Vec<u8>1197 ) -> DispatchResult {1198 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1199 let mut target_collection = Self::get_collection(collection_id)?;1200 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;12011202 // check schema limit1203 ensure!(schema.len() as u32 <= ChainLimit::get().variable_on_chain_schema_limit, "");12041205 target_collection.variable_on_chain_schema = schema;1206 Self::save_collection(target_collection);12071208 Ok(())1209 }12101211 // Sudo permissions function1212 #[weight = <T as Config>::WeightInfo::set_chain_limits()]1213 #[transactional]1214 pub fn set_chain_limits(1215 origin,1216 limits: ChainLimits1217 ) -> DispatchResult {12181219 #[cfg(not(feature = "runtime-benchmarks"))]1220 ensure_root(origin)?;12211222 <ChainLimit>::put(limits);1223 Ok(())1224 }12251226 #[weight = <T as Config>::WeightInfo::set_collection_limits()]1227 #[transactional]1228 pub fn set_collection_limits(1229 origin,1230 collection_id: u32,1231 new_limits: CollectionLimits<T::BlockNumber>,1232 ) -> DispatchResult {1233 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1234 let mut target_collection = Self::get_collection(collection_id)?;1235 Self::check_owner_permissions(&target_collection, &sender.as_sub())?;1236 let old_limits = &target_collection.limits;1237 let chain_limits = ChainLimit::get();12381239 // collection bounds1240 ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1241 new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP && 1242 new_limits.sponsored_data_size <= chain_limits.custom_data_limit,1243 Error::<T>::CollectionLimitBoundsExceeded);12441245 // token_limit check prev1246 ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1247 ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);12481249 ensure!(1250 (old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1251 (old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1252 Error::<T>::OwnerPermissionsCantBeReverted,1253 );12541255 target_collection.limits = new_limits;1256 Self::save_collection(target_collection);12571258 Ok(())1259 } 1260 }1261}12621263impl<T: Config> Module<T> {1264 pub fn create_item_internal(sender: &T::CrossAccountId, collection: &CollectionHandle<T>, owner: &T::CrossAccountId, data: CreateItemData) -> DispatchResult {1265 Self::can_create_items_in_collection(&collection, &sender, &owner, 1)?;1266 Self::validate_create_item_args(&collection, &data)?;1267 Self::create_item_no_validation(&collection, owner, data)?;12681269 Ok(())1270 }12711272 pub fn transfer_internal(sender: &T::CrossAccountId, recipient: &T::CrossAccountId, target_collection: &CollectionHandle<T>, item_id: TokenId, value: u128) -> DispatchResult {1273 target_collection.consume_gas(2000000)?;1274 // Limits check1275 Self::is_correct_transfer(target_collection, &recipient)?;12761277 // Transfer permissions check1278 ensure!(Self::is_item_owner(&sender, target_collection, item_id) ||1279 Self::is_owner_or_admin_permissions(target_collection, &sender),1280 Error::<T>::NoPermission);12811282 if target_collection.access == AccessMode::WhiteList {1283 Self::check_white_list(target_collection, &sender)?;1284 Self::check_white_list(target_collection, &recipient)?;1285 }12861287 match target_collection.mode1288 {1289 CollectionMode::NFT => Self::transfer_nft(target_collection, item_id, sender.clone(), recipient.clone())?,1290 CollectionMode::Fungible(_) => Self::transfer_fungible(target_collection, value, &sender, &recipient)?,1291 CollectionMode::ReFungible => Self::transfer_refungible(target_collection, item_id, value, sender.clone(), recipient.clone())?,1292 _ => ()1293 };12941295 Self::deposit_event(RawEvent::Transfer(target_collection.id, item_id, sender.clone(), recipient.clone(), value));12961297 Ok(())1298 }12991300 pub fn approve_internal(1301 sender: &T::CrossAccountId,1302 spender: &T::CrossAccountId,1303 collection: &CollectionHandle<T>,1304 item_id: TokenId,1305 amount: u1281306 ) -> DispatchResult {1307 collection.consume_gas(2000000)?;1308 Self::token_exists(&collection, item_id)?;13091310 // Transfer permissions check1311 let bypasses_limits = collection.limits.owner_can_transfer &&1312 Self::is_owner_or_admin_permissions(1313 &collection,1314 &sender,1315 );13161317 let allowance_limit = if bypasses_limits {1318 None1319 } else if let Some(amount) = Self::owned_amount(1320 &sender,1321 &collection,1322 item_id,1323 ) {1324 Some(amount)1325 } else {1326 fail!(Error::<T>::NoPermission);1327 };13281329 if collection.access == AccessMode::WhiteList {1330 Self::check_white_list(&collection, &sender)?;1331 Self::check_white_list(&collection, &spender)?;1332 }13331334 let allowance: u128 = amount1335 .checked_add(<Allowances<T>>::get(collection.id, (item_id, sender.as_sub(), spender.as_sub())))1336 .ok_or(Error::<T>::NumOverflow)?;1337 if let Some(limit) = allowance_limit {1338 ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1339 }1340 <Allowances<T>>::insert(collection.id, (item_id, sender.as_sub(), spender.as_sub()), allowance);13411342 if matches!(collection.mode, CollectionMode::NFT) {1343 // TODO: NFT: only one owner may exist for token in ERC7211344 collection.log(ERC721Events::Approval {1345 owner: *sender.as_eth(),1346 approved: *spender.as_eth(),1347 token_id: item_id.into(),1348 });1349 }13501351 if matches!(collection.mode, CollectionMode::Fungible(_)) {1352 // TODO: NFT: only one owner may exist for token in ERC201353 collection.log(ERC20Events::Approval {1354 owner: *sender.as_eth(),1355 spender: *spender.as_eth(),1356 value: allowance.into()1357 });1358 }13591360 Self::deposit_event(RawEvent::Approved(collection.id, item_id, sender.clone(), spender.clone(), allowance));1361 Ok(())1362 }13631364 pub fn transfer_from_internal(1365 sender: &T::CrossAccountId,1366 from: &T::CrossAccountId,1367 recipient: &T::CrossAccountId,1368 collection: &CollectionHandle<T>,1369 item_id: TokenId,1370 amount: u128,1371 ) -> DispatchResult {1372 collection.consume_gas(2000000)?;1373 // Check approval1374 let approval: u128 = <Allowances<T>>::get(collection.id, (item_id, from.as_sub(), sender.as_sub()));13751376 // Limits check1377 Self::is_correct_transfer(&collection, &recipient)?;13781379 // Transfer permissions check1380 ensure!(1381 approval >= amount || 1382 (1383 collection.limits.owner_can_transfer &&1384 Self::is_owner_or_admin_permissions(&collection, &sender)1385 ),1386 Error::<T>::NoPermission1387 );13881389 if collection.access == AccessMode::WhiteList {1390 Self::check_white_list(&collection, &sender)?;1391 Self::check_white_list(&collection, &recipient)?;1392 }13931394 // Reduce approval by transferred amount or remove if remaining approval drops to 01395 let allowance = approval.saturating_sub(amount);1396 if allowance > 0 {1397 <Allowances<T>>::insert(collection.id, (item_id, from.as_sub(), sender.as_sub()), allowance);1398 } else {1399 <Allowances<T>>::remove(collection.id, (item_id, from.as_sub(), sender.as_sub()));1400 }14011402 match collection.mode {1403 CollectionMode::NFT => {1404 Self::transfer_nft(&collection, item_id, from.clone(), recipient.clone())?1405 }1406 CollectionMode::Fungible(_) => {1407 Self::transfer_fungible(&collection, amount, &from, &recipient)?1408 }1409 CollectionMode::ReFungible => {1410 Self::transfer_refungible(&collection, item_id, amount, from.clone(), recipient.clone())?1411 }1412 _ => ()1413 };14141415 if matches!(collection.mode, CollectionMode::Fungible(_)) {1416 collection.log(ERC20Events::Approval {1417 owner: *from.as_eth(),1418 spender: *sender.as_eth(),1419 value: allowance.into()1420 });1421 }14221423 Ok(())1424 }14251426 pub fn set_variable_meta_data_internal(1427 sender: &T::CrossAccountId,1428 collection: &CollectionHandle<T>, 1429 item_id: TokenId,1430 data: Vec<u8>,1431 ) -> DispatchResult {1432 Self::token_exists(&collection, item_id)?;14331434 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);14351436 // Modify permissions check1437 ensure!(Self::is_item_owner(&sender, &collection, item_id) ||1438 Self::is_owner_or_admin_permissions(&collection, &sender),1439 Error::<T>::NoPermission);14401441 match collection.mode1442 {1443 CollectionMode::NFT => Self::set_nft_variable_data(&collection, item_id, data)?,1444 CollectionMode::ReFungible => Self::set_re_fungible_variable_data(&collection, item_id, data)?,1445 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1446 _ => fail!(Error::<T>::UnexpectedCollectionType)1447 };14481449 Ok(())1450 }14511452 pub fn create_multiple_items_internal(1453 sender: &T::CrossAccountId,1454 collection: &CollectionHandle<T>,1455 owner: &T::CrossAccountId,1456 items_data: Vec<CreateItemData>,1457 ) -> DispatchResult {1458 Self::can_create_items_in_collection(&collection, &sender, &owner, items_data.len() as u32)?;14591460 for data in &items_data {1461 Self::validate_create_item_args(&collection, data)?;1462 }1463 for data in &items_data {1464 Self::create_item_no_validation(&collection, owner, data.clone())?;1465 }14661467 Ok(())1468 }14691470 pub fn burn_item_internal(1471 sender: &T::CrossAccountId,1472 collection: &CollectionHandle<T>,1473 item_id: TokenId,1474 value: u128,1475 ) -> DispatchResult {1476 ensure!(1477 Self::is_item_owner(&sender, &collection, item_id) ||1478 (1479 collection.limits.owner_can_transfer &&1480 Self::is_owner_or_admin_permissions(&collection, &sender)1481 ),1482 Error::<T>::NoPermission1483 );14841485 if collection.access == AccessMode::WhiteList {1486 Self::check_white_list(&collection, &sender)?;1487 }14881489 match collection.mode1490 {1491 CollectionMode::NFT => Self::burn_nft_item(&collection, item_id)?,1492 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, &collection, value)?,1493 CollectionMode::ReFungible => Self::burn_refungible_item(&collection, item_id, &sender)?,1494 _ => ()1495 };14961497 Ok(())1498 }14991500 pub fn toggle_white_list_internal(1501 sender: &T::CrossAccountId,1502 collection: &CollectionHandle<T>,1503 address: &T::CrossAccountId,1504 whitelisted: bool,1505 ) -> DispatchResult {1506 Self::check_owner_or_admin_permissions(&collection, &sender)?;15071508 if whitelisted {1509 <WhiteList<T>>::insert(collection.id, address.as_sub(), true);1510 } else {1511 <WhiteList<T>>::remove(collection.id, address.as_sub());1512 }15131514 Ok(())1515 }15161517 fn is_correct_transfer(collection: &CollectionHandle<T>, recipient: &T::CrossAccountId) -> DispatchResult {1518 let collection_id = collection.id;15191520 // check token limit and account token limit1521 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient.as_sub()).len() as u32;1522 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1523 1524 Ok(())1525 }15261527 fn can_create_items_in_collection(collection: &CollectionHandle<T>, sender: &T::CrossAccountId, owner: &T::CrossAccountId, amount: u32) -> DispatchResult {1528 let collection_id = collection.id;15291530 // check token limit and account token limit1531 let total_items: u32 = ItemListIndex::get(collection_id)1532 .checked_add(amount)1533 .ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1534 let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner.as_sub()).len() as u32)1535 .checked_add(amount)1536 .ok_or(Error::<T>::AccountTokenLimitExceeded)?;1537 ensure!(collection.limits.token_limit >= total_items, Error::<T>::CollectionTokenLimitExceeded);1538 ensure!(collection.limits.account_token_ownership_limit >= account_items, Error::<T>::AccountTokenLimitExceeded);15391540 if !Self::is_owner_or_admin_permissions(collection, &sender) {1541 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1542 Self::check_white_list(collection, owner)?;1543 Self::check_white_list(collection, sender)?;1544 }15451546 Ok(())1547 }15481549 fn validate_create_item_args(target_collection: &CollectionHandle<T>, data: &CreateItemData) -> DispatchResult {1550 match target_collection.mode1551 {1552 CollectionMode::NFT => {1553 if let CreateItemData::NFT(data) = data {1554 // check sizes1555 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1556 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1557 } else {1558 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1559 }1560 },1561 CollectionMode::Fungible(_) => {1562 if let CreateItemData::Fungible(_) = data {1563 } else {1564 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1565 }1566 },1567 CollectionMode::ReFungible => {1568 if let CreateItemData::ReFungible(data) = data {15691570 // check sizes1571 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1572 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);15731574 // Check refungibility limits1575 ensure!(data.pieces <= MAX_REFUNGIBLE_PIECES, Error::<T>::WrongRefungiblePieces);1576 ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1577 } else {1578 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1579 }1580 },1581 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1582 };15831584 Ok(())1585 }15861587 fn create_item_no_validation(collection: &CollectionHandle<T>, owner: &T::CrossAccountId, data: CreateItemData) -> DispatchResult {1588 match data1589 {1590 CreateItemData::NFT(data) => {1591 let item = NftItemType {1592 owner: owner.clone(),1593 const_data: data.const_data,1594 variable_data: data.variable_data1595 };15961597 Self::add_nft_item(collection, item)?;1598 },1599 CreateItemData::Fungible(data) => {1600 Self::add_fungible_item(collection, &owner, data.value)?;1601 },1602 CreateItemData::ReFungible(data) => {1603 let mut owner_list = Vec::new();1604 owner_list.push(Ownership {owner: owner.clone(), fraction: data.pieces});16051606 let item = ReFungibleItemType {1607 owner: owner_list,1608 const_data: data.const_data,1609 variable_data: data.variable_data1610 };16111612 Self::add_refungible_item(collection, item)?;1613 }1614 };16151616 Ok(())1617 }16181619 fn add_fungible_item(collection: &CollectionHandle<T>, owner: &T::CrossAccountId, value: u128) -> DispatchResult {1620 let collection_id = collection.id;16211622 // Does new owner already have an account?1623 let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner.as_sub()).value;16241625 // Mint 1626 let item = FungibleItemType {1627 value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,1628 };1629 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), item);16301631 // Update balance1632 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1633 .checked_add(value)1634 .ok_or(Error::<T>::NumOverflow)?;1635 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);16361637 Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));1638 Ok(())1639 }16401641 fn add_refungible_item(collection: &CollectionHandle<T>, item: ReFungibleItemType<T::CrossAccountId>) -> DispatchResult {1642 let collection_id = collection.id;16431644 let current_index = <ItemListIndex>::get(collection_id)1645 .checked_add(1)1646 .ok_or(Error::<T>::NumOverflow)?;1647 let itemcopy = item.clone();16481649 ensure!(1650 item.owner.len() == 1,1651 Error::<T>::BadCreateRefungibleCall,1652 );1653 let item_owner = item.owner.first().expect("only one owner is defined");16541655 let value = item_owner.fraction;1656 let owner = item_owner.owner.clone();16571658 Self::add_token_index(collection_id, current_index, &owner)?;16591660 <ItemListIndex>::insert(collection_id, current_index);1661 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);16621663 // Update balance1664 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1665 .checked_add(value)1666 .ok_or(Error::<T>::NumOverflow)?;1667 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);16681669 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));1670 Ok(())1671 }16721673 fn add_nft_item(collection: &CollectionHandle<T>, item: NftItemType<T::CrossAccountId>) -> DispatchResult {1674 let collection_id = collection.id;16751676 let current_index = <ItemListIndex>::get(collection_id)1677 .checked_add(1)1678 .ok_or(Error::<T>::NumOverflow)?;16791680 let item_owner = item.owner.clone();1681 Self::add_token_index(collection_id, current_index, &item.owner)?;16821683 <ItemListIndex>::insert(collection_id, current_index);1684 <NftItemList<T>>::insert(collection_id, current_index, item);16851686 // Update balance1687 let new_balance = <Balance<T>>::get(collection_id, item_owner.as_sub())1688 .checked_add(1)1689 .ok_or(Error::<T>::NumOverflow)?;1690 <Balance<T>>::insert(collection_id, item_owner.as_sub(), new_balance);16911692 collection.log(ERC721Events::Transfer {1693 from: H160::default(),1694 to: *item_owner.as_eth(),1695 token_id: current_index.into(),1696 });1697 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, item_owner));1698 Ok(())1699 }17001701 fn burn_refungible_item(1702 collection: &CollectionHandle<T>,1703 item_id: TokenId,1704 owner: &T::CrossAccountId,1705 ) -> DispatchResult {1706 let collection_id = collection.id;17071708 let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)1709 .ok_or(Error::<T>::TokenNotFound)?;1710 let rft_balance = token1711 .owner1712 .iter()1713 .find(|&i| i.owner == *owner)1714 .ok_or(Error::<T>::TokenNotFound)?;1715 Self::remove_token_index(collection_id, item_id, owner)?;17161717 // update balance1718 let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.as_sub())1719 .checked_sub(rft_balance.fraction)1720 .ok_or(Error::<T>::NumOverflow)?;1721 <Balance<T>>::insert(collection_id, rft_balance.owner.as_sub(), new_balance);17221723 // Re-create owners list with sender removed1724 let index = token1725 .owner1726 .iter()1727 .position(|i| i.owner == *owner)1728 .expect("owned item is exists");1729 token.owner.remove(index);1730 let owner_count = token.owner.len();17311732 // Burn the token completely if this was the last (only) owner1733 if owner_count == 0 {1734 <ReFungibleItemList<T>>::remove(collection_id, item_id);1735 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);1736 }1737 else {1738 <ReFungibleItemList<T>>::insert(collection_id, item_id, token);1739 }17401741 Ok(())1742 }17431744 fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1745 let collection_id = collection.id;17461747 let item = <NftItemList<T>>::get(collection_id, item_id)1748 .ok_or(Error::<T>::TokenNotFound)?;1749 Self::remove_token_index(collection_id, item_id, &item.owner)?;17501751 // update balance1752 let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())1753 .checked_sub(1)1754 .ok_or(Error::<T>::NumOverflow)?;1755 <Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);1756 <NftItemList<T>>::remove(collection_id, item_id);1757 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);17581759 Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id));1760 Ok(())1761 }17621763 fn burn_fungible_item(owner: &T::CrossAccountId, collection: &CollectionHandle<T>, value: u128) -> DispatchResult {1764 let collection_id = collection.id;17651766 let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());1767 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);17681769 // update balance1770 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1771 .checked_sub(value)1772 .ok_or(Error::<T>::NumOverflow)?;1773 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);17741775 if balance.value - value > 0 {1776 balance.value -= value;1777 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);1778 }1779 else {1780 <FungibleItemList<T>>::remove(collection_id, owner.as_sub());1781 }17821783 collection.log(ERC20Events::Transfer {1784 from: *owner.as_eth(),1785 to: H160::default(),1786 value: value.into(),1787 });1788 Ok(())1789 }17901791 pub fn get_collection(collection_id: CollectionId) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {1792 Ok(<CollectionHandle<T>>::get(collection_id)1793 .ok_or(Error::<T>::CollectionNotFound)?)1794 }17951796 fn save_collection(collection: CollectionHandle<T>) {1797 <CollectionById<T>>::insert(collection.id, collection.into_inner());1798 }17991800 pub fn submit_logs(collection: CollectionHandle<T>) -> DispatchResult {1801 if collection.logs.is_empty() {1802 return Ok(())1803 }1804 T::EthereumTransactionSender::submit_logs_transaction(1805 eth::generate_transaction(collection.id, T::EthereumChainId::get()),1806 collection.logs.retrieve_logs(),1807 )1808 }18091810 fn check_owner_permissions(target_collection: &CollectionHandle<T>, subject: &T::AccountId) -> DispatchResult {1811 ensure!(1812 *subject == target_collection.owner,1813 Error::<T>::NoPermission1814 );18151816 Ok(())1817 }18181819 fn is_owner_or_admin_permissions(collection: &CollectionHandle<T>, subject: &T::CrossAccountId) -> bool {1820 *subject.as_sub() == collection.owner || <AdminList<T>>::get(collection.id).contains(&subject)1821 }18221823 fn check_owner_or_admin_permissions(1824 collection: &CollectionHandle<T>,1825 subject: &T::CrossAccountId,1826 ) -> DispatchResult {1827 ensure!(Self::is_owner_or_admin_permissions(collection, subject), Error::<T>::NoPermission);18281829 Ok(())1830 }18311832 fn owned_amount(1833 subject: &T::CrossAccountId,1834 target_collection: &CollectionHandle<T>,1835 item_id: TokenId,1836 ) -> Option<u128> {1837 let collection_id = target_collection.id;18381839 match target_collection.mode {1840 CollectionMode::NFT => (<NftItemList<T>>::get(collection_id, item_id)?.owner == *subject)1841 .then(|| 1),1842 CollectionMode::Fungible(_) => Some(<FungibleItemList<T>>::get(collection_id, &subject.as_sub())1843 .value),1844 CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?1845 .owner1846 .iter()1847 .find(|i| i.owner == *subject)1848 .map(|i| i.fraction),1849 CollectionMode::Invalid => None,1850 }1851 }18521853 fn is_item_owner(subject: &T::CrossAccountId, target_collection: &CollectionHandle<T>, item_id: TokenId) -> bool {1854 match target_collection.mode {1855 CollectionMode::Fungible(_) => true,1856 _ => Self::owned_amount(&subject, target_collection, item_id).is_some(),1857 }1858 }18591860 fn check_white_list(collection: &CollectionHandle<T>, address: &T::CrossAccountId) -> DispatchResult {1861 let collection_id = collection.id;18621863 let mes = Error::<T>::AddresNotInWhiteList;1864 ensure!(<WhiteList<T>>::contains_key(collection_id, address.as_sub()), mes);18651866 Ok(())1867 }18681869 /// Check if token exists. In case of Fungible, check if there is an entry for 1870 /// the owner in fungible balances double map1871 fn token_exists(1872 target_collection: &CollectionHandle<T>,1873 item_id: TokenId,1874 ) -> DispatchResult {1875 let collection_id = target_collection.id;1876 let exists = match target_collection.mode1877 {1878 CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),1879 CollectionMode::Fungible(_) => true,1880 CollectionMode::ReFungible => <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1881 _ => false1882 };18831884 ensure!(exists == true, Error::<T>::TokenNotFound);1885 Ok(())1886 }18871888 fn transfer_fungible(1889 collection: &CollectionHandle<T>,1890 value: u128,1891 owner: &T::CrossAccountId,1892 recipient: &T::CrossAccountId,1893 ) -> DispatchResult {1894 let collection_id = collection.id;18951896 let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());1897 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);18981899 // Send balance to recipient (updates balanceOf of recipient)1900 Self::add_fungible_item(collection, recipient, value)?;19011902 // update balanceOf of sender1903 <Balance<T>>::insert(collection_id, owner.as_sub(), balance.value - value);19041905 // Reduce or remove sender1906 if balance.value == value {1907 <FungibleItemList<T>>::remove(collection_id, owner.as_sub());1908 }1909 else {1910 balance.value -= value;1911 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);1912 }19131914 collection.log(ERC20Events::Transfer {1915 from: *owner.as_eth(),1916 to: *recipient.as_eth(),1917 value: value.into(),1918 });1919 Self::deposit_event(RawEvent::Transfer(collection.id, 1, owner.clone(), recipient.clone(), value));19201921 Ok(())1922 }19231924 fn transfer_refungible(1925 collection: &CollectionHandle<T>,1926 item_id: TokenId,1927 value: u128,1928 owner: T::CrossAccountId,1929 new_owner: T::CrossAccountId,1930 ) -> DispatchResult {1931 let collection_id = collection.id;1932 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)1933 .ok_or(Error::<T>::TokenNotFound)?;19341935 let item = full_item1936 .owner1937 .iter()1938 .filter(|i| i.owner == owner)1939 .next()1940 .ok_or(Error::<T>::TokenNotFound)?;1941 let amount = item.fraction;19421943 ensure!(amount >= value, Error::<T>::TokenValueTooLow);19441945 // update balance1946 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())1947 .checked_sub(value)1948 .ok_or(Error::<T>::NumOverflow)?;1949 <Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);19501951 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())1952 .checked_add(value)1953 .ok_or(Error::<T>::NumOverflow)?;1954 <Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);19551956 let old_owner = item.owner.clone();1957 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);19581959 // transfer1960 if amount == value && !new_owner_has_account {1961 // change owner1962 // new owner do not have account1963 let mut new_full_item = full_item.clone();1964 new_full_item1965 .owner1966 .iter_mut()1967 .find(|i| i.owner == owner)1968 .expect("old owner does present in refungible")1969 .owner = new_owner.clone();1970 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);19711972 // update index collection1973 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;1974 } else {1975 let mut new_full_item = full_item.clone();1976 new_full_item1977 .owner1978 .iter_mut()1979 .find(|i| i.owner == owner)1980 .expect("old owner does present in refungible")1981 .fraction -= value;19821983 // separate amount1984 if new_owner_has_account {1985 // new owner has account1986 new_full_item1987 .owner1988 .iter_mut()1989 .find(|i| i.owner == new_owner)1990 .expect("new owner has account")1991 .fraction += value;1992 } else {1993 // new owner do not have account1994 new_full_item.owner.push(Ownership {1995 owner: new_owner.clone(),1996 fraction: value,1997 });1998 Self::add_token_index(collection_id, item_id, &new_owner)?;1999 }20002001 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2002 }20032004 Self::deposit_event(RawEvent::Transfer(collection.id, item_id, owner, new_owner, amount));20052006 Ok(())2007 }20082009 fn transfer_nft(2010 collection: &CollectionHandle<T>,2011 item_id: TokenId,2012 sender: T::CrossAccountId,2013 new_owner: T::CrossAccountId,2014 ) -> DispatchResult {2015 let collection_id = collection.id;2016 let mut item = <NftItemList<T>>::get(collection_id, item_id)2017 .ok_or(Error::<T>::TokenNotFound)?;20182019 ensure!(2020 sender == item.owner,2021 Error::<T>::MustBeTokenOwner2022 );20232024 // update balance2025 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2026 .checked_sub(1)2027 .ok_or(Error::<T>::NumOverflow)?;2028 <Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);20292030 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2031 .checked_add(1)2032 .ok_or(Error::<T>::NumOverflow)?;2033 <Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);20342035 // change owner2036 let old_owner = item.owner.clone();2037 item.owner = new_owner.clone();2038 <NftItemList<T>>::insert(collection_id, item_id, item);20392040 // update index collection2041 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;20422043 collection.log(ERC721Events::Transfer {2044 from: *sender.as_eth(),2045 to: *new_owner.as_eth(),2046 token_id: item_id.into(),2047 });2048 Self::deposit_event(RawEvent::Transfer(collection.id, item_id, sender, new_owner, 1));20492050 Ok(())2051 }2052 2053 fn set_re_fungible_variable_data(2054 collection: &CollectionHandle<T>,2055 item_id: TokenId,2056 data: Vec<u8>2057 ) -> DispatchResult {2058 let collection_id = collection.id;2059 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2060 .ok_or(Error::<T>::TokenNotFound)?;20612062 item.variable_data = data;20632064 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);20652066 Ok(())2067 }20682069 fn set_nft_variable_data(2070 collection: &CollectionHandle<T>,2071 item_id: TokenId,2072 data: Vec<u8>2073 ) -> DispatchResult {2074 let collection_id = collection.id;2075 let mut item = <NftItemList<T>>::get(collection_id, item_id)2076 .ok_or(Error::<T>::TokenNotFound)?;2077 2078 item.variable_data = data;20792080 <NftItemList<T>>::insert(collection_id, item_id, item);2081 2082 Ok(())2083 }20842085 #[allow(dead_code)]2086 fn init_collection(item: &Collection<T>) {2087 // check params2088 assert!(2089 item.decimal_points <= MAX_DECIMAL_POINTS,2090 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2091 );2092 assert!(2093 item.name.len() <= 64,2094 "Collection name can not be longer than 63 char"2095 );2096 assert!(2097 item.name.len() <= 256,2098 "Collection description can not be longer than 255 char"2099 );2100 assert!(2101 item.token_prefix.len() <= 16,2102 "Token prefix can not be longer than 15 char"2103 );21042105 // Generate next collection ID2106 let next_id = CreatedCollectionCount::get()2107 .checked_add(1)2108 .unwrap();21092110 CreatedCollectionCount::put(next_id);2111 }21122113 #[allow(dead_code)]2114 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::CrossAccountId>) {2115 let current_index = <ItemListIndex>::get(collection_id)2116 .checked_add(1)2117 .unwrap();21182119 Self::add_token_index(collection_id, current_index, &item.owner).unwrap();21202121 <ItemListIndex>::insert(collection_id, current_index);21222123 // Update balance2124 let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2125 .checked_add(1)2126 .unwrap();2127 <Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2128 }21292130 #[allow(dead_code)]2131 fn init_fungible_token(collection_id: CollectionId, owner: &T::CrossAccountId, item: &FungibleItemType) {2132 let current_index = <ItemListIndex>::get(collection_id)2133 .checked_add(1)2134 .unwrap();21352136 Self::add_token_index(collection_id, current_index, owner).unwrap();21372138 <ItemListIndex>::insert(collection_id, current_index);21392140 // Update balance2141 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2142 .checked_add(item.value)2143 .unwrap();2144 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2145 }21462147 #[allow(dead_code)]2148 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::CrossAccountId>) {2149 let current_index = <ItemListIndex>::get(collection_id)2150 .checked_add(1)2151 .unwrap();21522153 let value = item.owner.first().unwrap().fraction;2154 let owner = item.owner.first().unwrap().owner.clone();21552156 Self::add_token_index(collection_id, current_index, &owner).unwrap();21572158 <ItemListIndex>::insert(collection_id, current_index);21592160 // Update balance2161 let new_balance = <Balance<T>>::get(collection_id, &owner.as_sub())2162 .checked_add(value)2163 .unwrap();2164 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2165 }21662167 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: &T::CrossAccountId) -> DispatchResult {2168 // add to account limit2169 if <AccountItemCount<T>>::contains_key(owner.as_sub()) {21702171 // bound Owned tokens by a single address2172 let count = <AccountItemCount<T>>::get(owner.as_sub());2173 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);21742175 <AccountItemCount<T>>::insert(owner.as_sub(), count2176 .checked_add(1)2177 .ok_or(Error::<T>::NumOverflow)?);2178 }2179 else {2180 <AccountItemCount<T>>::insert(owner.as_sub(), 1);2181 }21822183 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2184 if list_exists {2185 let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2186 let item_contains = list.contains(&item_index.clone());21872188 if !item_contains {2189 list.push(item_index.clone());2190 }21912192 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2193 } else {2194 let mut itm = Vec::new();2195 itm.push(item_index.clone());2196 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), itm);2197 }21982199 Ok(())2200 }22012202 fn remove_token_index(2203 collection_id: CollectionId,2204 item_index: TokenId,2205 owner: &T::CrossAccountId,2206 ) -> DispatchResult {22072208 // update counter2209 <AccountItemCount<T>>::insert(owner.as_sub(), 2210 <AccountItemCount<T>>::get(owner.as_sub())2211 .checked_sub(1)2212 .ok_or(Error::<T>::NumOverflow)?);221322142215 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2216 if list_exists {2217 let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2218 let item_contains = list.contains(&item_index.clone());22192220 if item_contains {2221 list.retain(|&item| item != item_index);2222 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2223 }2224 }22252226 Ok(())2227 }22282229 fn move_token_index(2230 collection_id: CollectionId,2231 item_index: TokenId,2232 old_owner: &T::CrossAccountId,2233 new_owner: &T::CrossAccountId,2234 ) -> DispatchResult {2235 Self::remove_token_index(collection_id, item_index, old_owner)?;2236 Self::add_token_index(collection_id, item_index, new_owner)?;22372238 Ok(())2239 }2240}22412242sp_api::decl_runtime_apis! {2243 pub trait NftApi {2244 /// Used for ethereum integration2245 fn eth_contract_code(account: H160) -> Option<Vec<u8>>;2246 }2247}pallets/nft/src/sponsorship.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/nft/src/sponsorship.rs
@@ -0,0 +1,203 @@
+use crate::{Config, Call, CollectionById, CreateItemBasket, VariableMetaDataBasket, ReFungibleTransferBasket, FungibleTransferBasket, NftTransferBasket, ChainLimit, CreateItemData, CollectionMode};
+use core::marker::PhantomData;
+use up_sponsorship::SponsorshipHandler;
+use frame_support::{
+ traits::IsSubType,
+ storage::{StorageMap, StorageDoubleMap, StorageValue},
+};
+use nft_data_structs::{TokenId, CollectionId};
+use alloc::vec::Vec;
+
+pub struct NftSponsorshipHandler<T>(PhantomData<T>);
+impl<T: Config> NftSponsorshipHandler<T> {
+ pub fn withdraw_create_item(
+ who: &T::AccountId,
+ collection_id: &CollectionId,
+ _properties: &CreateItemData,
+ ) -> Option<T::AccountId> {
+
+ let collection = CollectionById::<T>::get(collection_id)?;
+
+ // sponsor timeout
+ let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+
+ let limit = collection.limits.sponsor_transfer_timeout;
+ if CreateItemBasket::<T>::contains_key((collection_id, &who)) {
+ let last_tx_block = CreateItemBasket::<T>::get((collection_id, &who));
+ let limit_time = last_tx_block + limit.into();
+ if block_number <= limit_time {
+ return None;
+ }
+ }
+ CreateItemBasket::<T>::insert((collection_id, who.clone()), block_number);
+
+ // check free create limit
+ if collection.limits.sponsored_data_size >= (_properties.len() as u32) {
+ collection.sponsorship.sponsor()
+ .cloned()
+ } else {
+ None
+ }
+ }
+
+ pub fn withdraw_transfer(
+ who: &T::AccountId,
+ collection_id: &CollectionId,
+ item_id: &TokenId,
+ ) -> Option<T::AccountId> {
+
+ let collection = CollectionById::<T>::get(collection_id)?;
+ let limits = ChainLimit::get();
+
+ let mut sponsor_transfer = false;
+ if collection.sponsorship.confirmed() {
+
+ let collection_limits = collection.limits.clone();
+ let collection_mode = collection.mode.clone();
+
+ // sponsor timeout
+ let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+ sponsor_transfer = match collection_mode {
+ CollectionMode::NFT => {
+
+ // get correct limit
+ let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {
+ collection_limits.sponsor_transfer_timeout
+ } else {
+ limits.nft_sponsor_transfer_timeout
+ };
+
+ let mut sponsored = true;
+ if NftTransferBasket::<T>::contains_key(collection_id, item_id) {
+ let last_tx_block = NftTransferBasket::<T>::get(collection_id, item_id);
+ let limit_time = last_tx_block + limit.into();
+ if block_number <= limit_time {
+ sponsored = false;
+ }
+ }
+ if sponsored {
+ NftTransferBasket::<T>::insert(collection_id, item_id, block_number);
+ }
+
+ sponsored
+ }
+ CollectionMode::Fungible(_) => {
+
+ // get correct limit
+ let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {
+ collection_limits.sponsor_transfer_timeout
+ } else {
+ limits.fungible_sponsor_transfer_timeout
+ };
+
+ let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+ let mut sponsored = true;
+ if FungibleTransferBasket::<T>::contains_key(collection_id, who) {
+ let last_tx_block = FungibleTransferBasket::<T>::get(collection_id, who);
+ let limit_time = last_tx_block + limit.into();
+ if block_number <= limit_time {
+ sponsored = false;
+ }
+ }
+ if sponsored {
+ FungibleTransferBasket::<T>::insert(collection_id, who, block_number);
+ }
+
+ sponsored
+ }
+ CollectionMode::ReFungible => {
+
+ // get correct limit
+ let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {
+ collection_limits.sponsor_transfer_timeout
+ } else {
+ limits.refungible_sponsor_transfer_timeout
+ };
+
+ let mut sponsored = true;
+ if ReFungibleTransferBasket::<T>::contains_key(collection_id, item_id) {
+ let last_tx_block = ReFungibleTransferBasket::<T>::get(collection_id, item_id);
+ let limit_time = last_tx_block + limit.into();
+ if block_number <= limit_time {
+ sponsored = false;
+ }
+ }
+ if sponsored {
+ ReFungibleTransferBasket::<T>::insert(collection_id, item_id, block_number);
+ }
+
+ sponsored
+ }
+ _ => {
+ false
+ },
+ };
+ }
+
+ if !sponsor_transfer {
+ None
+ } else {
+ collection.sponsorship.sponsor()
+ .cloned()
+ }
+ }
+
+ pub fn withdraw_set_variable_meta_data(
+ collection_id: &CollectionId,
+ item_id: &TokenId,
+ data: &Vec<u8>,
+ ) -> Option<T::AccountId> {
+
+ let mut sponsor_metadata_changes = false;
+
+ let collection = CollectionById::<T>::get(collection_id)?;
+
+ if
+ collection.sponsorship.confirmed() &&
+ // Can't sponsor fungible collection, this tx will be rejected
+ // as invalid
+ !matches!(collection.mode, CollectionMode::Fungible(_)) &&
+ data.len() <= collection.limits.sponsored_data_size as usize
+ {
+ if let Some(rate_limit) = collection.limits.sponsored_data_rate_limit {
+ let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+
+ if VariableMetaDataBasket::<T>::get(collection_id, item_id)
+ .map(|last_block| block_number - last_block > rate_limit)
+ .unwrap_or(true)
+ {
+ sponsor_metadata_changes = true;
+ VariableMetaDataBasket::<T>::insert(collection_id, item_id, block_number);
+ }
+ }
+ }
+
+ if !sponsor_metadata_changes {
+ None
+ } else {
+ collection.sponsorship.sponsor().cloned()
+ }
+
+ }
+}
+
+impl<T, C> SponsorshipHandler<T::AccountId, C> for NftSponsorshipHandler<T>
+where
+ T: Config,
+ C: IsSubType<Call<T>>
+{
+ fn get_sponsor(who: &T::AccountId, call: &C) -> Option<T::AccountId> {
+ match IsSubType::<Call<T>>::is_sub_type(call)? {
+ Call::create_item(collection_id, _owner, _properties) => {
+ Self::withdraw_create_item(who, collection_id, &_properties)
+ },
+ Call::transfer(_new_owner, collection_id, item_id, _value) => {
+ Self::withdraw_transfer(who, collection_id, item_id)
+ },
+ Call::set_variable_meta_data(collection_id, item_id, data) => {
+ Self::withdraw_set_variable_meta_data(collection_id, item_id, &data)
+ },
+ _ => None,
+ }
+ }
+}
\ No newline at end of file