difftreelog
Merge pull request #31 from usetech-llc/feature/NFTPAR-183
in: master
Feature/NFTPAR-183 Storage Refactoring.
3 files changed
README.mddiffbeforeafterboth--- a/README.md
+++ b/README.md
@@ -164,12 +164,13 @@
"WhiteList"
]
},
+ "DecimalPoints": "u8",
"CollectionMode": {
"_enum": {
"Invalid": null,
"NFT": null,
- "Fungible": "u32",
- "ReFungible": "u32"
+ "Fungible": "DecimalPoints",
+ "ReFungible": "DecimalPoints"
}
},
"Ownership": {
@@ -177,17 +178,17 @@
"Fraction": "u128"
},
"FungibleItemType": {
- "Collection": "u64",
+ "Collection": "CollectionId",
"Owner": "AccountId",
"Value": "u128"
},
"ReFungibleItemType": {
- "Collection": "u64",
+ "Collection": "CollectionId",
"Owner": "Vec<Ownership>",
"Data": "Vec<u8>"
},
"NftItemType": {
- "Collection": "u64",
+ "Collection": "CollectionId",
"Owner": "AccountId",
"ConstData": "Vec<u8>",
"VariableData": "Vec<u8>"
@@ -197,7 +198,7 @@
"fraction": "u128"
},
"ReFungibleItemType": {
- "Collection": "u64",
+ "Collection": "CollectionId",
"Owner": "Vec<Ownership<AccountId>>",
"ConstData": "Vec<u8>",
"VariableData": "Vec<u8>"
@@ -206,7 +207,7 @@
"Owner": "AccountId",
"Mode": "CollectionMode",
"Access": "AccessMode",
- "DecimalPoints": "u32",
+ "DecimalPoints": "DecimalPoints",
"Name": "Vec<u16>",
"Description": "Vec<u16>",
"TokenPrefix": "Vec<u8>",
@@ -219,7 +220,7 @@
},
"ApprovePermissions": {
"Approved": "AccountId",
- "Amount": "u64"
+ "Amount": "u128"
},
"RawData": "Vec<u8>",
"Address": "AccountId",
@@ -240,7 +241,9 @@
"Fungible": "CreateFungibleData",
"ReFungible": "CreateReFungibleData"
}
- }
+ },
+ "CollectionId": "u32",
+ "TokenId": "u32"
}
```
\ No newline at end of file
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 CantStoreMetadataInFungibleTokens325 }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)?;1196 1197 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);11981199 // Modify permissions check1200 let target_collection = <Collection<T>>::get(collection_id);1201 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1202 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1203 Error::<T>::NoPermission);12041205 Self::item_exists(collection_id, item_id, &target_collection.mode)?;12061207 match target_collection.mode1208 {1209 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1210 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1211 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1212 _ => fail!(Error::<T>::UnexpectedCollectionType)1213 };12141215 Ok(())1216 }1217 12181219 /// Set off-chain data schema.1220 /// 1221 /// # Permissions1222 /// 1223 /// * Collection Owner1224 /// * Collection Admin1225 /// 1226 /// # Arguments1227 /// 1228 /// * collection_id.1229 /// 1230 /// * schema: String representing the offchain data schema.1231 #[weight = T::WeightInfo::set_offchain_schema()]1232 pub fn set_offchain_schema(1233 origin,1234 collection_id: u64,1235 schema: Vec<u8>1236 ) -> DispatchResult {1237 let sender = ensure_signed(origin)?;1238 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12391240 let mut target_collection = <Collection<T>>::get(collection_id);1241 target_collection.offchain_schema = schema;1242 <Collection<T>>::insert(collection_id, target_collection);12431244 Ok(())1245 }12461247 /// Set const on-chain data schema.1248 /// 1249 /// # Permissions1250 /// 1251 /// * Collection Owner1252 /// * Collection Admin1253 /// 1254 /// # Arguments1255 /// 1256 /// * collection_id.1257 /// 1258 /// * schema: String representing the const on-chain data schema.1259 #[weight = T::WeightInfo::set_const_on_chain_schema()]1260 pub fn set_const_on_chain_schema (1261 origin,1262 collection_id: u64,1263 schema: Vec<u8>1264 ) -> DispatchResult {1265 let sender = ensure_signed(origin)?;1266 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12671268 let mut target_collection = <Collection<T>>::get(collection_id);1269 target_collection.const_on_chain_schema = schema;1270 <Collection<T>>::insert(collection_id, target_collection);12711272 Ok(())1273 }12741275 /// Set variable on-chain data schema.1276 /// 1277 /// # Permissions1278 /// 1279 /// * Collection Owner1280 /// * Collection Admin1281 /// 1282 /// # Arguments1283 /// 1284 /// * collection_id.1285 /// 1286 /// * schema: String representing the variable on-chain data schema.1287 #[weight = T::WeightInfo::set_const_on_chain_schema()]1288 pub fn set_variable_on_chain_schema (1289 origin,1290 collection_id: u64,1291 schema: Vec<u8>1292 ) -> DispatchResult {1293 let sender = ensure_signed(origin)?;1294 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12951296 let mut target_collection = <Collection<T>>::get(collection_id);1297 target_collection.variable_on_chain_schema = schema;1298 <Collection<T>>::insert(collection_id, target_collection);12991300 Ok(())1301 }13021303 // Sudo permissions function1304 #[weight = 0]1305 pub fn set_chain_limits(1306 origin,1307 limits: ChainLimits1308 ) -> DispatchResult {1309 ensure_root(origin)?;1310 <ChainLimit>::put(limits);1311 Ok(())1312 }13131314 /// Enable smart contract self-sponsoring.1315 /// 1316 /// # Permissions1317 /// 1318 /// * Contract Owner1319 /// 1320 /// # Arguments1321 /// 1322 /// * contract address1323 /// * enable flag1324 /// 1325 #[weight = T::WeightInfo::enable_contract_sponsoring()]1326 pub fn enable_contract_sponsoring(1327 origin,1328 contract_address: T::AccountId,1329 enable: bool1330 ) -> DispatchResult {13311332 let sender = ensure_signed(origin)?;13331334 #[cfg(feature = "runtime-benchmarks")]1335 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13361337 let mut is_owner = false;1338 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1339 let owner = <ContractOwner<T>>::get(&contract_address);1340 is_owner = sender == owner;1341 }1342 ensure!(is_owner, Error::<T>::NoPermission);13431344 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1345 Ok(())1346 }13471348 /// Set the rate limit for contract sponsoring to specified number of blocks.1349 /// 1350 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1351 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1352 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1353 /// from contract endowment if there are at least B blocks between such transactions. 1354 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1355 /// 1356 /// # Permissions1357 /// 1358 /// * Contract Owner1359 /// 1360 /// # Arguments1361 /// 1362 /// -`contract_address`: Address of the contract to sponsor1363 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1364 /// 1365 #[weight = 0]1366 pub fn set_contract_sponsoring_rate_limit(1367 origin,1368 contract_address: T::AccountId,1369 rate_limit: T::BlockNumber1370 ) -> DispatchResult {1371 let sender = ensure_signed(origin)?;1372 let mut is_owner = false;1373 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1374 let owner = <ContractOwner<T>>::get(&contract_address);1375 is_owner = sender == owner;1376 }1377 ensure!(is_owner, Error::<T>::NoPermission);13781379 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1380 Ok(())1381 }13821383 // #[cfg(feature = "runtime-benchmarks")]1384 // #[weight = 0]1385 // pub fn add_contract_sponsoring_debug(1386 // origin,1387 // contract_address: T::AccountId, 1388 // owner: T::AccountId) -> DispatchResult {1389 // let sender = ensure_signed(origin)?;1390 // <ContractOwner<T>>::insert(contract_address.clone(), owner);1391 // Ok(())1392 // }1393 1394 }1395}13961397impl<T: Trait> Module<T> {13981399 fn can_create_items_in_collection(collection_id: u64, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {14001401 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1402 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1403 Self::check_white_list(collection_id, owner)?;1404 Self::check_white_list(collection_id, sender)?;1405 }14061407 Ok(())1408 }14091410 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1411 match target_collection.mode1412 {1413 CollectionMode::NFT => {1414 if let CreateItemData::NFT(data) = data {1415 // check sizes1416 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1417 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1418 } else {1419 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1420 }1421 },1422 CollectionMode::Fungible(_) => {1423 if let CreateItemData::Fungible(_) = data {1424 } else {1425 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1426 }1427 },1428 CollectionMode::ReFungible(_) => {1429 if let CreateItemData::ReFungible(data) = data {14301431 // check sizes1432 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1433 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1434 } else {1435 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1436 }1437 },1438 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1439 };14401441 Ok(())1442 }14431444 fn create_item_no_validation(collection_id: u64, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1445 match data1446 {1447 CreateItemData::NFT(data) => {1448 let item = NftItemType {1449 collection: collection_id,1450 owner,1451 const_data: data.const_data,1452 variable_data: data.variable_data1453 };14541455 Self::add_nft_item(item)?;1456 },1457 CreateItemData::Fungible(_) => {1458 let item = FungibleItemType {1459 collection: collection_id,1460 owner,1461 value: (10 as u128).pow(collection.decimal_points)1462 };14631464 Self::add_fungible_item(item)?;1465 },1466 CreateItemData::ReFungible(data) => {1467 let mut owner_list = Vec::new();1468 let value = (10 as u128).pow(collection.decimal_points);1469 owner_list.push(Ownership {owner: owner.clone(), fraction: value});14701471 let item = ReFungibleItemType {1472 collection: collection_id,1473 owner: owner_list,1474 const_data: data.const_data,1475 variable_data: data.variable_data1476 };14771478 Self::add_refungible_item(item)?;1479 }1480 };148114821483 // call event1484 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));14851486 Ok(())1487 }14881489 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {1490 let current_index = <ItemListIndex>::get(item.collection)1491 .checked_add(1)1492 .ok_or(Error::<T>::NumOverflow)?;1493 let itemcopy = item.clone();1494 let owner = item.owner.clone();1495 let value = item.value as u64;14961497 Self::add_token_index(item.collection, current_index, owner.clone())?;14981499 <ItemListIndex>::insert(item.collection, current_index);1500 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);15011502 // Add current block1503 let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1504 <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);1505 1506 // Update balance1507 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1508 .checked_add(value)1509 .ok_or(Error::<T>::NumOverflow)?;1510 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);15111512 Ok(())1513 }15141515 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1516 let current_index = <ItemListIndex>::get(item.collection)1517 .checked_add(1)1518 .ok_or(Error::<T>::NumOverflow)?;1519 let itemcopy = item.clone();15201521 let value = item.owner.first().unwrap().fraction as u64;1522 let owner = item.owner.first().unwrap().owner.clone();15231524 Self::add_token_index(item.collection, current_index, owner.clone())?;15251526 <ItemListIndex>::insert(item.collection, current_index);1527 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);15281529 // Add current block1530 let block_number: T::BlockNumber = 0.into();1531 <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);15321533 // Update balance1534 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1535 .checked_add(value)1536 .ok_or(Error::<T>::NumOverflow)?;1537 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);15381539 Ok(())1540 }15411542 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1543 let current_index = <ItemListIndex>::get(item.collection)1544 .checked_add(1)1545 .ok_or(Error::<T>::NumOverflow)?;15461547 let item_owner = item.owner.clone();1548 let collection_id = item.collection.clone();1549 Self::add_token_index(collection_id, current_index, item.owner.clone())?;15501551 <ItemListIndex>::insert(collection_id, current_index);1552 <NftItemList<T>>::insert(collection_id, current_index, item);15531554 // Add current block1555 let block_number: T::BlockNumber = 0.into();1556 <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);15571558 // Update balance1559 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1560 .checked_add(1)1561 .ok_or(Error::<T>::NumOverflow)?;1562 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);15631564 Ok(())1565 }15661567 fn burn_refungible_item(1568 collection_id: u64,1569 item_id: u64,1570 owner: T::AccountId,1571 ) -> DispatchResult {1572 ensure!(1573 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1574 Error::<T>::TokenNotFound1575 );1576 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1577 let item = collection1578 .owner1579 .iter()1580 .filter(|&i| i.owner == owner)1581 .next()1582 .unwrap();1583 Self::remove_token_index(collection_id, item_id, owner.clone())?;15841585 // remove approve list1586 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));15871588 // update balance1589 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1590 .checked_sub(item.fraction as u64)1591 .ok_or(Error::<T>::NumOverflow)?;1592 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);15931594 <ReFungibleItemList<T>>::remove(collection_id, item_id);15951596 Ok(())1597 }15981599 fn burn_nft_item(collection_id: u64, item_id: u64) -> DispatchResult {1600 ensure!(1601 <NftItemList<T>>::contains_key(collection_id, item_id),1602 Error::<T>::TokenNotFound1603 );1604 let item = <NftItemList<T>>::get(collection_id, item_id);1605 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;16061607 // remove approve list1608 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));16091610 // update balance1611 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1612 .checked_sub(1)1613 .ok_or(Error::<T>::NumOverflow)?;1614 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1615 <NftItemList<T>>::remove(collection_id, item_id);16161617 Ok(())1618 }16191620 fn burn_fungible_item(collection_id: u64, item_id: u64) -> DispatchResult {1621 ensure!(1622 <FungibleItemList<T>>::contains_key(collection_id, item_id),1623 Error::<T>::TokenNotFound1624 );1625 let item = <FungibleItemList<T>>::get(collection_id, item_id);1626 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;16271628 // remove approve list1629 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));16301631 // update balance1632 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1633 .checked_sub(item.value as u64)1634 .ok_or(Error::<T>::NumOverflow)?;1635 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);16361637 <FungibleItemList<T>>::remove(collection_id, item_id);16381639 Ok(())1640 }16411642 fn collection_exists(collection_id: u64) -> DispatchResult {1643 ensure!(1644 <Collection<T>>::contains_key(collection_id),1645 Error::<T>::CollectionNotFound1646 );1647 Ok(())1648 }16491650 fn check_owner_permissions(collection_id: u64, subject: T::AccountId) -> DispatchResult {1651 Self::collection_exists(collection_id)?;16521653 let target_collection = <Collection<T>>::get(collection_id);1654 ensure!(1655 subject == target_collection.owner,1656 Error::<T>::NoPermission1657 );16581659 Ok(())1660 }16611662 fn is_owner_or_admin_permissions(collection_id: u64, subject: T::AccountId) -> bool {1663 let target_collection = <Collection<T>>::get(collection_id);1664 let mut result: bool = subject == target_collection.owner;1665 let exists = <AdminList<T>>::contains_key(collection_id);16661667 if !result & exists {1668 if <AdminList<T>>::get(collection_id).contains(&subject) {1669 result = true1670 }1671 }16721673 result1674 }16751676 fn check_owner_or_admin_permissions(1677 collection_id: u64,1678 subject: T::AccountId,1679 ) -> DispatchResult {1680 Self::collection_exists(collection_id)?;1681 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());16821683 ensure!(1684 result,1685 Error::<T>::NoPermission1686 );1687 Ok(())1688 }16891690 fn is_item_owner(subject: T::AccountId, collection_id: u64, item_id: u64) -> bool {1691 let target_collection = <Collection<T>>::get(collection_id);16921693 match target_collection.mode {1694 CollectionMode::NFT => {1695 <NftItemList<T>>::get(collection_id, item_id).owner == subject1696 }1697 CollectionMode::Fungible(_) => {1698 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1699 }1700 CollectionMode::ReFungible(_) => {1701 <ReFungibleItemList<T>>::get(collection_id, item_id)1702 .owner1703 .iter()1704 .any(|i| i.owner == subject)1705 }1706 CollectionMode::Invalid => false,1707 }1708 }17091710 fn check_white_list(collection_id: u64, address: &T::AccountId) -> DispatchResult {1711 let mes = Error::<T>::AddresNotInWhiteList;1712 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1713 let wl = <WhiteList<T>>::get(collection_id);1714 ensure!(wl.contains(address), mes);17151716 Ok(())1717 }17181719 fn transfer_fungible(1720 collection_id: u64,1721 item_id: u64,1722 value: u64,1723 owner: T::AccountId,1724 new_owner: T::AccountId,1725 ) -> DispatchResult {1726 ensure!(1727 <FungibleItemList<T>>::contains_key(collection_id, item_id),1728 Error::<T>::TokenNotFound1729 );17301731 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1732 let amount = full_item.value;17331734 ensure!(amount >= value.into(), Error::<T>::TokenValueTooLow);17351736 // update balance1737 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1738 .checked_sub(value)1739 .ok_or(Error::<T>::NumOverflow)?;1740 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);17411742 let mut new_owner_account_id = 0;1743 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1744 if new_owner_items.len() > 0 {1745 new_owner_account_id = new_owner_items[0];1746 }17471748 let val64 = value.into();17491750 // transfer1751 if amount == val64 && new_owner_account_id == 0 {1752 // change owner1753 // new owner do not have account1754 let mut new_full_item = full_item.clone();1755 new_full_item.owner = new_owner.clone();1756 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);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 // update index collection1765 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1766 } else {1767 let mut new_full_item = full_item.clone();1768 new_full_item.value -= val64;17691770 // separate amount1771 if new_owner_account_id > 0 {1772 // new owner has account1773 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1774 item.value += val64;17751776 // update balance1777 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1778 .checked_add(value)1779 .ok_or(Error::<T>::NumOverflow)?;1780 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);17811782 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1783 } else {1784 // new owner do not have account1785 let item = FungibleItemType {1786 collection: collection_id,1787 owner: new_owner.clone(),1788 value: val64,1789 };17901791 Self::add_fungible_item(item)?;1792 }17931794 if amount == val64 {1795 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;17961797 // remove approve list1798 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1799 <FungibleItemList<T>>::remove(collection_id, item_id);1800 }18011802 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1803 }18041805 Ok(())1806 }18071808 fn transfer_refungible(1809 collection_id: u64,1810 item_id: u64,1811 value: u64,1812 owner: T::AccountId,1813 new_owner: T::AccountId,1814 ) -> DispatchResult {1815 ensure!(1816 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1817 Error::<T>::TokenNotFound1818 );18191820 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1821 let item = full_item1822 .owner1823 .iter()1824 .filter(|i| i.owner == owner)1825 .next()1826 .ok_or(Error::<T>::NumOverflow)?;1827 let amount = item.fraction;18281829 ensure!(amount >= value.into(), Error::<T>::TokenValueTooLow);18301831 // update balance1832 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1833 .checked_sub(value)1834 .ok_or(Error::<T>::NumOverflow)?;1835 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);18361837 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1838 .checked_add(value)1839 .ok_or(Error::<T>::NumOverflow)?;1840 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);18411842 let old_owner = item.owner.clone();1843 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);1844 let val64 = value.into();18451846 // transfer1847 if amount == val64 && !new_owner_has_account {1848 // change owner1849 // new owner do not have account1850 let mut new_full_item = full_item.clone();1851 new_full_item1852 .owner1853 .iter_mut()1854 .find(|i| i.owner == owner)1855 .unwrap()1856 .owner = new_owner.clone();1857 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);18581859 // update index collection1860 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1861 } else {1862 let mut new_full_item = full_item.clone();1863 new_full_item1864 .owner1865 .iter_mut()1866 .find(|i| i.owner == owner)1867 .unwrap()1868 .fraction -= val64;18691870 // separate amount1871 if new_owner_has_account {1872 // new owner has account1873 new_full_item1874 .owner1875 .iter_mut()1876 .find(|i| i.owner == new_owner)1877 .unwrap()1878 .fraction += val64;1879 } else {1880 // new owner do not have account1881 new_full_item.owner.push(Ownership {1882 owner: new_owner.clone(),1883 fraction: val64,1884 });1885 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1886 }18871888 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1889 }18901891 Ok(())1892 }18931894 fn transfer_nft(1895 collection_id: u64,1896 item_id: u64,1897 sender: T::AccountId,1898 new_owner: T::AccountId,1899 ) -> DispatchResult {1900 ensure!(1901 <NftItemList<T>>::contains_key(collection_id, item_id),1902 Error::<T>::TokenNotFound1903 );19041905 let mut item = <NftItemList<T>>::get(collection_id, item_id);19061907 ensure!(1908 sender == item.owner,1909 Error::<T>::MustBeTokenOwner1910 );19111912 // update balance1913 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1914 .checked_sub(1)1915 .ok_or(Error::<T>::NumOverflow)?;1916 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19171918 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1919 .checked_add(1)1920 .ok_or(Error::<T>::NumOverflow)?;1921 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);19221923 // change owner1924 let old_owner = item.owner.clone();1925 item.owner = new_owner.clone();1926 <NftItemList<T>>::insert(collection_id, item_id, item);19271928 // update index collection1929 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;19301931 // reset approved list1932 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));1933 Ok(())1934 }1935 1936 fn item_exists(1937 collection_id: u64,1938 item_id: u64,1939 mode: &CollectionMode1940 ) -> DispatchResult {1941 match mode {1942 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1943 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1944 CollectionMode::Fungible(_) => ensure!(<FungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1945 _ => ()1946 };1947 1948 Ok(())1949 }19501951 fn set_re_fungible_variable_data(1952 collection_id: u64,1953 item_id: u64,1954 data: Vec<u8>1955 ) -> DispatchResult {1956 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);19571958 item.variable_data = data;19591960 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);19611962 Ok(())1963 }19641965 fn set_nft_variable_data(1966 collection_id: u64,1967 item_id: u64,1968 data: Vec<u8>1969 ) -> DispatchResult {1970 let mut item = <NftItemList<T>>::get(collection_id, item_id);1971 1972 item.variable_data = data;19731974 <NftItemList<T>>::insert(collection_id, item_id, item);1975 1976 Ok(())1977 }19781979 fn init_collection(item: &CollectionType<T::AccountId>) {1980 // check params1981 assert!(1982 item.decimal_points <= 4,1983 "decimal_points parameter must be lower than 4"1984 );1985 assert!(1986 item.name.len() <= 64,1987 "Collection name can not be longer than 63 char"1988 );1989 assert!(1990 item.name.len() <= 256,1991 "Collection description can not be longer than 255 char"1992 );1993 assert!(1994 item.token_prefix.len() <= 16,1995 "Token prefix can not be longer than 15 char"1996 );19971998 // Generate next collection ID1999 let next_id = CreatedCollectionCount::get()2000 .checked_add(1)2001 .unwrap();20022003 CreatedCollectionCount::put(next_id);2004 }20052006 fn init_nft_token(item: &NftItemType<T::AccountId>) {2007 let current_index = <ItemListIndex>::get(item.collection)2008 .checked_add(1)2009 .unwrap();20102011 let item_owner = item.owner.clone();2012 let collection_id = item.collection.clone();2013 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();20142015 <ItemListIndex>::insert(collection_id, current_index);20162017 // Update balance2018 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())2019 .checked_add(1)2020 .unwrap();2021 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2022 }20232024 fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {2025 let current_index = <ItemListIndex>::get(item.collection)2026 .checked_add(1)2027 .unwrap();2028 let owner = item.owner.clone();2029 let value = item.value as u64;20302031 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();20322033 <ItemListIndex>::insert(item.collection, current_index);20342035 // Update balance2036 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2037 .checked_add(value)2038 .unwrap();2039 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2040 }20412042 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {2043 let current_index = <ItemListIndex>::get(item.collection)2044 .checked_add(1)2045 .unwrap();20462047 let value = item.owner.first().unwrap().fraction as u64;2048 let owner = item.owner.first().unwrap().owner.clone();20492050 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();20512052 <ItemListIndex>::insert(item.collection, current_index);20532054 // Update balance2055 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2056 .checked_add(value)2057 .unwrap();2058 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2059 }20602061 fn add_token_index(collection_id: u64, item_index: u64, owner: T::AccountId) -> DispatchResult {20622063 // add to account limit2064 if <AccountItemCount<T>>::contains_key(owner.clone()) {20652066 // bound Owned tokens by a single address2067 let count = <AccountItemCount<T>>::get(owner.clone());2068 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);20692070 <AccountItemCount<T>>::insert(owner.clone(), count2071 .checked_add(1)2072 .ok_or(Error::<T>::NumOverflow)?);2073 }2074 else {2075 <AccountItemCount<T>>::insert(owner.clone(), 1);2076 }20772078 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2079 if list_exists {2080 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2081 let item_contains = list.contains(&item_index.clone());20822083 if !item_contains {2084 list.push(item_index.clone());2085 }20862087 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2088 } else {2089 let mut itm = Vec::new();2090 itm.push(item_index.clone());2091 <AddressTokens<T>>::insert(collection_id, owner, itm);2092 2093 }20942095 Ok(())2096 }20972098 fn remove_token_index(2099 collection_id: u64,2100 item_index: u64,2101 owner: T::AccountId,2102 ) -> DispatchResult {21032104 // update counter2105 <AccountItemCount<T>>::insert(owner.clone(), 2106 <AccountItemCount<T>>::get(owner.clone())2107 .checked_sub(1)2108 .ok_or(Error::<T>::NumOverflow)?);210921102111 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2112 if list_exists {2113 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2114 let item_contains = list.contains(&item_index.clone());21152116 if item_contains {2117 list.retain(|&item| item != item_index);2118 <AddressTokens<T>>::insert(collection_id, owner, list);2119 }2120 }21212122 Ok(())2123 }21242125 fn move_token_index(2126 collection_id: u64,2127 item_index: u64,2128 old_owner: T::AccountId,2129 new_owner: T::AccountId,2130 ) -> DispatchResult {2131 Self::remove_token_index(collection_id, item_index, old_owner)?;2132 Self::add_token_index(collection_id, item_index, new_owner)?;21332134 Ok(())2135 }2136}21372138////////////////////////////////////////////////////////////////////////////////////////////////////2139// Economic models2140// #region21412142/// Fee multiplier.2143pub type Multiplier = FixedU128;21442145type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2146 <T as system::Trait>::AccountId,2147>>::Balance;2148type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2149 <T as system::Trait>::AccountId,2150>>::NegativeImbalance;21512152/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2153/// in the queue.2154#[derive(Encode, Decode, Clone, Eq, PartialEq)]2155pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2156 #[codec(compact)] BalanceOf<T>2157);21582159impl<T: Trait + Send + Sync> sp_std::fmt::Debug2160 for ChargeTransactionPayment<T>2161{2162 #[cfg(feature = "std")]2163 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2164 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2165 }2166 #[cfg(not(feature = "std"))]2167 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2168 Ok(())2169 }2170}21712172impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2173where2174 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2175 BalanceOf<T>: Send + Sync + FixedPointOperand,2176{2177 /// utility constructor. Used only in client/factory code.2178 pub fn from(fee: BalanceOf<T>) -> Self {2179 Self(fee)2180 }21812182 pub fn traditional_fee(2183 len: usize,2184 info: &DispatchInfoOf<T::Call>,2185 tip: BalanceOf<T>,2186 ) -> BalanceOf<T>2187 where2188 T::Call: Dispatchable<Info = DispatchInfo>,2189 {2190 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2191 }21922193 fn withdraw_fee(2194 &self,2195 who: &T::AccountId,2196 call: &T::Call,2197 info: &DispatchInfoOf<T::Call>,2198 len: usize,2199 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2200 let tip = self.0;22012202 // Set fee based on call type. Creating collection costs 1 Unique.2203 // All other transactions have traditional fees so far2204 // let fee = match call.is_sub_type() {2205 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2206 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2207 // // _ => <BalanceOf<T>>::from(100)2208 // };2209 let fee = Self::traditional_fee(len, info, tip);22102211 // Determine who is paying transaction fee based on ecnomic model2212 // Parse call to extract collection ID and access collection sponsor2213 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2214 Some(Call::create_item(collection_id, _properties, _owner)) => {2215 <Collection<T>>::get(collection_id).sponsor2216 }2217 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {2218 let _collection_mode = <Collection<T>>::get(collection_id).mode;22192220 // sponsor timeout2221 let sponsor_transfer = match _collection_mode {2222 CollectionMode::NFT => {2223 let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);2224 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2225 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();2226 if block_number >= limit_time {2227 <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);2228 true2229 }2230 else {2231 false2232 }2233 }2234 CollectionMode::Fungible(_) => {2235 let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);2236 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2237 if basket.iter().any(|i| i.address == _new_owner.clone())2238 {2239 let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();2240 let limit_time = item.start_block + ChainLimit::get().fungible_sponsor_transfer_timeout.into();2241 if block_number >= limit_time {2242 basket.retain(|x| x.address == item.address);2243 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });2244 <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);2245 true2246 }2247 else {2248 false2249 }2250 }2251 else {2252 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});2253 true2254 }2255 }2256 CollectionMode::ReFungible(_) => {2257 let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);2258 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2259 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();2260 if block_number >= limit_time {2261 <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);2262 true2263 } else {2264 false2265 }2266 }2267 _ => {2268 false2269 },2270 };22712272 if !sponsor_transfer {2273 T::AccountId::default()2274 } else {2275 <Collection<T>>::get(collection_id).sponsor2276 }2277 }22782279 _ => T::AccountId::default(),2280 };22812282 // Sponsor smart contracts2283 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {22842285 // On instantiation: set the contract owner2286 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {22872288 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2289 code_hash,2290 &data,2291 &who,2292 );2293 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());22942295 T::AccountId::default()2296 },22972298 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2299 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {23002301 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());23022303 let mut sponsor_transfer = false;2304 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2305 let last_tx_block = <ContractSponsorBasket<T>>::get(&called_contract);2306 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2307 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2308 let limit_time = last_tx_block + rate_limit;23092310 if block_number >= limit_time {2311 <ContractSponsorBasket<T>>::insert(called_contract.clone(), block_number);2312 sponsor_transfer = true;2313 }2314 } else {2315 sponsor_transfer = false;2316 }2317 2318 2319 let mut sp = T::AccountId::default();2320 if sponsor_transfer {2321 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2322 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2323 sp = called_contract;2324 }2325 }2326 }23272328 sp2329 },23302331 _ => sponsor,2332 };23332334 let mut who_pays_fee: T::AccountId = sponsor.clone();2335 if sponsor == T::AccountId::default() {2336 who_pays_fee = who.clone();2337 }23382339 // Only mess with balances if fee is not zero.2340 if fee.is_zero() {2341 return Ok((fee, None));2342 }23432344 match <T as transaction_payment::Trait>::Currency::withdraw(2345 &who_pays_fee,2346 fee,2347 if tip.is_zero() {2348 WithdrawReason::TransactionPayment.into()2349 } else {2350 WithdrawReason::TransactionPayment | WithdrawReason::Tip2351 },2352 ExistenceRequirement::KeepAlive,2353 ) {2354 Ok(imbalance) => Ok((fee, Some(imbalance))),2355 Err(_) => Err(InvalidTransaction::Payment.into()),2356 }2357 }2358}235923602361impl<T: Trait + Send + Sync> SignedExtension2362 for ChargeTransactionPayment<T>2363where2364 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2365 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2366{2367 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2368 type AccountId = T::AccountId;2369 type Call = T::Call;2370 type AdditionalSigned = ();2371 type Pre = (2372 BalanceOf<T>,2373 Self::AccountId,2374 Option<NegativeImbalanceOf<T>>,2375 BalanceOf<T>,2376 );2377 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2378 Ok(())2379 }23802381 fn validate(2382 &self,2383 _who: &Self::AccountId,2384 _call: &Self::Call,2385 _info: &DispatchInfoOf<Self::Call>,2386 _len: usize,2387 ) -> TransactionValidity {2388 Ok(ValidTransaction::default())2389 }23902391 fn pre_dispatch(2392 self,2393 who: &Self::AccountId,2394 call: &Self::Call,2395 info: &DispatchInfoOf<Self::Call>,2396 len: usize,2397 ) -> Result<Self::Pre, TransactionValidityError> {2398 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2399 Ok((self.0, who.clone(), imbalance, fee))2400 }24012402 fn post_dispatch(2403 pre: Self::Pre,2404 info: &DispatchInfoOf<Self::Call>,2405 post_info: &PostDispatchInfoOf<Self::Call>,2406 len: usize,2407 _result: &DispatchResult,2408 ) -> Result<(), TransactionValidityError> {2409 let (tip, who, imbalance, fee) = pre;2410 if let Some(payed) = imbalance {2411 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2412 len as u32, info, post_info, tip,2413 );2414 let refund = fee.saturating_sub(actual_fee);2415 let actual_payment =2416 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2417 &who, refund,2418 ) {2419 Ok(refund_imbalance) => {2420 // The refund cannot be larger than the up front payed max weight.2421 // `PostDispatchInfo::calc_unspent` guards against such a case.2422 match payed.offset(refund_imbalance) {2423 Ok(actual_payment) => actual_payment,2424 Err(_) => return Err(InvalidTransaction::Payment.into()),2425 }2426 }2427 // We do not recreate the account using the refund. The up front payment2428 // is gone in that case.2429 Err(_) => payed,2430 };2431 let imbalances = actual_payment.split(tip);2432 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2433 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2434 );2435 }2436 Ok(())2437 }2438}24392440// #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;4748pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;4950// Structs51// #region5253pub type CollectionId = u32;54pub type TokenId = u32;5556pub type DecimalPoints = u8;5758#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]59#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]60pub enum CollectionMode {61 Invalid,62 NFT,63 // decimal points64 Fungible(DecimalPoints),65 // decimal points66 ReFungible(DecimalPoints),67}6869impl Into<u8> for CollectionMode {70 fn into(self) -> u8 {71 match self {72 CollectionMode::Invalid => 0,73 CollectionMode::NFT => 1,74 CollectionMode::Fungible(_) => 2,75 CollectionMode::ReFungible(_) => 3,76 }77 }78}7980#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]81#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]82pub enum AccessMode {83 Normal,84 WhiteList,85}86impl Default for AccessMode {87 fn default() -> Self {88 Self::Normal89 }90}9192impl Default for CollectionMode {93 fn default() -> Self {94 Self::Invalid95 }96}9798#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]99#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]100pub struct Ownership<AccountId> {101 pub owner: AccountId,102 pub fraction: u128,103}104105#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]106#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]107pub struct CollectionType<AccountId> {108 pub owner: AccountId,109 pub mode: CollectionMode,110 pub access: AccessMode,111 pub decimal_points: DecimalPoints,112 pub name: Vec<u16>, // 64 include null escape char113 pub description: Vec<u16>, // 256 include null escape char114 pub token_prefix: Vec<u8>, // 16 include null escape char115 pub mint_mode: bool,116 pub offchain_schema: Vec<u8>,117 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender118 pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship119 pub variable_on_chain_schema: Vec<u8>, //120 pub const_on_chain_schema: Vec<u8>, //121}122123#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]124#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]125pub struct NftItemType<AccountId> {126 pub collection: CollectionId,127 pub owner: AccountId,128 pub const_data: Vec<u8>,129 pub variable_data: Vec<u8>,130}131132#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]133#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]134pub struct FungibleItemType<AccountId> {135 pub collection: CollectionId,136 pub owner: AccountId,137 pub value: u128,138}139140#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]141#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]142pub struct ReFungibleItemType<AccountId> {143 pub collection: CollectionId,144 pub owner: Vec<Ownership<AccountId>>,145 pub const_data: Vec<u8>,146 pub variable_data: Vec<u8>,147}148149#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]150#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]151pub struct ApprovePermissions<AccountId> {152 pub approved: AccountId,153 pub amount: u128,154}155156#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]157#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]158pub struct VestingItem<AccountId, Moment> {159 pub sender: AccountId,160 pub recipient: AccountId,161 pub collection_id: CollectionId,162 pub item_id: TokenId,163 pub amount: u64,164 pub vesting_date: Moment,165}166167#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]168#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]169pub struct BasketItem<AccountId, BlockNumber> {170 pub address: AccountId,171 pub start_block: BlockNumber,172}173174#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]175#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]176pub struct ChainLimits {177 pub collection_numbers_limit: u32,178 pub account_token_ownership_limit: u32,179 pub collections_admins_limit: u64,180 pub custom_data_limit: u32,181182 // Timeouts for item types in passed blocks183 pub nft_sponsor_transfer_timeout: u32,184 pub fungible_sponsor_transfer_timeout: u32,185 pub refungible_sponsor_transfer_timeout: u32,186}187188pub trait WeightInfo {189 fn create_collection() -> Weight;190 fn destroy_collection() -> Weight;191 fn add_to_white_list() -> Weight;192 fn remove_from_white_list() -> Weight;193 fn set_public_access_mode() -> Weight;194 fn set_mint_permission() -> Weight;195 fn change_collection_owner() -> Weight;196 fn add_collection_admin() -> Weight;197 fn remove_collection_admin() -> Weight;198 fn set_collection_sponsor() -> Weight;199 fn confirm_sponsorship() -> Weight;200 fn remove_collection_sponsor() -> Weight;201 fn create_item(s: usize) -> Weight;202 fn burn_item() -> Weight;203 fn transfer() -> Weight;204 fn approve() -> Weight;205 fn transfer_from() -> Weight;206 fn set_offchain_schema() -> Weight;207 fn set_const_on_chain_schema() -> Weight;208 fn set_variable_on_chain_schema() -> Weight;209 fn set_variable_meta_data() -> Weight;210 fn enable_contract_sponsoring() -> Weight;211}212213#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]214#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]215pub struct CreateNftData {216 pub const_data: Vec<u8>,217 pub variable_data: Vec<u8>,218}219220#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]221#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]222pub struct CreateFungibleData {223}224225#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]226#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]227pub struct CreateReFungibleData {228 pub const_data: Vec<u8>,229 pub variable_data: Vec<u8>,230}231232#[derive(Encode, Decode, Debug, Clone, PartialEq)]233#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]234pub enum CreateItemData {235 NFT(CreateNftData),236 Fungible(CreateFungibleData),237 ReFungible(CreateReFungibleData)238}239240impl CreateItemData {241 pub fn len(&self) -> usize {242 let len = match self {243 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),244 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),245 _ => 0246 };247 248 return len;249 }250}251252impl From<CreateNftData> for CreateItemData {253 fn from(item: CreateNftData) -> Self {254 CreateItemData::NFT(item)255 }256}257258impl From<CreateReFungibleData> for CreateItemData {259 fn from(item: CreateReFungibleData) -> Self {260 CreateItemData::ReFungible(item)261 }262}263264impl From<CreateFungibleData> for CreateItemData {265 fn from(item: CreateFungibleData) -> Self {266 CreateItemData::Fungible(item)267 }268}269270271decl_error! {272 /// Error for non-fungible-token module.273 pub enum Error for Module<T: Trait> {274 /// Total collections bound exceeded.275 TotalCollectionsLimitExceeded,276 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.277 CollectionDecimalPointLimitExceeded, 278 /// Collection name can not be longer than 63 char.279 CollectionNameLimitExceeded, 280 /// Collection description can not be longer than 255 char.281 CollectionDescriptionLimitExceeded, 282 /// Token prefix can not be longer than 15 char.283 CollectionTokenPrefixLimitExceeded,284 /// This collection does not exist.285 CollectionNotFound,286 /// Item not exists.287 TokenNotFound,288 /// Arithmetic calculation overflow.289 NumOverflow, 290 /// Account already has admin role.291 AlreadyAdmin, 292 /// You do not own this collection.293 NoPermission,294 /// This address is not set as sponsor, use setCollectionSponsor first.295 ConfirmUnsetSponsorFail,296 /// Collection is not in mint mode.297 PublicMintingNotAllowed,298 /// Sender parameter and item owner must be equal.299 MustBeTokenOwner,300 /// Item balance not enough.301 TokenValueTooLow,302 /// Size of item is too large.303 NftSizeLimitExceeded,304 /// No approve found305 ApproveNotFound,306 /// Requested value more than approved.307 TokenValueNotEnough,308 /// Only approved addresses can call this method.309 ApproveRequired,310 /// Address is not in white list.311 AddresNotInWhiteList,312 /// Number of collection admins bound exceeded.313 CollectionAdminsLimitExceeded,314 /// Owned tokens by a single address bound exceeded.315 AddressOwnershipLimitExceeded,316 /// Length of items properties must be greater than 0.317 EmptyArgument,318 /// const_data exceeded data limit.319 TokenConstDataLimitExceeded,320 /// variable_data exceeded data limit.321 TokenVariableDataLimitExceeded,322 /// Not NFT item data used to mint in NFT collection.323 NotNftDataUsedToMintNftCollectionToken,324 /// Not Fungible item data used to mint in Fungible collection.325 NotFungibleDataUsedToMintFungibleCollectionToken,326 /// Not Re Fungible item data used to mint in Re Fungible collection.327 NotReFungibleDataUsedToMintReFungibleCollectionToken,328 /// Unexpected collection type.329 UnexpectedCollectionType,330 /// Can't store metadata in fungible tokens.331 CantStoreMetadataInFungibleTokens332 }333}334335pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {336 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;337338 /// Weight information for extrinsics in this pallet.339 type WeightInfo: WeightInfo;340}341342#[cfg(feature = "runtime-benchmarks")]343mod benchmarking;344345// #endregion346347decl_storage! {348 trait Store for Module<T: Trait> as Nft {349350 // Private members351 NextCollectionID: CollectionId;352 CreatedCollectionCount: u32;353 ChainVersion: u64;354 ItemListIndex: map hasher(identity) CollectionId => TokenId;355356 // Chain limits struct357 pub ChainLimit get(fn chain_limit) config(): ChainLimits;358359 // Bound counters360 CollectionCount: u32;361 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;362363 // Basic collections364 pub Collection get(fn collection) config(): map hasher(identity) CollectionId => CollectionType<T::AccountId>;365 pub AdminList get(fn admin_list_collection): map hasher(identity) CollectionId => Vec<T::AccountId>;366 pub WhiteList get(fn white_list): map hasher(identity) CollectionId => Vec<T::AccountId>;367368 /// Balance owner per collection map369 pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;370371 /// second parameter: item id + owner account id372 pub ApprovedList get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;373374 /// Item collections375 pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;376 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => FungibleItemType<T::AccountId>;377 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;378379 /// Index list380 pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;381382 /// Tokens transfer baskets383 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;384 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => Vec<BasketItem<T::AccountId, T::BlockNumber>>;385 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;386387 // Contract Sponsorship and Ownership388 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;389 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;390 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;391 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;392 }393 add_extra_genesis {394 build(|config: &GenesisConfig<T>| {395 // Modification of storage396 for (_num, _c) in &config.collection {397 <Module<T>>::init_collection(_c);398 }399400 for (_num, _q, _i) in &config.nft_item_id {401 <Module<T>>::init_nft_token(_i);402 }403404 for (_num, _q, _i) in &config.fungible_item_id {405 <Module<T>>::init_fungible_token(_i);406 }407408 for (_num, _q, _i) in &config.refungible_item_id {409 <Module<T>>::init_refungible_token(_i);410 }411 })412 }413}414415decl_event!(416 pub enum Event<T>417 where418 AccountId = <T as system::Trait>::AccountId,419 {420 /// New collection was created421 /// 422 /// # Arguments423 /// 424 /// * collection_id: Globally unique identifier of newly created collection.425 /// 426 /// * mode: [CollectionMode] converted into u8.427 /// 428 /// * account_id: Collection owner.429 Created(CollectionId, u8, AccountId),430431 /// New item was created.432 /// 433 /// # Arguments434 /// 435 /// * collection_id: Id of the collection where item was created.436 /// 437 /// * item_id: Id of an item. Unique within the collection.438 ItemCreated(CollectionId, TokenId),439440 /// Collection item was burned.441 /// 442 /// # Arguments443 /// 444 /// collection_id.445 /// 446 /// item_id: Identifier of burned NFT.447 ItemDestroyed(CollectionId, TokenId),448 }449);450451decl_module! {452 pub struct Module<T: Trait> for enum Call where origin: T::Origin {453454 fn deposit_event() = default;455 type Error = Error<T>;456457 fn on_initialize(now: T::BlockNumber) -> Weight {458459 if ChainVersion::get() < 2460 {461 let value = NextCollectionID::get();462 CreatedCollectionCount::put(value);463 ChainVersion::put(2);464 }465466 0467 }468469 /// 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.470 /// 471 /// # Permissions472 /// 473 /// * Anyone.474 /// 475 /// # Arguments476 /// 477 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.478 /// 479 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.480 /// 481 /// * token_prefix: UTF-8 string with token prefix.482 /// 483 /// * mode: [CollectionMode] collection type and type dependent data.484 // returns collection ID485 #[weight = T::WeightInfo::create_collection()]486 pub fn create_collection(origin,487 collection_name: Vec<u16>,488 collection_description: Vec<u16>,489 token_prefix: Vec<u8>,490 mode: CollectionMode) -> DispatchResult {491492 // Anyone can create a collection493 let who = ensure_signed(origin)?;494495 let decimal_points = match mode {496 CollectionMode::Fungible(points) => points,497 CollectionMode::ReFungible(points) => points,498 _ => 0499 };500501 // bound Total number of collections502 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);503504 // check params505 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);506507 let mut name = collection_name.to_vec();508 name.push(0);509 ensure!(name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);510511 let mut description = collection_description.to_vec();512 description.push(0);513 ensure!(name.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);514515 let mut prefix = token_prefix.to_vec();516 prefix.push(0);517 ensure!(prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);518519 // Generate next collection ID520 let next_id = CreatedCollectionCount::get()521 .checked_add(1)522 .ok_or(Error::<T>::NumOverflow)?;523524 // bound counter525 let total = CollectionCount::get()526 .checked_add(1)527 .ok_or(Error::<T>::NumOverflow)?;528529 CreatedCollectionCount::put(next_id);530 CollectionCount::put(total);531532 // Create new collection533 let new_collection = CollectionType {534 owner: who.clone(),535 name: name,536 mode: mode.clone(),537 mint_mode: false,538 access: AccessMode::Normal,539 description: description,540 decimal_points: decimal_points,541 token_prefix: prefix,542 offchain_schema: Vec::new(),543 sponsor: T::AccountId::default(),544 unconfirmed_sponsor: T::AccountId::default(),545 variable_on_chain_schema: Vec::new(),546 const_on_chain_schema: Vec::new(),547 };548549 // Add new collection to map550 <Collection<T>>::insert(next_id, new_collection);551552 // call event553 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));554555 Ok(())556 }557558 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.559 /// 560 /// # Permissions561 /// 562 /// * Collection Owner.563 /// 564 /// # Arguments565 /// 566 /// * collection_id: collection to destroy.567 #[weight = T::WeightInfo::destroy_collection()]568 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {569570 let sender = ensure_signed(origin)?;571 Self::check_owner_permissions(collection_id, sender)?;572573 <AddressTokens<T>>::remove_prefix(collection_id);574 <ApprovedList<T>>::remove_prefix(collection_id);575 <Balance<T>>::remove_prefix(collection_id);576 <ItemListIndex>::remove(collection_id);577 <AdminList<T>>::remove(collection_id);578 <Collection<T>>::remove(collection_id);579 <WhiteList<T>>::remove(collection_id);580581 <NftItemList<T>>::remove_prefix(collection_id);582 <FungibleItemList<T>>::remove_prefix(collection_id);583 <ReFungibleItemList<T>>::remove_prefix(collection_id);584585 <NftTransferBasket<T>>::remove_prefix(collection_id);586 <FungibleTransferBasket<T>>::remove_prefix(collection_id);587 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);588589 if CollectionCount::get() > 0590 {591 // bound couter592 let total = CollectionCount::get()593 .checked_sub(1)594 .ok_or(Error::<T>::NumOverflow)?;595596 CollectionCount::put(total);597 }598599 Ok(())600 }601602 /// Add an address to white list.603 /// 604 /// # Permissions605 /// 606 /// * Collection Owner607 /// * Collection Admin608 /// 609 /// # Arguments610 /// 611 /// * collection_id.612 /// 613 /// * address.614 #[weight = T::WeightInfo::add_to_white_list()]615 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{616617 let sender = ensure_signed(origin)?;618 Self::check_owner_or_admin_permissions(collection_id, sender)?;619620 let mut white_list_collection: Vec<T::AccountId>;621 if <WhiteList<T>>::contains_key(collection_id) {622 white_list_collection = <WhiteList<T>>::get(collection_id);623 if !white_list_collection.contains(&address.clone())624 {625 white_list_collection.push(address.clone());626 }627 }628 else {629 white_list_collection = Vec::new();630 white_list_collection.push(address.clone());631 }632633 <WhiteList<T>>::insert(collection_id, white_list_collection);634 Ok(())635 }636637 /// Remove an address from white list.638 /// 639 /// # Permissions640 /// 641 /// * Collection Owner642 /// * Collection Admin643 /// 644 /// # Arguments645 /// 646 /// * collection_id.647 /// 648 /// * address.649 #[weight = T::WeightInfo::remove_from_white_list()]650 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{651652 let sender = ensure_signed(origin)?;653 Self::check_owner_or_admin_permissions(collection_id, sender)?;654655 if <WhiteList<T>>::contains_key(collection_id) {656 let mut white_list_collection = <WhiteList<T>>::get(collection_id);657 if white_list_collection.contains(&address.clone())658 {659 white_list_collection.retain(|i| *i != address.clone());660 <WhiteList<T>>::insert(collection_id, white_list_collection);661 }662 }663664 Ok(())665 }666667 /// Toggle between normal and white list access for the methods with access for `Anyone`.668 /// 669 /// # Permissions670 /// 671 /// * Collection Owner.672 /// 673 /// # Arguments674 /// 675 /// * collection_id.676 /// 677 /// * mode: [AccessMode]678 #[weight = T::WeightInfo::set_public_access_mode()]679 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult680 {681 let sender = ensure_signed(origin)?;682683 Self::check_owner_permissions(collection_id, sender)?;684 let mut target_collection = <Collection<T>>::get(collection_id);685 target_collection.access = mode;686 <Collection<T>>::insert(collection_id, target_collection);687688 Ok(())689 }690691 /// Allows Anyone to create tokens if:692 /// * White List is enabled, and693 /// * Address is added to white list, and694 /// * This method was called with True parameter695 /// 696 /// # Permissions697 /// * Collection Owner698 ///699 /// # Arguments700 /// 701 /// * collection_id.702 /// 703 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.704 #[weight = T::WeightInfo::set_mint_permission()]705 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult706 {707 let sender = ensure_signed(origin)?;708709 Self::check_owner_permissions(collection_id, sender)?;710 let mut target_collection = <Collection<T>>::get(collection_id);711 target_collection.mint_mode = mint_permission;712 <Collection<T>>::insert(collection_id, target_collection);713714 Ok(())715 }716717 /// Change the owner of the collection.718 /// 719 /// # Permissions720 /// 721 /// * Collection Owner.722 /// 723 /// # Arguments724 /// 725 /// * collection_id.726 /// 727 /// * new_owner.728 #[weight = T::WeightInfo::change_collection_owner()]729 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {730731 let sender = ensure_signed(origin)?;732 Self::check_owner_permissions(collection_id, sender)?;733 let mut target_collection = <Collection<T>>::get(collection_id);734 target_collection.owner = new_owner;735 <Collection<T>>::insert(collection_id, target_collection);736737 Ok(())738 }739740 /// Adds an admin of the Collection.741 /// 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. 742 /// 743 /// # Permissions744 /// 745 /// * Collection Owner.746 /// * Collection Admin.747 /// 748 /// # Arguments749 /// 750 /// * collection_id: ID of the Collection to add admin for.751 /// 752 /// * new_admin_id: Address of new admin to add.753 #[weight = T::WeightInfo::add_collection_admin()]754 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {755756 let sender = ensure_signed(origin)?;757 Self::check_owner_or_admin_permissions(collection_id, sender)?;758 let mut admin_arr: Vec<T::AccountId> = Vec::new();759760 if <AdminList<T>>::contains_key(collection_id)761 {762 admin_arr = <AdminList<T>>::get(collection_id);763 ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);764 }765766 // Number of collection admins767 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);768769 admin_arr.push(new_admin_id);770 <AdminList<T>>::insert(collection_id, admin_arr);771772 Ok(())773 }774775 /// 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.776 ///777 /// # Permissions778 /// 779 /// * Collection Owner.780 /// * Collection Admin.781 /// 782 /// # Arguments783 /// 784 /// * collection_id: ID of the Collection to remove admin for.785 /// 786 /// * account_id: Address of admin to remove.787 #[weight = T::WeightInfo::remove_collection_admin()]788 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {789790 let sender = ensure_signed(origin)?;791 Self::check_owner_or_admin_permissions(collection_id, sender)?;792793 if <AdminList<T>>::contains_key(collection_id)794 {795 let mut admin_arr = <AdminList<T>>::get(collection_id);796 admin_arr.retain(|i| *i != account_id);797 <AdminList<T>>::insert(collection_id, admin_arr);798 }799800 Ok(())801 }802803 /// # Permissions804 /// 805 /// * Collection Owner806 /// 807 /// # Arguments808 /// 809 /// * collection_id.810 /// 811 /// * new_sponsor.812 #[weight = T::WeightInfo::set_collection_sponsor()]813 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {814815 let sender = ensure_signed(origin)?;816 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);817818 let mut target_collection = <Collection<T>>::get(collection_id);819 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);820821 target_collection.unconfirmed_sponsor = new_sponsor;822 <Collection<T>>::insert(collection_id, target_collection);823824 Ok(())825 }826827 /// # Permissions828 /// 829 /// * Sponsor.830 /// 831 /// # Arguments832 /// 833 /// * collection_id.834 #[weight = T::WeightInfo::confirm_sponsorship()]835 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {836837 let sender = ensure_signed(origin)?;838 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);839840 let mut target_collection = <Collection<T>>::get(collection_id);841 ensure!(sender == target_collection.unconfirmed_sponsor, Error::<T>::ConfirmUnsetSponsorFail);842843 target_collection.sponsor = target_collection.unconfirmed_sponsor;844 target_collection.unconfirmed_sponsor = T::AccountId::default();845 <Collection<T>>::insert(collection_id, target_collection);846847 Ok(())848 }849850 /// Switch back to pay-per-own-transaction model.851 ///852 /// # Permissions853 ///854 /// * Collection owner.855 /// 856 /// # Arguments857 /// 858 /// * collection_id.859 #[weight = T::WeightInfo::remove_collection_sponsor()]860 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {861862 let sender = ensure_signed(origin)?;863 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);864865 let mut target_collection = <Collection<T>>::get(collection_id);866 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);867868 target_collection.sponsor = T::AccountId::default();869 <Collection<T>>::insert(collection_id, target_collection);870871 Ok(())872 }873874 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.875 /// 876 /// # Permissions877 /// 878 /// * Collection Owner.879 /// * Collection Admin.880 /// * Anyone if881 /// * White List is enabled, and882 /// * Address is added to white list, and883 /// * MintPermission is enabled (see SetMintPermission method)884 /// 885 /// # Arguments886 /// 887 /// * collection_id: ID of the collection.888 /// 889 /// * owner: Address, initial owner of the NFT.890 ///891 /// * data: Token data to store on chain.892 // #[weight =893 // (130_000_000 as Weight)894 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))895 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))896 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]897898 #[weight = T::WeightInfo::create_item(data.len())]899 pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {900901 let sender = ensure_signed(origin)?;902903 Self::collection_exists(collection_id)?;904905 let target_collection = <Collection<T>>::get(collection_id);906907 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;908 Self::validate_create_item_args(&target_collection, &data)?;909 Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;910911 Ok(())912 }913914 /// This method creates multiple instances of NFT Collection created with CreateCollection method.915 /// 916 /// # Permissions917 /// 918 /// * Collection Owner.919 /// * Collection Admin.920 /// * Anyone if921 /// * White List is enabled, and922 /// * Address is added to white list, and923 /// * MintPermission is enabled (see SetMintPermission method)924 /// 925 /// # Arguments926 /// 927 /// * collection_id: ID of the collection.928 /// 929 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].930 /// 931 /// * owner: Address, initial owner of the NFT.932 #[weight = T::WeightInfo::create_item(items_data.into_iter()933 .map(|data| { data.len() })934 .sum())]935 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {936937 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);938 let sender = ensure_signed(origin)?;939940 Self::collection_exists(collection_id)?;941 let target_collection = <Collection<T>>::get(collection_id);942943 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;944945 for data in &items_data {946 Self::validate_create_item_args(&target_collection, data)?;947 }948 for data in &items_data {949 Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;950 }951952 Ok(())953 }954955 /// Destroys a concrete instance of NFT.956 /// 957 /// # Permissions958 /// 959 /// * Collection Owner.960 /// * Collection Admin.961 /// * Current NFT Owner.962 /// 963 /// # Arguments964 /// 965 /// * collection_id: ID of the collection.966 /// 967 /// * item_id: ID of NFT to burn.968 #[weight = T::WeightInfo::burn_item()]969 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {970971 let sender = ensure_signed(origin)?;972 Self::collection_exists(collection_id)?;973974 // Transfer permissions check975 let target_collection = <Collection<T>>::get(collection_id);976 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||977 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),978 Error::<T>::NoPermission);979980 if target_collection.access == AccessMode::WhiteList {981 Self::check_white_list(collection_id, &sender)?;982 }983984 match target_collection.mode985 {986 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,987 CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?,988 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,989 _ => ()990 };991992 // call event993 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));994995 Ok(())996 }997998 /// Change ownership of the token.999 /// 1000 /// # Permissions1001 /// 1002 /// * Collection Owner1003 /// * Collection Admin1004 /// * Current NFT owner1005 ///1006 /// # Arguments1007 /// 1008 /// * recipient: Address of token recipient.1009 /// 1010 /// * collection_id.1011 /// 1012 /// * item_id: ID of the item1013 /// * Non-Fungible Mode: Required.1014 /// * Fungible Mode: Ignored.1015 /// * Re-Fungible Mode: Required.1016 /// 1017 /// * value: Amount to transfer.1018 /// * Non-Fungible Mode: Ignored1019 /// * Fungible Mode: Must specify transferred amount1020 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1021 #[weight = T::WeightInfo::transfer()]1022 pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10231024 let sender = ensure_signed(origin)?;10251026 // Transfer permissions check1027 let target_collection = <Collection<T>>::get(collection_id);1028 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1029 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1030 Error::<T>::NoPermission);10311032 if target_collection.access == AccessMode::WhiteList {1033 Self::check_white_list(collection_id, &sender)?;1034 Self::check_white_list(collection_id, &recipient)?;1035 }10361037 match target_collection.mode1038 {1039 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1040 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,1041 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1042 _ => ()1043 };10441045 Ok(())1046 }10471048 /// Set, change, or remove approved address to transfer the ownership of the NFT.1049 /// 1050 /// # Permissions1051 /// 1052 /// * Collection Owner1053 /// * Collection Admin1054 /// * Current NFT owner1055 /// 1056 /// # Arguments1057 /// 1058 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1059 /// 1060 /// * collection_id.1061 /// 1062 /// * item_id: ID of the item.1063 #[weight = T::WeightInfo::approve()]1064 pub fn approve(origin, approved: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {10651066 let sender = ensure_signed(origin)?;10671068 // Transfer permissions check1069 let target_collection = <Collection<T>>::get(collection_id);1070 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1071 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1072 Error::<T>::NoPermission);10731074 if target_collection.access == AccessMode::WhiteList {1075 Self::check_white_list(collection_id, &sender)?;1076 Self::check_white_list(collection_id, &approved)?;1077 }10781079 // amount param stub1080 let amount = 100000000;10811082 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));1083 if list_exists {10841085 let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));1086 let item_contains = list.iter().any(|i| i.approved == approved);10871088 if !item_contains {1089 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1090 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1091 }1092 } else {10931094 let mut list = Vec::new();1095 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1096 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1097 }10981099 Ok(())1100 }1101 1102 /// 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.1103 /// 1104 /// # Permissions1105 /// * Collection Owner1106 /// * Collection Admin1107 /// * Current NFT owner1108 /// * Address approved by current NFT owner1109 /// 1110 /// # Arguments1111 /// 1112 /// * from: Address that owns token.1113 /// 1114 /// * recipient: Address of token recipient.1115 /// 1116 /// * collection_id.1117 /// 1118 /// * item_id: ID of the item.1119 /// 1120 /// * value: Amount to transfer.1121 #[weight = T::WeightInfo::transfer_from()]1122 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {11231124 let sender = ensure_signed(origin)?;1125 let mut appoved_transfer = false;11261127 // Check approve1128 if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {1129 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));1130 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());1131 if opt_item.is_some()1132 {1133 appoved_transfer = true;1134 ensure!(opt_item.unwrap().amount >= value, Error::<T>::TokenValueNotEnough);1135 }1136 }11371138 // Transfer permissions check1139 let target_collection = <Collection<T>>::get(collection_id);1140 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1141 Error::<T>::NoPermission);11421143 if target_collection.access == AccessMode::WhiteList {1144 Self::check_white_list(collection_id, &sender)?;1145 Self::check_white_list(collection_id, &recipient)?;1146 }11471148 // remove approve1149 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))1150 .into_iter().filter(|i| i.approved != sender.clone()).collect();1151 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);115211531154 match target_collection.mode1155 {1156 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1157 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,1158 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1159 _ => ()1160 };11611162 Ok(())1163 }11641165 ///1166 #[weight = 0]1167 pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {11681169 // let no_perm_mes = "You do not have permissions to modify this collection";1170 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1171 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1172 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);11731174 // // on_nft_received call11751176 // Self::transfer(origin, collection_id, item_id, new_owner)?;11771178 Ok(())1179 }11801181 /// Set off-chain data schema.1182 /// 1183 /// # Permissions1184 /// 1185 /// * Collection Owner1186 /// * Collection Admin1187 /// 1188 /// # Arguments1189 /// 1190 /// * collection_id.1191 /// 1192 /// * schema: String representing the offchain data schema.1193 #[weight = T::WeightInfo::set_variable_meta_data()]1194 pub fn set_variable_meta_data (1195 origin,1196 collection_id: CollectionId,1197 item_id: TokenId,1198 data: Vec<u8>1199 ) -> DispatchResult {1200 let sender = ensure_signed(origin)?;1201 1202 Self::collection_exists(collection_id)?;1203 1204 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12051206 // Modify permissions check1207 let target_collection = <Collection<T>>::get(collection_id);1208 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1209 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1210 Error::<T>::NoPermission);12111212 Self::item_exists(collection_id, item_id, &target_collection.mode)?;12131214 match target_collection.mode1215 {1216 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1217 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1218 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1219 _ => fail!(Error::<T>::UnexpectedCollectionType)1220 };12211222 Ok(())1223 }1224 12251226 /// Set off-chain data schema.1227 /// 1228 /// # Permissions1229 /// 1230 /// * Collection Owner1231 /// * Collection Admin1232 /// 1233 /// # Arguments1234 /// 1235 /// * collection_id.1236 /// 1237 /// * schema: String representing the offchain data schema.1238 #[weight = T::WeightInfo::set_offchain_schema()]1239 pub fn set_offchain_schema(1240 origin,1241 collection_id: CollectionId,1242 schema: Vec<u8>1243 ) -> DispatchResult {1244 let sender = ensure_signed(origin)?;1245 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12461247 let mut target_collection = <Collection<T>>::get(collection_id);1248 target_collection.offchain_schema = schema;1249 <Collection<T>>::insert(collection_id, target_collection);12501251 Ok(())1252 }12531254 /// Set const on-chain data schema.1255 /// 1256 /// # Permissions1257 /// 1258 /// * Collection Owner1259 /// * Collection Admin1260 /// 1261 /// # Arguments1262 /// 1263 /// * collection_id.1264 /// 1265 /// * schema: String representing the const on-chain data schema.1266 #[weight = T::WeightInfo::set_const_on_chain_schema()]1267 pub fn set_const_on_chain_schema (1268 origin,1269 collection_id: CollectionId,1270 schema: Vec<u8>1271 ) -> DispatchResult {1272 let sender = ensure_signed(origin)?;1273 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12741275 let mut target_collection = <Collection<T>>::get(collection_id);1276 target_collection.const_on_chain_schema = schema;1277 <Collection<T>>::insert(collection_id, target_collection);12781279 Ok(())1280 }12811282 /// Set variable on-chain data schema.1283 /// 1284 /// # Permissions1285 /// 1286 /// * Collection Owner1287 /// * Collection Admin1288 /// 1289 /// # Arguments1290 /// 1291 /// * collection_id.1292 /// 1293 /// * schema: String representing the variable on-chain data schema.1294 #[weight = T::WeightInfo::set_const_on_chain_schema()]1295 pub fn set_variable_on_chain_schema (1296 origin,1297 collection_id: CollectionId,1298 schema: Vec<u8>1299 ) -> DispatchResult {1300 let sender = ensure_signed(origin)?;1301 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13021303 let mut target_collection = <Collection<T>>::get(collection_id);1304 target_collection.variable_on_chain_schema = schema;1305 <Collection<T>>::insert(collection_id, target_collection);13061307 Ok(())1308 }13091310 // Sudo permissions function1311 #[weight = 0]1312 pub fn set_chain_limits(1313 origin,1314 limits: ChainLimits1315 ) -> DispatchResult {1316 ensure_root(origin)?;1317 <ChainLimit>::put(limits);1318 Ok(())1319 }13201321 /// Enable smart contract self-sponsoring.1322 /// 1323 /// # Permissions1324 /// 1325 /// * Contract Owner1326 /// 1327 /// # Arguments1328 /// 1329 /// * contract address1330 /// * enable flag1331 /// 1332 #[weight = T::WeightInfo::enable_contract_sponsoring()]1333 pub fn enable_contract_sponsoring(1334 origin,1335 contract_address: T::AccountId,1336 enable: bool1337 ) -> DispatchResult {13381339 let sender = ensure_signed(origin)?;13401341 #[cfg(feature = "runtime-benchmarks")]1342 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13431344 let mut is_owner = false;1345 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1346 let owner = <ContractOwner<T>>::get(&contract_address);1347 is_owner = sender == owner;1348 }1349 ensure!(is_owner, Error::<T>::NoPermission);13501351 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1352 Ok(())1353 }13541355 /// Set the rate limit for contract sponsoring to specified number of blocks.1356 /// 1357 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1358 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1359 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1360 /// from contract endowment if there are at least B blocks between such transactions. 1361 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1362 /// 1363 /// # Permissions1364 /// 1365 /// * Contract Owner1366 /// 1367 /// # Arguments1368 /// 1369 /// -`contract_address`: Address of the contract to sponsor1370 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1371 /// 1372 #[weight = 0]1373 pub fn set_contract_sponsoring_rate_limit(1374 origin,1375 contract_address: T::AccountId,1376 rate_limit: T::BlockNumber1377 ) -> DispatchResult {1378 let sender = ensure_signed(origin)?;1379 let mut is_owner = false;1380 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1381 let owner = <ContractOwner<T>>::get(&contract_address);1382 is_owner = sender == owner;1383 }1384 ensure!(is_owner, Error::<T>::NoPermission);13851386 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1387 Ok(())1388 }13891390 // #[cfg(feature = "runtime-benchmarks")]1391 // #[weight = 0]1392 // pub fn add_contract_sponsoring_debug(1393 // origin,1394 // contract_address: T::AccountId, 1395 // owner: T::AccountId) -> DispatchResult {1396 // let sender = ensure_signed(origin)?;1397 // <ContractOwner<T>>::insert(contract_address.clone(), owner);1398 // Ok(())1399 // }1400 1401 }1402}14031404impl<T: Trait> Module<T> {14051406 fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {14071408 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1409 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1410 Self::check_white_list(collection_id, owner)?;1411 Self::check_white_list(collection_id, sender)?;1412 }14131414 Ok(())1415 }14161417 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1418 match target_collection.mode1419 {1420 CollectionMode::NFT => {1421 if let CreateItemData::NFT(data) = data {1422 // check sizes1423 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1424 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1425 } else {1426 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1427 }1428 },1429 CollectionMode::Fungible(_) => {1430 if let CreateItemData::Fungible(_) = data {1431 } else {1432 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1433 }1434 },1435 CollectionMode::ReFungible(_) => {1436 if let CreateItemData::ReFungible(data) = data {14371438 // check sizes1439 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1440 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1441 } else {1442 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1443 }1444 },1445 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1446 };14471448 Ok(())1449 }14501451 fn create_item_no_validation(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1452 match data1453 {1454 CreateItemData::NFT(data) => {1455 let item = NftItemType {1456 collection: collection_id,1457 owner,1458 const_data: data.const_data,1459 variable_data: data.variable_data1460 };14611462 Self::add_nft_item(item)?;1463 },1464 CreateItemData::Fungible(_) => {1465 let item = FungibleItemType {1466 collection: collection_id,1467 owner,1468 value: (10 as u128).pow(collection.decimal_points as u32)1469 };14701471 Self::add_fungible_item(item)?;1472 },1473 CreateItemData::ReFungible(data) => {1474 let mut owner_list = Vec::new();1475 let value = (10 as u128).pow(collection.decimal_points as u32);1476 owner_list.push(Ownership {owner: owner.clone(), fraction: value});14771478 let item = ReFungibleItemType {1479 collection: collection_id,1480 owner: owner_list,1481 const_data: data.const_data,1482 variable_data: data.variable_data1483 };14841485 Self::add_refungible_item(item)?;1486 }1487 };148814891490 // call event1491 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));14921493 Ok(())1494 }14951496 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {1497 let current_index = <ItemListIndex>::get(item.collection)1498 .checked_add(1)1499 .ok_or(Error::<T>::NumOverflow)?;1500 let itemcopy = item.clone();1501 let owner = item.owner.clone();15021503 Self::add_token_index(item.collection, current_index, owner.clone())?;15041505 <ItemListIndex>::insert(item.collection, current_index);1506 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);15071508 // Add current block1509 let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1510 <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);1511 1512 // Update balance1513 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1514 .checked_add(item.value)1515 .ok_or(Error::<T>::NumOverflow)?;1516 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);15171518 Ok(())1519 }15201521 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1522 let current_index = <ItemListIndex>::get(item.collection)1523 .checked_add(1)1524 .ok_or(Error::<T>::NumOverflow)?;1525 let itemcopy = item.clone();15261527 let value = item.owner.first().unwrap().fraction;1528 let owner = item.owner.first().unwrap().owner.clone();15291530 Self::add_token_index(item.collection, current_index, owner.clone())?;15311532 <ItemListIndex>::insert(item.collection, current_index);1533 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);15341535 // Add current block1536 let block_number: T::BlockNumber = 0.into();1537 <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);15381539 // Update balance1540 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1541 .checked_add(value)1542 .ok_or(Error::<T>::NumOverflow)?;1543 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);15441545 Ok(())1546 }15471548 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1549 let current_index = <ItemListIndex>::get(item.collection)1550 .checked_add(1)1551 .ok_or(Error::<T>::NumOverflow)?;15521553 let item_owner = item.owner.clone();1554 let collection_id = item.collection.clone();1555 Self::add_token_index(collection_id, current_index, item.owner.clone())?;15561557 <ItemListIndex>::insert(collection_id, current_index);1558 <NftItemList<T>>::insert(collection_id, current_index, item);15591560 // Add current block1561 let block_number: T::BlockNumber = 0.into();1562 <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);15631564 // Update balance1565 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1566 .checked_add(1)1567 .ok_or(Error::<T>::NumOverflow)?;1568 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);15691570 Ok(())1571 }15721573 fn burn_refungible_item(1574 collection_id: CollectionId,1575 item_id: TokenId,1576 owner: T::AccountId,1577 ) -> DispatchResult {1578 ensure!(1579 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1580 Error::<T>::TokenNotFound1581 );1582 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1583 let item = collection1584 .owner1585 .iter()1586 .filter(|&i| i.owner == owner)1587 .next()1588 .unwrap();1589 Self::remove_token_index(collection_id, item_id, owner.clone())?;15901591 // remove approve list1592 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));15931594 // update balance1595 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1596 .checked_sub(item.fraction)1597 .ok_or(Error::<T>::NumOverflow)?;1598 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);15991600 <ReFungibleItemList<T>>::remove(collection_id, item_id);16011602 Ok(())1603 }16041605 fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1606 ensure!(1607 <NftItemList<T>>::contains_key(collection_id, item_id),1608 Error::<T>::TokenNotFound1609 );1610 let item = <NftItemList<T>>::get(collection_id, item_id);1611 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;16121613 // remove approve list1614 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));16151616 // update balance1617 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1618 .checked_sub(1)1619 .ok_or(Error::<T>::NumOverflow)?;1620 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1621 <NftItemList<T>>::remove(collection_id, item_id);16221623 Ok(())1624 }16251626 fn burn_fungible_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1627 ensure!(1628 <FungibleItemList<T>>::contains_key(collection_id, item_id),1629 Error::<T>::TokenNotFound1630 );1631 let item = <FungibleItemList<T>>::get(collection_id, item_id);1632 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;16331634 // remove approve list1635 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));16361637 // update balance1638 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1639 .checked_sub(item.value)1640 .ok_or(Error::<T>::NumOverflow)?;1641 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);16421643 <FungibleItemList<T>>::remove(collection_id, item_id);16441645 Ok(())1646 }16471648 fn collection_exists(collection_id: CollectionId) -> DispatchResult {1649 ensure!(1650 <Collection<T>>::contains_key(collection_id),1651 Error::<T>::CollectionNotFound1652 );1653 Ok(())1654 }16551656 fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1657 Self::collection_exists(collection_id)?;16581659 let target_collection = <Collection<T>>::get(collection_id);1660 ensure!(1661 subject == target_collection.owner,1662 Error::<T>::NoPermission1663 );16641665 Ok(())1666 }16671668 fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1669 let target_collection = <Collection<T>>::get(collection_id);1670 let mut result: bool = subject == target_collection.owner;1671 let exists = <AdminList<T>>::contains_key(collection_id);16721673 if !result & exists {1674 if <AdminList<T>>::get(collection_id).contains(&subject) {1675 result = true1676 }1677 }16781679 result1680 }16811682 fn check_owner_or_admin_permissions(1683 collection_id: CollectionId,1684 subject: T::AccountId,1685 ) -> DispatchResult {1686 Self::collection_exists(collection_id)?;1687 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());16881689 ensure!(1690 result,1691 Error::<T>::NoPermission1692 );1693 Ok(())1694 }16951696 fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1697 let target_collection = <Collection<T>>::get(collection_id);16981699 match target_collection.mode {1700 CollectionMode::NFT => {1701 <NftItemList<T>>::get(collection_id, item_id).owner == subject1702 }1703 CollectionMode::Fungible(_) => {1704 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1705 }1706 CollectionMode::ReFungible(_) => {1707 <ReFungibleItemList<T>>::get(collection_id, item_id)1708 .owner1709 .iter()1710 .any(|i| i.owner == subject)1711 }1712 CollectionMode::Invalid => false,1713 }1714 }17151716 fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1717 let mes = Error::<T>::AddresNotInWhiteList;1718 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1719 let wl = <WhiteList<T>>::get(collection_id);1720 ensure!(wl.contains(address), mes);17211722 Ok(())1723 }17241725 fn transfer_fungible(1726 collection_id: CollectionId,1727 item_id: TokenId,1728 value: u128,1729 owner: T::AccountId,1730 new_owner: T::AccountId,1731 ) -> DispatchResult {1732 ensure!(1733 <FungibleItemList<T>>::contains_key(collection_id, item_id),1734 Error::<T>::TokenNotFound1735 );17361737 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1738 let amount = full_item.value;17391740 ensure!(amount >= value, Error::<T>::TokenValueTooLow);17411742 // update balance1743 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1744 .checked_sub(value)1745 .ok_or(Error::<T>::NumOverflow)?;1746 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);17471748 let mut new_owner_account_id = 0;1749 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1750 if new_owner_items.len() > 0 {1751 new_owner_account_id = new_owner_items[0];1752 }17531754 // transfer1755 if amount == value && new_owner_account_id == 0 {1756 // change owner1757 // new owner do not have account1758 let mut new_full_item = full_item.clone();1759 new_full_item.owner = new_owner.clone();1760 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);17611762 // update balance1763 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1764 .checked_add(value)1765 .ok_or(Error::<T>::NumOverflow)?;1766 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);17671768 // update index collection1769 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1770 } else {1771 let mut new_full_item = full_item.clone();1772 new_full_item.value -= value;17731774 // separate amount1775 if new_owner_account_id > 0 {1776 // new owner has account1777 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1778 item.value += value;17791780 // update balance1781 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 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1787 } else {1788 // new owner do not have account1789 let item = FungibleItemType {1790 collection: collection_id,1791 owner: new_owner.clone(),1792 value1793 };17941795 Self::add_fungible_item(item)?;1796 }17971798 if amount == value {1799 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;18001801 // remove approve list1802 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1803 <FungibleItemList<T>>::remove(collection_id, item_id);1804 }18051806 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1807 }18081809 Ok(())1810 }18111812 fn transfer_refungible(1813 collection_id: CollectionId,1814 item_id: TokenId,1815 value: u128,1816 owner: T::AccountId,1817 new_owner: T::AccountId,1818 ) -> DispatchResult {1819 ensure!(1820 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1821 Error::<T>::TokenNotFound1822 );18231824 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1825 let item = full_item1826 .owner1827 .iter()1828 .filter(|i| i.owner == owner)1829 .next()1830 .ok_or(Error::<T>::NumOverflow)?;1831 let amount = item.fraction;18321833 ensure!(amount >= value, Error::<T>::TokenValueTooLow);18341835 // update balance1836 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1837 .checked_sub(value)1838 .ok_or(Error::<T>::NumOverflow)?;1839 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);18401841 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1842 .checked_add(value)1843 .ok_or(Error::<T>::NumOverflow)?;1844 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);18451846 let old_owner = item.owner.clone();1847 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);18481849 // transfer1850 if amount == value && !new_owner_has_account {1851 // change owner1852 // new owner do not have account1853 let mut new_full_item = full_item.clone();1854 new_full_item1855 .owner1856 .iter_mut()1857 .find(|i| i.owner == owner)1858 .unwrap()1859 .owner = new_owner.clone();1860 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);18611862 // update index collection1863 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1864 } else {1865 let mut new_full_item = full_item.clone();1866 new_full_item1867 .owner1868 .iter_mut()1869 .find(|i| i.owner == owner)1870 .unwrap()1871 .fraction -= value;18721873 // separate amount1874 if new_owner_has_account {1875 // new owner has account1876 new_full_item1877 .owner1878 .iter_mut()1879 .find(|i| i.owner == new_owner)1880 .unwrap()1881 .fraction += value;1882 } else {1883 // new owner do not have account1884 new_full_item.owner.push(Ownership {1885 owner: new_owner.clone(),1886 fraction: value,1887 });1888 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1889 }18901891 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1892 }18931894 Ok(())1895 }18961897 fn transfer_nft(1898 collection_id: CollectionId,1899 item_id: TokenId,1900 sender: T::AccountId,1901 new_owner: T::AccountId,1902 ) -> DispatchResult {1903 ensure!(1904 <NftItemList<T>>::contains_key(collection_id, item_id),1905 Error::<T>::TokenNotFound1906 );19071908 let mut item = <NftItemList<T>>::get(collection_id, item_id);19091910 ensure!(1911 sender == item.owner,1912 Error::<T>::MustBeTokenOwner1913 );19141915 // update balance1916 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1917 .checked_sub(1)1918 .ok_or(Error::<T>::NumOverflow)?;1919 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19201921 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1922 .checked_add(1)1923 .ok_or(Error::<T>::NumOverflow)?;1924 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);19251926 // change owner1927 let old_owner = item.owner.clone();1928 item.owner = new_owner.clone();1929 <NftItemList<T>>::insert(collection_id, item_id, item);19301931 // update index collection1932 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;19331934 // reset approved list1935 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));1936 Ok(())1937 }1938 1939 fn item_exists(1940 collection_id: CollectionId,1941 item_id: TokenId,1942 mode: &CollectionMode1943 ) -> DispatchResult {1944 match mode {1945 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1946 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1947 CollectionMode::Fungible(_) => ensure!(<FungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1948 _ => ()1949 };1950 1951 Ok(())1952 }19531954 fn set_re_fungible_variable_data(1955 collection_id: CollectionId,1956 item_id: TokenId,1957 data: Vec<u8>1958 ) -> DispatchResult {1959 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);19601961 item.variable_data = data;19621963 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);19641965 Ok(())1966 }19671968 fn set_nft_variable_data(1969 collection_id: CollectionId,1970 item_id: TokenId,1971 data: Vec<u8>1972 ) -> DispatchResult {1973 let mut item = <NftItemList<T>>::get(collection_id, item_id);1974 1975 item.variable_data = data;19761977 <NftItemList<T>>::insert(collection_id, item_id, item);1978 1979 Ok(())1980 }19811982 fn init_collection(item: &CollectionType<T::AccountId>) {1983 // check params1984 assert!(1985 item.decimal_points <= MAX_DECIMAL_POINTS,1986 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"1987 );1988 assert!(1989 item.name.len() <= 64,1990 "Collection name can not be longer than 63 char"1991 );1992 assert!(1993 item.name.len() <= 256,1994 "Collection description can not be longer than 255 char"1995 );1996 assert!(1997 item.token_prefix.len() <= 16,1998 "Token prefix can not be longer than 15 char"1999 );20002001 // Generate next collection ID2002 let next_id = CreatedCollectionCount::get()2003 .checked_add(1)2004 .unwrap();20052006 CreatedCollectionCount::put(next_id);2007 }20082009 fn init_nft_token(item: &NftItemType<T::AccountId>) {2010 let current_index = <ItemListIndex>::get(item.collection)2011 .checked_add(1)2012 .unwrap();20132014 let item_owner = item.owner.clone();2015 let collection_id = item.collection.clone();2016 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();20172018 <ItemListIndex>::insert(collection_id, current_index);20192020 // Update balance2021 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())2022 .checked_add(1)2023 .unwrap();2024 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2025 }20262027 fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {2028 let current_index = <ItemListIndex>::get(item.collection)2029 .checked_add(1)2030 .unwrap();2031 let owner = item.owner.clone();20322033 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();20342035 <ItemListIndex>::insert(item.collection, current_index);20362037 // Update balance2038 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2039 .checked_add(item.value)2040 .unwrap();2041 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2042 }20432044 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {2045 let current_index = <ItemListIndex>::get(item.collection)2046 .checked_add(1)2047 .unwrap();20482049 let value = item.owner.first().unwrap().fraction;2050 let owner = item.owner.first().unwrap().owner.clone();20512052 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();20532054 <ItemListIndex>::insert(item.collection, current_index);20552056 // Update balance2057 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2058 .checked_add(value)2059 .unwrap();2060 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2061 }20622063 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {20642065 // add to account limit2066 if <AccountItemCount<T>>::contains_key(owner.clone()) {20672068 // bound Owned tokens by a single address2069 let count = <AccountItemCount<T>>::get(owner.clone());2070 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);20712072 <AccountItemCount<T>>::insert(owner.clone(), count2073 .checked_add(1)2074 .ok_or(Error::<T>::NumOverflow)?);2075 }2076 else {2077 <AccountItemCount<T>>::insert(owner.clone(), 1);2078 }20792080 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2081 if list_exists {2082 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2083 let item_contains = list.contains(&item_index.clone());20842085 if !item_contains {2086 list.push(item_index.clone());2087 }20882089 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2090 } else {2091 let mut itm = Vec::new();2092 itm.push(item_index.clone());2093 <AddressTokens<T>>::insert(collection_id, owner, itm);2094 2095 }20962097 Ok(())2098 }20992100 fn remove_token_index(2101 collection_id: CollectionId,2102 item_index: TokenId,2103 owner: T::AccountId,2104 ) -> DispatchResult {21052106 // update counter2107 <AccountItemCount<T>>::insert(owner.clone(), 2108 <AccountItemCount<T>>::get(owner.clone())2109 .checked_sub(1)2110 .ok_or(Error::<T>::NumOverflow)?);211121122113 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2114 if list_exists {2115 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2116 let item_contains = list.contains(&item_index.clone());21172118 if item_contains {2119 list.retain(|&item| item != item_index);2120 <AddressTokens<T>>::insert(collection_id, owner, list);2121 }2122 }21232124 Ok(())2125 }21262127 fn move_token_index(2128 collection_id: CollectionId,2129 item_index: TokenId,2130 old_owner: T::AccountId,2131 new_owner: T::AccountId,2132 ) -> DispatchResult {2133 Self::remove_token_index(collection_id, item_index, old_owner)?;2134 Self::add_token_index(collection_id, item_index, new_owner)?;21352136 Ok(())2137 }2138}21392140////////////////////////////////////////////////////////////////////////////////////////////////////2141// Economic models2142// #region21432144/// Fee multiplier.2145pub type Multiplier = FixedU128;21462147type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2148 <T as system::Trait>::AccountId,2149>>::Balance;2150type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2151 <T as system::Trait>::AccountId,2152>>::NegativeImbalance;21532154/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2155/// in the queue.2156#[derive(Encode, Decode, Clone, Eq, PartialEq)]2157pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2158 #[codec(compact)] BalanceOf<T>2159);21602161impl<T: Trait + Send + Sync> sp_std::fmt::Debug2162 for ChargeTransactionPayment<T>2163{2164 #[cfg(feature = "std")]2165 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2166 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2167 }2168 #[cfg(not(feature = "std"))]2169 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2170 Ok(())2171 }2172}21732174impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2175where2176 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2177 BalanceOf<T>: Send + Sync + FixedPointOperand,2178{2179 /// utility constructor. Used only in client/factory code.2180 pub fn from(fee: BalanceOf<T>) -> Self {2181 Self(fee)2182 }21832184 pub fn traditional_fee(2185 len: usize,2186 info: &DispatchInfoOf<T::Call>,2187 tip: BalanceOf<T>,2188 ) -> BalanceOf<T>2189 where2190 T::Call: Dispatchable<Info = DispatchInfo>,2191 {2192 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2193 }21942195 fn withdraw_fee(2196 &self,2197 who: &T::AccountId,2198 call: &T::Call,2199 info: &DispatchInfoOf<T::Call>,2200 len: usize,2201 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2202 let tip = self.0;22032204 // Set fee based on call type. Creating collection costs 1 Unique.2205 // All other transactions have traditional fees so far2206 // let fee = match call.is_sub_type() {2207 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2208 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2209 // // _ => <BalanceOf<T>>::from(100)2210 // };2211 let fee = Self::traditional_fee(len, info, tip);22122213 // Determine who is paying transaction fee based on ecnomic model2214 // Parse call to extract collection ID and access collection sponsor2215 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2216 Some(Call::create_item(collection_id, _properties, _owner)) => {2217 <Collection<T>>::get(collection_id).sponsor2218 }2219 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {2220 let _collection_mode = <Collection<T>>::get(collection_id).mode;22212222 // sponsor timeout2223 let sponsor_transfer = match _collection_mode {2224 CollectionMode::NFT => {2225 let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);2226 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2227 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();2228 if block_number >= limit_time {2229 <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);2230 true2231 }2232 else {2233 false2234 }2235 }2236 CollectionMode::Fungible(_) => {2237 let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);2238 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2239 if basket.iter().any(|i| i.address == _new_owner.clone())2240 {2241 let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();2242 let limit_time = item.start_block + ChainLimit::get().fungible_sponsor_transfer_timeout.into();2243 if block_number >= limit_time {2244 basket.retain(|x| x.address == item.address);2245 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });2246 <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);2247 true2248 }2249 else {2250 false2251 }2252 }2253 else {2254 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});2255 true2256 }2257 }2258 CollectionMode::ReFungible(_) => {2259 let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);2260 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2261 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();2262 if block_number >= limit_time {2263 <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);2264 true2265 } else {2266 false2267 }2268 }2269 _ => {2270 false2271 },2272 };22732274 if !sponsor_transfer {2275 T::AccountId::default()2276 } else {2277 <Collection<T>>::get(collection_id).sponsor2278 }2279 }22802281 _ => T::AccountId::default(),2282 };22832284 // Sponsor smart contracts2285 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {22862287 // On instantiation: set the contract owner2288 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {22892290 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2291 code_hash,2292 &data,2293 &who,2294 );2295 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());22962297 T::AccountId::default()2298 },22992300 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2301 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {23022303 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());23042305 let mut sponsor_transfer = false;2306 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2307 let last_tx_block = <ContractSponsorBasket<T>>::get(&called_contract);2308 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2309 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2310 let limit_time = last_tx_block + rate_limit;23112312 if block_number >= limit_time {2313 <ContractSponsorBasket<T>>::insert(called_contract.clone(), block_number);2314 sponsor_transfer = true;2315 }2316 } else {2317 sponsor_transfer = false;2318 }2319 2320 2321 let mut sp = T::AccountId::default();2322 if sponsor_transfer {2323 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2324 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2325 sp = called_contract;2326 }2327 }2328 }23292330 sp2331 },23322333 _ => sponsor,2334 };23352336 let mut who_pays_fee: T::AccountId = sponsor.clone();2337 if sponsor == T::AccountId::default() {2338 who_pays_fee = who.clone();2339 }23402341 // Only mess with balances if fee is not zero.2342 if fee.is_zero() {2343 return Ok((fee, None));2344 }23452346 match <T as transaction_payment::Trait>::Currency::withdraw(2347 &who_pays_fee,2348 fee,2349 if tip.is_zero() {2350 WithdrawReason::TransactionPayment.into()2351 } else {2352 WithdrawReason::TransactionPayment | WithdrawReason::Tip2353 },2354 ExistenceRequirement::KeepAlive,2355 ) {2356 Ok(imbalance) => Ok((fee, Some(imbalance))),2357 Err(_) => Err(InvalidTransaction::Payment.into()),2358 }2359 }2360}236123622363impl<T: Trait + Send + Sync> SignedExtension2364 for ChargeTransactionPayment<T>2365where2366 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2367 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2368{2369 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2370 type AccountId = T::AccountId;2371 type Call = T::Call;2372 type AdditionalSigned = ();2373 type Pre = (2374 BalanceOf<T>,2375 Self::AccountId,2376 Option<NegativeImbalanceOf<T>>,2377 BalanceOf<T>,2378 );2379 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2380 Ok(())2381 }23822383 fn validate(2384 &self,2385 _who: &Self::AccountId,2386 _call: &Self::Call,2387 _info: &DispatchInfoOf<Self::Call>,2388 _len: usize,2389 ) -> TransactionValidity {2390 Ok(ValidTransaction::default())2391 }23922393 fn pre_dispatch(2394 self,2395 who: &Self::AccountId,2396 call: &Self::Call,2397 info: &DispatchInfoOf<Self::Call>,2398 len: usize,2399 ) -> Result<Self::Pre, TransactionValidityError> {2400 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2401 Ok((self.0, who.clone(), imbalance, fee))2402 }24032404 fn post_dispatch(2405 pre: Self::Pre,2406 info: &DispatchInfoOf<Self::Call>,2407 post_info: &PostDispatchInfoOf<Self::Call>,2408 len: usize,2409 _result: &DispatchResult,2410 ) -> Result<(), TransactionValidityError> {2411 let (tip, who, imbalance, fee) = pre;2412 if let Some(payed) = imbalance {2413 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2414 len as u32, info, post_info, tip,2415 );2416 let refund = fee.saturating_sub(actual_fee);2417 let actual_payment =2418 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2419 &who, refund,2420 ) {2421 Ok(refund_imbalance) => {2422 // The refund cannot be larger than the up front payed max weight.2423 // `PostDispatchInfo::calc_unspent` guards against such a case.2424 match payed.offset(refund_imbalance) {2425 Ok(actual_payment) => actual_payment,2426 Err(_) => return Err(InvalidTransaction::Payment.into()),2427 }2428 }2429 // We do not recreate the account using the refund. The up front payment2430 // is gone in that case.2431 Err(_) => payed,2432 };2433 let imbalances = actual_payment.split(tip);2434 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2435 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2436 );2437 }2438 Ok(())2439 }2440}24412442// #endregionpallets/nft/src/tests.rsdiffbeforeafterboth--- a/pallets/nft/src/tests.rs
+++ b/pallets/nft/src/tests.rs
@@ -2,11 +2,12 @@
use super::*;
use crate::mock::*;
use crate::{AccessMode, ApprovePermissions, CollectionMode,
- Ownership, ChainLimits, CreateItemData, CreateNftData, CreateFungibleData, CreateReFungibleData}; //Err
+ Ownership, ChainLimits, CreateItemData, CreateNftData, CreateFungibleData, CreateReFungibleData,
+ CollectionId, TokenId, MAX_DECIMAL_POINTS}; //Err
use frame_support::{assert_noop, assert_ok};
use frame_system::{ RawOrigin };
-fn default_collection_numbers_limit() -> u64 {
+fn default_collection_numbers_limit() -> u32 {
10
}
@@ -34,7 +35,7 @@
CreateReFungibleData { const_data: vec![1, 2, 3], variable_data: vec![3, 2, 1] }
}
-fn create_test_collection_for_owner(mode: &CollectionMode, owner: u64, id: u64) -> u64 {
+fn create_test_collection_for_owner(mode: &CollectionMode, owner: u64, id: CollectionId) -> CollectionId {
let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
@@ -59,11 +60,11 @@
id
}
-fn create_test_collection(mode: &CollectionMode, id: u64) -> u64 {
+fn create_test_collection(mode: &CollectionMode, id: CollectionId) -> CollectionId {
create_test_collection_for_owner(&mode, 1, id)
}
-fn create_test_item(collection_id: u64, data: &CreateItemData) {
+fn create_test_item(collection_id: CollectionId, data: &CreateItemData) {
let origin1 = Origin::signed(1);
assert_ok!(TemplateModule::create_item(
origin1.clone(),
@@ -77,6 +78,46 @@
// Use cases tests region
// #region
#[test]
+fn create_fungible_collection_fails_with_large_decimal_numbers() {
+ new_test_ext().execute_with(|| {
+ default_limits();
+
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+
+ let origin1 = Origin::signed(1);
+ assert_noop!(TemplateModule::create_collection(
+ origin1,
+ col_name1,
+ col_desc1,
+ token_prefix1,
+ CollectionMode::Fungible(MAX_DECIMAL_POINTS + 1)
+ ), Error::<Test>::CollectionDecimalPointLimitExceeded);
+ });
+}
+
+#[test]
+fn create_re_fungible_collection_fails_with_large_decimal_numbers() {
+ new_test_ext().execute_with(|| {
+ default_limits();
+
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+
+ let origin1 = Origin::signed(1);
+ assert_noop!(TemplateModule::create_collection(
+ origin1,
+ col_name1,
+ col_desc1,
+ token_prefix1,
+ CollectionMode::ReFungible(MAX_DECIMAL_POINTS + 1)
+ ), Error::<Test>::CollectionDecimalPointLimitExceeded);
+ });
+}
+
+#[test]
fn create_nft_item() {
new_test_ext().execute_with(|| {
default_limits();
@@ -109,8 +150,8 @@
items_data.clone().into_iter().map(|d| { d.into() }).collect()
));
for (index, data) in items_data.iter().enumerate() {
- assert_eq!(TemplateModule::nft_item_id(1, (index + 1) as u64).const_data.to_vec(), data.const_data);
- assert_eq!(TemplateModule::nft_item_id(1, (index + 1) as u64).variable_data.to_vec(), data.variable_data);
+ assert_eq!(TemplateModule::nft_item_id(1, (index + 1) as TokenId).const_data.to_vec(), data.const_data);
+ assert_eq!(TemplateModule::nft_item_id(1, (index + 1) as TokenId).variable_data.to_vec(), data.variable_data);
}
});
}
@@ -160,7 +201,7 @@
));
for (index, data) in items_data.iter().enumerate() {
- let item = TemplateModule::refungible_item_id(1, (index + 1) as u64);
+ let item = TemplateModule::refungible_item_id(1, (index + 1) as TokenId);
assert_eq!(item.const_data.to_vec(), data.const_data);
assert_eq!(item.variable_data.to_vec(), data.variable_data);
assert_eq!(
@@ -207,7 +248,7 @@
));
for (index, _) in items_data.iter().enumerate() {
- assert_eq!(TemplateModule::fungible_item_id(1, (index + 1) as u64).owner, 1);
+ assert_eq!(TemplateModule::fungible_item_id(1, (index + 1) as TokenId).owner, 1);
}
assert_eq!(TemplateModule::balance_count(1, 1), 3000);
assert_eq!(TemplateModule::address_tokens(1, 1), [1, 2, 3]);