difftreelog
Merge branch 'develop' into feature/NFTPAR-142
in: master
# Conflicts: # pallets/nft/src/lib.rs
5 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3737,7 +3737,10 @@
"frame-support",
"frame-system",
"log",
+ "pallet-balances",
"pallet-contracts",
+ "pallet-randomness-collective-flip",
+ "pallet-timestamp",
"pallet-transaction-payment",
"parity-scale-codec",
"serde",
pallets/nft/src/default_weights.rsdiffbeforeafterboth--- a/pallets/nft/src/default_weights.rs
+++ b/pallets/nft/src/default_weights.rs
@@ -107,9 +107,19 @@
.saturating_add(DbWeight::get().reads(2 as Weight))
.saturating_add(DbWeight::get().writes(1 as Weight))
}
+ // fn set_chain_limits() -> Weight {
+ // (0 as Weight)
+ // .saturating_add(DbWeight::get().reads(1 as Weight))
+ // .saturating_add(DbWeight::get().writes(1 as Weight))
+ // }
// fn enable_contract_sponsoring() -> Weight {
// (0 as Weight)
// .saturating_add(DbWeight::get().reads(1 as Weight))
// .saturating_add(DbWeight::get().writes(1 as Weight))
// }
+ // fn set_contract_sponsoring_rate_limit() -> Weight {
+ // (0 as Weight)
+ // .saturating_add(DbWeight::get().reads(1 as Weight))
+ // .saturating_add(DbWeight::get().writes(1 as Weight))
+ // }
}
pallets/nft/src/lib.rsdiffbeforeafterboth1#![cfg_attr(not(feature = "std"), no_std)]23#[cfg(feature = "std")]4pub use std::*;56#[cfg(feature = "std")]7pub use serde::*;89use codec::{Decode, Encode};10pub use frame_support::{11 construct_runtime, decl_event, decl_module, decl_storage, decl_error,12 dispatch::DispatchResult,13 ensure, fail, parameter_types,14 traits::{15 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,16 Randomness, WithdrawReason,17 },18 weights::{19 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},20 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,21 WeightToFeePolynomial,22 },23 IsSubType, StorageValue,24};2526use frame_system::{self as system, ensure_signed, ensure_root};27use sp_runtime::sp_std::prelude::Vec;28use sp_runtime::{29 traits::{30 DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SignedExtension, Zero,31 },32 transaction_validity::{33 InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,34 },35 FixedPointOperand, FixedU128,36};37use pallet_contracts::ContractAddressFor;38use sp_runtime::traits::StaticLookup;3940#[cfg(test)]41mod mock;4243#[cfg(test)]44mod tests;4546mod default_weights;4748// Structs49// #region5051#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]52#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]53pub enum CollectionMode {54 Invalid,55 NFT,56 // decimal points57 Fungible(u32),58 // decimal points59 ReFungible(u32),60}6162impl Into<u8> for CollectionMode {63 fn into(self) -> u8 {64 match self {65 CollectionMode::Invalid => 0,66 CollectionMode::NFT => 1,67 CollectionMode::Fungible(_) => 2,68 CollectionMode::ReFungible(_) => 3,69 }70 }71}7273#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]74#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]75pub enum AccessMode {76 Normal,77 WhiteList,78}79impl Default for AccessMode {80 fn default() -> Self {81 Self::Normal82 }83}8485impl Default for CollectionMode {86 fn default() -> Self {87 Self::Invalid88 }89}9091#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]92#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]93pub struct Ownership<AccountId> {94 pub owner: AccountId,95 pub fraction: u128,96}9798#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]99#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]100pub struct CollectionType<AccountId> {101 pub owner: AccountId,102 pub mode: CollectionMode,103 pub access: AccessMode,104 pub decimal_points: u32,105 pub name: Vec<u16>, // 64 include null escape char106 pub description: Vec<u16>, // 256 include null escape char107 pub token_prefix: Vec<u8>, // 16 include null escape char108 pub mint_mode: bool,109 pub offchain_schema: Vec<u8>,110 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender111 pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship112 pub variable_on_chain_schema: Vec<u8>, //113 pub const_on_chain_schema: Vec<u8>, //114}115116#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]117#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]118pub struct NftItemType<AccountId> {119 pub collection: u64,120 pub owner: AccountId,121 pub const_data: Vec<u8>,122 pub variable_data: Vec<u8>,123}124125#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]126#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]127pub struct FungibleItemType<AccountId> {128 pub collection: u64,129 pub owner: AccountId,130 pub value: u128,131}132133#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]134#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]135pub struct ReFungibleItemType<AccountId> {136 pub collection: u64,137 pub owner: Vec<Ownership<AccountId>>,138 pub const_data: Vec<u8>,139 pub variable_data: Vec<u8>,140}141142#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]143#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]144pub struct ApprovePermissions<AccountId> {145 pub approved: AccountId,146 pub amount: u64,147}148149#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]150#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]151pub struct VestingItem<AccountId, Moment> {152 pub sender: AccountId,153 pub recipient: AccountId,154 pub collection_id: u64,155 pub item_id: u64,156 pub amount: u64,157 pub vesting_date: Moment,158}159160#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]161#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]162pub struct BasketItem<AccountId, BlockNumber> {163 pub address: AccountId,164 pub start_block: BlockNumber,165}166167#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]168#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]169pub struct ChainLimits {170 pub collection_numbers_limit: u64,171 pub account_token_ownership_limit: u64,172 pub collections_admins_limit: u64,173 pub custom_data_limit: u32,174175 // Timeouts for item types in passed blocks176 pub nft_sponsor_transfer_timeout: u32,177 pub fungible_sponsor_transfer_timeout: u32,178 pub refungible_sponsor_transfer_timeout: u32,179}180181pub trait WeightInfo {182 fn create_collection() -> Weight;183 fn destroy_collection() -> Weight;184 fn add_to_white_list() -> Weight;185 fn remove_from_white_list() -> Weight;186 fn set_public_access_mode() -> Weight;187 fn set_mint_permission() -> Weight;188 fn change_collection_owner() -> Weight;189 fn add_collection_admin() -> Weight;190 fn remove_collection_admin() -> Weight;191 fn set_collection_sponsor() -> Weight;192 fn confirm_sponsorship() -> Weight;193 fn remove_collection_sponsor() -> Weight;194 fn create_item(s: usize) -> Weight;195 fn burn_item() -> Weight;196 fn transfer() -> Weight;197 fn approve() -> Weight;198 fn transfer_from() -> Weight;199 fn set_offchain_schema() -> Weight;200 fn set_const_on_chain_schema() -> Weight;201 fn set_variable_on_chain_schema() -> Weight;202 fn set_variable_meta_data() -> Weight;203 // fn enable_contract_sponsoring() -> Weight;204}205206#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]207#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]208pub struct CreateNftData {209 pub const_data: Vec<u8>,210 pub variable_data: Vec<u8>,211}212213#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]214#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]215pub struct CreateFungibleData {216}217218#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]219#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]220pub struct CreateReFungibleData {221 pub const_data: Vec<u8>,222 pub variable_data: Vec<u8>,223}224225#[derive(Encode, Decode, Debug, Clone, PartialEq)]226#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]227pub enum CreateItemData {228 NFT(CreateNftData),229 Fungible(CreateFungibleData),230 ReFungible(CreateReFungibleData)231}232233impl CreateItemData {234 pub fn len(&self) -> usize {235 let len = match self {236 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),237 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),238 _ => 0239 };240 241 return len;242 }243}244245impl From<CreateNftData> for CreateItemData {246 fn from(item: CreateNftData) -> Self {247 CreateItemData::NFT(item)248 }249}250251impl From<CreateReFungibleData> for CreateItemData {252 fn from(item: CreateReFungibleData) -> Self {253 CreateItemData::ReFungible(item)254 }255}256257impl From<CreateFungibleData> for CreateItemData {258 fn from(item: CreateFungibleData) -> Self {259 CreateItemData::Fungible(item)260 }261}262263264decl_error! {265 /// Error for non-fungible-token module.266 pub enum Error for Module<T: Trait> {267 /// Total collections bound exceeded.268 TotalCollectionsLimitExceeded,269 /// Decimal_points parameter must be lower than 4.270 CollectionDecimalPointLimitExceeded, 271 /// Collection name can not be longer than 63 char.272 CollectionNameLimitExceeded, 273 /// Collection description can not be longer than 255 char.274 CollectionDescriptionLimitExceeded, 275 /// Token prefix can not be longer than 15 char.276 CollectionTokenPrefixLimitExceeded,277 /// This collection does not exist.278 CollectionNotFound,279 /// Item not exists.280 TokenNotFound,281 /// Arithmetic calculation overflow.282 NumOverflow, 283 /// Account already has admin role.284 AlreadyAdmin, 285 /// You do not own this collection.286 NoPermission,287 /// This address is not set as sponsor, use setCollectionSponsor first.288 ConfirmUnsetSponsorFail,289 /// Collection is not in mint mode.290 PublicMintingNotAllowed,291 /// Sender parameter and item owner must be equal.292 MustBeTokenOwner,293 /// Item balance not enough.294 TokenValueTooLow,295 /// Size of item is too large.296 NftSizeLimitExceeded,297 /// No approve found298 ApproveNotFound,299 /// Requested value more than approved.300 TokenValueNotEnough,301 /// Only approved addresses can call this method.302 ApproveRequired,303 /// Address is not in white list.304 AddresNotInWhiteList,305 /// Number of collection admins bound exceeded.306 CollectionAdminsLimitExceeded,307 /// Owned tokens by a single address bound exceeded.308 AddressOwnershipLimitExceeded,309 /// Length of items properties must be greater than 0.310 EmptyArgument,311 /// const_data exceeded data limit.312 TokenConstDataLimitExceeded,313 /// variable_data exceeded data limit.314 TokenVariableDataLimitExceeded,315 /// Not NFT item data used to mint in NFT collection.316 NotNftDataUsedToMintNftCollectionToken,317 /// Not Fungible item data used to mint in Fungible collection.318 NotFungibleDataUsedToMintFungibleCollectionToken,319 /// Not Re Fungible item data used to mint in Re Fungible collection.320 NotReFungibleDataUsedToMintReFungibleCollectionToken,321 /// Unexpected collection type.322 UnexpectedCollectionType,323 /// Can't store metadata in fungible tokens.324 CantSotreMetadataInFungibleTokens325 }326}327328pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {329 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;330331 /// Weight information for extrinsics in this pallet.332 type WeightInfo: WeightInfo;333}334335#[cfg(feature = "runtime-benchmarks")]336mod benchmarking;337338// #endregion339340decl_storage! {341 trait Store for Module<T: Trait> as Nft {342343 // Private members344 NextCollectionID: u64;345 CreatedCollectionCount: u64;346 ChainVersion: u64;347 ItemListIndex: map hasher(blake2_128_concat) u64 => u64;348349 // Chain limits struct350 pub ChainLimit get(fn chain_limit) config(): ChainLimits;351352 // Bound counters353 CollectionCount: u64;354 pub AccountItemCount get(fn account_item_count): map hasher(identity) T::AccountId => u64;355356 // Basic collections357 pub Collection get(fn collection) config(): map hasher(identity) u64 => CollectionType<T::AccountId>;358 pub AdminList get(fn admin_list_collection): map hasher(identity) u64 => Vec<T::AccountId>;359 pub WhiteList get(fn white_list): map hasher(identity) u64 => Vec<T::AccountId>;360361 /// Balance owner per collection map362 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => u64;363364 /// second parameter: item id + owner account id365 pub ApprovedList get(fn approved): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) (u64, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;366367 /// Item collections368 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => NftItemType<T::AccountId>;369 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => FungibleItemType<T::AccountId>;370 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => ReFungibleItemType<T::AccountId>;371372 /// Index list373 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => Vec<u64>;374375 /// Tokens transfer baskets376 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => T::BlockNumber;377 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => Vec<BasketItem<T::AccountId, T::BlockNumber>>;378 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => T::BlockNumber;379380 // Contract Sponsorship and Ownership381 pub ContractOwner get(fn contract_owner): map hasher(identity) T::AccountId => T::AccountId;382 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(identity) T::AccountId => bool;383 }384 add_extra_genesis {385 build(|config: &GenesisConfig<T>| {386 // Modification of storage387 for (_num, _c) in &config.collection {388 <Module<T>>::init_collection(_c);389 }390391 for (_num, _q, _i) in &config.nft_item_id {392 <Module<T>>::init_nft_token(_i);393 }394395 for (_num, _q, _i) in &config.fungible_item_id {396 <Module<T>>::init_fungible_token(_i);397 }398399 for (_num, _q, _i) in &config.refungible_item_id {400 <Module<T>>::init_refungible_token(_i);401 }402 })403 }404}405406decl_event!(407 pub enum Event<T>408 where409 AccountId = <T as system::Trait>::AccountId,410 {411 /// New collection was created412 /// 413 /// # Arguments414 /// 415 /// * collection_id: Globally unique identifier of newly created collection.416 /// 417 /// * mode: [CollectionMode] converted into u8.418 /// 419 /// * account_id: Collection owner.420 Created(u64, u8, AccountId),421422 /// New item was created.423 /// 424 /// # Arguments425 /// 426 /// * collection_id: Id of the collection where item was created.427 /// 428 /// * item_id: Id of an item. Unique within the collection.429 ItemCreated(u64, u64),430431 /// Collection item was burned.432 /// 433 /// # Arguments434 /// 435 /// collection_id.436 /// 437 /// item_id: Identifier of burned NFT.438 ItemDestroyed(u64, u64),439 }440);441442decl_module! {443 pub struct Module<T: Trait> for enum Call where origin: T::Origin {444445 fn deposit_event() = default;446 type Error = Error<T>;447448 fn on_initialize(now: T::BlockNumber) -> Weight {449450 if ChainVersion::get() < 2451 {452 let value = NextCollectionID::get();453 CreatedCollectionCount::put(value);454 ChainVersion::put(2);455 }456457 0458 }459460 /// 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.461 /// 462 /// # Permissions463 /// 464 /// * Anyone.465 /// 466 /// # Arguments467 /// 468 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.469 /// 470 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.471 /// 472 /// * token_prefix: UTF-8 string with token prefix.473 /// 474 /// * mode: [CollectionMode] collection type and type dependent data.475 // returns collection ID476 #[weight = T::WeightInfo::create_collection()]477 pub fn create_collection(origin,478 collection_name: Vec<u16>,479 collection_description: Vec<u16>,480 token_prefix: Vec<u8>,481 mode: CollectionMode) -> DispatchResult {482483 // Anyone can create a collection484 let who = ensure_signed(origin)?;485486 let decimal_points = match mode {487 CollectionMode::Fungible(points) => points,488 CollectionMode::ReFungible(points) => points,489 _ => 0490 };491492 // bound Total number of collections493 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);494495 // check params496 ensure!(decimal_points <= 4, Error::<T>::CollectionDecimalPointLimitExceeded);497498 let mut name = collection_name.to_vec();499 name.push(0);500 ensure!(name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);501502 let mut description = collection_description.to_vec();503 description.push(0);504 ensure!(name.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);505506 let mut prefix = token_prefix.to_vec();507 prefix.push(0);508 ensure!(prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);509510 // Generate next collection ID511 let next_id = CreatedCollectionCount::get()512 .checked_add(1)513 .ok_or(Error::<T>::NumOverflow)?;514515 // bound counter516 let total = CollectionCount::get()517 .checked_add(1)518 .ok_or(Error::<T>::NumOverflow)?;519520 CreatedCollectionCount::put(next_id);521 CollectionCount::put(total);522523 // Create new collection524 let new_collection = CollectionType {525 owner: who.clone(),526 name: name,527 mode: mode.clone(),528 mint_mode: false,529 access: AccessMode::Normal,530 description: description,531 decimal_points: decimal_points,532 token_prefix: prefix,533 offchain_schema: Vec::new(),534 sponsor: T::AccountId::default(),535 unconfirmed_sponsor: T::AccountId::default(),536 variable_on_chain_schema: Vec::new(),537 const_on_chain_schema: Vec::new(),538 };539540 // Add new collection to map541 <Collection<T>>::insert(next_id, new_collection);542543 // call event544 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));545546 Ok(())547 }548549 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.550 /// 551 /// # Permissions552 /// 553 /// * Collection Owner.554 /// 555 /// # Arguments556 /// 557 /// * collection_id: collection to destroy.558 #[weight = T::WeightInfo::destroy_collection()]559 pub fn destroy_collection(origin, collection_id: u64) -> DispatchResult {560561 let sender = ensure_signed(origin)?;562 Self::check_owner_permissions(collection_id, sender)?;563564 <AddressTokens<T>>::remove_prefix(collection_id);565 <ApprovedList<T>>::remove_prefix(collection_id);566 <Balance<T>>::remove_prefix(collection_id);567 <ItemListIndex>::remove(collection_id);568 <AdminList<T>>::remove(collection_id);569 <Collection<T>>::remove(collection_id);570 <WhiteList<T>>::remove(collection_id);571572 <NftItemList<T>>::remove_prefix(collection_id);573 <FungibleItemList<T>>::remove_prefix(collection_id);574 <ReFungibleItemList<T>>::remove_prefix(collection_id);575576 <NftTransferBasket<T>>::remove_prefix(collection_id);577 <FungibleTransferBasket<T>>::remove_prefix(collection_id);578 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);579580 if CollectionCount::get() > 0581 {582 // bound couter583 let total = CollectionCount::get()584 .checked_sub(1)585 .ok_or(Error::<T>::NumOverflow)?;586587 CollectionCount::put(total);588 }589590 Ok(())591 }592593 /// Add an address to white list.594 /// 595 /// # Permissions596 /// 597 /// * Collection Owner598 /// * Collection Admin599 /// 600 /// # Arguments601 /// 602 /// * collection_id.603 /// 604 /// * address.605 #[weight = T::WeightInfo::add_to_white_list()]606 pub fn add_to_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{607608 let sender = ensure_signed(origin)?;609 Self::check_owner_or_admin_permissions(collection_id, sender)?;610611 let mut white_list_collection: Vec<T::AccountId>;612 if <WhiteList<T>>::contains_key(collection_id) {613 white_list_collection = <WhiteList<T>>::get(collection_id);614 if !white_list_collection.contains(&address.clone())615 {616 white_list_collection.push(address.clone());617 }618 }619 else {620 white_list_collection = Vec::new();621 white_list_collection.push(address.clone());622 }623624 <WhiteList<T>>::insert(collection_id, white_list_collection);625 Ok(())626 }627628 /// Remove an address from white list.629 /// 630 /// # Permissions631 /// 632 /// * Collection Owner633 /// * Collection Admin634 /// 635 /// # Arguments636 /// 637 /// * collection_id.638 /// 639 /// * address.640 #[weight = T::WeightInfo::remove_from_white_list()]641 pub fn remove_from_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{642643 let sender = ensure_signed(origin)?;644 Self::check_owner_or_admin_permissions(collection_id, sender)?;645646 if <WhiteList<T>>::contains_key(collection_id) {647 let mut white_list_collection = <WhiteList<T>>::get(collection_id);648 if white_list_collection.contains(&address.clone())649 {650 white_list_collection.retain(|i| *i != address.clone());651 <WhiteList<T>>::insert(collection_id, white_list_collection);652 }653 }654655 Ok(())656 }657658 /// Toggle between normal and white list access for the methods with access for `Anyone`.659 /// 660 /// # Permissions661 /// 662 /// * Collection Owner.663 /// 664 /// # Arguments665 /// 666 /// * collection_id.667 /// 668 /// * mode: [AccessMode]669 #[weight = T::WeightInfo::set_public_access_mode()]670 pub fn set_public_access_mode(origin, collection_id: u64, mode: AccessMode) -> DispatchResult671 {672 let sender = ensure_signed(origin)?;673674 Self::check_owner_permissions(collection_id, sender)?;675 let mut target_collection = <Collection<T>>::get(collection_id);676 target_collection.access = mode;677 <Collection<T>>::insert(collection_id, target_collection);678679 Ok(())680 }681682 /// Allows Anyone to create tokens if:683 /// * White List is enabled, and684 /// * Address is added to white list, and685 /// * This method was called with True parameter686 /// 687 /// # Permissions688 /// * Collection Owner689 ///690 /// # Arguments691 /// 692 /// * collection_id.693 /// 694 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.695 #[weight = T::WeightInfo::set_mint_permission()]696 pub fn set_mint_permission(origin, collection_id: u64, mint_permission: bool) -> DispatchResult697 {698 let sender = ensure_signed(origin)?;699700 Self::check_owner_permissions(collection_id, sender)?;701 let mut target_collection = <Collection<T>>::get(collection_id);702 target_collection.mint_mode = mint_permission;703 <Collection<T>>::insert(collection_id, target_collection);704705 Ok(())706 }707708 /// Change the owner of the collection.709 /// 710 /// # Permissions711 /// 712 /// * Collection Owner.713 /// 714 /// # Arguments715 /// 716 /// * collection_id.717 /// 718 /// * new_owner.719 #[weight = T::WeightInfo::change_collection_owner()]720 pub fn change_collection_owner(origin, collection_id: u64, new_owner: T::AccountId) -> DispatchResult {721722 let sender = ensure_signed(origin)?;723 Self::check_owner_permissions(collection_id, sender)?;724 let mut target_collection = <Collection<T>>::get(collection_id);725 target_collection.owner = new_owner;726 <Collection<T>>::insert(collection_id, target_collection);727728 Ok(())729 }730731 /// Adds an admin of the Collection.732 /// 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. 733 /// 734 /// # Permissions735 /// 736 /// * Collection Owner.737 /// * Collection Admin.738 /// 739 /// # Arguments740 /// 741 /// * collection_id: ID of the Collection to add admin for.742 /// 743 /// * new_admin_id: Address of new admin to add.744 #[weight = T::WeightInfo::add_collection_admin()]745 pub fn add_collection_admin(origin, collection_id: u64, new_admin_id: T::AccountId) -> DispatchResult {746747 let sender = ensure_signed(origin)?;748 Self::check_owner_or_admin_permissions(collection_id, sender)?;749 let mut admin_arr: Vec<T::AccountId> = Vec::new();750751 if <AdminList<T>>::contains_key(collection_id)752 {753 admin_arr = <AdminList<T>>::get(collection_id);754 ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);755 }756757 // Number of collection admins758 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);759760 admin_arr.push(new_admin_id);761 <AdminList<T>>::insert(collection_id, admin_arr);762763 Ok(())764 }765766 /// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.767 ///768 /// # Permissions769 /// 770 /// * Collection Owner.771 /// * Collection Admin.772 /// 773 /// # Arguments774 /// 775 /// * collection_id: ID of the Collection to remove admin for.776 /// 777 /// * account_id: Address of admin to remove.778 #[weight = T::WeightInfo::remove_collection_admin()]779 pub fn remove_collection_admin(origin, collection_id: u64, account_id: T::AccountId) -> DispatchResult {780781 let sender = ensure_signed(origin)?;782 Self::check_owner_or_admin_permissions(collection_id, sender)?;783784 if <AdminList<T>>::contains_key(collection_id)785 {786 let mut admin_arr = <AdminList<T>>::get(collection_id);787 admin_arr.retain(|i| *i != account_id);788 <AdminList<T>>::insert(collection_id, admin_arr);789 }790791 Ok(())792 }793794 /// # Permissions795 /// 796 /// * Collection Owner797 /// 798 /// # Arguments799 /// 800 /// * collection_id.801 /// 802 /// * new_sponsor.803 #[weight = T::WeightInfo::set_collection_sponsor()]804 pub fn set_collection_sponsor(origin, collection_id: u64, new_sponsor: T::AccountId) -> DispatchResult {805806 let sender = ensure_signed(origin)?;807 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);808809 let mut target_collection = <Collection<T>>::get(collection_id);810 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);811812 target_collection.unconfirmed_sponsor = new_sponsor;813 <Collection<T>>::insert(collection_id, target_collection);814815 Ok(())816 }817818 /// # Permissions819 /// 820 /// * Sponsor.821 /// 822 /// # Arguments823 /// 824 /// * collection_id.825 #[weight = T::WeightInfo::confirm_sponsorship()]826 pub fn confirm_sponsorship(origin, collection_id: u64) -> DispatchResult {827828 let sender = ensure_signed(origin)?;829 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);830831 let mut target_collection = <Collection<T>>::get(collection_id);832 ensure!(sender == target_collection.unconfirmed_sponsor, Error::<T>::ConfirmUnsetSponsorFail);833834 target_collection.sponsor = target_collection.unconfirmed_sponsor;835 target_collection.unconfirmed_sponsor = T::AccountId::default();836 <Collection<T>>::insert(collection_id, target_collection);837838 Ok(())839 }840841 /// Switch back to pay-per-own-transaction model.842 ///843 /// # Permissions844 ///845 /// * Collection owner.846 /// 847 /// # Arguments848 /// 849 /// * collection_id.850 #[weight = T::WeightInfo::remove_collection_sponsor()]851 pub fn remove_collection_sponsor(origin, collection_id: u64) -> DispatchResult {852853 let sender = ensure_signed(origin)?;854 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);855856 let mut target_collection = <Collection<T>>::get(collection_id);857 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);858859 target_collection.sponsor = T::AccountId::default();860 <Collection<T>>::insert(collection_id, target_collection);861862 Ok(())863 }864865 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.866 /// 867 /// # Permissions868 /// 869 /// * Collection Owner.870 /// * Collection Admin.871 /// * Anyone if872 /// * White List is enabled, and873 /// * Address is added to white list, and874 /// * MintPermission is enabled (see SetMintPermission method)875 /// 876 /// # Arguments877 /// 878 /// * collection_id: ID of the collection.879 /// 880 /// * owner: Address, initial owner of the NFT.881 ///882 /// * data: Token data to store on chain.883 // #[weight =884 // (130_000_000 as Weight)885 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))886 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))887 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]888889 #[weight = T::WeightInfo::create_item(data.len())]890 pub fn create_item(origin, collection_id: u64, owner: T::AccountId, data: CreateItemData) -> DispatchResult {891892 let sender = ensure_signed(origin)?;893894 Self::collection_exists(collection_id)?;895896 let target_collection = <Collection<T>>::get(collection_id);897898 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;899 Self::validate_create_item_args(&target_collection, &data)?;900 Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;901902 Ok(())903 }904905 /// This method creates multiple instances of NFT Collection created with CreateCollection method.906 /// 907 /// # Permissions908 /// 909 /// * Collection Owner.910 /// * Collection Admin.911 /// * Anyone if912 /// * White List is enabled, and913 /// * Address is added to white list, and914 /// * MintPermission is enabled (see SetMintPermission method)915 /// 916 /// # Arguments917 /// 918 /// * collection_id: ID of the collection.919 /// 920 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].921 /// 922 /// * owner: Address, initial owner of the NFT.923 #[weight = T::WeightInfo::create_item(items_data.into_iter()924 .map(|data| { data.len() })925 .sum())]926 pub fn create_multiple_items(origin, collection_id: u64, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {927928 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);929 let sender = ensure_signed(origin)?;930931 Self::collection_exists(collection_id)?;932 let target_collection = <Collection<T>>::get(collection_id);933934 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;935936 for data in &items_data {937 Self::validate_create_item_args(&target_collection, data)?;938 }939 for data in &items_data {940 Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;941 }942943 Ok(())944 }945946 /// Destroys a concrete instance of NFT.947 /// 948 /// # Permissions949 /// 950 /// * Collection Owner.951 /// * Collection Admin.952 /// * Current NFT Owner.953 /// 954 /// # Arguments955 /// 956 /// * collection_id: ID of the collection.957 /// 958 /// * item_id: ID of NFT to burn.959 #[weight = T::WeightInfo::burn_item()]960 pub fn burn_item(origin, collection_id: u64, item_id: u64) -> DispatchResult {961962 let sender = ensure_signed(origin)?;963 Self::collection_exists(collection_id)?;964965 // Transfer permissions check966 let target_collection = <Collection<T>>::get(collection_id);967 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||968 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),969 Error::<T>::NoPermission);970971 if target_collection.access == AccessMode::WhiteList {972 Self::check_white_list(collection_id, &sender)?;973 }974975 match target_collection.mode976 {977 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,978 CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?,979 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,980 _ => ()981 };982983 // call event984 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));985986 Ok(())987 }988989 /// Change ownership of the token.990 /// 991 /// # Permissions992 /// 993 /// * Collection Owner994 /// * Collection Admin995 /// * Current NFT owner996 ///997 /// # Arguments998 /// 999 /// * recipient: Address of token recipient.1000 /// 1001 /// * collection_id.1002 /// 1003 /// * item_id: ID of the item1004 /// * Non-Fungible Mode: Required.1005 /// * Fungible Mode: Ignored.1006 /// * Re-Fungible Mode: Required.1007 /// 1008 /// * value: Amount to transfer.1009 /// * Non-Fungible Mode: Ignored1010 /// * Fungible Mode: Must specify transferred amount1011 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1012 #[weight = T::WeightInfo::transfer()]1013 pub fn transfer(origin, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64) -> DispatchResult {10141015 let sender = ensure_signed(origin)?;10161017 // Transfer permissions check1018 let target_collection = <Collection<T>>::get(collection_id);1019 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1020 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1021 Error::<T>::NoPermission);10221023 if target_collection.access == AccessMode::WhiteList {1024 Self::check_white_list(collection_id, &sender)?;1025 Self::check_white_list(collection_id, &recipient)?;1026 }10271028 match target_collection.mode1029 {1030 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1031 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,1032 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1033 _ => ()1034 };10351036 Ok(())1037 }10381039 /// Set, change, or remove approved address to transfer the ownership of the NFT.1040 /// 1041 /// # Permissions1042 /// 1043 /// * Collection Owner1044 /// * Collection Admin1045 /// * Current NFT owner1046 /// 1047 /// # Arguments1048 /// 1049 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1050 /// 1051 /// * collection_id.1052 /// 1053 /// * item_id: ID of the item.1054 #[weight = T::WeightInfo::approve()]1055 pub fn approve(origin, approved: T::AccountId, collection_id: u64, item_id: u64) -> DispatchResult {10561057 let sender = ensure_signed(origin)?;10581059 // Transfer permissions check1060 let target_collection = <Collection<T>>::get(collection_id);1061 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1062 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1063 Error::<T>::NoPermission);10641065 if target_collection.access == AccessMode::WhiteList {1066 Self::check_white_list(collection_id, &sender)?;1067 Self::check_white_list(collection_id, &approved)?;1068 }10691070 // amount param stub1071 let amount = 100000000;10721073 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));1074 if list_exists {10751076 let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));1077 let item_contains = list.iter().any(|i| i.approved == approved);10781079 if !item_contains {1080 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1081 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1082 }1083 } else {10841085 let mut list = Vec::new();1086 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1087 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1088 }10891090 Ok(())1091 }1092 1093 /// 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.1094 /// 1095 /// # Permissions1096 /// * Collection Owner1097 /// * Collection Admin1098 /// * Current NFT owner1099 /// * Address approved by current NFT owner1100 /// 1101 /// # Arguments1102 /// 1103 /// * from: Address that owns token.1104 /// 1105 /// * recipient: Address of token recipient.1106 /// 1107 /// * collection_id.1108 /// 1109 /// * item_id: ID of the item.1110 /// 1111 /// * value: Amount to transfer.1112 #[weight = T::WeightInfo::transfer_from()]1113 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64 ) -> DispatchResult {11141115 let sender = ensure_signed(origin)?;1116 let mut appoved_transfer = false;11171118 // Check approve1119 if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {1120 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));1121 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());1122 if opt_item.is_some()1123 {1124 appoved_transfer = true;1125 ensure!(opt_item.unwrap().amount >= value, Error::<T>::TokenValueNotEnough);1126 }1127 }11281129 // Transfer permissions check1130 let target_collection = <Collection<T>>::get(collection_id);1131 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1132 Error::<T>::NoPermission);11331134 if target_collection.access == AccessMode::WhiteList {1135 Self::check_white_list(collection_id, &sender)?;1136 Self::check_white_list(collection_id, &recipient)?;1137 }11381139 // remove approve1140 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))1141 .into_iter().filter(|i| i.approved != sender.clone()).collect();1142 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);114311441145 match target_collection.mode1146 {1147 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1148 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,1149 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1150 _ => ()1151 };11521153 Ok(())1154 }11551156 ///1157 #[weight = 0]1158 pub fn safe_transfer_from(origin, collection_id: u64, item_id: u64, new_owner: T::AccountId) -> DispatchResult {11591160 // let no_perm_mes = "You do not have permissions to modify this collection";1161 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1162 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1163 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);11641165 // // on_nft_received call11661167 // Self::transfer(origin, collection_id, item_id, new_owner)?;11681169 Ok(())1170 }11711172 /// Set off-chain data schema.1173 /// 1174 /// # Permissions1175 /// 1176 /// * Collection Owner1177 /// * Collection Admin1178 /// 1179 /// # Arguments1180 /// 1181 /// * collection_id.1182 /// 1183 /// * schema: String representing the offchain data schema.1184 #[weight = T::WeightInfo::set_variable_meta_data()]1185 pub fn set_variable_meta_data (1186 origin,1187 collection_id: u64,1188 item_id: u64,1189 data: Vec<u8>1190 ) -> DispatchResult {1191 let sender = ensure_signed(origin)?;1192 1193 Self::collection_exists(collection_id)?;11941195 // Modify permissions check1196 let target_collection = <Collection<T>>::get(collection_id);1197 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1198 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1199 Error::<T>::NoPermission);12001201 Self::item_exists(collection_id, item_id, &target_collection.mode)?;12021203 match target_collection.mode1204 {1205 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1206 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1207 CollectionMode::Fungible(_) => fail!(Error::<T>::CantSotreMetadataInFungibleTokens),1208 _ => fail!(Error::<T>::UnexpectedCollectionType)1209 };12101211 Ok(())1212 }1213 12141215 /// Set off-chain data schema.1216 /// 1217 /// # Permissions1218 /// 1219 /// * Collection Owner1220 /// * Collection Admin1221 /// 1222 /// # Arguments1223 /// 1224 /// * collection_id.1225 /// 1226 /// * schema: String representing the offchain data schema.1227 #[weight = T::WeightInfo::set_offchain_schema()]1228 pub fn set_offchain_schema(1229 origin,1230 collection_id: u64,1231 schema: Vec<u8>1232 ) -> DispatchResult {1233 let sender = ensure_signed(origin)?;1234 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12351236 let mut target_collection = <Collection<T>>::get(collection_id);1237 target_collection.offchain_schema = schema;1238 <Collection<T>>::insert(collection_id, target_collection);12391240 Ok(())1241 }12421243 /// Set const on-chain data schema.1244 /// 1245 /// # Permissions1246 /// 1247 /// * Collection Owner1248 /// * Collection Admin1249 /// 1250 /// # Arguments1251 /// 1252 /// * collection_id.1253 /// 1254 /// * schema: String representing the const on-chain data schema.1255 #[weight = T::WeightInfo::set_const_on_chain_schema()]1256 pub fn set_const_on_chain_schema (1257 origin,1258 collection_id: u64,1259 schema: Vec<u8>1260 ) -> DispatchResult {1261 let sender = ensure_signed(origin)?;1262 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12631264 let mut target_collection = <Collection<T>>::get(collection_id);1265 target_collection.const_on_chain_schema = schema;1266 <Collection<T>>::insert(collection_id, target_collection);12671268 Ok(())1269 }12701271 /// Set variable on-chain data schema.1272 /// 1273 /// # Permissions1274 /// 1275 /// * Collection Owner1276 /// * Collection Admin1277 /// 1278 /// # Arguments1279 /// 1280 /// * collection_id.1281 /// 1282 /// * schema: String representing the variable on-chain data schema.1283 #[weight = T::WeightInfo::set_const_on_chain_schema()]1284 pub fn set_variable_on_chain_schema (1285 origin,1286 collection_id: u64,1287 schema: Vec<u8>1288 ) -> DispatchResult {1289 let sender = ensure_signed(origin)?;1290 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12911292 let mut target_collection = <Collection<T>>::get(collection_id);1293 target_collection.variable_on_chain_schema = schema;1294 <Collection<T>>::insert(collection_id, target_collection);12951296 Ok(())1297 }12981299 // Sudo permissions function1300 #[weight = 0]1301 pub fn set_chain_limits(1302 origin,1303 limits: ChainLimits1304 ) -> DispatchResult {1305 ensure_root(origin)?;1306 <ChainLimit>::put(limits);1307 Ok(())1308 }13091310 /// Enable smart contract self-sponsoring.1311 /// 1312 /// # Permissions1313 /// 1314 /// * Contract Owner1315 /// 1316 /// # Arguments1317 /// 1318 /// * contract address1319 /// * enable flag1320 /// 1321 #[weight = 0]1322 pub fn enable_contract_sponsoring(1323 origin,1324 contract_address: T::AccountId,1325 enable: bool1326 ) -> DispatchResult {1327 let sender = ensure_signed(origin)?;1328 let mut is_owner = false;1329 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1330 let owner = <ContractOwner<T>>::get(&contract_address);1331 is_owner = sender == owner;1332 }1333 ensure!(is_owner, Error::<T>::NoPermission);13341335 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1336 Ok(())1337 }1338 }1339}13401341impl<T: Trait> Module<T> {13421343 fn can_create_items_in_collection(collection_id: u64, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {13441345 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1346 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1347 Self::check_white_list(collection_id, owner)?;1348 Self::check_white_list(collection_id, sender)?;1349 }13501351 Ok(())1352 }13531354 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1355 match target_collection.mode1356 {1357 CollectionMode::NFT => {1358 if let CreateItemData::NFT(data) = data {1359 // check sizes1360 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1361 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1362 } else {1363 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1364 }1365 },1366 CollectionMode::Fungible(_) => {1367 if let CreateItemData::Fungible(_) = data {1368 } else {1369 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1370 }1371 },1372 CollectionMode::ReFungible(_) => {1373 if let CreateItemData::ReFungible(data) = data {13741375 // check sizes1376 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1377 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1378 } else {1379 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1380 }1381 },1382 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1383 };13841385 Ok(())1386 }13871388 fn create_item_no_validation(collection_id: u64, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1389 match data1390 {1391 CreateItemData::NFT(data) => {1392 let item = NftItemType {1393 collection: collection_id,1394 owner,1395 const_data: data.const_data,1396 variable_data: data.variable_data1397 };13981399 Self::add_nft_item(item)?;1400 },1401 CreateItemData::Fungible(_) => {1402 let item = FungibleItemType {1403 collection: collection_id,1404 owner,1405 value: (10 as u128).pow(collection.decimal_points)1406 };14071408 Self::add_fungible_item(item)?;1409 },1410 CreateItemData::ReFungible(data) => {1411 let mut owner_list = Vec::new();1412 let value = (10 as u128).pow(collection.decimal_points);1413 owner_list.push(Ownership {owner: owner.clone(), fraction: value});14141415 let item = ReFungibleItemType {1416 collection: collection_id,1417 owner: owner_list,1418 const_data: data.const_data,1419 variable_data: data.variable_data1420 };14211422 Self::add_refungible_item(item)?;1423 }1424 };142514261427 // call event1428 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));14291430 Ok(())1431 }14321433 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {1434 let current_index = <ItemListIndex>::get(item.collection)1435 .checked_add(1)1436 .ok_or(Error::<T>::NumOverflow)?;1437 let itemcopy = item.clone();1438 let owner = item.owner.clone();1439 let value = item.value as u64;14401441 Self::add_token_index(item.collection, current_index, owner.clone())?;14421443 <ItemListIndex>::insert(item.collection, current_index);1444 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);14451446 // Add current block1447 let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1448 <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);1449 1450 // Update balance1451 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1452 .checked_add(value)1453 .ok_or(Error::<T>::NumOverflow)?;1454 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);14551456 Ok(())1457 }14581459 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1460 let current_index = <ItemListIndex>::get(item.collection)1461 .checked_add(1)1462 .ok_or(Error::<T>::NumOverflow)?;1463 let itemcopy = item.clone();14641465 let value = item.owner.first().unwrap().fraction as u64;1466 let owner = item.owner.first().unwrap().owner.clone();14671468 Self::add_token_index(item.collection, current_index, owner.clone())?;14691470 <ItemListIndex>::insert(item.collection, current_index);1471 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);14721473 // Add current block1474 let block_number: T::BlockNumber = 0.into();1475 <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);14761477 // Update balance1478 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1479 .checked_add(value)1480 .ok_or(Error::<T>::NumOverflow)?;1481 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);14821483 Ok(())1484 }14851486 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1487 let current_index = <ItemListIndex>::get(item.collection)1488 .checked_add(1)1489 .ok_or(Error::<T>::NumOverflow)?;14901491 let item_owner = item.owner.clone();1492 let collection_id = item.collection.clone();1493 Self::add_token_index(collection_id, current_index, item.owner.clone())?;14941495 <ItemListIndex>::insert(collection_id, current_index);1496 <NftItemList<T>>::insert(collection_id, current_index, item);14971498 // Add current block1499 let block_number: T::BlockNumber = 0.into();1500 <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);15011502 // Update balance1503 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1504 .checked_add(1)1505 .ok_or(Error::<T>::NumOverflow)?;1506 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);15071508 Ok(())1509 }15101511 fn burn_refungible_item(1512 collection_id: u64,1513 item_id: u64,1514 owner: T::AccountId,1515 ) -> DispatchResult {1516 ensure!(1517 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1518 Error::<T>::TokenNotFound1519 );1520 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1521 let item = collection1522 .owner1523 .iter()1524 .filter(|&i| i.owner == owner)1525 .next()1526 .unwrap();1527 Self::remove_token_index(collection_id, item_id, owner.clone())?;15281529 // remove approve list1530 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));15311532 // update balance1533 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1534 .checked_sub(item.fraction as u64)1535 .ok_or(Error::<T>::NumOverflow)?;1536 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);15371538 <ReFungibleItemList<T>>::remove(collection_id, item_id);15391540 Ok(())1541 }15421543 fn burn_nft_item(collection_id: u64, item_id: u64) -> DispatchResult {1544 ensure!(1545 <NftItemList<T>>::contains_key(collection_id, item_id),1546 Error::<T>::TokenNotFound1547 );1548 let item = <NftItemList<T>>::get(collection_id, item_id);1549 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;15501551 // remove approve list1552 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));15531554 // update balance1555 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1556 .checked_sub(1)1557 .ok_or(Error::<T>::NumOverflow)?;1558 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1559 <NftItemList<T>>::remove(collection_id, item_id);15601561 Ok(())1562 }15631564 fn burn_fungible_item(collection_id: u64, item_id: u64) -> DispatchResult {1565 ensure!(1566 <FungibleItemList<T>>::contains_key(collection_id, item_id),1567 Error::<T>::TokenNotFound1568 );1569 let item = <FungibleItemList<T>>::get(collection_id, item_id);1570 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;15711572 // remove approve list1573 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));15741575 // update balance1576 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1577 .checked_sub(item.value as u64)1578 .ok_or(Error::<T>::NumOverflow)?;1579 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);15801581 <FungibleItemList<T>>::remove(collection_id, item_id);15821583 Ok(())1584 }15851586 fn collection_exists(collection_id: u64) -> DispatchResult {1587 ensure!(1588 <Collection<T>>::contains_key(collection_id),1589 Error::<T>::CollectionNotFound1590 );1591 Ok(())1592 }15931594 fn check_owner_permissions(collection_id: u64, subject: T::AccountId) -> DispatchResult {1595 Self::collection_exists(collection_id)?;15961597 let target_collection = <Collection<T>>::get(collection_id);1598 ensure!(1599 subject == target_collection.owner,1600 Error::<T>::NoPermission1601 );16021603 Ok(())1604 }16051606 fn is_owner_or_admin_permissions(collection_id: u64, subject: T::AccountId) -> bool {1607 let target_collection = <Collection<T>>::get(collection_id);1608 let mut result: bool = subject == target_collection.owner;1609 let exists = <AdminList<T>>::contains_key(collection_id);16101611 if !result & exists {1612 if <AdminList<T>>::get(collection_id).contains(&subject) {1613 result = true1614 }1615 }16161617 result1618 }16191620 fn check_owner_or_admin_permissions(1621 collection_id: u64,1622 subject: T::AccountId,1623 ) -> DispatchResult {1624 Self::collection_exists(collection_id)?;1625 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());16261627 ensure!(1628 result,1629 Error::<T>::NoPermission1630 );1631 Ok(())1632 }16331634 fn is_item_owner(subject: T::AccountId, collection_id: u64, item_id: u64) -> bool {1635 let target_collection = <Collection<T>>::get(collection_id);16361637 match target_collection.mode {1638 CollectionMode::NFT => {1639 <NftItemList<T>>::get(collection_id, item_id).owner == subject1640 }1641 CollectionMode::Fungible(_) => {1642 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1643 }1644 CollectionMode::ReFungible(_) => {1645 <ReFungibleItemList<T>>::get(collection_id, item_id)1646 .owner1647 .iter()1648 .any(|i| i.owner == subject)1649 }1650 CollectionMode::Invalid => false,1651 }1652 }16531654 fn check_white_list(collection_id: u64, address: &T::AccountId) -> DispatchResult {1655 let mes = Error::<T>::AddresNotInWhiteList;1656 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1657 let wl = <WhiteList<T>>::get(collection_id);1658 ensure!(wl.contains(address), mes);16591660 Ok(())1661 }16621663 fn transfer_fungible(1664 collection_id: u64,1665 item_id: u64,1666 value: u64,1667 owner: T::AccountId,1668 new_owner: T::AccountId,1669 ) -> DispatchResult {1670 ensure!(1671 <FungibleItemList<T>>::contains_key(collection_id, item_id),1672 Error::<T>::TokenNotFound1673 );16741675 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1676 let amount = full_item.value;16771678 ensure!(amount >= value.into(), Error::<T>::TokenValueTooLow);16791680 // update balance1681 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1682 .checked_sub(value)1683 .ok_or(Error::<T>::NumOverflow)?;1684 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);16851686 let mut new_owner_account_id = 0;1687 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1688 if new_owner_items.len() > 0 {1689 new_owner_account_id = new_owner_items[0];1690 }16911692 let val64 = value.into();16931694 // transfer1695 if amount == val64 && new_owner_account_id == 0 {1696 // change owner1697 // new owner do not have account1698 let mut new_full_item = full_item.clone();1699 new_full_item.owner = new_owner.clone();1700 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);17011702 // update balance1703 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1704 .checked_add(value)1705 .ok_or(Error::<T>::NumOverflow)?;1706 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);17071708 // update index collection1709 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1710 } else {1711 let mut new_full_item = full_item.clone();1712 new_full_item.value -= val64;17131714 // separate amount1715 if new_owner_account_id > 0 {1716 // new owner has account1717 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1718 item.value += val64;17191720 // update balance1721 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1722 .checked_add(value)1723 .ok_or(Error::<T>::NumOverflow)?;1724 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);17251726 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1727 } else {1728 // new owner do not have account1729 let item = FungibleItemType {1730 collection: collection_id,1731 owner: new_owner.clone(),1732 value: val64,1733 };17341735 Self::add_fungible_item(item)?;1736 }17371738 if amount == val64 {1739 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;17401741 // remove approve list1742 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1743 <FungibleItemList<T>>::remove(collection_id, item_id);1744 }17451746 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1747 }17481749 Ok(())1750 }17511752 fn transfer_refungible(1753 collection_id: u64,1754 item_id: u64,1755 value: u64,1756 owner: T::AccountId,1757 new_owner: T::AccountId,1758 ) -> DispatchResult {1759 ensure!(1760 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1761 Error::<T>::TokenNotFound1762 );17631764 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1765 let item = full_item1766 .owner1767 .iter()1768 .filter(|i| i.owner == owner)1769 .next()1770 .ok_or(Error::<T>::NumOverflow)?;1771 let amount = item.fraction;17721773 ensure!(amount >= value.into(), Error::<T>::TokenValueTooLow);17741775 // update balance1776 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1777 .checked_sub(value)1778 .ok_or(Error::<T>::NumOverflow)?;1779 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);17801781 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1782 .checked_add(value)1783 .ok_or(Error::<T>::NumOverflow)?;1784 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);17851786 let old_owner = item.owner.clone();1787 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);1788 let val64 = value.into();17891790 // transfer1791 if amount == val64 && !new_owner_has_account {1792 // change owner1793 // new owner do not have account1794 let mut new_full_item = full_item.clone();1795 new_full_item1796 .owner1797 .iter_mut()1798 .find(|i| i.owner == owner)1799 .unwrap()1800 .owner = new_owner.clone();1801 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);18021803 // update index collection1804 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1805 } else {1806 let mut new_full_item = full_item.clone();1807 new_full_item1808 .owner1809 .iter_mut()1810 .find(|i| i.owner == owner)1811 .unwrap()1812 .fraction -= val64;18131814 // separate amount1815 if new_owner_has_account {1816 // new owner has account1817 new_full_item1818 .owner1819 .iter_mut()1820 .find(|i| i.owner == new_owner)1821 .unwrap()1822 .fraction += val64;1823 } else {1824 // new owner do not have account1825 new_full_item.owner.push(Ownership {1826 owner: new_owner.clone(),1827 fraction: val64,1828 });1829 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1830 }18311832 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1833 }18341835 Ok(())1836 }18371838 fn transfer_nft(1839 collection_id: u64,1840 item_id: u64,1841 sender: T::AccountId,1842 new_owner: T::AccountId,1843 ) -> DispatchResult {1844 ensure!(1845 <NftItemList<T>>::contains_key(collection_id, item_id),1846 Error::<T>::TokenNotFound1847 );18481849 let mut item = <NftItemList<T>>::get(collection_id, item_id);18501851 ensure!(1852 sender == item.owner,1853 Error::<T>::MustBeTokenOwner1854 );18551856 // update balance1857 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1858 .checked_sub(1)1859 .ok_or(Error::<T>::NumOverflow)?;1860 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);18611862 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1863 .checked_add(1)1864 .ok_or(Error::<T>::NumOverflow)?;1865 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);18661867 // change owner1868 let old_owner = item.owner.clone();1869 item.owner = new_owner.clone();1870 <NftItemList<T>>::insert(collection_id, item_id, item);18711872 // update index collection1873 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;18741875 // reset approved list1876 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));1877 Ok(())1878 }1879 1880 fn item_exists(1881 collection_id: u64,1882 item_id: u64,1883 mode: &CollectionMode1884 ) -> DispatchResult {1885 match mode {1886 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1887 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1888 CollectionMode::Fungible(_) => ensure!(<FungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1889 _ => ()1890 };1891 1892 Ok(())1893 }18941895 fn set_re_fungible_variable_data(1896 collection_id: u64,1897 item_id: u64,1898 data: Vec<u8>1899 ) -> DispatchResult {1900 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);19011902 item.variable_data = data;19031904 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);19051906 Ok(())1907 }19081909 fn set_nft_variable_data(1910 collection_id: u64,1911 item_id: u64,1912 data: Vec<u8>1913 ) -> DispatchResult {1914 let mut item = <NftItemList<T>>::get(collection_id, item_id);1915 1916 item.variable_data = data;19171918 <NftItemList<T>>::insert(collection_id, item_id, item);1919 1920 Ok(())1921 }19221923 fn init_collection(item: &CollectionType<T::AccountId>) {1924 // check params1925 assert!(1926 item.decimal_points <= 4,1927 "decimal_points parameter must be lower than 4"1928 );1929 assert!(1930 item.name.len() <= 64,1931 "Collection name can not be longer than 63 char"1932 );1933 assert!(1934 item.name.len() <= 256,1935 "Collection description can not be longer than 255 char"1936 );1937 assert!(1938 item.token_prefix.len() <= 16,1939 "Token prefix can not be longer than 15 char"1940 );19411942 // Generate next collection ID1943 let next_id = CreatedCollectionCount::get()1944 .checked_add(1)1945 .unwrap();19461947 CreatedCollectionCount::put(next_id);1948 }19491950 fn init_nft_token(item: &NftItemType<T::AccountId>) {1951 let current_index = <ItemListIndex>::get(item.collection)1952 .checked_add(1)1953 .unwrap();19541955 let item_owner = item.owner.clone();1956 let collection_id = item.collection.clone();1957 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();19581959 <ItemListIndex>::insert(collection_id, current_index);19601961 // Update balance1962 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1963 .checked_add(1)1964 .unwrap();1965 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);1966 }19671968 fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {1969 let current_index = <ItemListIndex>::get(item.collection)1970 .checked_add(1)1971 .unwrap();1972 let owner = item.owner.clone();1973 let value = item.value as u64;19741975 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();19761977 <ItemListIndex>::insert(item.collection, current_index);19781979 // Update balance1980 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1981 .checked_add(value)1982 .unwrap();1983 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1984 }19851986 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {1987 let current_index = <ItemListIndex>::get(item.collection)1988 .checked_add(1)1989 .unwrap();19901991 let value = item.owner.first().unwrap().fraction as u64;1992 let owner = item.owner.first().unwrap().owner.clone();19931994 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();19951996 <ItemListIndex>::insert(item.collection, current_index);19971998 // Update balance1999 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2000 .checked_add(value)2001 .unwrap();2002 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2003 }20042005 fn add_token_index(collection_id: u64, item_index: u64, owner: T::AccountId) -> DispatchResult {20062007 // add to account limit2008 if <AccountItemCount<T>>::contains_key(owner.clone()) {20092010 // bound Owned tokens by a single address2011 let count = <AccountItemCount<T>>::get(owner.clone());2012 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);20132014 <AccountItemCount<T>>::insert(owner.clone(), count2015 .checked_add(1)2016 .ok_or(Error::<T>::NumOverflow)?);2017 }2018 else {2019 <AccountItemCount<T>>::insert(owner.clone(), 1);2020 }20212022 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2023 if list_exists {2024 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2025 let item_contains = list.contains(&item_index.clone());20262027 if !item_contains {2028 list.push(item_index.clone());2029 }20302031 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2032 } else {2033 let mut itm = Vec::new();2034 itm.push(item_index.clone());2035 <AddressTokens<T>>::insert(collection_id, owner, itm);2036 2037 }20382039 Ok(())2040 }20412042 fn remove_token_index(2043 collection_id: u64,2044 item_index: u64,2045 owner: T::AccountId,2046 ) -> DispatchResult {20472048 // update counter2049 <AccountItemCount<T>>::insert(owner.clone(), 2050 <AccountItemCount<T>>::get(owner.clone())2051 .checked_sub(1)2052 .ok_or(Error::<T>::NumOverflow)?);205320542055 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2056 if list_exists {2057 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2058 let item_contains = list.contains(&item_index.clone());20592060 if item_contains {2061 list.retain(|&item| item != item_index);2062 <AddressTokens<T>>::insert(collection_id, owner, list);2063 }2064 }20652066 Ok(())2067 }20682069 fn move_token_index(2070 collection_id: u64,2071 item_index: u64,2072 old_owner: T::AccountId,2073 new_owner: T::AccountId,2074 ) -> DispatchResult {2075 Self::remove_token_index(collection_id, item_index, old_owner)?;2076 Self::add_token_index(collection_id, item_index, new_owner)?;20772078 Ok(())2079 }2080}20812082////////////////////////////////////////////////////////////////////////////////////////////////////2083// Economic models2084// #region20852086/// Fee multiplier.2087pub type Multiplier = FixedU128;20882089type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2090 <T as system::Trait>::AccountId,2091>>::Balance;2092type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2093 <T as system::Trait>::AccountId,2094>>::NegativeImbalance;20952096/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2097/// in the queue.2098#[derive(Encode, Decode, Clone, Eq, PartialEq)]2099pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2100 #[codec(compact)] BalanceOf<T>2101);21022103impl<T: Trait + Send + Sync> sp_std::fmt::Debug2104 for ChargeTransactionPayment<T>2105{2106 #[cfg(feature = "std")]2107 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2108 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2109 }2110 #[cfg(not(feature = "std"))]2111 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2112 Ok(())2113 }2114}21152116impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2117where2118 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2119 BalanceOf<T>: Send + Sync + FixedPointOperand,2120{2121 /// utility constructor. Used only in client/factory code.2122 pub fn from(fee: BalanceOf<T>) -> Self {2123 Self(fee)2124 }21252126 pub fn traditional_fee(2127 len: usize,2128 info: &DispatchInfoOf<T::Call>,2129 tip: BalanceOf<T>,2130 ) -> BalanceOf<T>2131 where2132 T::Call: Dispatchable<Info = DispatchInfo>,2133 {2134 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2135 }21362137 fn withdraw_fee(2138 &self,2139 who: &T::AccountId,2140 call: &T::Call,2141 info: &DispatchInfoOf<T::Call>,2142 len: usize,2143 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2144 let tip = self.0;21452146 // Set fee based on call type. Creating collection costs 1 Unique.2147 // All other transactions have traditional fees so far2148 // let fee = match call.is_sub_type() {2149 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2150 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2151 // // _ => <BalanceOf<T>>::from(100)2152 // };2153 let fee = Self::traditional_fee(len, info, tip);21542155 // Determine who is paying transaction fee based on ecnomic model2156 // Parse call to extract collection ID and access collection sponsor2157 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2158 Some(Call::create_item(collection_id, _properties, _owner)) => {2159 <Collection<T>>::get(collection_id).sponsor2160 }2161 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {2162 let _collection_mode = <Collection<T>>::get(collection_id).mode;21632164 // sponsor timeout2165 let sponsor_transfer = match _collection_mode {2166 CollectionMode::NFT => {2167 let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);2168 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2169 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();2170 if block_number >= limit_time {2171 <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);2172 true2173 }2174 else {2175 false2176 }2177 }2178 CollectionMode::Fungible(_) => {2179 let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);2180 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2181 if basket.iter().any(|i| i.address == _new_owner.clone())2182 {2183 let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();2184 let limit_time = item.start_block + ChainLimit::get().fungible_sponsor_transfer_timeout.into();2185 if block_number >= limit_time {2186 basket.retain(|x| x.address == item.address);2187 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });2188 <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);2189 true2190 }2191 else {2192 false2193 }2194 }2195 else {2196 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});2197 true2198 }2199 }2200 CollectionMode::ReFungible(_) => {2201 let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);2202 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2203 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();2204 if block_number >= limit_time {2205 <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);2206 true2207 } else {2208 false2209 }2210 }2211 _ => {2212 false2213 },2214 };22152216 if !sponsor_transfer {2217 T::AccountId::default()2218 } else {2219 <Collection<T>>::get(collection_id).sponsor2220 }2221 }22222223 _ => T::AccountId::default(),2224 };22252226 // Sponsor smart contracts2227 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {22282229 // On instantiation: set the contract owner2230 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {22312232 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2233 code_hash,2234 &data,2235 &who,2236 );2237 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());22382239 T::AccountId::default()2240 },22412242 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2243 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {22442245 let mut sp = T::AccountId::default();2246 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());2247 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2248 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2249 sp = called_contract;2250 }2251 }22522253 sp2254 },22552256 _ => sponsor,2257 };22582259 let mut who_pays_fee: T::AccountId = sponsor.clone();2260 if sponsor == T::AccountId::default() {2261 who_pays_fee = who.clone();2262 }22632264 // Only mess with balances if fee is not zero.2265 if fee.is_zero() {2266 return Ok((fee, None));2267 }22682269 match <T as transaction_payment::Trait>::Currency::withdraw(2270 &who_pays_fee,2271 fee,2272 if tip.is_zero() {2273 WithdrawReason::TransactionPayment.into()2274 } else {2275 WithdrawReason::TransactionPayment | WithdrawReason::Tip2276 },2277 ExistenceRequirement::KeepAlive,2278 ) {2279 Ok(imbalance) => Ok((fee, Some(imbalance))),2280 Err(_) => Err(InvalidTransaction::Payment.into()),2281 }2282 }2283}228422852286impl<T: Trait + Send + Sync> SignedExtension2287 for ChargeTransactionPayment<T>2288where2289 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2290 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2291{2292 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2293 type AccountId = T::AccountId;2294 type Call = T::Call;2295 type AdditionalSigned = ();2296 type Pre = (2297 BalanceOf<T>,2298 Self::AccountId,2299 Option<NegativeImbalanceOf<T>>,2300 BalanceOf<T>,2301 );2302 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2303 Ok(())2304 }23052306 fn validate(2307 &self,2308 _who: &Self::AccountId,2309 _call: &Self::Call,2310 _info: &DispatchInfoOf<Self::Call>,2311 _len: usize,2312 ) -> TransactionValidity {2313 Ok(ValidTransaction::default())2314 }23152316 fn pre_dispatch(2317 self,2318 who: &Self::AccountId,2319 call: &Self::Call,2320 info: &DispatchInfoOf<Self::Call>,2321 len: usize,2322 ) -> Result<Self::Pre, TransactionValidityError> {2323 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2324 Ok((self.0, who.clone(), imbalance, fee))2325 }23262327 fn post_dispatch(2328 pre: Self::Pre,2329 info: &DispatchInfoOf<Self::Call>,2330 post_info: &PostDispatchInfoOf<Self::Call>,2331 len: usize,2332 _result: &DispatchResult,2333 ) -> Result<(), TransactionValidityError> {2334 let (tip, who, imbalance, fee) = pre;2335 if let Some(payed) = imbalance {2336 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2337 len as u32, info, post_info, tip,2338 );2339 let refund = fee.saturating_sub(actual_fee);2340 let actual_payment =2341 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2342 &who, refund,2343 ) {2344 Ok(refund_imbalance) => {2345 // The refund cannot be larger than the up front payed max weight.2346 // `PostDispatchInfo::calc_unspent` guards against such a case.2347 match payed.offset(refund_imbalance) {2348 Ok(actual_payment) => actual_payment,2349 Err(_) => return Err(InvalidTransaction::Payment.into()),2350 }2351 }2352 // We do not recreate the account using the refund. The up front payment2353 // is gone in that case.2354 Err(_) => payed,2355 };2356 let imbalances = actual_payment.split(tip);2357 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2358 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2359 );2360 }2361 Ok(())2362 }2363}23642365// #endregion1#![cfg_attr(not(feature = "std"), no_std)]23#[cfg(feature = "std")]4pub use std::*;56#[cfg(feature = "std")]7pub use serde::*;89use codec::{Decode, Encode};10pub use frame_support::{11 construct_runtime, decl_event, decl_module, decl_storage, decl_error,12 dispatch::DispatchResult,13 ensure, fail, parameter_types,14 traits::{15 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,16 Randomness, WithdrawReason,17 },18 weights::{19 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},20 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,21 WeightToFeePolynomial,22 },23 IsSubType, StorageValue,24};2526use frame_system::{self as system, ensure_signed, ensure_root};27use sp_runtime::sp_std::prelude::Vec;28use sp_runtime::{29 traits::{30 DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SignedExtension, Zero,31 },32 transaction_validity::{33 InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,34 },35 FixedPointOperand, FixedU128,36};37use pallet_contracts::ContractAddressFor;38use sp_runtime::traits::StaticLookup;3940#[cfg(test)]41mod mock;4243#[cfg(test)]44mod tests;4546mod default_weights;4748// Structs49// #region5051#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]52#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]53pub enum CollectionMode {54 Invalid,55 NFT,56 // decimal points57 Fungible(u32),58 // decimal points59 ReFungible(u32),60}6162impl Into<u8> for CollectionMode {63 fn into(self) -> u8 {64 match self {65 CollectionMode::Invalid => 0,66 CollectionMode::NFT => 1,67 CollectionMode::Fungible(_) => 2,68 CollectionMode::ReFungible(_) => 3,69 }70 }71}7273#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]74#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]75pub enum AccessMode {76 Normal,77 WhiteList,78}79impl Default for AccessMode {80 fn default() -> Self {81 Self::Normal82 }83}8485impl Default for CollectionMode {86 fn default() -> Self {87 Self::Invalid88 }89}9091#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]92#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]93pub struct Ownership<AccountId> {94 pub owner: AccountId,95 pub fraction: u128,96}9798#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]99#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]100pub struct CollectionType<AccountId> {101 pub owner: AccountId,102 pub mode: CollectionMode,103 pub access: AccessMode,104 pub decimal_points: u32,105 pub name: Vec<u16>, // 64 include null escape char106 pub description: Vec<u16>, // 256 include null escape char107 pub token_prefix: Vec<u8>, // 16 include null escape char108 pub mint_mode: bool,109 pub offchain_schema: Vec<u8>,110 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender111 pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship112 pub variable_on_chain_schema: Vec<u8>, //113 pub const_on_chain_schema: Vec<u8>, //114}115116#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]117#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]118pub struct NftItemType<AccountId> {119 pub collection: u64,120 pub owner: AccountId,121 pub const_data: Vec<u8>,122 pub variable_data: Vec<u8>,123}124125#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]126#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]127pub struct FungibleItemType<AccountId> {128 pub collection: u64,129 pub owner: AccountId,130 pub value: u128,131}132133#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]134#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]135pub struct ReFungibleItemType<AccountId> {136 pub collection: u64,137 pub owner: Vec<Ownership<AccountId>>,138 pub const_data: Vec<u8>,139 pub variable_data: Vec<u8>,140}141142#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]143#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]144pub struct ApprovePermissions<AccountId> {145 pub approved: AccountId,146 pub amount: u64,147}148149#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]150#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]151pub struct VestingItem<AccountId, Moment> {152 pub sender: AccountId,153 pub recipient: AccountId,154 pub collection_id: u64,155 pub item_id: u64,156 pub amount: u64,157 pub vesting_date: Moment,158}159160#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]161#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]162pub struct BasketItem<AccountId, BlockNumber> {163 pub address: AccountId,164 pub start_block: BlockNumber,165}166167#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]168#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]169pub struct ChainLimits {170 pub collection_numbers_limit: u64,171 pub account_token_ownership_limit: u64,172 pub collections_admins_limit: u64,173 pub custom_data_limit: u32,174175 // Timeouts for item types in passed blocks176 pub nft_sponsor_transfer_timeout: u32,177 pub fungible_sponsor_transfer_timeout: u32,178 pub refungible_sponsor_transfer_timeout: u32,179}180181pub trait WeightInfo {182 fn create_collection() -> Weight;183 fn destroy_collection() -> Weight;184 fn add_to_white_list() -> Weight;185 fn remove_from_white_list() -> Weight;186 fn set_public_access_mode() -> Weight;187 fn set_mint_permission() -> Weight;188 fn change_collection_owner() -> Weight;189 fn add_collection_admin() -> Weight;190 fn remove_collection_admin() -> Weight;191 fn set_collection_sponsor() -> Weight;192 fn confirm_sponsorship() -> Weight;193 fn remove_collection_sponsor() -> Weight;194 fn create_item(s: usize) -> Weight;195 fn burn_item() -> Weight;196 fn transfer() -> Weight;197 fn approve() -> Weight;198 fn transfer_from() -> Weight;199 fn set_offchain_schema() -> Weight;200 fn set_const_on_chain_schema() -> Weight;201 fn set_variable_on_chain_schema() -> Weight;202 fn set_variable_meta_data() -> Weight;203 // fn enable_contract_sponsoring() -> Weight;204}205206#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]207#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]208pub struct CreateNftData {209 pub const_data: Vec<u8>,210 pub variable_data: Vec<u8>,211}212213#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]214#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]215pub struct CreateFungibleData {216}217218#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]219#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]220pub struct CreateReFungibleData {221 pub const_data: Vec<u8>,222 pub variable_data: Vec<u8>,223}224225#[derive(Encode, Decode, Debug, Clone, PartialEq)]226#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]227pub enum CreateItemData {228 NFT(CreateNftData),229 Fungible(CreateFungibleData),230 ReFungible(CreateReFungibleData)231}232233impl CreateItemData {234 pub fn len(&self) -> usize {235 let len = match self {236 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),237 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),238 _ => 0239 };240 241 return len;242 }243}244245impl From<CreateNftData> for CreateItemData {246 fn from(item: CreateNftData) -> Self {247 CreateItemData::NFT(item)248 }249}250251impl From<CreateReFungibleData> for CreateItemData {252 fn from(item: CreateReFungibleData) -> Self {253 CreateItemData::ReFungible(item)254 }255}256257impl From<CreateFungibleData> for CreateItemData {258 fn from(item: CreateFungibleData) -> Self {259 CreateItemData::Fungible(item)260 }261}262263264decl_error! {265 /// Error for non-fungible-token module.266 pub enum Error for Module<T: Trait> {267 /// Total collections bound exceeded.268 TotalCollectionsLimitExceeded,269 /// Decimal_points parameter must be lower than 4.270 CollectionDecimalPointLimitExceeded, 271 /// Collection name can not be longer than 63 char.272 CollectionNameLimitExceeded, 273 /// Collection description can not be longer than 255 char.274 CollectionDescriptionLimitExceeded, 275 /// Token prefix can not be longer than 15 char.276 CollectionTokenPrefixLimitExceeded,277 /// This collection does not exist.278 CollectionNotFound,279 /// Item not exists.280 TokenNotFound,281 /// Arithmetic calculation overflow.282 NumOverflow, 283 /// Account already has admin role.284 AlreadyAdmin, 285 /// You do not own this collection.286 NoPermission,287 /// This address is not set as sponsor, use setCollectionSponsor first.288 ConfirmUnsetSponsorFail,289 /// Collection is not in mint mode.290 PublicMintingNotAllowed,291 /// Sender parameter and item owner must be equal.292 MustBeTokenOwner,293 /// Item balance not enough.294 TokenValueTooLow,295 /// Size of item is too large.296 NftSizeLimitExceeded,297 /// No approve found298 ApproveNotFound,299 /// Requested value more than approved.300 TokenValueNotEnough,301 /// Only approved addresses can call this method.302 ApproveRequired,303 /// Address is not in white list.304 AddresNotInWhiteList,305 /// Number of collection admins bound exceeded.306 CollectionAdminsLimitExceeded,307 /// Owned tokens by a single address bound exceeded.308 AddressOwnershipLimitExceeded,309 /// Length of items properties must be greater than 0.310 EmptyArgument,311 /// const_data exceeded data limit.312 TokenConstDataLimitExceeded,313 /// variable_data exceeded data limit.314 TokenVariableDataLimitExceeded,315 /// Not NFT item data used to mint in NFT collection.316 NotNftDataUsedToMintNftCollectionToken,317 /// Not Fungible item data used to mint in Fungible collection.318 NotFungibleDataUsedToMintFungibleCollectionToken,319 /// Not Re Fungible item data used to mint in Re Fungible collection.320 NotReFungibleDataUsedToMintReFungibleCollectionToken,321 /// Unexpected collection type.322 UnexpectedCollectionType,323 /// Can't store metadata in fungible tokens.324 CantSotreMetadataInFungibleTokens325 }326}327328pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {329 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;330331 /// Weight information for extrinsics in this pallet.332 type WeightInfo: WeightInfo;333}334335#[cfg(feature = "runtime-benchmarks")]336mod benchmarking;337338// #endregion339340decl_storage! {341 trait Store for Module<T: Trait> as Nft {342343 // Private members344 NextCollectionID: u64;345 CreatedCollectionCount: u64;346 ChainVersion: u64;347 ItemListIndex: map hasher(blake2_128_concat) u64 => u64;348349 // Chain limits struct350 pub ChainLimit get(fn chain_limit) config(): ChainLimits;351352 // Bound counters353 CollectionCount: u64;354 pub AccountItemCount get(fn account_item_count): map hasher(identity) T::AccountId => u64;355356 // Basic collections357 pub Collection get(fn collection) config(): map hasher(identity) u64 => CollectionType<T::AccountId>;358 pub AdminList get(fn admin_list_collection): map hasher(identity) u64 => Vec<T::AccountId>;359 pub WhiteList get(fn white_list): map hasher(identity) u64 => Vec<T::AccountId>;360361 /// Balance owner per collection map362 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => u64;363364 /// second parameter: item id + owner account id365 pub ApprovedList get(fn approved): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) (u64, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;366367 /// Item collections368 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => NftItemType<T::AccountId>;369 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => FungibleItemType<T::AccountId>;370 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => ReFungibleItemType<T::AccountId>;371372 /// Index list373 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => Vec<u64>;374375 /// Tokens transfer baskets376 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => T::BlockNumber;377 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => Vec<BasketItem<T::AccountId, T::BlockNumber>>;378 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => T::BlockNumber;379380 // Contract Sponsorship and Ownership381 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;382 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;383 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;384 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;385 }386 add_extra_genesis {387 build(|config: &GenesisConfig<T>| {388 // Modification of storage389 for (_num, _c) in &config.collection {390 <Module<T>>::init_collection(_c);391 }392393 for (_num, _q, _i) in &config.nft_item_id {394 <Module<T>>::init_nft_token(_i);395 }396397 for (_num, _q, _i) in &config.fungible_item_id {398 <Module<T>>::init_fungible_token(_i);399 }400401 for (_num, _q, _i) in &config.refungible_item_id {402 <Module<T>>::init_refungible_token(_i);403 }404 })405 }406}407408decl_event!(409 pub enum Event<T>410 where411 AccountId = <T as system::Trait>::AccountId,412 {413 /// New collection was created414 /// 415 /// # Arguments416 /// 417 /// * collection_id: Globally unique identifier of newly created collection.418 /// 419 /// * mode: [CollectionMode] converted into u8.420 /// 421 /// * account_id: Collection owner.422 Created(u64, u8, AccountId),423424 /// New item was created.425 /// 426 /// # Arguments427 /// 428 /// * collection_id: Id of the collection where item was created.429 /// 430 /// * item_id: Id of an item. Unique within the collection.431 ItemCreated(u64, u64),432433 /// Collection item was burned.434 /// 435 /// # Arguments436 /// 437 /// collection_id.438 /// 439 /// item_id: Identifier of burned NFT.440 ItemDestroyed(u64, u64),441 }442);443444decl_module! {445 pub struct Module<T: Trait> for enum Call where origin: T::Origin {446447 fn deposit_event() = default;448 type Error = Error<T>;449450 fn on_initialize(now: T::BlockNumber) -> Weight {451452 if ChainVersion::get() < 2453 {454 let value = NextCollectionID::get();455 CreatedCollectionCount::put(value);456 ChainVersion::put(2);457 }458459 0460 }461462 /// 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.463 /// 464 /// # Permissions465 /// 466 /// * Anyone.467 /// 468 /// # Arguments469 /// 470 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.471 /// 472 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.473 /// 474 /// * token_prefix: UTF-8 string with token prefix.475 /// 476 /// * mode: [CollectionMode] collection type and type dependent data.477 // returns collection ID478 #[weight = T::WeightInfo::create_collection()]479 pub fn create_collection(origin,480 collection_name: Vec<u16>,481 collection_description: Vec<u16>,482 token_prefix: Vec<u8>,483 mode: CollectionMode) -> DispatchResult {484485 // Anyone can create a collection486 let who = ensure_signed(origin)?;487488 let decimal_points = match mode {489 CollectionMode::Fungible(points) => points,490 CollectionMode::ReFungible(points) => points,491 _ => 0492 };493494 // bound Total number of collections495 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);496497 // check params498 ensure!(decimal_points <= 4, Error::<T>::CollectionDecimalPointLimitExceeded);499500 let mut name = collection_name.to_vec();501 name.push(0);502 ensure!(name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);503504 let mut description = collection_description.to_vec();505 description.push(0);506 ensure!(name.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);507508 let mut prefix = token_prefix.to_vec();509 prefix.push(0);510 ensure!(prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);511512 // Generate next collection ID513 let next_id = CreatedCollectionCount::get()514 .checked_add(1)515 .ok_or(Error::<T>::NumOverflow)?;516517 // bound counter518 let total = CollectionCount::get()519 .checked_add(1)520 .ok_or(Error::<T>::NumOverflow)?;521522 CreatedCollectionCount::put(next_id);523 CollectionCount::put(total);524525 // Create new collection526 let new_collection = CollectionType {527 owner: who.clone(),528 name: name,529 mode: mode.clone(),530 mint_mode: false,531 access: AccessMode::Normal,532 description: description,533 decimal_points: decimal_points,534 token_prefix: prefix,535 offchain_schema: Vec::new(),536 sponsor: T::AccountId::default(),537 unconfirmed_sponsor: T::AccountId::default(),538 variable_on_chain_schema: Vec::new(),539 const_on_chain_schema: Vec::new(),540 };541542 // Add new collection to map543 <Collection<T>>::insert(next_id, new_collection);544545 // call event546 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));547548 Ok(())549 }550551 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.552 /// 553 /// # Permissions554 /// 555 /// * Collection Owner.556 /// 557 /// # Arguments558 /// 559 /// * collection_id: collection to destroy.560 #[weight = T::WeightInfo::destroy_collection()]561 pub fn destroy_collection(origin, collection_id: u64) -> DispatchResult {562563 let sender = ensure_signed(origin)?;564 Self::check_owner_permissions(collection_id, sender)?;565566 <AddressTokens<T>>::remove_prefix(collection_id);567 <ApprovedList<T>>::remove_prefix(collection_id);568 <Balance<T>>::remove_prefix(collection_id);569 <ItemListIndex>::remove(collection_id);570 <AdminList<T>>::remove(collection_id);571 <Collection<T>>::remove(collection_id);572 <WhiteList<T>>::remove(collection_id);573574 <NftItemList<T>>::remove_prefix(collection_id);575 <FungibleItemList<T>>::remove_prefix(collection_id);576 <ReFungibleItemList<T>>::remove_prefix(collection_id);577578 <NftTransferBasket<T>>::remove_prefix(collection_id);579 <FungibleTransferBasket<T>>::remove_prefix(collection_id);580 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);581582 if CollectionCount::get() > 0583 {584 // bound couter585 let total = CollectionCount::get()586 .checked_sub(1)587 .ok_or(Error::<T>::NumOverflow)?;588589 CollectionCount::put(total);590 }591592 Ok(())593 }594595 /// Add an address to white list.596 /// 597 /// # Permissions598 /// 599 /// * Collection Owner600 /// * Collection Admin601 /// 602 /// # Arguments603 /// 604 /// * collection_id.605 /// 606 /// * address.607 #[weight = T::WeightInfo::add_to_white_list()]608 pub fn add_to_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{609610 let sender = ensure_signed(origin)?;611 Self::check_owner_or_admin_permissions(collection_id, sender)?;612613 let mut white_list_collection: Vec<T::AccountId>;614 if <WhiteList<T>>::contains_key(collection_id) {615 white_list_collection = <WhiteList<T>>::get(collection_id);616 if !white_list_collection.contains(&address.clone())617 {618 white_list_collection.push(address.clone());619 }620 }621 else {622 white_list_collection = Vec::new();623 white_list_collection.push(address.clone());624 }625626 <WhiteList<T>>::insert(collection_id, white_list_collection);627 Ok(())628 }629630 /// Remove an address from white list.631 /// 632 /// # Permissions633 /// 634 /// * Collection Owner635 /// * Collection Admin636 /// 637 /// # Arguments638 /// 639 /// * collection_id.640 /// 641 /// * address.642 #[weight = T::WeightInfo::remove_from_white_list()]643 pub fn remove_from_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{644645 let sender = ensure_signed(origin)?;646 Self::check_owner_or_admin_permissions(collection_id, sender)?;647648 if <WhiteList<T>>::contains_key(collection_id) {649 let mut white_list_collection = <WhiteList<T>>::get(collection_id);650 if white_list_collection.contains(&address.clone())651 {652 white_list_collection.retain(|i| *i != address.clone());653 <WhiteList<T>>::insert(collection_id, white_list_collection);654 }655 }656657 Ok(())658 }659660 /// Toggle between normal and white list access for the methods with access for `Anyone`.661 /// 662 /// # Permissions663 /// 664 /// * Collection Owner.665 /// 666 /// # Arguments667 /// 668 /// * collection_id.669 /// 670 /// * mode: [AccessMode]671 #[weight = T::WeightInfo::set_public_access_mode()]672 pub fn set_public_access_mode(origin, collection_id: u64, mode: AccessMode) -> DispatchResult673 {674 let sender = ensure_signed(origin)?;675676 Self::check_owner_permissions(collection_id, sender)?;677 let mut target_collection = <Collection<T>>::get(collection_id);678 target_collection.access = mode;679 <Collection<T>>::insert(collection_id, target_collection);680681 Ok(())682 }683684 /// Allows Anyone to create tokens if:685 /// * White List is enabled, and686 /// * Address is added to white list, and687 /// * This method was called with True parameter688 /// 689 /// # Permissions690 /// * Collection Owner691 ///692 /// # Arguments693 /// 694 /// * collection_id.695 /// 696 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.697 #[weight = T::WeightInfo::set_mint_permission()]698 pub fn set_mint_permission(origin, collection_id: u64, mint_permission: bool) -> DispatchResult699 {700 let sender = ensure_signed(origin)?;701702 Self::check_owner_permissions(collection_id, sender)?;703 let mut target_collection = <Collection<T>>::get(collection_id);704 target_collection.mint_mode = mint_permission;705 <Collection<T>>::insert(collection_id, target_collection);706707 Ok(())708 }709710 /// Change the owner of the collection.711 /// 712 /// # Permissions713 /// 714 /// * Collection Owner.715 /// 716 /// # Arguments717 /// 718 /// * collection_id.719 /// 720 /// * new_owner.721 #[weight = T::WeightInfo::change_collection_owner()]722 pub fn change_collection_owner(origin, collection_id: u64, new_owner: T::AccountId) -> DispatchResult {723724 let sender = ensure_signed(origin)?;725 Self::check_owner_permissions(collection_id, sender)?;726 let mut target_collection = <Collection<T>>::get(collection_id);727 target_collection.owner = new_owner;728 <Collection<T>>::insert(collection_id, target_collection);729730 Ok(())731 }732733 /// Adds an admin of the Collection.734 /// 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. 735 /// 736 /// # Permissions737 /// 738 /// * Collection Owner.739 /// * Collection Admin.740 /// 741 /// # Arguments742 /// 743 /// * collection_id: ID of the Collection to add admin for.744 /// 745 /// * new_admin_id: Address of new admin to add.746 #[weight = T::WeightInfo::add_collection_admin()]747 pub fn add_collection_admin(origin, collection_id: u64, new_admin_id: T::AccountId) -> DispatchResult {748749 let sender = ensure_signed(origin)?;750 Self::check_owner_or_admin_permissions(collection_id, sender)?;751 let mut admin_arr: Vec<T::AccountId> = Vec::new();752753 if <AdminList<T>>::contains_key(collection_id)754 {755 admin_arr = <AdminList<T>>::get(collection_id);756 ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);757 }758759 // Number of collection admins760 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);761762 admin_arr.push(new_admin_id);763 <AdminList<T>>::insert(collection_id, admin_arr);764765 Ok(())766 }767768 /// 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.769 ///770 /// # Permissions771 /// 772 /// * Collection Owner.773 /// * Collection Admin.774 /// 775 /// # Arguments776 /// 777 /// * collection_id: ID of the Collection to remove admin for.778 /// 779 /// * account_id: Address of admin to remove.780 #[weight = T::WeightInfo::remove_collection_admin()]781 pub fn remove_collection_admin(origin, collection_id: u64, account_id: T::AccountId) -> DispatchResult {782783 let sender = ensure_signed(origin)?;784 Self::check_owner_or_admin_permissions(collection_id, sender)?;785786 if <AdminList<T>>::contains_key(collection_id)787 {788 let mut admin_arr = <AdminList<T>>::get(collection_id);789 admin_arr.retain(|i| *i != account_id);790 <AdminList<T>>::insert(collection_id, admin_arr);791 }792793 Ok(())794 }795796 /// # Permissions797 /// 798 /// * Collection Owner799 /// 800 /// # Arguments801 /// 802 /// * collection_id.803 /// 804 /// * new_sponsor.805 #[weight = T::WeightInfo::set_collection_sponsor()]806 pub fn set_collection_sponsor(origin, collection_id: u64, new_sponsor: T::AccountId) -> DispatchResult {807808 let sender = ensure_signed(origin)?;809 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);810811 let mut target_collection = <Collection<T>>::get(collection_id);812 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);813814 target_collection.unconfirmed_sponsor = new_sponsor;815 <Collection<T>>::insert(collection_id, target_collection);816817 Ok(())818 }819820 /// # Permissions821 /// 822 /// * Sponsor.823 /// 824 /// # Arguments825 /// 826 /// * collection_id.827 #[weight = T::WeightInfo::confirm_sponsorship()]828 pub fn confirm_sponsorship(origin, collection_id: u64) -> DispatchResult {829830 let sender = ensure_signed(origin)?;831 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);832833 let mut target_collection = <Collection<T>>::get(collection_id);834 ensure!(sender == target_collection.unconfirmed_sponsor, Error::<T>::ConfirmUnsetSponsorFail);835836 target_collection.sponsor = target_collection.unconfirmed_sponsor;837 target_collection.unconfirmed_sponsor = T::AccountId::default();838 <Collection<T>>::insert(collection_id, target_collection);839840 Ok(())841 }842843 /// Switch back to pay-per-own-transaction model.844 ///845 /// # Permissions846 ///847 /// * Collection owner.848 /// 849 /// # Arguments850 /// 851 /// * collection_id.852 #[weight = T::WeightInfo::remove_collection_sponsor()]853 pub fn remove_collection_sponsor(origin, collection_id: u64) -> DispatchResult {854855 let sender = ensure_signed(origin)?;856 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);857858 let mut target_collection = <Collection<T>>::get(collection_id);859 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);860861 target_collection.sponsor = T::AccountId::default();862 <Collection<T>>::insert(collection_id, target_collection);863864 Ok(())865 }866867 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.868 /// 869 /// # Permissions870 /// 871 /// * Collection Owner.872 /// * Collection Admin.873 /// * Anyone if874 /// * White List is enabled, and875 /// * Address is added to white list, and876 /// * MintPermission is enabled (see SetMintPermission method)877 /// 878 /// # Arguments879 /// 880 /// * collection_id: ID of the collection.881 /// 882 /// * owner: Address, initial owner of the NFT.883 ///884 /// * data: Token data to store on chain.885 // #[weight =886 // (130_000_000 as Weight)887 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))888 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))889 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]890891 #[weight = T::WeightInfo::create_item(data.len())]892 pub fn create_item(origin, collection_id: u64, owner: T::AccountId, data: CreateItemData) -> DispatchResult {893894 let sender = ensure_signed(origin)?;895896 Self::collection_exists(collection_id)?;897898 let target_collection = <Collection<T>>::get(collection_id);899900 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;901 Self::validate_create_item_args(&target_collection, &data)?;902 Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;903904 Ok(())905 }906907 /// This method creates multiple instances of NFT Collection created with CreateCollection method.908 /// 909 /// # Permissions910 /// 911 /// * Collection Owner.912 /// * Collection Admin.913 /// * Anyone if914 /// * White List is enabled, and915 /// * Address is added to white list, and916 /// * MintPermission is enabled (see SetMintPermission method)917 /// 918 /// # Arguments919 /// 920 /// * collection_id: ID of the collection.921 /// 922 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].923 /// 924 /// * owner: Address, initial owner of the NFT.925 #[weight = T::WeightInfo::create_item(items_data.into_iter()926 .map(|data| { data.len() })927 .sum())]928 pub fn create_multiple_items(origin, collection_id: u64, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {929930 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);931 let sender = ensure_signed(origin)?;932933 Self::collection_exists(collection_id)?;934 let target_collection = <Collection<T>>::get(collection_id);935936 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;937938 for data in &items_data {939 Self::validate_create_item_args(&target_collection, data)?;940 }941 for data in &items_data {942 Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;943 }944945 Ok(())946 }947948 /// Destroys a concrete instance of NFT.949 /// 950 /// # Permissions951 /// 952 /// * Collection Owner.953 /// * Collection Admin.954 /// * Current NFT Owner.955 /// 956 /// # Arguments957 /// 958 /// * collection_id: ID of the collection.959 /// 960 /// * item_id: ID of NFT to burn.961 #[weight = T::WeightInfo::burn_item()]962 pub fn burn_item(origin, collection_id: u64, item_id: u64) -> DispatchResult {963964 let sender = ensure_signed(origin)?;965 Self::collection_exists(collection_id)?;966967 // Transfer permissions check968 let target_collection = <Collection<T>>::get(collection_id);969 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||970 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),971 Error::<T>::NoPermission);972973 if target_collection.access == AccessMode::WhiteList {974 Self::check_white_list(collection_id, &sender)?;975 }976977 match target_collection.mode978 {979 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,980 CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?,981 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,982 _ => ()983 };984985 // call event986 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));987988 Ok(())989 }990991 /// Change ownership of the token.992 /// 993 /// # Permissions994 /// 995 /// * Collection Owner996 /// * Collection Admin997 /// * Current NFT owner998 ///999 /// # Arguments1000 /// 1001 /// * recipient: Address of token recipient.1002 /// 1003 /// * collection_id.1004 /// 1005 /// * item_id: ID of the item1006 /// * Non-Fungible Mode: Required.1007 /// * Fungible Mode: Ignored.1008 /// * Re-Fungible Mode: Required.1009 /// 1010 /// * value: Amount to transfer.1011 /// * Non-Fungible Mode: Ignored1012 /// * Fungible Mode: Must specify transferred amount1013 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1014 #[weight = T::WeightInfo::transfer()]1015 pub fn transfer(origin, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64) -> DispatchResult {10161017 let sender = ensure_signed(origin)?;10181019 // Transfer permissions check1020 let target_collection = <Collection<T>>::get(collection_id);1021 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1022 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1023 Error::<T>::NoPermission);10241025 if target_collection.access == AccessMode::WhiteList {1026 Self::check_white_list(collection_id, &sender)?;1027 Self::check_white_list(collection_id, &recipient)?;1028 }10291030 match target_collection.mode1031 {1032 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1033 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,1034 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1035 _ => ()1036 };10371038 Ok(())1039 }10401041 /// Set, change, or remove approved address to transfer the ownership of the NFT.1042 /// 1043 /// # Permissions1044 /// 1045 /// * Collection Owner1046 /// * Collection Admin1047 /// * Current NFT owner1048 /// 1049 /// # Arguments1050 /// 1051 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1052 /// 1053 /// * collection_id.1054 /// 1055 /// * item_id: ID of the item.1056 #[weight = T::WeightInfo::approve()]1057 pub fn approve(origin, approved: T::AccountId, collection_id: u64, item_id: u64) -> DispatchResult {10581059 let sender = ensure_signed(origin)?;10601061 // Transfer permissions check1062 let target_collection = <Collection<T>>::get(collection_id);1063 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1064 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1065 Error::<T>::NoPermission);10661067 if target_collection.access == AccessMode::WhiteList {1068 Self::check_white_list(collection_id, &sender)?;1069 Self::check_white_list(collection_id, &approved)?;1070 }10711072 // amount param stub1073 let amount = 100000000;10741075 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));1076 if list_exists {10771078 let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));1079 let item_contains = list.iter().any(|i| i.approved == approved);10801081 if !item_contains {1082 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1083 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1084 }1085 } else {10861087 let mut list = Vec::new();1088 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1089 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1090 }10911092 Ok(())1093 }1094 1095 /// 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.1096 /// 1097 /// # Permissions1098 /// * Collection Owner1099 /// * Collection Admin1100 /// * Current NFT owner1101 /// * Address approved by current NFT owner1102 /// 1103 /// # Arguments1104 /// 1105 /// * from: Address that owns token.1106 /// 1107 /// * recipient: Address of token recipient.1108 /// 1109 /// * collection_id.1110 /// 1111 /// * item_id: ID of the item.1112 /// 1113 /// * value: Amount to transfer.1114 #[weight = T::WeightInfo::transfer_from()]1115 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64 ) -> DispatchResult {11161117 let sender = ensure_signed(origin)?;1118 let mut appoved_transfer = false;11191120 // Check approve1121 if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {1122 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));1123 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());1124 if opt_item.is_some()1125 {1126 appoved_transfer = true;1127 ensure!(opt_item.unwrap().amount >= value, Error::<T>::TokenValueNotEnough);1128 }1129 }11301131 // Transfer permissions check1132 let target_collection = <Collection<T>>::get(collection_id);1133 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1134 Error::<T>::NoPermission);11351136 if target_collection.access == AccessMode::WhiteList {1137 Self::check_white_list(collection_id, &sender)?;1138 Self::check_white_list(collection_id, &recipient)?;1139 }11401141 // remove approve1142 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))1143 .into_iter().filter(|i| i.approved != sender.clone()).collect();1144 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);114511461147 match target_collection.mode1148 {1149 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1150 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,1151 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1152 _ => ()1153 };11541155 Ok(())1156 }11571158 ///1159 #[weight = 0]1160 pub fn safe_transfer_from(origin, collection_id: u64, item_id: u64, new_owner: T::AccountId) -> DispatchResult {11611162 // let no_perm_mes = "You do not have permissions to modify this collection";1163 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1164 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1165 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);11661167 // // on_nft_received call11681169 // Self::transfer(origin, collection_id, item_id, new_owner)?;11701171 Ok(())1172 }11731174 /// Set off-chain data schema.1175 /// 1176 /// # Permissions1177 /// 1178 /// * Collection Owner1179 /// * Collection Admin1180 /// 1181 /// # Arguments1182 /// 1183 /// * collection_id.1184 /// 1185 /// * schema: String representing the offchain data schema.1186 #[weight = T::WeightInfo::set_variable_meta_data()]1187 pub fn set_variable_meta_data (1188 origin,1189 collection_id: u64,1190 item_id: u64,1191 data: Vec<u8>1192 ) -> DispatchResult {1193 let sender = ensure_signed(origin)?;1194 1195 Self::collection_exists(collection_id)?;11961197 // Modify permissions check1198 let target_collection = <Collection<T>>::get(collection_id);1199 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1200 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1201 Error::<T>::NoPermission);12021203 Self::item_exists(collection_id, item_id, &target_collection.mode)?;12041205 match target_collection.mode1206 {1207 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1208 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1209 CollectionMode::Fungible(_) => fail!(Error::<T>::CantSotreMetadataInFungibleTokens),1210 _ => fail!(Error::<T>::UnexpectedCollectionType)1211 };12121213 Ok(())1214 }1215 12161217 /// Set off-chain data schema.1218 /// 1219 /// # Permissions1220 /// 1221 /// * Collection Owner1222 /// * Collection Admin1223 /// 1224 /// # Arguments1225 /// 1226 /// * collection_id.1227 /// 1228 /// * schema: String representing the offchain data schema.1229 #[weight = T::WeightInfo::set_offchain_schema()]1230 pub fn set_offchain_schema(1231 origin,1232 collection_id: u64,1233 schema: Vec<u8>1234 ) -> DispatchResult {1235 let sender = ensure_signed(origin)?;1236 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12371238 let mut target_collection = <Collection<T>>::get(collection_id);1239 target_collection.offchain_schema = schema;1240 <Collection<T>>::insert(collection_id, target_collection);12411242 Ok(())1243 }12441245 /// Set const on-chain data schema.1246 /// 1247 /// # Permissions1248 /// 1249 /// * Collection Owner1250 /// * Collection Admin1251 /// 1252 /// # Arguments1253 /// 1254 /// * collection_id.1255 /// 1256 /// * schema: String representing the const on-chain data schema.1257 #[weight = T::WeightInfo::set_const_on_chain_schema()]1258 pub fn set_const_on_chain_schema (1259 origin,1260 collection_id: u64,1261 schema: Vec<u8>1262 ) -> DispatchResult {1263 let sender = ensure_signed(origin)?;1264 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12651266 let mut target_collection = <Collection<T>>::get(collection_id);1267 target_collection.const_on_chain_schema = schema;1268 <Collection<T>>::insert(collection_id, target_collection);12691270 Ok(())1271 }12721273 /// Set variable on-chain data schema.1274 /// 1275 /// # Permissions1276 /// 1277 /// * Collection Owner1278 /// * Collection Admin1279 /// 1280 /// # Arguments1281 /// 1282 /// * collection_id.1283 /// 1284 /// * schema: String representing the variable on-chain data schema.1285 #[weight = T::WeightInfo::set_const_on_chain_schema()]1286 pub fn set_variable_on_chain_schema (1287 origin,1288 collection_id: u64,1289 schema: Vec<u8>1290 ) -> DispatchResult {1291 let sender = ensure_signed(origin)?;1292 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12931294 let mut target_collection = <Collection<T>>::get(collection_id);1295 target_collection.variable_on_chain_schema = schema;1296 <Collection<T>>::insert(collection_id, target_collection);12971298 Ok(())1299 }13001301 // Sudo permissions function1302 #[weight = 0]1303 pub fn set_chain_limits(1304 origin,1305 limits: ChainLimits1306 ) -> DispatchResult {1307 ensure_root(origin)?;1308 <ChainLimit>::put(limits);1309 Ok(())1310 }13111312 /// Enable smart contract self-sponsoring.1313 /// 1314 /// # Permissions1315 /// 1316 /// * Contract Owner1317 /// 1318 /// # Arguments1319 /// 1320 /// * contract address1321 /// * enable flag1322 /// 1323 #[weight = 0]1324 pub fn enable_contract_sponsoring(1325 origin,1326 contract_address: T::AccountId,1327 enable: bool1328 ) -> DispatchResult {1329 let sender = ensure_signed(origin)?;1330 let mut is_owner = false;1331 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1332 let owner = <ContractOwner<T>>::get(&contract_address);1333 is_owner = sender == owner;1334 }1335 ensure!(is_owner, Error::<T>::NoPermission);13361337 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1338 Ok(())1339 }13401341 /// Set the rate limit for contract sponsoring to specified number of blocks.1342 /// 1343 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1344 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1345 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1346 /// from contract endowment if there are at least B blocks between such transactions. 1347 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1348 /// 1349 /// # Permissions1350 /// 1351 /// * Contract Owner1352 /// 1353 /// # Arguments1354 /// 1355 /// -`contract_address`: Address of the contract to sponsor1356 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1357 /// 1358 #[weight = 0]1359 pub fn set_contract_sponsoring_rate_limit(1360 origin,1361 contract_address: T::AccountId,1362 rate_limit: T::BlockNumber1363 ) -> DispatchResult {1364 let sender = ensure_signed(origin)?;1365 let mut is_owner = false;1366 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1367 let owner = <ContractOwner<T>>::get(&contract_address);1368 is_owner = sender == owner;1369 }1370 ensure!(is_owner, Error::<T>::NoPermission);13711372 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1373 Ok(())1374 }13751376 }1377}13781379impl<T: Trait> Module<T> {13801381 fn can_create_items_in_collection(collection_id: u64, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {13821383 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1384 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1385 Self::check_white_list(collection_id, owner)?;1386 Self::check_white_list(collection_id, sender)?;1387 }13881389 Ok(())1390 }13911392 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1393 match target_collection.mode1394 {1395 CollectionMode::NFT => {1396 if let CreateItemData::NFT(data) = data {1397 // check sizes1398 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1399 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1400 } else {1401 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1402 }1403 },1404 CollectionMode::Fungible(_) => {1405 if let CreateItemData::Fungible(_) = data {1406 } else {1407 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1408 }1409 },1410 CollectionMode::ReFungible(_) => {1411 if let CreateItemData::ReFungible(data) = data {14121413 // check sizes1414 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1415 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1416 } else {1417 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1418 }1419 },1420 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1421 };14221423 Ok(())1424 }14251426 fn create_item_no_validation(collection_id: u64, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1427 match data1428 {1429 CreateItemData::NFT(data) => {1430 let item = NftItemType {1431 collection: collection_id,1432 owner,1433 const_data: data.const_data,1434 variable_data: data.variable_data1435 };14361437 Self::add_nft_item(item)?;1438 },1439 CreateItemData::Fungible(_) => {1440 let item = FungibleItemType {1441 collection: collection_id,1442 owner,1443 value: (10 as u128).pow(collection.decimal_points)1444 };14451446 Self::add_fungible_item(item)?;1447 },1448 CreateItemData::ReFungible(data) => {1449 let mut owner_list = Vec::new();1450 let value = (10 as u128).pow(collection.decimal_points);1451 owner_list.push(Ownership {owner: owner.clone(), fraction: value});14521453 let item = ReFungibleItemType {1454 collection: collection_id,1455 owner: owner_list,1456 const_data: data.const_data,1457 variable_data: data.variable_data1458 };14591460 Self::add_refungible_item(item)?;1461 }1462 };146314641465 // call event1466 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));14671468 Ok(())1469 }14701471 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {1472 let current_index = <ItemListIndex>::get(item.collection)1473 .checked_add(1)1474 .ok_or(Error::<T>::NumOverflow)?;1475 let itemcopy = item.clone();1476 let owner = item.owner.clone();1477 let value = item.value as u64;14781479 Self::add_token_index(item.collection, current_index, owner.clone())?;14801481 <ItemListIndex>::insert(item.collection, current_index);1482 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);14831484 // Add current block1485 let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1486 <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);1487 1488 // Update balance1489 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1490 .checked_add(value)1491 .ok_or(Error::<T>::NumOverflow)?;1492 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);14931494 Ok(())1495 }14961497 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1498 let current_index = <ItemListIndex>::get(item.collection)1499 .checked_add(1)1500 .ok_or(Error::<T>::NumOverflow)?;1501 let itemcopy = item.clone();15021503 let value = item.owner.first().unwrap().fraction as u64;1504 let owner = item.owner.first().unwrap().owner.clone();15051506 Self::add_token_index(item.collection, current_index, owner.clone())?;15071508 <ItemListIndex>::insert(item.collection, current_index);1509 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);15101511 // Add current block1512 let block_number: T::BlockNumber = 0.into();1513 <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);15141515 // Update balance1516 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1517 .checked_add(value)1518 .ok_or(Error::<T>::NumOverflow)?;1519 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);15201521 Ok(())1522 }15231524 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1525 let current_index = <ItemListIndex>::get(item.collection)1526 .checked_add(1)1527 .ok_or(Error::<T>::NumOverflow)?;15281529 let item_owner = item.owner.clone();1530 let collection_id = item.collection.clone();1531 Self::add_token_index(collection_id, current_index, item.owner.clone())?;15321533 <ItemListIndex>::insert(collection_id, current_index);1534 <NftItemList<T>>::insert(collection_id, current_index, item);15351536 // Add current block1537 let block_number: T::BlockNumber = 0.into();1538 <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);15391540 // Update balance1541 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1542 .checked_add(1)1543 .ok_or(Error::<T>::NumOverflow)?;1544 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);15451546 Ok(())1547 }15481549 fn burn_refungible_item(1550 collection_id: u64,1551 item_id: u64,1552 owner: T::AccountId,1553 ) -> DispatchResult {1554 ensure!(1555 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1556 Error::<T>::TokenNotFound1557 );1558 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1559 let item = collection1560 .owner1561 .iter()1562 .filter(|&i| i.owner == owner)1563 .next()1564 .unwrap();1565 Self::remove_token_index(collection_id, item_id, owner.clone())?;15661567 // remove approve list1568 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));15691570 // update balance1571 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1572 .checked_sub(item.fraction as u64)1573 .ok_or(Error::<T>::NumOverflow)?;1574 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);15751576 <ReFungibleItemList<T>>::remove(collection_id, item_id);15771578 Ok(())1579 }15801581 fn burn_nft_item(collection_id: u64, item_id: u64) -> DispatchResult {1582 ensure!(1583 <NftItemList<T>>::contains_key(collection_id, item_id),1584 Error::<T>::TokenNotFound1585 );1586 let item = <NftItemList<T>>::get(collection_id, item_id);1587 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;15881589 // remove approve list1590 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));15911592 // update balance1593 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1594 .checked_sub(1)1595 .ok_or(Error::<T>::NumOverflow)?;1596 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1597 <NftItemList<T>>::remove(collection_id, item_id);15981599 Ok(())1600 }16011602 fn burn_fungible_item(collection_id: u64, item_id: u64) -> DispatchResult {1603 ensure!(1604 <FungibleItemList<T>>::contains_key(collection_id, item_id),1605 Error::<T>::TokenNotFound1606 );1607 let item = <FungibleItemList<T>>::get(collection_id, item_id);1608 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;16091610 // remove approve list1611 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));16121613 // update balance1614 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1615 .checked_sub(item.value as u64)1616 .ok_or(Error::<T>::NumOverflow)?;1617 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);16181619 <FungibleItemList<T>>::remove(collection_id, item_id);16201621 Ok(())1622 }16231624 fn collection_exists(collection_id: u64) -> DispatchResult {1625 ensure!(1626 <Collection<T>>::contains_key(collection_id),1627 Error::<T>::CollectionNotFound1628 );1629 Ok(())1630 }16311632 fn check_owner_permissions(collection_id: u64, subject: T::AccountId) -> DispatchResult {1633 Self::collection_exists(collection_id)?;16341635 let target_collection = <Collection<T>>::get(collection_id);1636 ensure!(1637 subject == target_collection.owner,1638 Error::<T>::NoPermission1639 );16401641 Ok(())1642 }16431644 fn is_owner_or_admin_permissions(collection_id: u64, subject: T::AccountId) -> bool {1645 let target_collection = <Collection<T>>::get(collection_id);1646 let mut result: bool = subject == target_collection.owner;1647 let exists = <AdminList<T>>::contains_key(collection_id);16481649 if !result & exists {1650 if <AdminList<T>>::get(collection_id).contains(&subject) {1651 result = true1652 }1653 }16541655 result1656 }16571658 fn check_owner_or_admin_permissions(1659 collection_id: u64,1660 subject: T::AccountId,1661 ) -> DispatchResult {1662 Self::collection_exists(collection_id)?;1663 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());16641665 ensure!(1666 result,1667 Error::<T>::NoPermission1668 );1669 Ok(())1670 }16711672 fn is_item_owner(subject: T::AccountId, collection_id: u64, item_id: u64) -> bool {1673 let target_collection = <Collection<T>>::get(collection_id);16741675 match target_collection.mode {1676 CollectionMode::NFT => {1677 <NftItemList<T>>::get(collection_id, item_id).owner == subject1678 }1679 CollectionMode::Fungible(_) => {1680 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1681 }1682 CollectionMode::ReFungible(_) => {1683 <ReFungibleItemList<T>>::get(collection_id, item_id)1684 .owner1685 .iter()1686 .any(|i| i.owner == subject)1687 }1688 CollectionMode::Invalid => false,1689 }1690 }16911692 fn check_white_list(collection_id: u64, address: &T::AccountId) -> DispatchResult {1693 let mes = Error::<T>::AddresNotInWhiteList;1694 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1695 let wl = <WhiteList<T>>::get(collection_id);1696 ensure!(wl.contains(address), mes);16971698 Ok(())1699 }17001701 fn transfer_fungible(1702 collection_id: u64,1703 item_id: u64,1704 value: u64,1705 owner: T::AccountId,1706 new_owner: T::AccountId,1707 ) -> DispatchResult {1708 ensure!(1709 <FungibleItemList<T>>::contains_key(collection_id, item_id),1710 Error::<T>::TokenNotFound1711 );17121713 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1714 let amount = full_item.value;17151716 ensure!(amount >= value.into(), Error::<T>::TokenValueTooLow);17171718 // update balance1719 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1720 .checked_sub(value)1721 .ok_or(Error::<T>::NumOverflow)?;1722 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);17231724 let mut new_owner_account_id = 0;1725 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1726 if new_owner_items.len() > 0 {1727 new_owner_account_id = new_owner_items[0];1728 }17291730 let val64 = value.into();17311732 // transfer1733 if amount == val64 && new_owner_account_id == 0 {1734 // change owner1735 // new owner do not have account1736 let mut new_full_item = full_item.clone();1737 new_full_item.owner = new_owner.clone();1738 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);17391740 // update balance1741 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1742 .checked_add(value)1743 .ok_or(Error::<T>::NumOverflow)?;1744 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);17451746 // update index collection1747 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1748 } else {1749 let mut new_full_item = full_item.clone();1750 new_full_item.value -= val64;17511752 // separate amount1753 if new_owner_account_id > 0 {1754 // new owner has account1755 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1756 item.value += val64;17571758 // update balance1759 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1760 .checked_add(value)1761 .ok_or(Error::<T>::NumOverflow)?;1762 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);17631764 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1765 } else {1766 // new owner do not have account1767 let item = FungibleItemType {1768 collection: collection_id,1769 owner: new_owner.clone(),1770 value: val64,1771 };17721773 Self::add_fungible_item(item)?;1774 }17751776 if amount == val64 {1777 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;17781779 // remove approve list1780 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1781 <FungibleItemList<T>>::remove(collection_id, item_id);1782 }17831784 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1785 }17861787 Ok(())1788 }17891790 fn transfer_refungible(1791 collection_id: u64,1792 item_id: u64,1793 value: u64,1794 owner: T::AccountId,1795 new_owner: T::AccountId,1796 ) -> DispatchResult {1797 ensure!(1798 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1799 Error::<T>::TokenNotFound1800 );18011802 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1803 let item = full_item1804 .owner1805 .iter()1806 .filter(|i| i.owner == owner)1807 .next()1808 .ok_or(Error::<T>::NumOverflow)?;1809 let amount = item.fraction;18101811 ensure!(amount >= value.into(), Error::<T>::TokenValueTooLow);18121813 // update balance1814 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1815 .checked_sub(value)1816 .ok_or(Error::<T>::NumOverflow)?;1817 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);18181819 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1820 .checked_add(value)1821 .ok_or(Error::<T>::NumOverflow)?;1822 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);18231824 let old_owner = item.owner.clone();1825 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);1826 let val64 = value.into();18271828 // transfer1829 if amount == val64 && !new_owner_has_account {1830 // change owner1831 // new owner do not have account1832 let mut new_full_item = full_item.clone();1833 new_full_item1834 .owner1835 .iter_mut()1836 .find(|i| i.owner == owner)1837 .unwrap()1838 .owner = new_owner.clone();1839 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);18401841 // update index collection1842 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1843 } else {1844 let mut new_full_item = full_item.clone();1845 new_full_item1846 .owner1847 .iter_mut()1848 .find(|i| i.owner == owner)1849 .unwrap()1850 .fraction -= val64;18511852 // separate amount1853 if new_owner_has_account {1854 // new owner has account1855 new_full_item1856 .owner1857 .iter_mut()1858 .find(|i| i.owner == new_owner)1859 .unwrap()1860 .fraction += val64;1861 } else {1862 // new owner do not have account1863 new_full_item.owner.push(Ownership {1864 owner: new_owner.clone(),1865 fraction: val64,1866 });1867 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1868 }18691870 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1871 }18721873 Ok(())1874 }18751876 fn transfer_nft(1877 collection_id: u64,1878 item_id: u64,1879 sender: T::AccountId,1880 new_owner: T::AccountId,1881 ) -> DispatchResult {1882 ensure!(1883 <NftItemList<T>>::contains_key(collection_id, item_id),1884 Error::<T>::TokenNotFound1885 );18861887 let mut item = <NftItemList<T>>::get(collection_id, item_id);18881889 ensure!(1890 sender == item.owner,1891 Error::<T>::MustBeTokenOwner1892 );18931894 // update balance1895 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1896 .checked_sub(1)1897 .ok_or(Error::<T>::NumOverflow)?;1898 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);18991900 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1901 .checked_add(1)1902 .ok_or(Error::<T>::NumOverflow)?;1903 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);19041905 // change owner1906 let old_owner = item.owner.clone();1907 item.owner = new_owner.clone();1908 <NftItemList<T>>::insert(collection_id, item_id, item);19091910 // update index collection1911 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;19121913 // reset approved list1914 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));1915 Ok(())1916 }1917 1918 fn item_exists(1919 collection_id: u64,1920 item_id: u64,1921 mode: &CollectionMode1922 ) -> DispatchResult {1923 match mode {1924 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1925 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1926 CollectionMode::Fungible(_) => ensure!(<FungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1927 _ => ()1928 };1929 1930 Ok(())1931 }19321933 fn set_re_fungible_variable_data(1934 collection_id: u64,1935 item_id: u64,1936 data: Vec<u8>1937 ) -> DispatchResult {1938 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);19391940 item.variable_data = data;19411942 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);19431944 Ok(())1945 }19461947 fn set_nft_variable_data(1948 collection_id: u64,1949 item_id: u64,1950 data: Vec<u8>1951 ) -> DispatchResult {1952 let mut item = <NftItemList<T>>::get(collection_id, item_id);1953 1954 item.variable_data = data;19551956 <NftItemList<T>>::insert(collection_id, item_id, item);1957 1958 Ok(())1959 }19601961 fn init_collection(item: &CollectionType<T::AccountId>) {1962 // check params1963 assert!(1964 item.decimal_points <= 4,1965 "decimal_points parameter must be lower than 4"1966 );1967 assert!(1968 item.name.len() <= 64,1969 "Collection name can not be longer than 63 char"1970 );1971 assert!(1972 item.name.len() <= 256,1973 "Collection description can not be longer than 255 char"1974 );1975 assert!(1976 item.token_prefix.len() <= 16,1977 "Token prefix can not be longer than 15 char"1978 );19791980 // Generate next collection ID1981 let next_id = CreatedCollectionCount::get()1982 .checked_add(1)1983 .unwrap();19841985 CreatedCollectionCount::put(next_id);1986 }19871988 fn init_nft_token(item: &NftItemType<T::AccountId>) {1989 let current_index = <ItemListIndex>::get(item.collection)1990 .checked_add(1)1991 .unwrap();19921993 let item_owner = item.owner.clone();1994 let collection_id = item.collection.clone();1995 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();19961997 <ItemListIndex>::insert(collection_id, current_index);19981999 // Update balance2000 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())2001 .checked_add(1)2002 .unwrap();2003 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2004 }20052006 fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {2007 let current_index = <ItemListIndex>::get(item.collection)2008 .checked_add(1)2009 .unwrap();2010 let owner = item.owner.clone();2011 let value = item.value as u64;20122013 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();20142015 <ItemListIndex>::insert(item.collection, current_index);20162017 // Update balance2018 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2019 .checked_add(value)2020 .unwrap();2021 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2022 }20232024 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {2025 let current_index = <ItemListIndex>::get(item.collection)2026 .checked_add(1)2027 .unwrap();20282029 let value = item.owner.first().unwrap().fraction as u64;2030 let owner = item.owner.first().unwrap().owner.clone();20312032 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();20332034 <ItemListIndex>::insert(item.collection, current_index);20352036 // Update balance2037 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2038 .checked_add(value)2039 .unwrap();2040 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2041 }20422043 fn add_token_index(collection_id: u64, item_index: u64, owner: T::AccountId) -> DispatchResult {20442045 // add to account limit2046 if <AccountItemCount<T>>::contains_key(owner.clone()) {20472048 // bound Owned tokens by a single address2049 let count = <AccountItemCount<T>>::get(owner.clone());2050 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);20512052 <AccountItemCount<T>>::insert(owner.clone(), count2053 .checked_add(1)2054 .ok_or(Error::<T>::NumOverflow)?);2055 }2056 else {2057 <AccountItemCount<T>>::insert(owner.clone(), 1);2058 }20592060 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2061 if list_exists {2062 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2063 let item_contains = list.contains(&item_index.clone());20642065 if !item_contains {2066 list.push(item_index.clone());2067 }20682069 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2070 } else {2071 let mut itm = Vec::new();2072 itm.push(item_index.clone());2073 <AddressTokens<T>>::insert(collection_id, owner, itm);2074 2075 }20762077 Ok(())2078 }20792080 fn remove_token_index(2081 collection_id: u64,2082 item_index: u64,2083 owner: T::AccountId,2084 ) -> DispatchResult {20852086 // update counter2087 <AccountItemCount<T>>::insert(owner.clone(), 2088 <AccountItemCount<T>>::get(owner.clone())2089 .checked_sub(1)2090 .ok_or(Error::<T>::NumOverflow)?);209120922093 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2094 if list_exists {2095 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2096 let item_contains = list.contains(&item_index.clone());20972098 if item_contains {2099 list.retain(|&item| item != item_index);2100 <AddressTokens<T>>::insert(collection_id, owner, list);2101 }2102 }21032104 Ok(())2105 }21062107 fn move_token_index(2108 collection_id: u64,2109 item_index: u64,2110 old_owner: T::AccountId,2111 new_owner: T::AccountId,2112 ) -> DispatchResult {2113 Self::remove_token_index(collection_id, item_index, old_owner)?;2114 Self::add_token_index(collection_id, item_index, new_owner)?;21152116 Ok(())2117 }2118}21192120////////////////////////////////////////////////////////////////////////////////////////////////////2121// Economic models2122// #region21232124/// Fee multiplier.2125pub type Multiplier = FixedU128;21262127type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2128 <T as system::Trait>::AccountId,2129>>::Balance;2130type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2131 <T as system::Trait>::AccountId,2132>>::NegativeImbalance;21332134/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2135/// in the queue.2136#[derive(Encode, Decode, Clone, Eq, PartialEq)]2137pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2138 #[codec(compact)] BalanceOf<T>2139);21402141impl<T: Trait + Send + Sync> sp_std::fmt::Debug2142 for ChargeTransactionPayment<T>2143{2144 #[cfg(feature = "std")]2145 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2146 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2147 }2148 #[cfg(not(feature = "std"))]2149 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2150 Ok(())2151 }2152}21532154impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2155where2156 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2157 BalanceOf<T>: Send + Sync + FixedPointOperand,2158{2159 /// utility constructor. Used only in client/factory code.2160 pub fn from(fee: BalanceOf<T>) -> Self {2161 Self(fee)2162 }21632164 pub fn traditional_fee(2165 len: usize,2166 info: &DispatchInfoOf<T::Call>,2167 tip: BalanceOf<T>,2168 ) -> BalanceOf<T>2169 where2170 T::Call: Dispatchable<Info = DispatchInfo>,2171 {2172 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2173 }21742175 fn withdraw_fee(2176 &self,2177 who: &T::AccountId,2178 call: &T::Call,2179 info: &DispatchInfoOf<T::Call>,2180 len: usize,2181 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2182 let tip = self.0;21832184 // Set fee based on call type. Creating collection costs 1 Unique.2185 // All other transactions have traditional fees so far2186 // let fee = match call.is_sub_type() {2187 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2188 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2189 // // _ => <BalanceOf<T>>::from(100)2190 // };2191 let fee = Self::traditional_fee(len, info, tip);21922193 // Determine who is paying transaction fee based on ecnomic model2194 // Parse call to extract collection ID and access collection sponsor2195 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2196 Some(Call::create_item(collection_id, _properties, _owner)) => {2197 <Collection<T>>::get(collection_id).sponsor2198 }2199 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {2200 let _collection_mode = <Collection<T>>::get(collection_id).mode;22012202 // sponsor timeout2203 let sponsor_transfer = match _collection_mode {2204 CollectionMode::NFT => {2205 let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);2206 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2207 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();2208 if block_number >= limit_time {2209 <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);2210 true2211 }2212 else {2213 false2214 }2215 }2216 CollectionMode::Fungible(_) => {2217 let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);2218 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2219 if basket.iter().any(|i| i.address == _new_owner.clone())2220 {2221 let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();2222 let limit_time = item.start_block + ChainLimit::get().fungible_sponsor_transfer_timeout.into();2223 if block_number >= limit_time {2224 basket.retain(|x| x.address == item.address);2225 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });2226 <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);2227 true2228 }2229 else {2230 false2231 }2232 }2233 else {2234 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});2235 true2236 }2237 }2238 CollectionMode::ReFungible(_) => {2239 let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);2240 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2241 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();2242 if block_number >= limit_time {2243 <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);2244 true2245 } else {2246 false2247 }2248 }2249 _ => {2250 false2251 },2252 };22532254 if !sponsor_transfer {2255 T::AccountId::default()2256 } else {2257 <Collection<T>>::get(collection_id).sponsor2258 }2259 }22602261 _ => T::AccountId::default(),2262 };22632264 // Sponsor smart contracts2265 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {22662267 // On instantiation: set the contract owner2268 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {22692270 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2271 code_hash,2272 &data,2273 &who,2274 );2275 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());22762277 T::AccountId::default()2278 },22792280 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2281 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {22822283 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());22842285 let mut sponsor_transfer = false;2286 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2287 let last_tx_block = <ContractSponsorBasket<T>>::get(&called_contract);2288 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2289 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2290 let limit_time = last_tx_block + rate_limit;22912292 if block_number >= limit_time {2293 <ContractSponsorBasket<T>>::insert(called_contract.clone(), block_number);2294 sponsor_transfer = true;2295 }2296 } else {2297 sponsor_transfer = false;2298 }2299 2300 2301 let mut sp = T::AccountId::default();2302 if sponsor_transfer {2303 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2304 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2305 sp = called_contract;2306 }2307 }2308 }23092310 sp2311 },23122313 _ => sponsor,2314 };23152316 let mut who_pays_fee: T::AccountId = sponsor.clone();2317 if sponsor == T::AccountId::default() {2318 who_pays_fee = who.clone();2319 }23202321 // Only mess with balances if fee is not zero.2322 if fee.is_zero() {2323 return Ok((fee, None));2324 }23252326 match <T as transaction_payment::Trait>::Currency::withdraw(2327 &who_pays_fee,2328 fee,2329 if tip.is_zero() {2330 WithdrawReason::TransactionPayment.into()2331 } else {2332 WithdrawReason::TransactionPayment | WithdrawReason::Tip2333 },2334 ExistenceRequirement::KeepAlive,2335 ) {2336 Ok(imbalance) => Ok((fee, Some(imbalance))),2337 Err(_) => Err(InvalidTransaction::Payment.into()),2338 }2339 }2340}234123422343impl<T: Trait + Send + Sync> SignedExtension2344 for ChargeTransactionPayment<T>2345where2346 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2347 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2348{2349 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2350 type AccountId = T::AccountId;2351 type Call = T::Call;2352 type AdditionalSigned = ();2353 type Pre = (2354 BalanceOf<T>,2355 Self::AccountId,2356 Option<NegativeImbalanceOf<T>>,2357 BalanceOf<T>,2358 );2359 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2360 Ok(())2361 }23622363 fn validate(2364 &self,2365 _who: &Self::AccountId,2366 _call: &Self::Call,2367 _info: &DispatchInfoOf<Self::Call>,2368 _len: usize,2369 ) -> TransactionValidity {2370 Ok(ValidTransaction::default())2371 }23722373 fn pre_dispatch(2374 self,2375 who: &Self::AccountId,2376 call: &Self::Call,2377 info: &DispatchInfoOf<Self::Call>,2378 len: usize,2379 ) -> Result<Self::Pre, TransactionValidityError> {2380 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2381 Ok((self.0, who.clone(), imbalance, fee))2382 }23832384 fn post_dispatch(2385 pre: Self::Pre,2386 info: &DispatchInfoOf<Self::Call>,2387 post_info: &PostDispatchInfoOf<Self::Call>,2388 len: usize,2389 _result: &DispatchResult,2390 ) -> Result<(), TransactionValidityError> {2391 let (tip, who, imbalance, fee) = pre;2392 if let Some(payed) = imbalance {2393 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2394 len as u32, info, post_info, tip,2395 );2396 let refund = fee.saturating_sub(actual_fee);2397 let actual_payment =2398 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2399 &who, refund,2400 ) {2401 Ok(refund_imbalance) => {2402 // The refund cannot be larger than the up front payed max weight.2403 // `PostDispatchInfo::calc_unspent` guards against such a case.2404 match payed.offset(refund_imbalance) {2405 Ok(actual_payment) => actual_payment,2406 Err(_) => return Err(InvalidTransaction::Payment.into()),2407 }2408 }2409 // We do not recreate the account using the refund. The up front payment2410 // is gone in that case.2411 Err(_) => payed,2412 };2413 let imbalances = actual_payment.split(tip);2414 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2415 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2416 );2417 }2418 Ok(())2419 }2420}24212422// #endregionpallets/nft/src/tests.rsdiffbeforeafterboth--- a/pallets/nft/src/tests.rs
+++ b/pallets/nft/src/tests.rs
@@ -1,6 +1,8 @@
// Tests to be written here
+use super::*;
use crate::mock::*;
-use crate::{AccessMode, ApprovePermissions, CollectionMode, Ownership, ChainLimits, CreateItemData, CreateNftData, CreateFungibleData, CreateReFungibleData};
+use crate::{AccessMode, ApprovePermissions, CollectionMode,
+ Ownership, ChainLimits, CreateItemData, CreateNftData, CreateFungibleData, CreateReFungibleData}; //Err
use frame_support::{assert_noop, assert_ok};
use frame_system::{ RawOrigin };
@@ -392,7 +394,7 @@
2,
1,
1,
- 1), "Only item owner, collection owner and admins can modify items");
+ 1), Error::<Test>::NoPermission);
// do approve
assert_ok!(TemplateModule::approve(origin1.clone(), 2, 1, 1));
@@ -672,7 +674,7 @@
assert_ok!(TemplateModule::burn_item(origin1.clone(), 1, 1));
assert_noop!(
TemplateModule::burn_item(origin1.clone(), 1, 1),
- "Item does not exists"
+ Error::<Test>::TokenNotFound
);
assert_eq!(TemplateModule::balance_count(1, 1), 0);
@@ -699,7 +701,7 @@
assert_ok!(TemplateModule::burn_item(origin1.clone(), 1, 1));
assert_noop!(
TemplateModule::burn_item(origin1.clone(), 1, 1),
- "Item does not exists"
+ Error::<Test>::TokenNotFound
);
assert_eq!(TemplateModule::balance_count(1, 1), 0);
@@ -738,7 +740,7 @@
assert_ok!(TemplateModule::burn_item(origin1.clone(), 1, 1));
assert_noop!(
TemplateModule::burn_item(origin1.clone(), 1, 1),
- "Item does not exists"
+ Error::<Test>::TokenNotFound
);
assert_eq!(TemplateModule::balance_count(1, 1), 0);
@@ -933,7 +935,7 @@
let origin2 = Origin::signed(2);
assert_noop!(
TemplateModule::add_to_white_list(origin2.clone(), collection_id, 3),
- "You do not have permissions to modify this collection"
+ Error::<Test>::NoPermission
);
});
}
@@ -947,7 +949,7 @@
assert_noop!(
TemplateModule::add_to_white_list(origin1.clone(), 1, 2),
- "This collection does not exist"
+ Error::<Test>::CollectionNotFound
);
});
}
@@ -963,7 +965,7 @@
assert_ok!(TemplateModule::destroy_collection(origin1.clone(), collection_id));
assert_noop!(
TemplateModule::add_to_white_list(origin1.clone(), collection_id, 2),
- "This collection does not exist"
+ Error::<Test>::CollectionNotFound
);
});
}
@@ -1035,7 +1037,7 @@
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), collection_id, 2));
assert_noop!(
TemplateModule::remove_from_white_list(origin2.clone(), collection_id, 2),
- "You do not have permissions to modify this collection"
+ Error::<Test>::NoPermission
);
assert_eq!(TemplateModule::white_list(collection_id)[0], 2);
});
@@ -1049,7 +1051,7 @@
assert_noop!(
TemplateModule::remove_from_white_list(origin1.clone(), 1, 2),
- "This collection does not exist"
+ Error::<Test>::CollectionNotFound
);
});
}
@@ -1067,7 +1069,7 @@
assert_ok!(TemplateModule::destroy_collection(origin1.clone(), collection_id));
assert_noop!(
TemplateModule::remove_from_white_list(origin2.clone(), collection_id, 2),
- "This collection does not exist"
+ Error::<Test>::CollectionNotFound
);
assert_eq!(TemplateModule::white_list(collection_id).len(), 0);
});
@@ -1119,7 +1121,7 @@
assert_noop!(
TemplateModule::transfer(origin1.clone(), 3, 1, 1, 1),
- "Address is not in white list"
+ Error::<Test>::AddresNotInWhiteList
);
});
}
@@ -1155,7 +1157,7 @@
assert_noop!(
TemplateModule::transfer_from(origin1.clone(), 1, 3, 1, 1, 1),
- "Address is not in white list"
+ Error::<Test>::AddresNotInWhiteList
);
});
}
@@ -1182,7 +1184,7 @@
assert_noop!(
TemplateModule::transfer(origin1.clone(), 3, 1, 1, 1),
- "Address is not in white list"
+ Error::<Test>::AddresNotInWhiteList
);
});
}
@@ -1219,7 +1221,7 @@
assert_noop!(
TemplateModule::transfer_from(origin1.clone(), 1, 3, 1, 1, 1),
- "Address is not in white list"
+ Error::<Test>::AddresNotInWhiteList
);
});
}
@@ -1244,7 +1246,7 @@
));
assert_noop!(
TemplateModule::burn_item(origin1.clone(), 1, 1),
- "Address is not in white list"
+ Error::<Test>::AddresNotInWhiteList
);
});
}
@@ -1267,7 +1269,7 @@
// do approve
assert_noop!(
TemplateModule::approve(origin1.clone(), 1, 1, 1),
- "Address is not in white list"
+ Error::<Test>::AddresNotInWhiteList
);
});
}
@@ -1416,7 +1418,7 @@
assert_noop!(
TemplateModule::create_item(origin2.clone(), 1, 2, default_nft_data().into()),
- "Public minting is not allowed for this collection"
+ Error::<Test>::PublicMintingNotAllowed
);
});
}
@@ -1445,7 +1447,7 @@
assert_noop!(
TemplateModule::create_item(origin2.clone(), 1, 2, default_nft_data().into()),
- "Public minting is not allowed for this collection"
+ Error::<Test>::PublicMintingNotAllowed
);
});
}
@@ -1533,7 +1535,7 @@
assert_noop!(
TemplateModule::create_item(origin2.clone(), 1, 2, default_nft_data().into()),
- "Address is not in white list"
+ Error::<Test>::AddresNotInWhiteList
);
});
}
@@ -1603,7 +1605,7 @@
col_desc1.clone(),
token_prefix1.clone(),
CollectionMode::NFT
- ), "Total collections bound exceeded");
+ ), Error::<Test>::TotalCollectionsLimitExceeded);
});
}
@@ -1646,7 +1648,7 @@
1,
1,
data.into()
- ), "Owned tokens by a single address bound exceeded");
+ ), Error::<Test>::AddressOwnershipLimitExceeded);
});
}
@@ -1692,7 +1694,7 @@
let origin1 = Origin::signed(1);
assert_ok!(TemplateModule::add_collection_admin(origin1.clone(), collection_id, 2));
- assert_noop!(TemplateModule::add_collection_admin(origin1.clone(), collection_id, 3), "Number of collection admins bound exceeded");
+ assert_noop!(TemplateModule::add_collection_admin(origin1.clone(), collection_id, 3), Error::<Test>::CollectionAdminsLimitExceeded);
});
}
runtime/src/nft_weights.rsdiffbeforeafterboth--- a/runtime/src/nft_weights.rs
+++ b/runtime/src/nft_weights.rs
@@ -108,4 +108,19 @@
.saturating_add(DbWeight::get().reads(2 as Weight))
.saturating_add(DbWeight::get().writes(1 as Weight))
}
+ // fn set_chain_limits() -> Weight {
+ // (0 as Weight)
+ // .saturating_add(DbWeight::get().reads(1 as Weight))
+ // .saturating_add(DbWeight::get().writes(1 as Weight))
+ // }
+ // fn enable_contract_sponsoring() -> Weight {
+ // (0 as Weight)
+ // .saturating_add(DbWeight::get().reads(1 as Weight))
+ // .saturating_add(DbWeight::get().writes(1 as Weight))
+ // }
+ // fn set_contract_sponsoring_rate_limit() -> Weight {
+ // (0 as Weight)
+ // .saturating_add(DbWeight::get().reads(1 as Weight))
+ // .saturating_add(DbWeight::get().writes(1 as Weight))
+ // }
}