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