difftreelog
CORE-178
in: master
7 files changed
client/rpc/src/lib.rsdiffbeforeafterboth--- a/client/rpc/src/lib.rs
+++ b/client/rpc/src/lib.rs
@@ -119,6 +119,15 @@
) -> Result<Option<Collection<AccountId>>>;
#[rpc(name = "unique_collectionStats")]
fn collection_stats(&self, at: Option<BlockHash>) -> Result<CollectionStats>;
+
+ #[rpc(name = "unique_nextSponsored")]
+ fn next_sponsored(
+ &self,
+ collection: CollectionId,
+ account: CrossAccountId,
+ token: TokenId,
+ at: Option<BlockHash>,
+ ) -> Result<Option<u64>>;
}
pub struct Unique<C, P> {
@@ -222,4 +231,5 @@
pass_method!(last_token_id(collection: CollectionId) -> TokenId);
pass_method!(collection_by_id(collection: CollectionId) -> Option<Collection<AccountId>>);
pass_method!(collection_stats() -> CollectionStats);
+ pass_method!(next_sponsored(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Option<u64>);
}
pallets/unique/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![recursion_limit = "1024"]18#![cfg_attr(not(feature = "std"), no_std)]19#![allow(20 clippy::too_many_arguments,21 clippy::unnecessary_mut_passed,22 clippy::unused_unit23)]2425extern crate alloc;2627pub use serde::{Serialize, Deserialize};2829pub use frame_support::{30 construct_runtime, decl_module, decl_storage, decl_error, decl_event,31 dispatch::DispatchResult,32 ensure, fail, parameter_types,33 traits::{34 ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced, Randomness,35 IsSubType, WithdrawReasons,36 },37 weights::{38 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},39 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,40 WeightToFeePolynomial, DispatchClass,41 },42 StorageValue, transactional,43 pallet_prelude::{DispatchResultWithPostInfo, ConstU32},44 BoundedVec,45};46use scale_info::TypeInfo;47use frame_system::{self as system, ensure_signed};48use sp_runtime::{sp_std::prelude::Vec};49use up_data_structs::{50 MAX_DECIMAL_POINTS, VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT,51 OFFCHAIN_SCHEMA_LIMIT, MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH,52 MAX_TOKEN_PREFIX_LENGTH, AccessMode, CreateItemData, CollectionLimits, CollectionId,53 CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,54 CreateCollectionData, CustomDataLimit, CreateItemExData,55};56use pallet_common::{57 account::CrossAccountId, CollectionHandle, Pallet as PalletCommon, Error as CommonError,58 CommonWeightInfo,59};60use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle};61use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};62use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle};6364#[cfg(test)]65mod mock;6667#[cfg(test)]68mod tests;6970mod eth;71mod sponsorship;72pub use sponsorship::UniqueSponsorshipHandler;73pub use eth::sponsoring::UniqueEthSponsorshipHandler;7475pub use eth::UniqueErcSupport;7677pub mod common;78use common::CommonWeights;79pub mod dispatch;80use dispatch::dispatch_call;8182#[cfg(feature = "runtime-benchmarks")]83mod benchmarking;84pub mod weights;85use weights::WeightInfo;8687decl_error! {88 /// Error for non-fungible-token module.89 pub enum Error for Module<T: Config> {90 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.91 CollectionDecimalPointLimitExceeded,92 /// This address is not set as sponsor, use setCollectionSponsor first.93 ConfirmUnsetSponsorFail,94 /// Length of items properties must be greater than 0.95 EmptyArgument,96 }97}9899pub trait Config:100 system::Config101 + pallet_evm_coder_substrate::Config102 + pallet_common::Config103 + pallet_nonfungible::Config104 + pallet_refungible::Config105 + pallet_fungible::Config106 + Sized107 + TypeInfo108{109 type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;110111 /// Weight information for extrinsics in this pallet.112 type WeightInfo: WeightInfo;113}114115decl_event! {116 pub enum Event<T>117 where118 <T as frame_system::Config>::AccountId,119 <T as pallet_common::Config>::CrossAccountId,120 {121 /// Collection sponsor was removed122 ///123 /// # Arguments124 ///125 /// * collection_id: Globally unique collection identifier.126 CollectionSponsorRemoved(CollectionId),127128 /// Collection admin was added129 ///130 /// # Arguments131 ///132 /// * collection_id: Globally unique collection identifier.133 ///134 /// * admin: Admin address.135 CollectionAdminAdded(CollectionId, CrossAccountId),136137 /// Collection owned was change138 ///139 /// # Arguments140 ///141 /// * collection_id: Globally unique collection identifier.142 ///143 /// * owner: New owner address.144 CollectionOwnedChanged(CollectionId, AccountId),145146 /// Collection sponsor was set147 ///148 /// # Arguments149 ///150 /// * collection_id: Globally unique collection identifier.151 ///152 /// * owner: New sponsor address.153 CollectionSponsorSet(CollectionId, AccountId),154155 /// const on chain schema was set156 ///157 /// # Arguments158 ///159 /// * collection_id: Globally unique collection identifier.160 ConstOnChainSchemaSet(CollectionId),161162 /// New sponsor was confirm163 ///164 /// # Arguments165 ///166 /// * collection_id: Globally unique collection identifier.167 ///168 /// * sponsor: New sponsor address.169 SponsorshipConfirmed(CollectionId, AccountId),170171 /// Collection admin was removed172 ///173 /// # Arguments174 ///175 /// * collection_id: Globally unique collection identifier.176 ///177 /// * admin: Admin address.178 CollectionAdminRemoved(CollectionId, CrossAccountId),179180 /// Address was remove from allow list181 ///182 /// # Arguments183 ///184 /// * collection_id: Globally unique collection identifier.185 ///186 /// * user: Address.187 AllowListAddressRemoved(CollectionId, CrossAccountId),188189 /// Address was add to allow list190 ///191 /// # Arguments192 ///193 /// * collection_id: Globally unique collection identifier.194 ///195 /// * user: Address.196 AllowListAddressAdded(CollectionId, CrossAccountId),197198 /// Collection limits was set199 ///200 /// # Arguments201 ///202 /// * collection_id: Globally unique collection identifier.203 CollectionLimitSet(CollectionId),204205 /// Mint permission was set206 ///207 /// # Arguments208 ///209 /// * collection_id: Globally unique collection identifier.210 MintPermissionSet(CollectionId),211212 /// Offchain schema was set213 ///214 /// # Arguments215 ///216 /// * collection_id: Globally unique collection identifier.217 OffchainSchemaSet(CollectionId),218219 /// Public access mode was set220 ///221 /// # Arguments222 ///223 /// * collection_id: Globally unique collection identifier.224 ///225 /// * mode: New access state.226 PublicAccessModeSet(CollectionId, AccessMode),227228 /// Schema version was set229 ///230 /// # Arguments231 ///232 /// * collection_id: Globally unique collection identifier.233 SchemaVersionSet(CollectionId),234235 /// Variable on chain schema was set236 ///237 /// # Arguments238 ///239 /// * collection_id: Globally unique collection identifier.240 VariableOnChainSchemaSet(CollectionId),241 }242}243244type SelfWeightOf<T> = <T as Config>::WeightInfo;245246// # Used definitions247//248// ## User control levels249//250// chain-controlled - key is uncontrolled by user251// i.e autoincrementing index252// can use non-cryptographic hash253// real - key is controlled by user254// but it is hard to generate enough colliding values, i.e owner of signed txs255// can use non-cryptographic hash256// controlled - key is completly controlled by users257// i.e maps with mutable keys258// should use cryptographic hash259//260// ## User control level downgrade reasons261//262// ?1 - chain-controlled -> controlled263// collections/tokens can be destroyed, resulting in massive holes264// ?2 - chain-controlled -> controlled265// same as ?1, but can be only added, resulting in easier exploitation266// ?3 - real -> controlled267// no confirmation required, so addresses can be easily generated268decl_storage! {269 trait Store for Module<T: Config> as Unique {270271 //#region Private members272 /// Used for migrations273 ChainVersion: u64;274 //#endregion275276 //#region Tokens transfer rate limit baskets277 /// (Collection id (controlled?2), who created (real))278 /// TODO: Off chain worker should remove from this map when collection gets removed279 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;280 /// Collection id (controlled?2), token id (controlled?2)281 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;282 /// Collection id (controlled?2), owning user (real)283 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;284 /// Collection id (controlled?2), token id (controlled?2)285 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;286 //#endregion287288 /// Variable metadata sponsoring289 /// Collection id (controlled?2), token id (controlled?2)290 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;291 /// Approval sponsoring292 pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;293 pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;294 pub RefungibleApproveBasket get(fn refungible_approve_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;295 }296}297298decl_module! {299 pub struct Module<T: Config> for enum Call300 where301 origin: T::Origin302 {303 type Error = Error<T>;304305 fn deposit_event() = default;306307 fn on_initialize(_now: T::BlockNumber) -> Weight {308 0309 }310311 /// 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 of the collection is set to the address that signed the transaction and can be changed later.312 ///313 /// # Permissions314 ///315 /// * Anyone.316 ///317 /// # Arguments318 ///319 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.320 ///321 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.322 ///323 /// * token_prefix: UTF-8 string with token prefix.324 ///325 /// * mode: [CollectionMode] collection type and type dependent data.326 // returns collection ID327 #[weight = <SelfWeightOf<T>>::create_collection()]328 #[transactional]329 #[deprecated]330 pub fn create_collection(origin,331 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,332 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,333 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,334 mode: CollectionMode) -> DispatchResult {335 let data: CreateCollectionData<T::AccountId> = CreateCollectionData {336 name: collection_name,337 description: collection_description,338 token_prefix,339 mode,340 ..Default::default()341 };342 Self::create_collection_ex(origin, data)343 }344345 /// This method creates a collection346 ///347 /// Prefer it to deprecated [`created_collection`] method348 #[weight = <SelfWeightOf<T>>::create_collection()]349 #[transactional]350 pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {351 let owner = ensure_signed(origin)?;352353 let _id = match data.mode {354 CollectionMode::NFT => {<PalletNonfungible<T>>::init_collection(owner, data)?},355 CollectionMode::Fungible(decimal_points) => {356 // check params357 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);358 <PalletFungible<T>>::init_collection(owner, data)?359 }360 CollectionMode::ReFungible => {361 <PalletRefungible<T>>::init_collection(owner, data)?362 }363 };364365 Ok(())366 }367368 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.369 ///370 /// # Permissions371 ///372 /// * Collection Owner.373 ///374 /// # Arguments375 ///376 /// * collection_id: collection to destroy.377 #[weight = <SelfWeightOf<T>>::destroy_collection()]378 #[transactional]379 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {380 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);381382 let collection = <CollectionHandle<T>>::try_get(collection_id)?;383 collection.check_is_owner(&sender)?;384385 // =========386387 match collection.mode {388 CollectionMode::ReFungible => PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?,389 CollectionMode::Fungible(_) => PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)?,390 CollectionMode::NFT => PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)?,391 }392393 <NftTransferBasket<T>>::remove_prefix(collection_id, None);394 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);395 <ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);396397 <VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);398 <NftApproveBasket<T>>::remove_prefix(collection_id, None);399 <FungibleApproveBasket<T>>::remove_prefix(collection_id, None);400 <RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);401402 Ok(())403 }404405 /// Add an address to allow list.406 ///407 /// # Permissions408 ///409 /// * Collection Owner410 /// * Collection Admin411 ///412 /// # Arguments413 ///414 /// * collection_id.415 ///416 /// * address.417 #[weight = <SelfWeightOf<T>>::add_to_allow_list()]418 #[transactional]419 pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{420421 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);422 let collection = <CollectionHandle<T>>::try_get(collection_id)?;423424 <PalletCommon<T>>::toggle_allowlist(425 &collection,426 &sender,427 &address,428 true,429 )?;430431 Self::deposit_event(Event::<T>::AllowListAddressAdded(432 collection_id,433 address434 ));435436 Ok(())437 }438439 /// Remove an address from allow list.440 ///441 /// # Permissions442 ///443 /// * Collection Owner444 /// * Collection Admin445 ///446 /// # Arguments447 ///448 /// * collection_id.449 ///450 /// * address.451 #[weight = <SelfWeightOf<T>>::remove_from_allow_list()]452 #[transactional]453 pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{454455 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);456 let collection = <CollectionHandle<T>>::try_get(collection_id)?;457458 <PalletCommon<T>>::toggle_allowlist(459 &collection,460 &sender,461 &address,462 false,463 )?;464465 <Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(466 collection_id,467 address468 ));469470 Ok(())471 }472473 /// Toggle between normal and allow list access for the methods with access for `Anyone`.474 ///475 /// # Permissions476 ///477 /// * Collection Owner.478 ///479 /// # Arguments480 ///481 /// * collection_id.482 ///483 /// * mode: [AccessMode]484 #[weight = <SelfWeightOf<T>>::set_public_access_mode()]485 #[transactional]486 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult487 {488 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);489490 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;491 target_collection.check_is_owner(&sender)?;492493 target_collection.access = mode.clone();494495 <Pallet<T>>::deposit_event(Event::<T>::PublicAccessModeSet(496 collection_id,497 mode498 ));499500 target_collection.save()501 }502503 /// Allows Anyone to create tokens if:504 /// * Allow List is enabled, and505 /// * Address is added to allow list, and506 /// * This method was called with True parameter507 ///508 /// # Permissions509 /// * Collection Owner510 ///511 /// # Arguments512 ///513 /// * collection_id.514 ///515 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.516 #[weight = <SelfWeightOf<T>>::set_mint_permission()]517 #[transactional]518 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult519 {520 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);521522 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;523 target_collection.check_is_owner(&sender)?;524525 target_collection.mint_mode = mint_permission;526527 <Pallet<T>>::deposit_event(Event::<T>::MintPermissionSet(528 collection_id529 ));530531 target_collection.save()532 }533534 /// Change the owner of the collection.535 ///536 /// # Permissions537 ///538 /// * Collection Owner.539 ///540 /// # Arguments541 ///542 /// * collection_id.543 ///544 /// * new_owner.545 #[weight = <SelfWeightOf<T>>::change_collection_owner()]546 #[transactional]547 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {548549 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);550551 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;552 target_collection.check_is_owner(&sender)?;553554 target_collection.owner = new_owner.clone();555 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(556 collection_id,557 new_owner558 ));559560 target_collection.save()561 }562563 /// Adds an admin of the Collection.564 /// 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.565 ///566 /// # Permissions567 ///568 /// * Collection Owner.569 /// * Collection Admin.570 ///571 /// # Arguments572 ///573 /// * collection_id: ID of the Collection to add admin for.574 ///575 /// * new_admin_id: Address of new admin to add.576 #[weight = <SelfWeightOf<T>>::add_collection_admin()]577 #[transactional]578 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {579 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);580 let collection = <CollectionHandle<T>>::try_get(collection_id)?;581582 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(583 collection_id,584 new_admin_id.clone()585 ));586587 <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)588 }589590 /// 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.591 ///592 /// # Permissions593 ///594 /// * Collection Owner.595 /// * Collection Admin.596 ///597 /// # Arguments598 ///599 /// * collection_id: ID of the Collection to remove admin for.600 ///601 /// * account_id: Address of admin to remove.602 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]603 #[transactional]604 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {605 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);606 let collection = <CollectionHandle<T>>::try_get(collection_id)?;607608 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(609 collection_id,610 account_id.clone()611 ));612613 <PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)614 }615616 /// # Permissions617 ///618 /// * Collection Owner619 ///620 /// # Arguments621 ///622 /// * collection_id.623 ///624 /// * new_sponsor.625 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]626 #[transactional]627 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {628 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);629630 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;631 target_collection.check_is_owner(&sender)?;632633 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor.clone());634635 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(636 collection_id,637 new_sponsor638 ));639640 target_collection.save()641 }642643 /// # Permissions644 ///645 /// * Sponsor.646 ///647 /// # Arguments648 ///649 /// * collection_id.650 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]651 #[transactional]652 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {653 let sender = ensure_signed(origin)?;654655 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;656 ensure!(657 target_collection.sponsorship.pending_sponsor() == Some(&sender),658 Error::<T>::ConfirmUnsetSponsorFail659 );660661 target_collection.sponsorship = SponsorshipState::Confirmed(sender.clone());662663 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(664 collection_id,665 sender666 ));667668 target_collection.save()669 }670671 /// Switch back to pay-per-own-transaction model.672 ///673 /// # Permissions674 ///675 /// * Collection owner.676 ///677 /// # Arguments678 ///679 /// * collection_id.680 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]681 #[transactional]682 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {683 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);684685 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;686 target_collection.check_is_owner(&sender)?;687688 target_collection.sponsorship = SponsorshipState::Disabled;689690 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(691 collection_id692 ));693 target_collection.save()694 }695696 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.697 ///698 /// # Permissions699 ///700 /// * Collection Owner.701 /// * Collection Admin.702 /// * Anyone if703 /// * Allow List is enabled, and704 /// * Address is added to allow list, and705 /// * MintPermission is enabled (see SetMintPermission method)706 ///707 /// # Arguments708 ///709 /// * collection_id: ID of the collection.710 ///711 /// * owner: Address, initial owner of the NFT.712 ///713 /// * data: Token data to store on chain.714 #[weight = <CommonWeights<T>>::create_item()]715 #[transactional]716 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {717 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);718719 dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data))720 }721722 /// This method creates multiple items in a collection created with CreateCollection method.723 ///724 /// # Permissions725 ///726 /// * Collection Owner.727 /// * Collection Admin.728 /// * Anyone if729 /// * Allow List is enabled, and730 /// * Address is added to allow list, and731 /// * MintPermission is enabled (see SetMintPermission method)732 ///733 /// # Arguments734 ///735 /// * collection_id: ID of the collection.736 ///737 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].738 ///739 /// * owner: Address, initial owner of the NFT.740 #[weight = <CommonWeights<T>>::create_multiple_items(items_data.len() as u32)]741 #[transactional]742 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {743 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);744 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);745746 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data))747 }748749 #[weight = <CommonWeights<T>>::create_multiple_items_ex(&data)]750 #[transactional]751 pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {752 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);753754 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data))755 }756757 // TODO! transaction weight758759 /// Set transfers_enabled value for particular collection760 ///761 /// # Permissions762 ///763 /// * Collection Owner.764 ///765 /// # Arguments766 ///767 /// * collection_id: ID of the collection.768 ///769 /// * value: New flag value.770 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]771 #[transactional]772 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {773 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);774 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;775 target_collection.check_is_owner(&sender)?;776777 // =========778779 target_collection.limits.transfers_enabled = Some(value);780 target_collection.save()781 }782783 /// Destroys a concrete instance of NFT.784 ///785 /// # Permissions786 ///787 /// * Collection Owner.788 /// * Collection Admin.789 /// * Current NFT Owner.790 ///791 /// # Arguments792 ///793 /// * collection_id: ID of the collection.794 ///795 /// * item_id: ID of NFT to burn.796 #[weight = <CommonWeights<T>>::burn_item()]797 #[transactional]798 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {799 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);800801 let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;802 if value == 1 {803 <NftTransferBasket<T>>::remove(collection_id, item_id);804 <NftApproveBasket<T>>::remove(collection_id, item_id);805 }806 // Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?807 // <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());808 // <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));809 Ok(post_info)810 }811812 /// Destroys a concrete instance of NFT on behalf of the owner813 /// See also: [`approve`]814 ///815 /// # Permissions816 ///817 /// * Collection Owner.818 /// * Collection Admin.819 /// * Current NFT Owner.820 ///821 /// # Arguments822 ///823 /// * collection_id: ID of the collection.824 ///825 /// * item_id: ID of NFT to burn.826 ///827 /// * from: owner of item828 #[weight = <CommonWeights<T>>::burn_from()]829 #[transactional]830 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {831 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);832833 dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value))834 }835836 /// Change ownership of the token.837 ///838 /// # Permissions839 ///840 /// * Collection Owner841 /// * Collection Admin842 /// * Current NFT owner843 ///844 /// # Arguments845 ///846 /// * recipient: Address of token recipient.847 ///848 /// * collection_id.849 ///850 /// * item_id: ID of the item851 /// * Non-Fungible Mode: Required.852 /// * Fungible Mode: Ignored.853 /// * Re-Fungible Mode: Required.854 ///855 /// * value: Amount to transfer.856 /// * Non-Fungible Mode: Ignored857 /// * Fungible Mode: Must specify transferred amount858 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)859 #[weight = <CommonWeights<T>>::transfer()]860 #[transactional]861 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {862 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);863864 dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value))865 }866867 /// Set, change, or remove approved address to transfer the ownership of the NFT.868 ///869 /// # Permissions870 ///871 /// * Collection Owner872 /// * Collection Admin873 /// * Current NFT owner874 ///875 /// # Arguments876 ///877 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).878 ///879 /// * collection_id.880 ///881 /// * item_id: ID of the item.882 #[weight = <CommonWeights<T>>::approve()]883 #[transactional]884 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {885 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);886887 dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))888 }889890 /// 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.891 ///892 /// # Permissions893 /// * Collection Owner894 /// * Collection Admin895 /// * Current NFT owner896 /// * Address approved by current NFT owner897 ///898 /// # Arguments899 ///900 /// * from: Address that owns token.901 ///902 /// * recipient: Address of token recipient.903 ///904 /// * collection_id.905 ///906 /// * item_id: ID of the item.907 ///908 /// * value: Amount to transfer.909 #[weight = <CommonWeights<T>>::transfer_from()]910 #[transactional]911 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {912 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);913914 dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value))915 }916917 /// Set off-chain data schema.918 ///919 /// # Permissions920 ///921 /// * Collection Owner922 /// * Collection Admin923 ///924 /// # Arguments925 ///926 /// * collection_id.927 ///928 /// * schema: String representing the offchain data schema.929 #[weight = <CommonWeights<T>>::set_variable_metadata(data.len() as u32)]930 #[transactional]931 pub fn set_variable_meta_data (932 origin,933 collection_id: CollectionId,934 item_id: TokenId,935 data: BoundedVec<u8, CustomDataLimit>,936 ) -> DispatchResultWithPostInfo {937 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);938939 dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))940 }941942 /// Set meta_update_permission value for particular collection943 ///944 /// # Permissions945 ///946 /// * Collection Owner.947 ///948 /// # Arguments949 ///950 /// * collection_id: ID of the collection.951 ///952 /// * value: New flag value.953 #[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]954 #[transactional]955 pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {956 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);957 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;958959 ensure!(960 target_collection.meta_update_permission != MetaUpdatePermission::None,961 <CommonError<T>>::MetadataFlagFrozen,962 );963 target_collection.check_is_owner(&sender)?;964965 target_collection.meta_update_permission = value;966967 target_collection.save()968 }969970 /// Set schema standard971 /// ImageURL972 /// Unique973 ///974 /// # Permissions975 ///976 /// * Collection Owner977 /// * Collection Admin978 ///979 /// # Arguments980 ///981 /// * collection_id.982 ///983 /// * schema: SchemaVersion: enum984 #[weight = <SelfWeightOf<T>>::set_schema_version()]985 #[transactional]986 pub fn set_schema_version(987 origin,988 collection_id: CollectionId,989 version: SchemaVersion990 ) -> DispatchResult {991 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);992 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;993 target_collection.check_is_owner_or_admin(&sender)?;994 target_collection.schema_version = version;995996 <Pallet<T>>::deposit_event(Event::<T>::SchemaVersionSet(997 collection_id998 ));9991000 target_collection.save()1001 }10021003 /// Set off-chain data schema.1004 ///1005 /// # Permissions1006 ///1007 /// * Collection Owner1008 /// * Collection Admin1009 ///1010 /// # Arguments1011 ///1012 /// * collection_id.1013 ///1014 /// * schema: String representing the offchain data schema.1015 #[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]1016 #[transactional]1017 pub fn set_offchain_schema(1018 origin,1019 collection_id: CollectionId,1020 schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,1021 ) -> DispatchResult {1022 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1023 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1024 target_collection.check_is_owner_or_admin(&sender)?;10251026 target_collection.offchain_schema = schema;10271028 <Pallet<T>>::deposit_event(Event::<T>::OffchainSchemaSet(1029 collection_id1030 ));10311032 target_collection.save()1033 }10341035 /// Set const on-chain data schema.1036 ///1037 /// # Permissions1038 ///1039 /// * Collection Owner1040 /// * Collection Admin1041 ///1042 /// # Arguments1043 ///1044 /// * collection_id.1045 ///1046 /// * schema: String representing the const on-chain data schema.1047 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1048 #[transactional]1049 pub fn set_const_on_chain_schema (1050 origin,1051 collection_id: CollectionId,1052 schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>1053 ) -> DispatchResult {1054 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1055 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1056 target_collection.check_is_owner_or_admin(&sender)?;10571058 target_collection.const_on_chain_schema = schema;10591060 <Pallet<T>>::deposit_event(Event::<T>::ConstOnChainSchemaSet(1061 collection_id1062 ));10631064 target_collection.save()1065 }10661067 /// Set variable on-chain data schema.1068 ///1069 /// # Permissions1070 ///1071 /// * Collection Owner1072 /// * Collection Admin1073 ///1074 /// # Arguments1075 ///1076 /// * collection_id.1077 ///1078 /// * schema: String representing the variable on-chain data schema.1079 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1080 #[transactional]1081 pub fn set_variable_on_chain_schema (1082 origin,1083 collection_id: CollectionId,1084 schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>1085 ) -> DispatchResult {1086 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1087 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1088 target_collection.check_is_owner_or_admin(&sender)?;10891090 target_collection.variable_on_chain_schema = schema;10911092 <Pallet<T>>::deposit_event(Event::<T>::VariableOnChainSchemaSet(1093 collection_id1094 ));10951096 target_collection.save()1097 }10981099 #[weight = <SelfWeightOf<T>>::set_collection_limits()]1100 #[transactional]1101 pub fn set_collection_limits(1102 origin,1103 collection_id: CollectionId,1104 new_limit: CollectionLimits,1105 ) -> DispatchResult {1106 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1107 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1108 target_collection.check_is_owner(&sender)?;1109 let old_limit = &target_collection.limits;11101111 target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;11121113 <Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1114 collection_id1115 ));11161117 target_collection.save()1118 }1119 }1120}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![recursion_limit = "1024"]18#![cfg_attr(not(feature = "std"), no_std)]19#![allow(20 clippy::too_many_arguments,21 clippy::unnecessary_mut_passed,22 clippy::unused_unit23)]2425extern crate alloc;2627pub use serde::{Serialize, Deserialize};2829pub use frame_support::{30 construct_runtime, decl_module, decl_storage, decl_error, decl_event,31 dispatch::DispatchResult,32 ensure, fail, parameter_types,33 traits::{34 ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced, Randomness,35 IsSubType, WithdrawReasons,36 },37 weights::{38 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},39 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,40 WeightToFeePolynomial, DispatchClass,41 },42 StorageValue, transactional,43 pallet_prelude::{DispatchResultWithPostInfo, ConstU32},44 BoundedVec,45};46use scale_info::TypeInfo;47use frame_system::{self as system, ensure_signed};48use sp_runtime::{sp_std::prelude::Vec};49use up_data_structs::{50 MAX_DECIMAL_POINTS, VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT,51 OFFCHAIN_SCHEMA_LIMIT, MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH,52 MAX_TOKEN_PREFIX_LENGTH, AccessMode, CreateItemData, CollectionLimits, CollectionId,53 CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,54 CreateCollectionData, CustomDataLimit, CreateItemExData,55};56use pallet_common::{57 account::CrossAccountId, CollectionHandle, Pallet as PalletCommon, Error as CommonError,58 CommonWeightInfo,59};60use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle};61use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};62use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle};6364#[cfg(test)]65mod mock;6667#[cfg(test)]68mod tests;6970mod eth;71mod sponsorship;72pub use sponsorship::{UniqueSponsorshipHandler, UniqueSponsorshipPredict};73pub use eth::sponsoring::UniqueEthSponsorshipHandler;7475pub use eth::UniqueErcSupport;7677pub mod common;78use common::CommonWeights;79pub mod dispatch;80use dispatch::dispatch_call;8182#[cfg(feature = "runtime-benchmarks")]83mod benchmarking;84pub mod weights;85use weights::WeightInfo;8687pub trait SponsorshipPredict<T: Config> {88 fn predict(collection: CollectionId, account: T::CrossAccountId, token: TokenId) -> Option<u64>89 where90 u64: From<<T as frame_system::Config>::BlockNumber>;91}9293decl_error! {94 /// Error for non-fungible-token module.95 pub enum Error for Module<T: Config> {96 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.97 CollectionDecimalPointLimitExceeded,98 /// This address is not set as sponsor, use setCollectionSponsor first.99 ConfirmUnsetSponsorFail,100 /// Length of items properties must be greater than 0.101 EmptyArgument,102 }103}104105pub trait Config:106 system::Config107 + pallet_evm_coder_substrate::Config108 + pallet_common::Config109 + pallet_nonfungible::Config110 + pallet_refungible::Config111 + pallet_fungible::Config112 + Sized113 + TypeInfo114{115 type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;116117 /// Weight information for extrinsics in this pallet.118 type WeightInfo: WeightInfo;119}120121decl_event! {122 pub enum Event<T>123 where124 <T as frame_system::Config>::AccountId,125 <T as pallet_common::Config>::CrossAccountId,126 {127 /// Collection sponsor was removed128 ///129 /// # Arguments130 ///131 /// * collection_id: Globally unique collection identifier.132 CollectionSponsorRemoved(CollectionId),133134 /// Collection admin was added135 ///136 /// # Arguments137 ///138 /// * collection_id: Globally unique collection identifier.139 ///140 /// * admin: Admin address.141 CollectionAdminAdded(CollectionId, CrossAccountId),142143 /// Collection owned was change144 ///145 /// # Arguments146 ///147 /// * collection_id: Globally unique collection identifier.148 ///149 /// * owner: New owner address.150 CollectionOwnedChanged(CollectionId, AccountId),151152 /// Collection sponsor was set153 ///154 /// # Arguments155 ///156 /// * collection_id: Globally unique collection identifier.157 ///158 /// * owner: New sponsor address.159 CollectionSponsorSet(CollectionId, AccountId),160161 /// const on chain schema was set162 ///163 /// # Arguments164 ///165 /// * collection_id: Globally unique collection identifier.166 ConstOnChainSchemaSet(CollectionId),167168 /// New sponsor was confirm169 ///170 /// # Arguments171 ///172 /// * collection_id: Globally unique collection identifier.173 ///174 /// * sponsor: New sponsor address.175 SponsorshipConfirmed(CollectionId, AccountId),176177 /// Collection admin was removed178 ///179 /// # Arguments180 ///181 /// * collection_id: Globally unique collection identifier.182 ///183 /// * admin: Admin address.184 CollectionAdminRemoved(CollectionId, CrossAccountId),185186 /// Address was remove from allow list187 ///188 /// # Arguments189 ///190 /// * collection_id: Globally unique collection identifier.191 ///192 /// * user: Address.193 AllowListAddressRemoved(CollectionId, CrossAccountId),194195 /// Address was add to allow list196 ///197 /// # Arguments198 ///199 /// * collection_id: Globally unique collection identifier.200 ///201 /// * user: Address.202 AllowListAddressAdded(CollectionId, CrossAccountId),203204 /// Collection limits was set205 ///206 /// # Arguments207 ///208 /// * collection_id: Globally unique collection identifier.209 CollectionLimitSet(CollectionId),210211 /// Mint permission was set212 ///213 /// # Arguments214 ///215 /// * collection_id: Globally unique collection identifier.216 MintPermissionSet(CollectionId),217218 /// Offchain schema was set219 ///220 /// # Arguments221 ///222 /// * collection_id: Globally unique collection identifier.223 OffchainSchemaSet(CollectionId),224225 /// Public access mode was set226 ///227 /// # Arguments228 ///229 /// * collection_id: Globally unique collection identifier.230 ///231 /// * mode: New access state.232 PublicAccessModeSet(CollectionId, AccessMode),233234 /// Schema version was set235 ///236 /// # Arguments237 ///238 /// * collection_id: Globally unique collection identifier.239 SchemaVersionSet(CollectionId),240241 /// Variable on chain schema was set242 ///243 /// # Arguments244 ///245 /// * collection_id: Globally unique collection identifier.246 VariableOnChainSchemaSet(CollectionId),247 }248}249250type SelfWeightOf<T> = <T as Config>::WeightInfo;251252// # Used definitions253//254// ## User control levels255//256// chain-controlled - key is uncontrolled by user257// i.e autoincrementing index258// can use non-cryptographic hash259// real - key is controlled by user260// but it is hard to generate enough colliding values, i.e owner of signed txs261// can use non-cryptographic hash262// controlled - key is completly controlled by users263// i.e maps with mutable keys264// should use cryptographic hash265//266// ## User control level downgrade reasons267//268// ?1 - chain-controlled -> controlled269// collections/tokens can be destroyed, resulting in massive holes270// ?2 - chain-controlled -> controlled271// same as ?1, but can be only added, resulting in easier exploitation272// ?3 - real -> controlled273// no confirmation required, so addresses can be easily generated274decl_storage! {275 trait Store for Module<T: Config> as Unique {276277 //#region Private members278 /// Used for migrations279 ChainVersion: u64;280 //#endregion281282 //#region Tokens transfer rate limit baskets283 /// (Collection id (controlled?2), who created (real))284 /// TODO: Off chain worker should remove from this map when collection gets removed285 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;286 /// Collection id (controlled?2), token id (controlled?2)287 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;288 /// Collection id (controlled?2), owning user (real)289 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;290 /// Collection id (controlled?2), token id (controlled?2)291 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;292 //#endregion293294 /// Variable metadata sponsoring295 /// Collection id (controlled?2), token id (controlled?2)296 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;297 /// Approval sponsoring298 pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;299 pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;300 pub RefungibleApproveBasket get(fn refungible_approve_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;301 }302}303304decl_module! {305 pub struct Module<T: Config> for enum Call306 where307 origin: T::Origin308 {309 type Error = Error<T>;310311 fn deposit_event() = default;312313 fn on_initialize(_now: T::BlockNumber) -> Weight {314 0315 }316317 /// 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 of the collection is set to the address that signed the transaction and can be changed later.318 ///319 /// # Permissions320 ///321 /// * Anyone.322 ///323 /// # Arguments324 ///325 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.326 ///327 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.328 ///329 /// * token_prefix: UTF-8 string with token prefix.330 ///331 /// * mode: [CollectionMode] collection type and type dependent data.332 // returns collection ID333 #[weight = <SelfWeightOf<T>>::create_collection()]334 #[transactional]335 #[deprecated]336 pub fn create_collection(origin,337 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,338 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,339 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,340 mode: CollectionMode) -> DispatchResult {341 let data: CreateCollectionData<T::AccountId> = CreateCollectionData {342 name: collection_name,343 description: collection_description,344 token_prefix,345 mode,346 ..Default::default()347 };348 Self::create_collection_ex(origin, data)349 }350351 /// This method creates a collection352 ///353 /// Prefer it to deprecated [`created_collection`] method354 #[weight = <SelfWeightOf<T>>::create_collection()]355 #[transactional]356 pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {357 let owner = ensure_signed(origin)?;358359 let _id = match data.mode {360 CollectionMode::NFT => {<PalletNonfungible<T>>::init_collection(owner, data)?},361 CollectionMode::Fungible(decimal_points) => {362 // check params363 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);364 <PalletFungible<T>>::init_collection(owner, data)?365 }366 CollectionMode::ReFungible => {367 <PalletRefungible<T>>::init_collection(owner, data)?368 }369 };370371 Ok(())372 }373374 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.375 ///376 /// # Permissions377 ///378 /// * Collection Owner.379 ///380 /// # Arguments381 ///382 /// * collection_id: collection to destroy.383 #[weight = <SelfWeightOf<T>>::destroy_collection()]384 #[transactional]385 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {386 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);387388 let collection = <CollectionHandle<T>>::try_get(collection_id)?;389 collection.check_is_owner(&sender)?;390391 // =========392393 match collection.mode {394 CollectionMode::ReFungible => PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?,395 CollectionMode::Fungible(_) => PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)?,396 CollectionMode::NFT => PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)?,397 }398399 <NftTransferBasket<T>>::remove_prefix(collection_id, None);400 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);401 <ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);402403 <VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);404 <NftApproveBasket<T>>::remove_prefix(collection_id, None);405 <FungibleApproveBasket<T>>::remove_prefix(collection_id, None);406 <RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);407408 Ok(())409 }410411 /// Add an address to allow list.412 ///413 /// # Permissions414 ///415 /// * Collection Owner416 /// * Collection Admin417 ///418 /// # Arguments419 ///420 /// * collection_id.421 ///422 /// * address.423 #[weight = <SelfWeightOf<T>>::add_to_allow_list()]424 #[transactional]425 pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{426427 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);428 let collection = <CollectionHandle<T>>::try_get(collection_id)?;429430 <PalletCommon<T>>::toggle_allowlist(431 &collection,432 &sender,433 &address,434 true,435 )?;436437 Self::deposit_event(Event::<T>::AllowListAddressAdded(438 collection_id,439 address440 ));441442 Ok(())443 }444445 /// Remove an address from allow list.446 ///447 /// # Permissions448 ///449 /// * Collection Owner450 /// * Collection Admin451 ///452 /// # Arguments453 ///454 /// * collection_id.455 ///456 /// * address.457 #[weight = <SelfWeightOf<T>>::remove_from_allow_list()]458 #[transactional]459 pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{460461 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);462 let collection = <CollectionHandle<T>>::try_get(collection_id)?;463464 <PalletCommon<T>>::toggle_allowlist(465 &collection,466 &sender,467 &address,468 false,469 )?;470471 <Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(472 collection_id,473 address474 ));475476 Ok(())477 }478479 /// Toggle between normal and allow list access for the methods with access for `Anyone`.480 ///481 /// # Permissions482 ///483 /// * Collection Owner.484 ///485 /// # Arguments486 ///487 /// * collection_id.488 ///489 /// * mode: [AccessMode]490 #[weight = <SelfWeightOf<T>>::set_public_access_mode()]491 #[transactional]492 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult493 {494 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);495496 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;497 target_collection.check_is_owner(&sender)?;498499 target_collection.access = mode.clone();500501 <Pallet<T>>::deposit_event(Event::<T>::PublicAccessModeSet(502 collection_id,503 mode504 ));505506 target_collection.save()507 }508509 /// Allows Anyone to create tokens if:510 /// * Allow List is enabled, and511 /// * Address is added to allow list, and512 /// * This method was called with True parameter513 ///514 /// # Permissions515 /// * Collection Owner516 ///517 /// # Arguments518 ///519 /// * collection_id.520 ///521 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.522 #[weight = <SelfWeightOf<T>>::set_mint_permission()]523 #[transactional]524 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult525 {526 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);527528 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;529 target_collection.check_is_owner(&sender)?;530531 target_collection.mint_mode = mint_permission;532533 <Pallet<T>>::deposit_event(Event::<T>::MintPermissionSet(534 collection_id535 ));536537 target_collection.save()538 }539540 /// Change the owner of the collection.541 ///542 /// # Permissions543 ///544 /// * Collection Owner.545 ///546 /// # Arguments547 ///548 /// * collection_id.549 ///550 /// * new_owner.551 #[weight = <SelfWeightOf<T>>::change_collection_owner()]552 #[transactional]553 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {554555 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);556557 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;558 target_collection.check_is_owner(&sender)?;559560 target_collection.owner = new_owner.clone();561 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(562 collection_id,563 new_owner564 ));565566 target_collection.save()567 }568569 /// Adds an admin of the Collection.570 /// 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.571 ///572 /// # Permissions573 ///574 /// * Collection Owner.575 /// * Collection Admin.576 ///577 /// # Arguments578 ///579 /// * collection_id: ID of the Collection to add admin for.580 ///581 /// * new_admin_id: Address of new admin to add.582 #[weight = <SelfWeightOf<T>>::add_collection_admin()]583 #[transactional]584 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {585 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);586 let collection = <CollectionHandle<T>>::try_get(collection_id)?;587588 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(589 collection_id,590 new_admin_id.clone()591 ));592593 <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)594 }595596 /// 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.597 ///598 /// # Permissions599 ///600 /// * Collection Owner.601 /// * Collection Admin.602 ///603 /// # Arguments604 ///605 /// * collection_id: ID of the Collection to remove admin for.606 ///607 /// * account_id: Address of admin to remove.608 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]609 #[transactional]610 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {611 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);612 let collection = <CollectionHandle<T>>::try_get(collection_id)?;613614 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(615 collection_id,616 account_id.clone()617 ));618619 <PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)620 }621622 /// # Permissions623 ///624 /// * Collection Owner625 ///626 /// # Arguments627 ///628 /// * collection_id.629 ///630 /// * new_sponsor.631 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]632 #[transactional]633 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {634 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);635636 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;637 target_collection.check_is_owner(&sender)?;638639 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor.clone());640641 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(642 collection_id,643 new_sponsor644 ));645646 target_collection.save()647 }648649 /// # Permissions650 ///651 /// * Sponsor.652 ///653 /// # Arguments654 ///655 /// * collection_id.656 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]657 #[transactional]658 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {659 let sender = ensure_signed(origin)?;660661 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;662 ensure!(663 target_collection.sponsorship.pending_sponsor() == Some(&sender),664 Error::<T>::ConfirmUnsetSponsorFail665 );666667 target_collection.sponsorship = SponsorshipState::Confirmed(sender.clone());668669 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(670 collection_id,671 sender672 ));673674 target_collection.save()675 }676677 /// Switch back to pay-per-own-transaction model.678 ///679 /// # Permissions680 ///681 /// * Collection owner.682 ///683 /// # Arguments684 ///685 /// * collection_id.686 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]687 #[transactional]688 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {689 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);690691 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;692 target_collection.check_is_owner(&sender)?;693694 target_collection.sponsorship = SponsorshipState::Disabled;695696 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(697 collection_id698 ));699 target_collection.save()700 }701702 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.703 ///704 /// # Permissions705 ///706 /// * Collection Owner.707 /// * Collection Admin.708 /// * Anyone if709 /// * Allow List is enabled, and710 /// * Address is added to allow list, and711 /// * MintPermission is enabled (see SetMintPermission method)712 ///713 /// # Arguments714 ///715 /// * collection_id: ID of the collection.716 ///717 /// * owner: Address, initial owner of the NFT.718 ///719 /// * data: Token data to store on chain.720 #[weight = <CommonWeights<T>>::create_item()]721 #[transactional]722 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {723 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);724725 dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data))726 }727728 /// This method creates multiple items in a collection created with CreateCollection method.729 ///730 /// # Permissions731 ///732 /// * Collection Owner.733 /// * Collection Admin.734 /// * Anyone if735 /// * Allow List is enabled, and736 /// * Address is added to allow list, and737 /// * MintPermission is enabled (see SetMintPermission method)738 ///739 /// # Arguments740 ///741 /// * collection_id: ID of the collection.742 ///743 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].744 ///745 /// * owner: Address, initial owner of the NFT.746 #[weight = <CommonWeights<T>>::create_multiple_items(items_data.len() as u32)]747 #[transactional]748 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {749 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);750 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);751752 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data))753 }754755 #[weight = <CommonWeights<T>>::create_multiple_items_ex(&data)]756 #[transactional]757 pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {758 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);759760 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data))761 }762763 // TODO! transaction weight764765 /// Set transfers_enabled value for particular collection766 ///767 /// # Permissions768 ///769 /// * Collection Owner.770 ///771 /// # Arguments772 ///773 /// * collection_id: ID of the collection.774 ///775 /// * value: New flag value.776 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]777 #[transactional]778 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {779 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);780 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;781 target_collection.check_is_owner(&sender)?;782783 // =========784785 target_collection.limits.transfers_enabled = Some(value);786 target_collection.save()787 }788789 /// Destroys a concrete instance of NFT.790 ///791 /// # Permissions792 ///793 /// * Collection Owner.794 /// * Collection Admin.795 /// * Current NFT Owner.796 ///797 /// # Arguments798 ///799 /// * collection_id: ID of the collection.800 ///801 /// * item_id: ID of NFT to burn.802 #[weight = <CommonWeights<T>>::burn_item()]803 #[transactional]804 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {805 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);806807 let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;808 if value == 1 {809 <NftTransferBasket<T>>::remove(collection_id, item_id);810 <NftApproveBasket<T>>::remove(collection_id, item_id);811 }812 // Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?813 // <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());814 // <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));815 Ok(post_info)816 }817818 /// Destroys a concrete instance of NFT on behalf of the owner819 /// See also: [`approve`]820 ///821 /// # Permissions822 ///823 /// * Collection Owner.824 /// * Collection Admin.825 /// * Current NFT Owner.826 ///827 /// # Arguments828 ///829 /// * collection_id: ID of the collection.830 ///831 /// * item_id: ID of NFT to burn.832 ///833 /// * from: owner of item834 #[weight = <CommonWeights<T>>::burn_from()]835 #[transactional]836 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {837 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);838839 dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value))840 }841842 /// Change ownership of the token.843 ///844 /// # Permissions845 ///846 /// * Collection Owner847 /// * Collection Admin848 /// * Current NFT owner849 ///850 /// # Arguments851 ///852 /// * recipient: Address of token recipient.853 ///854 /// * collection_id.855 ///856 /// * item_id: ID of the item857 /// * Non-Fungible Mode: Required.858 /// * Fungible Mode: Ignored.859 /// * Re-Fungible Mode: Required.860 ///861 /// * value: Amount to transfer.862 /// * Non-Fungible Mode: Ignored863 /// * Fungible Mode: Must specify transferred amount864 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)865 #[weight = <CommonWeights<T>>::transfer()]866 #[transactional]867 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {868 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);869870 dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value))871 }872873 /// Set, change, or remove approved address to transfer the ownership of the NFT.874 ///875 /// # Permissions876 ///877 /// * Collection Owner878 /// * Collection Admin879 /// * Current NFT owner880 ///881 /// # Arguments882 ///883 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).884 ///885 /// * collection_id.886 ///887 /// * item_id: ID of the item.888 #[weight = <CommonWeights<T>>::approve()]889 #[transactional]890 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {891 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);892893 dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))894 }895896 /// 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.897 ///898 /// # Permissions899 /// * Collection Owner900 /// * Collection Admin901 /// * Current NFT owner902 /// * Address approved by current NFT owner903 ///904 /// # Arguments905 ///906 /// * from: Address that owns token.907 ///908 /// * recipient: Address of token recipient.909 ///910 /// * collection_id.911 ///912 /// * item_id: ID of the item.913 ///914 /// * value: Amount to transfer.915 #[weight = <CommonWeights<T>>::transfer_from()]916 #[transactional]917 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {918 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);919920 dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value))921 }922923 /// Set off-chain data schema.924 ///925 /// # Permissions926 ///927 /// * Collection Owner928 /// * Collection Admin929 ///930 /// # Arguments931 ///932 /// * collection_id.933 ///934 /// * schema: String representing the offchain data schema.935 #[weight = <CommonWeights<T>>::set_variable_metadata(data.len() as u32)]936 #[transactional]937 pub fn set_variable_meta_data (938 origin,939 collection_id: CollectionId,940 item_id: TokenId,941 data: BoundedVec<u8, CustomDataLimit>,942 ) -> DispatchResultWithPostInfo {943 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);944945 dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))946 }947948 /// Set meta_update_permission value for particular collection949 ///950 /// # Permissions951 ///952 /// * Collection Owner.953 ///954 /// # Arguments955 ///956 /// * collection_id: ID of the collection.957 ///958 /// * value: New flag value.959 #[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]960 #[transactional]961 pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {962 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);963 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;964965 ensure!(966 target_collection.meta_update_permission != MetaUpdatePermission::None,967 <CommonError<T>>::MetadataFlagFrozen,968 );969 target_collection.check_is_owner(&sender)?;970971 target_collection.meta_update_permission = value;972973 target_collection.save()974 }975976 /// Set schema standard977 /// ImageURL978 /// Unique979 ///980 /// # Permissions981 ///982 /// * Collection Owner983 /// * Collection Admin984 ///985 /// # Arguments986 ///987 /// * collection_id.988 ///989 /// * schema: SchemaVersion: enum990 #[weight = <SelfWeightOf<T>>::set_schema_version()]991 #[transactional]992 pub fn set_schema_version(993 origin,994 collection_id: CollectionId,995 version: SchemaVersion996 ) -> DispatchResult {997 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);998 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;999 target_collection.check_is_owner_or_admin(&sender)?;1000 target_collection.schema_version = version;10011002 <Pallet<T>>::deposit_event(Event::<T>::SchemaVersionSet(1003 collection_id1004 ));10051006 target_collection.save()1007 }10081009 /// Set off-chain data schema.1010 ///1011 /// # Permissions1012 ///1013 /// * Collection Owner1014 /// * Collection Admin1015 ///1016 /// # Arguments1017 ///1018 /// * collection_id.1019 ///1020 /// * schema: String representing the offchain data schema.1021 #[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]1022 #[transactional]1023 pub fn set_offchain_schema(1024 origin,1025 collection_id: CollectionId,1026 schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,1027 ) -> DispatchResult {1028 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1029 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1030 target_collection.check_is_owner_or_admin(&sender)?;10311032 target_collection.offchain_schema = schema;10331034 <Pallet<T>>::deposit_event(Event::<T>::OffchainSchemaSet(1035 collection_id1036 ));10371038 target_collection.save()1039 }10401041 /// Set const on-chain data schema.1042 ///1043 /// # Permissions1044 ///1045 /// * Collection Owner1046 /// * Collection Admin1047 ///1048 /// # Arguments1049 ///1050 /// * collection_id.1051 ///1052 /// * schema: String representing the const on-chain data schema.1053 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1054 #[transactional]1055 pub fn set_const_on_chain_schema (1056 origin,1057 collection_id: CollectionId,1058 schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>1059 ) -> DispatchResult {1060 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1061 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1062 target_collection.check_is_owner_or_admin(&sender)?;10631064 target_collection.const_on_chain_schema = schema;10651066 <Pallet<T>>::deposit_event(Event::<T>::ConstOnChainSchemaSet(1067 collection_id1068 ));10691070 target_collection.save()1071 }10721073 /// Set variable on-chain data schema.1074 ///1075 /// # Permissions1076 ///1077 /// * Collection Owner1078 /// * Collection Admin1079 ///1080 /// # Arguments1081 ///1082 /// * collection_id.1083 ///1084 /// * schema: String representing the variable on-chain data schema.1085 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1086 #[transactional]1087 pub fn set_variable_on_chain_schema (1088 origin,1089 collection_id: CollectionId,1090 schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>1091 ) -> DispatchResult {1092 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1093 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1094 target_collection.check_is_owner_or_admin(&sender)?;10951096 target_collection.variable_on_chain_schema = schema;10971098 <Pallet<T>>::deposit_event(Event::<T>::VariableOnChainSchemaSet(1099 collection_id1100 ));11011102 target_collection.save()1103 }11041105 #[weight = <SelfWeightOf<T>>::set_collection_limits()]1106 #[transactional]1107 pub fn set_collection_limits(1108 origin,1109 collection_id: CollectionId,1110 new_limit: CollectionLimits,1111 ) -> DispatchResult {1112 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1113 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1114 target_collection.check_is_owner(&sender)?;1115 let old_limit = &target_collection.limits;11161117 target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;11181119 <Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1120 collection_id1121 ));11221123 target_collection.save()1124 }1125 }1126}pallets/unique/src/sponsorship.rsdiffbeforeafterboth--- a/pallets/unique/src/sponsorship.rs
+++ b/pallets/unique/src/sponsorship.rs
@@ -301,3 +301,80 @@
}
}
}
+
+use crate::SponsorshipPredict;
+use up_data_structs::SponsorshipState;
+pub struct UniqueSponsorshipPredict<T>(PhantomData<T>);
+
+impl<T> SponsorshipPredict<T> for UniqueSponsorshipPredict<T>
+where
+ T: Config,
+{
+ fn predict(collection_id: CollectionId, who: T::CrossAccountId, token: TokenId) -> Option<u64>
+ where
+ u64: From<<T as frame_system::Config>::BlockNumber>,
+ {
+ let collection = <CollectionHandle<T>>::try_get(collection_id).ok()?;
+
+ // preliminary sponsoring correctness check
+ match collection.sponsorship {
+ SponsorshipState::Disabled | SponsorshipState::Unconfirmed(_) => return None,
+ _ => (),
+ }
+
+ // sponsor timeout
+ let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+ let limit = collection
+ .limits
+ .sponsor_transfer_timeout(match collection.mode {
+ CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,
+ CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+ CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+ });
+
+ let last_tx_block = match collection.mode {
+ CollectionMode::NFT => <NftTransferBasket<T>>::get(collection.id, token),
+ CollectionMode::Fungible(_) => {
+ <FungibleTransferBasket<T>>::get(collection.id, who.as_sub())
+ }
+ CollectionMode::ReFungible => {
+ <ReFungibleTransferBasket<T>>::get((collection.id, token, who.as_sub()))
+ }
+ };
+
+ if let Some(last_tx_block) = last_tx_block {
+ let timeout = last_tx_block + limit.into();
+ if block_number < timeout {
+ return Some((timeout - block_number).into());
+ }
+ return Some(0);
+ }
+
+ let token_exists = match collection.mode {
+ CollectionMode::NFT => {
+ <pallet_nonfungible::TokenData<T>>::contains_key((collection.id, token))
+ }
+ CollectionMode::Fungible(_) => true,
+ CollectionMode::ReFungible => {
+ <pallet_refungible::TotalSupply<T>>::contains_key((collection.id, token))
+ }
+ };
+
+ if token_exists {
+ Some(0)
+ } else {
+ None
+ }
+
+ // // existance check
+ // match collection.mode {
+ // CollectionMode::NFT => <NftTransferBasket<T>>::get(collection.id, token),
+ // CollectionMode::Fungible(_) => {
+ // <FungibleTransferBasket<T>>::get(collection.id, who.as_sub())
+ // }
+ // CollectionMode::ReFungible => {
+ // <ReFungibleTransferBasket<T>>::get((collection.id, token, who.as_sub()))
+ // }
+ // };
+ }
+}
primitives/rpc/src/lib.rsdiffbeforeafterboth--- a/primitives/rpc/src/lib.rs
+++ b/primitives/rpc/src/lib.rs
@@ -59,5 +59,6 @@
fn last_token_id(collection: CollectionId) -> Result<TokenId>;
fn collection_by_id(collection: CollectionId) -> Result<Option<Collection<AccountId>>>;
fn collection_stats() -> Result<CollectionStats>;
+ fn next_sponsored(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Result<Option<u64>>;
}
}
runtime/common/src/runtime_apis.rsdiffbeforeafterboth--- a/runtime/common/src/runtime_apis.rs
+++ b/runtime/common/src/runtime_apis.rs
@@ -69,6 +69,13 @@
fn collection_stats() -> Result<CollectionStats, DispatchError> {
Ok(<pallet_common::Pallet<Runtime>>::collection_stats())
}
+ fn next_sponsored(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Result<Option<u64>, DispatchError> {
+ Ok(<pallet_unique::UniqueSponsorshipPredict<Runtime> as
+ pallet_unique::SponsorshipPredict<Runtime>>::predict(
+ collection,
+ account,
+ token))
+ }
}
impl sp_api::Core<Block> for Runtime {
tests/src/interfaces/augment-api-rpc.tsdiffbeforeafterboth--- a/tests/src/interfaces/augment-api-rpc.ts
+++ b/tests/src/interfaces/augment-api-rpc.ts
@@ -619,6 +619,10 @@
* Get token variable metadata
**/
variableMetadata: AugmentedRpc<(collection: u32 | AnyNumber | Uint8Array, tokenId: u32 | AnyNumber | Uint8Array, at?: Hash | string | Uint8Array) => Observable<Bytes>>;
+ /**
+ * nextSponsored transaction
+ **/
+ nextSponsored: AugmentedRpc<(collection: u32 | AnyNumber | Uint8Array, account: PalletCommonAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, tokenId: u32 | AnyNumber | Uint8Array, at?: Hash | string | Uint8Array) => Observable<Option<u64>>>;
};
web3: {
/**
tests/src/interfaces/unique/definitions.tsdiffbeforeafterboth--- a/tests/src/interfaces/unique/definitions.ts
+++ b/tests/src/interfaces/unique/definitions.ts
@@ -55,5 +55,6 @@
collectionById: fun('Get collection by specified id', [collectionParam], 'Option<UpDataStructsCollection>'),
collectionStats: fun('Get collection stats', [], 'UpDataStructsCollectionStats'),
allowed: fun('Check if user is allowed to use collection', [collectionParam, crossAccountParam()], 'bool'),
+ nextSponsored: fun('Check if user is allowed to use collection', [collectionParam, crossAccountParam(), tokenParam], 'Option<u64>'),
},
};