difftreelog
NFTPAR-183: Storage Refactoring. Decimal points u32 -> u8. Also extended decimal points limit 4 -> 30.
in: master
2 files changed
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// #region5051pub type CollectionId = u32;52pub type TokenId = u32;5354#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]55#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]56pub enum CollectionMode {57 Invalid,58 NFT,59 // decimal points60 Fungible(u32),61 // decimal points62 ReFungible(u32),63}6465impl Into<u8> for CollectionMode {66 fn into(self) -> u8 {67 match self {68 CollectionMode::Invalid => 0,69 CollectionMode::NFT => 1,70 CollectionMode::Fungible(_) => 2,71 CollectionMode::ReFungible(_) => 3,72 }73 }74}7576#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]77#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]78pub enum AccessMode {79 Normal,80 WhiteList,81}82impl Default for AccessMode {83 fn default() -> Self {84 Self::Normal85 }86}8788impl Default for CollectionMode {89 fn default() -> Self {90 Self::Invalid91 }92}9394#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]95#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]96pub struct Ownership<AccountId> {97 pub owner: AccountId,98 pub fraction: u128,99}100101#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]102#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]103pub struct CollectionType<AccountId> {104 pub owner: AccountId,105 pub mode: CollectionMode,106 pub access: AccessMode,107 pub decimal_points: u32,108 pub name: Vec<u16>, // 64 include null escape char109 pub description: Vec<u16>, // 256 include null escape char110 pub token_prefix: Vec<u8>, // 16 include null escape char111 pub mint_mode: bool,112 pub offchain_schema: Vec<u8>,113 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender114 pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship115 pub variable_on_chain_schema: Vec<u8>, //116 pub const_on_chain_schema: Vec<u8>, //117}118119#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]120#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]121pub struct NftItemType<AccountId> {122 pub collection: CollectionId,123 pub owner: AccountId,124 pub const_data: Vec<u8>,125 pub variable_data: Vec<u8>,126}127128#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]129#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]130pub struct FungibleItemType<AccountId> {131 pub collection: CollectionId,132 pub owner: AccountId,133 pub value: u128,134}135136#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]137#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]138pub struct ReFungibleItemType<AccountId> {139 pub collection: CollectionId,140 pub owner: Vec<Ownership<AccountId>>,141 pub const_data: Vec<u8>,142 pub variable_data: Vec<u8>,143}144145#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]146#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]147pub struct ApprovePermissions<AccountId> {148 pub approved: AccountId,149 pub amount: u64,150}151152#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]153#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]154pub struct VestingItem<AccountId, Moment> {155 pub sender: AccountId,156 pub recipient: AccountId,157 pub collection_id: CollectionId,158 pub item_id: TokenId,159 pub amount: u64,160 pub vesting_date: Moment,161}162163#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]164#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]165pub struct BasketItem<AccountId, BlockNumber> {166 pub address: AccountId,167 pub start_block: BlockNumber,168}169170#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]171#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]172pub struct ChainLimits {173 pub collection_numbers_limit: u32,174 pub account_token_ownership_limit: u32,175 pub collections_admins_limit: u64,176 pub custom_data_limit: u32,177178 // Timeouts for item types in passed blocks179 pub nft_sponsor_transfer_timeout: u32,180 pub fungible_sponsor_transfer_timeout: u32,181 pub refungible_sponsor_transfer_timeout: u32,182}183184pub trait WeightInfo {185 fn create_collection() -> Weight;186 fn destroy_collection() -> Weight;187 fn add_to_white_list() -> Weight;188 fn remove_from_white_list() -> Weight;189 fn set_public_access_mode() -> Weight;190 fn set_mint_permission() -> Weight;191 fn change_collection_owner() -> Weight;192 fn add_collection_admin() -> Weight;193 fn remove_collection_admin() -> Weight;194 fn set_collection_sponsor() -> Weight;195 fn confirm_sponsorship() -> Weight;196 fn remove_collection_sponsor() -> Weight;197 fn create_item(s: usize) -> Weight;198 fn burn_item() -> Weight;199 fn transfer() -> Weight;200 fn approve() -> Weight;201 fn transfer_from() -> Weight;202 fn set_offchain_schema() -> Weight;203 fn set_const_on_chain_schema() -> Weight;204 fn set_variable_on_chain_schema() -> Weight;205 fn set_variable_meta_data() -> Weight;206 fn enable_contract_sponsoring() -> Weight;207}208209#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]210#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]211pub struct CreateNftData {212 pub const_data: Vec<u8>,213 pub variable_data: Vec<u8>,214}215216#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]217#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]218pub struct CreateFungibleData {219}220221#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]222#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]223pub struct CreateReFungibleData {224 pub const_data: Vec<u8>,225 pub variable_data: Vec<u8>,226}227228#[derive(Encode, Decode, Debug, Clone, PartialEq)]229#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]230pub enum CreateItemData {231 NFT(CreateNftData),232 Fungible(CreateFungibleData),233 ReFungible(CreateReFungibleData)234}235236impl CreateItemData {237 pub fn len(&self) -> usize {238 let len = match self {239 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),240 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),241 _ => 0242 };243 244 return len;245 }246}247248impl From<CreateNftData> for CreateItemData {249 fn from(item: CreateNftData) -> Self {250 CreateItemData::NFT(item)251 }252}253254impl From<CreateReFungibleData> for CreateItemData {255 fn from(item: CreateReFungibleData) -> Self {256 CreateItemData::ReFungible(item)257 }258}259260impl From<CreateFungibleData> for CreateItemData {261 fn from(item: CreateFungibleData) -> Self {262 CreateItemData::Fungible(item)263 }264}265266267decl_error! {268 /// Error for non-fungible-token module.269 pub enum Error for Module<T: Trait> {270 /// Total collections bound exceeded.271 TotalCollectionsLimitExceeded,272 /// Decimal_points parameter must be lower than 4.273 CollectionDecimalPointLimitExceeded, 274 /// Collection name can not be longer than 63 char.275 CollectionNameLimitExceeded, 276 /// Collection description can not be longer than 255 char.277 CollectionDescriptionLimitExceeded, 278 /// Token prefix can not be longer than 15 char.279 CollectionTokenPrefixLimitExceeded,280 /// This collection does not exist.281 CollectionNotFound,282 /// Item not exists.283 TokenNotFound,284 /// Arithmetic calculation overflow.285 NumOverflow, 286 /// Account already has admin role.287 AlreadyAdmin, 288 /// You do not own this collection.289 NoPermission,290 /// This address is not set as sponsor, use setCollectionSponsor first.291 ConfirmUnsetSponsorFail,292 /// Collection is not in mint mode.293 PublicMintingNotAllowed,294 /// Sender parameter and item owner must be equal.295 MustBeTokenOwner,296 /// Item balance not enough.297 TokenValueTooLow,298 /// Size of item is too large.299 NftSizeLimitExceeded,300 /// No approve found301 ApproveNotFound,302 /// Requested value more than approved.303 TokenValueNotEnough,304 /// Only approved addresses can call this method.305 ApproveRequired,306 /// Address is not in white list.307 AddresNotInWhiteList,308 /// Number of collection admins bound exceeded.309 CollectionAdminsLimitExceeded,310 /// Owned tokens by a single address bound exceeded.311 AddressOwnershipLimitExceeded,312 /// Length of items properties must be greater than 0.313 EmptyArgument,314 /// const_data exceeded data limit.315 TokenConstDataLimitExceeded,316 /// variable_data exceeded data limit.317 TokenVariableDataLimitExceeded,318 /// Not NFT item data used to mint in NFT collection.319 NotNftDataUsedToMintNftCollectionToken,320 /// Not Fungible item data used to mint in Fungible collection.321 NotFungibleDataUsedToMintFungibleCollectionToken,322 /// Not Re Fungible item data used to mint in Re Fungible collection.323 NotReFungibleDataUsedToMintReFungibleCollectionToken,324 /// Unexpected collection type.325 UnexpectedCollectionType,326 /// Can't store metadata in fungible tokens.327 CantStoreMetadataInFungibleTokens328 }329}330331pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {332 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;333334 /// Weight information for extrinsics in this pallet.335 type WeightInfo: WeightInfo;336}337338#[cfg(feature = "runtime-benchmarks")]339mod benchmarking;340341// #endregion342343decl_storage! {344 trait Store for Module<T: Trait> as Nft {345346 // Private members347 NextCollectionID: CollectionId;348 CreatedCollectionCount: u32;349 ChainVersion: u64;350 ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;351352 // Chain limits struct353 pub ChainLimit get(fn chain_limit) config(): ChainLimits;354355 // Bound counters356 CollectionCount: u32;357 pub AccountItemCount get(fn account_item_count): map hasher(identity) T::AccountId => u32;358359 // Basic collections360 pub Collection get(fn collection) config(): map hasher(identity) CollectionId => CollectionType<T::AccountId>;361 pub AdminList get(fn admin_list_collection): map hasher(identity) CollectionId => Vec<T::AccountId>;362 pub WhiteList get(fn white_list): map hasher(identity) CollectionId => Vec<T::AccountId>;363364 /// Balance owner per collection map365 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => u64;366367 /// second parameter: item id + owner account id368 pub ApprovedList get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;369370 /// Item collections371 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => NftItemType<T::AccountId>;372 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => FungibleItemType<T::AccountId>;373 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => ReFungibleItemType<T::AccountId>;374375 /// Index list376 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;377378 /// Tokens transfer baskets379 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;380 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Vec<BasketItem<T::AccountId, T::BlockNumber>>;381 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;382383 // Contract Sponsorship and Ownership384 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;385 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;386 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;387 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;388 }389 add_extra_genesis {390 build(|config: &GenesisConfig<T>| {391 // Modification of storage392 for (_num, _c) in &config.collection {393 <Module<T>>::init_collection(_c);394 }395396 for (_num, _q, _i) in &config.nft_item_id {397 <Module<T>>::init_nft_token(_i);398 }399400 for (_num, _q, _i) in &config.fungible_item_id {401 <Module<T>>::init_fungible_token(_i);402 }403404 for (_num, _q, _i) in &config.refungible_item_id {405 <Module<T>>::init_refungible_token(_i);406 }407 })408 }409}410411decl_event!(412 pub enum Event<T>413 where414 AccountId = <T as system::Trait>::AccountId,415 {416 /// New collection was created417 /// 418 /// # Arguments419 /// 420 /// * collection_id: Globally unique identifier of newly created collection.421 /// 422 /// * mode: [CollectionMode] converted into u8.423 /// 424 /// * account_id: Collection owner.425 Created(CollectionId, u8, AccountId),426427 /// New item was created.428 /// 429 /// # Arguments430 /// 431 /// * collection_id: Id of the collection where item was created.432 /// 433 /// * item_id: Id of an item. Unique within the collection.434 ItemCreated(CollectionId, TokenId),435436 /// Collection item was burned.437 /// 438 /// # Arguments439 /// 440 /// collection_id.441 /// 442 /// item_id: Identifier of burned NFT.443 ItemDestroyed(CollectionId, TokenId),444 }445);446447decl_module! {448 pub struct Module<T: Trait> for enum Call where origin: T::Origin {449450 fn deposit_event() = default;451 type Error = Error<T>;452453 fn on_initialize(now: T::BlockNumber) -> Weight {454455 if ChainVersion::get() < 2456 {457 let value = NextCollectionID::get();458 CreatedCollectionCount::put(value);459 ChainVersion::put(2);460 }461462 0463 }464465 /// 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.466 /// 467 /// # Permissions468 /// 469 /// * Anyone.470 /// 471 /// # Arguments472 /// 473 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.474 /// 475 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.476 /// 477 /// * token_prefix: UTF-8 string with token prefix.478 /// 479 /// * mode: [CollectionMode] collection type and type dependent data.480 // returns collection ID481 #[weight = T::WeightInfo::create_collection()]482 pub fn create_collection(origin,483 collection_name: Vec<u16>,484 collection_description: Vec<u16>,485 token_prefix: Vec<u8>,486 mode: CollectionMode) -> DispatchResult {487488 // Anyone can create a collection489 let who = ensure_signed(origin)?;490491 let decimal_points = match mode {492 CollectionMode::Fungible(points) => points,493 CollectionMode::ReFungible(points) => points,494 _ => 0495 };496497 // bound Total number of collections498 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);499500 // check params501 ensure!(decimal_points <= 4, Error::<T>::CollectionDecimalPointLimitExceeded);502503 let mut name = collection_name.to_vec();504 name.push(0);505 ensure!(name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);506507 let mut description = collection_description.to_vec();508 description.push(0);509 ensure!(name.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);510511 let mut prefix = token_prefix.to_vec();512 prefix.push(0);513 ensure!(prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);514515 // Generate next collection ID516 let next_id = CreatedCollectionCount::get()517 .checked_add(1)518 .ok_or(Error::<T>::NumOverflow)?;519520 // bound counter521 let total = CollectionCount::get()522 .checked_add(1)523 .ok_or(Error::<T>::NumOverflow)?;524525 CreatedCollectionCount::put(next_id);526 CollectionCount::put(total);527528 // Create new collection529 let new_collection = CollectionType {530 owner: who.clone(),531 name: name,532 mode: mode.clone(),533 mint_mode: false,534 access: AccessMode::Normal,535 description: description,536 decimal_points: decimal_points,537 token_prefix: prefix,538 offchain_schema: Vec::new(),539 sponsor: T::AccountId::default(),540 unconfirmed_sponsor: T::AccountId::default(),541 variable_on_chain_schema: Vec::new(),542 const_on_chain_schema: Vec::new(),543 };544545 // Add new collection to map546 <Collection<T>>::insert(next_id, new_collection);547548 // call event549 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));550551 Ok(())552 }553554 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.555 /// 556 /// # Permissions557 /// 558 /// * Collection Owner.559 /// 560 /// # Arguments561 /// 562 /// * collection_id: collection to destroy.563 #[weight = T::WeightInfo::destroy_collection()]564 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {565566 let sender = ensure_signed(origin)?;567 Self::check_owner_permissions(collection_id, sender)?;568569 <AddressTokens<T>>::remove_prefix(collection_id);570 <ApprovedList<T>>::remove_prefix(collection_id);571 <Balance<T>>::remove_prefix(collection_id);572 <ItemListIndex>::remove(collection_id);573 <AdminList<T>>::remove(collection_id);574 <Collection<T>>::remove(collection_id);575 <WhiteList<T>>::remove(collection_id);576577 <NftItemList<T>>::remove_prefix(collection_id);578 <FungibleItemList<T>>::remove_prefix(collection_id);579 <ReFungibleItemList<T>>::remove_prefix(collection_id);580581 <NftTransferBasket<T>>::remove_prefix(collection_id);582 <FungibleTransferBasket<T>>::remove_prefix(collection_id);583 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);584585 if CollectionCount::get() > 0586 {587 // bound couter588 let total = CollectionCount::get()589 .checked_sub(1)590 .ok_or(Error::<T>::NumOverflow)?;591592 CollectionCount::put(total);593 }594595 Ok(())596 }597598 /// Add an address to white list.599 /// 600 /// # Permissions601 /// 602 /// * Collection Owner603 /// * Collection Admin604 /// 605 /// # Arguments606 /// 607 /// * collection_id.608 /// 609 /// * address.610 #[weight = T::WeightInfo::add_to_white_list()]611 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{612613 let sender = ensure_signed(origin)?;614 Self::check_owner_or_admin_permissions(collection_id, sender)?;615616 let mut white_list_collection: Vec<T::AccountId>;617 if <WhiteList<T>>::contains_key(collection_id) {618 white_list_collection = <WhiteList<T>>::get(collection_id);619 if !white_list_collection.contains(&address.clone())620 {621 white_list_collection.push(address.clone());622 }623 }624 else {625 white_list_collection = Vec::new();626 white_list_collection.push(address.clone());627 }628629 <WhiteList<T>>::insert(collection_id, white_list_collection);630 Ok(())631 }632633 /// Remove an address from white list.634 /// 635 /// # Permissions636 /// 637 /// * Collection Owner638 /// * Collection Admin639 /// 640 /// # Arguments641 /// 642 /// * collection_id.643 /// 644 /// * address.645 #[weight = T::WeightInfo::remove_from_white_list()]646 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{647648 let sender = ensure_signed(origin)?;649 Self::check_owner_or_admin_permissions(collection_id, sender)?;650651 if <WhiteList<T>>::contains_key(collection_id) {652 let mut white_list_collection = <WhiteList<T>>::get(collection_id);653 if white_list_collection.contains(&address.clone())654 {655 white_list_collection.retain(|i| *i != address.clone());656 <WhiteList<T>>::insert(collection_id, white_list_collection);657 }658 }659660 Ok(())661 }662663 /// Toggle between normal and white list access for the methods with access for `Anyone`.664 /// 665 /// # Permissions666 /// 667 /// * Collection Owner.668 /// 669 /// # Arguments670 /// 671 /// * collection_id.672 /// 673 /// * mode: [AccessMode]674 #[weight = T::WeightInfo::set_public_access_mode()]675 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult676 {677 let sender = ensure_signed(origin)?;678679 Self::check_owner_permissions(collection_id, sender)?;680 let mut target_collection = <Collection<T>>::get(collection_id);681 target_collection.access = mode;682 <Collection<T>>::insert(collection_id, target_collection);683684 Ok(())685 }686687 /// Allows Anyone to create tokens if:688 /// * White List is enabled, and689 /// * Address is added to white list, and690 /// * This method was called with True parameter691 /// 692 /// # Permissions693 /// * Collection Owner694 ///695 /// # Arguments696 /// 697 /// * collection_id.698 /// 699 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.700 #[weight = T::WeightInfo::set_mint_permission()]701 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult702 {703 let sender = ensure_signed(origin)?;704705 Self::check_owner_permissions(collection_id, sender)?;706 let mut target_collection = <Collection<T>>::get(collection_id);707 target_collection.mint_mode = mint_permission;708 <Collection<T>>::insert(collection_id, target_collection);709710 Ok(())711 }712713 /// Change the owner of the collection.714 /// 715 /// # Permissions716 /// 717 /// * Collection Owner.718 /// 719 /// # Arguments720 /// 721 /// * collection_id.722 /// 723 /// * new_owner.724 #[weight = T::WeightInfo::change_collection_owner()]725 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {726727 let sender = ensure_signed(origin)?;728 Self::check_owner_permissions(collection_id, sender)?;729 let mut target_collection = <Collection<T>>::get(collection_id);730 target_collection.owner = new_owner;731 <Collection<T>>::insert(collection_id, target_collection);732733 Ok(())734 }735736 /// Adds an admin of the Collection.737 /// 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. 738 /// 739 /// # Permissions740 /// 741 /// * Collection Owner.742 /// * Collection Admin.743 /// 744 /// # Arguments745 /// 746 /// * collection_id: ID of the Collection to add admin for.747 /// 748 /// * new_admin_id: Address of new admin to add.749 #[weight = T::WeightInfo::add_collection_admin()]750 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {751752 let sender = ensure_signed(origin)?;753 Self::check_owner_or_admin_permissions(collection_id, sender)?;754 let mut admin_arr: Vec<T::AccountId> = Vec::new();755756 if <AdminList<T>>::contains_key(collection_id)757 {758 admin_arr = <AdminList<T>>::get(collection_id);759 ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);760 }761762 // Number of collection admins763 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);764765 admin_arr.push(new_admin_id);766 <AdminList<T>>::insert(collection_id, admin_arr);767768 Ok(())769 }770771 /// 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.772 ///773 /// # Permissions774 /// 775 /// * Collection Owner.776 /// * Collection Admin.777 /// 778 /// # Arguments779 /// 780 /// * collection_id: ID of the Collection to remove admin for.781 /// 782 /// * account_id: Address of admin to remove.783 #[weight = T::WeightInfo::remove_collection_admin()]784 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {785786 let sender = ensure_signed(origin)?;787 Self::check_owner_or_admin_permissions(collection_id, sender)?;788789 if <AdminList<T>>::contains_key(collection_id)790 {791 let mut admin_arr = <AdminList<T>>::get(collection_id);792 admin_arr.retain(|i| *i != account_id);793 <AdminList<T>>::insert(collection_id, admin_arr);794 }795796 Ok(())797 }798799 /// # Permissions800 /// 801 /// * Collection Owner802 /// 803 /// # Arguments804 /// 805 /// * collection_id.806 /// 807 /// * new_sponsor.808 #[weight = T::WeightInfo::set_collection_sponsor()]809 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {810811 let sender = ensure_signed(origin)?;812 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);813814 let mut target_collection = <Collection<T>>::get(collection_id);815 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);816817 target_collection.unconfirmed_sponsor = new_sponsor;818 <Collection<T>>::insert(collection_id, target_collection);819820 Ok(())821 }822823 /// # Permissions824 /// 825 /// * Sponsor.826 /// 827 /// # Arguments828 /// 829 /// * collection_id.830 #[weight = T::WeightInfo::confirm_sponsorship()]831 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {832833 let sender = ensure_signed(origin)?;834 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);835836 let mut target_collection = <Collection<T>>::get(collection_id);837 ensure!(sender == target_collection.unconfirmed_sponsor, Error::<T>::ConfirmUnsetSponsorFail);838839 target_collection.sponsor = target_collection.unconfirmed_sponsor;840 target_collection.unconfirmed_sponsor = T::AccountId::default();841 <Collection<T>>::insert(collection_id, target_collection);842843 Ok(())844 }845846 /// Switch back to pay-per-own-transaction model.847 ///848 /// # Permissions849 ///850 /// * Collection owner.851 /// 852 /// # Arguments853 /// 854 /// * collection_id.855 #[weight = T::WeightInfo::remove_collection_sponsor()]856 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {857858 let sender = ensure_signed(origin)?;859 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);860861 let mut target_collection = <Collection<T>>::get(collection_id);862 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);863864 target_collection.sponsor = T::AccountId::default();865 <Collection<T>>::insert(collection_id, target_collection);866867 Ok(())868 }869870 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.871 /// 872 /// # Permissions873 /// 874 /// * Collection Owner.875 /// * Collection Admin.876 /// * Anyone if877 /// * White List is enabled, and878 /// * Address is added to white list, and879 /// * MintPermission is enabled (see SetMintPermission method)880 /// 881 /// # Arguments882 /// 883 /// * collection_id: ID of the collection.884 /// 885 /// * owner: Address, initial owner of the NFT.886 ///887 /// * data: Token data to store on chain.888 // #[weight =889 // (130_000_000 as Weight)890 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))891 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))892 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]893894 #[weight = T::WeightInfo::create_item(data.len())]895 pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {896897 let sender = ensure_signed(origin)?;898899 Self::collection_exists(collection_id)?;900901 let target_collection = <Collection<T>>::get(collection_id);902903 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;904 Self::validate_create_item_args(&target_collection, &data)?;905 Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;906907 Ok(())908 }909910 /// This method creates multiple instances of NFT Collection created with CreateCollection method.911 /// 912 /// # Permissions913 /// 914 /// * Collection Owner.915 /// * Collection Admin.916 /// * Anyone if917 /// * White List is enabled, and918 /// * Address is added to white list, and919 /// * MintPermission is enabled (see SetMintPermission method)920 /// 921 /// # Arguments922 /// 923 /// * collection_id: ID of the collection.924 /// 925 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].926 /// 927 /// * owner: Address, initial owner of the NFT.928 #[weight = T::WeightInfo::create_item(items_data.into_iter()929 .map(|data| { data.len() })930 .sum())]931 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {932933 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);934 let sender = ensure_signed(origin)?;935936 Self::collection_exists(collection_id)?;937 let target_collection = <Collection<T>>::get(collection_id);938939 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;940941 for data in &items_data {942 Self::validate_create_item_args(&target_collection, data)?;943 }944 for data in &items_data {945 Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;946 }947948 Ok(())949 }950951 /// Destroys a concrete instance of NFT.952 /// 953 /// # Permissions954 /// 955 /// * Collection Owner.956 /// * Collection Admin.957 /// * Current NFT Owner.958 /// 959 /// # Arguments960 /// 961 /// * collection_id: ID of the collection.962 /// 963 /// * item_id: ID of NFT to burn.964 #[weight = T::WeightInfo::burn_item()]965 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {966967 let sender = ensure_signed(origin)?;968 Self::collection_exists(collection_id)?;969970 // Transfer permissions check971 let target_collection = <Collection<T>>::get(collection_id);972 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||973 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),974 Error::<T>::NoPermission);975976 if target_collection.access == AccessMode::WhiteList {977 Self::check_white_list(collection_id, &sender)?;978 }979980 match target_collection.mode981 {982 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,983 CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?,984 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,985 _ => ()986 };987988 // call event989 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));990991 Ok(())992 }993994 /// Change ownership of the token.995 /// 996 /// # Permissions997 /// 998 /// * Collection Owner999 /// * Collection Admin1000 /// * Current NFT owner1001 ///1002 /// # Arguments1003 /// 1004 /// * recipient: Address of token recipient.1005 /// 1006 /// * collection_id.1007 /// 1008 /// * item_id: ID of the item1009 /// * Non-Fungible Mode: Required.1010 /// * Fungible Mode: Ignored.1011 /// * Re-Fungible Mode: Required.1012 /// 1013 /// * value: Amount to transfer.1014 /// * Non-Fungible Mode: Ignored1015 /// * Fungible Mode: Must specify transferred amount1016 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1017 #[weight = T::WeightInfo::transfer()]1018 pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u64) -> DispatchResult {10191020 let sender = ensure_signed(origin)?;10211022 // Transfer permissions check1023 let target_collection = <Collection<T>>::get(collection_id);1024 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1025 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1026 Error::<T>::NoPermission);10271028 if target_collection.access == AccessMode::WhiteList {1029 Self::check_white_list(collection_id, &sender)?;1030 Self::check_white_list(collection_id, &recipient)?;1031 }10321033 match target_collection.mode1034 {1035 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1036 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,1037 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1038 _ => ()1039 };10401041 Ok(())1042 }10431044 /// Set, change, or remove approved address to transfer the ownership of the NFT.1045 /// 1046 /// # Permissions1047 /// 1048 /// * Collection Owner1049 /// * Collection Admin1050 /// * Current NFT owner1051 /// 1052 /// # Arguments1053 /// 1054 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1055 /// 1056 /// * collection_id.1057 /// 1058 /// * item_id: ID of the item.1059 #[weight = T::WeightInfo::approve()]1060 pub fn approve(origin, approved: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {10611062 let sender = ensure_signed(origin)?;10631064 // Transfer permissions check1065 let target_collection = <Collection<T>>::get(collection_id);1066 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1067 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1068 Error::<T>::NoPermission);10691070 if target_collection.access == AccessMode::WhiteList {1071 Self::check_white_list(collection_id, &sender)?;1072 Self::check_white_list(collection_id, &approved)?;1073 }10741075 // amount param stub1076 let amount = 100000000;10771078 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));1079 if list_exists {10801081 let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));1082 let item_contains = list.iter().any(|i| i.approved == approved);10831084 if !item_contains {1085 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1086 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1087 }1088 } else {10891090 let mut list = Vec::new();1091 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1092 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1093 }10941095 Ok(())1096 }1097 1098 /// 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.1099 /// 1100 /// # Permissions1101 /// * Collection Owner1102 /// * Collection Admin1103 /// * Current NFT owner1104 /// * Address approved by current NFT owner1105 /// 1106 /// # Arguments1107 /// 1108 /// * from: Address that owns token.1109 /// 1110 /// * recipient: Address of token recipient.1111 /// 1112 /// * collection_id.1113 /// 1114 /// * item_id: ID of the item.1115 /// 1116 /// * value: Amount to transfer.1117 #[weight = T::WeightInfo::transfer_from()]1118 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u64 ) -> DispatchResult {11191120 let sender = ensure_signed(origin)?;1121 let mut appoved_transfer = false;11221123 // Check approve1124 if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {1125 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));1126 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());1127 if opt_item.is_some()1128 {1129 appoved_transfer = true;1130 ensure!(opt_item.unwrap().amount >= value, Error::<T>::TokenValueNotEnough);1131 }1132 }11331134 // Transfer permissions check1135 let target_collection = <Collection<T>>::get(collection_id);1136 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1137 Error::<T>::NoPermission);11381139 if target_collection.access == AccessMode::WhiteList {1140 Self::check_white_list(collection_id, &sender)?;1141 Self::check_white_list(collection_id, &recipient)?;1142 }11431144 // remove approve1145 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))1146 .into_iter().filter(|i| i.approved != sender.clone()).collect();1147 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);114811491150 match target_collection.mode1151 {1152 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1153 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,1154 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1155 _ => ()1156 };11571158 Ok(())1159 }11601161 ///1162 #[weight = 0]1163 pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {11641165 // let no_perm_mes = "You do not have permissions to modify this collection";1166 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1167 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1168 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);11691170 // // on_nft_received call11711172 // Self::transfer(origin, collection_id, item_id, new_owner)?;11731174 Ok(())1175 }11761177 /// Set off-chain data schema.1178 /// 1179 /// # Permissions1180 /// 1181 /// * Collection Owner1182 /// * Collection Admin1183 /// 1184 /// # Arguments1185 /// 1186 /// * collection_id.1187 /// 1188 /// * schema: String representing the offchain data schema.1189 #[weight = T::WeightInfo::set_variable_meta_data()]1190 pub fn set_variable_meta_data (1191 origin,1192 collection_id: CollectionId,1193 item_id: TokenId,1194 data: Vec<u8>1195 ) -> DispatchResult {1196 let sender = ensure_signed(origin)?;1197 1198 Self::collection_exists(collection_id)?;1199 1200 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12011202 // Modify permissions check1203 let target_collection = <Collection<T>>::get(collection_id);1204 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1205 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1206 Error::<T>::NoPermission);12071208 Self::item_exists(collection_id, item_id, &target_collection.mode)?;12091210 match target_collection.mode1211 {1212 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1213 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1214 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1215 _ => fail!(Error::<T>::UnexpectedCollectionType)1216 };12171218 Ok(())1219 }1220 12211222 /// Set off-chain data schema.1223 /// 1224 /// # Permissions1225 /// 1226 /// * Collection Owner1227 /// * Collection Admin1228 /// 1229 /// # Arguments1230 /// 1231 /// * collection_id.1232 /// 1233 /// * schema: String representing the offchain data schema.1234 #[weight = T::WeightInfo::set_offchain_schema()]1235 pub fn set_offchain_schema(1236 origin,1237 collection_id: CollectionId,1238 schema: Vec<u8>1239 ) -> DispatchResult {1240 let sender = ensure_signed(origin)?;1241 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12421243 let mut target_collection = <Collection<T>>::get(collection_id);1244 target_collection.offchain_schema = schema;1245 <Collection<T>>::insert(collection_id, target_collection);12461247 Ok(())1248 }12491250 /// Set const on-chain data schema.1251 /// 1252 /// # Permissions1253 /// 1254 /// * Collection Owner1255 /// * Collection Admin1256 /// 1257 /// # Arguments1258 /// 1259 /// * collection_id.1260 /// 1261 /// * schema: String representing the const on-chain data schema.1262 #[weight = T::WeightInfo::set_const_on_chain_schema()]1263 pub fn set_const_on_chain_schema (1264 origin,1265 collection_id: CollectionId,1266 schema: Vec<u8>1267 ) -> DispatchResult {1268 let sender = ensure_signed(origin)?;1269 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12701271 let mut target_collection = <Collection<T>>::get(collection_id);1272 target_collection.const_on_chain_schema = schema;1273 <Collection<T>>::insert(collection_id, target_collection);12741275 Ok(())1276 }12771278 /// Set variable on-chain data schema.1279 /// 1280 /// # Permissions1281 /// 1282 /// * Collection Owner1283 /// * Collection Admin1284 /// 1285 /// # Arguments1286 /// 1287 /// * collection_id.1288 /// 1289 /// * schema: String representing the variable on-chain data schema.1290 #[weight = T::WeightInfo::set_const_on_chain_schema()]1291 pub fn set_variable_on_chain_schema (1292 origin,1293 collection_id: CollectionId,1294 schema: Vec<u8>1295 ) -> DispatchResult {1296 let sender = ensure_signed(origin)?;1297 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12981299 let mut target_collection = <Collection<T>>::get(collection_id);1300 target_collection.variable_on_chain_schema = schema;1301 <Collection<T>>::insert(collection_id, target_collection);13021303 Ok(())1304 }13051306 // Sudo permissions function1307 #[weight = 0]1308 pub fn set_chain_limits(1309 origin,1310 limits: ChainLimits1311 ) -> DispatchResult {1312 ensure_root(origin)?;1313 <ChainLimit>::put(limits);1314 Ok(())1315 }13161317 /// Enable smart contract self-sponsoring.1318 /// 1319 /// # Permissions1320 /// 1321 /// * Contract Owner1322 /// 1323 /// # Arguments1324 /// 1325 /// * contract address1326 /// * enable flag1327 /// 1328 #[weight = T::WeightInfo::enable_contract_sponsoring()]1329 pub fn enable_contract_sponsoring(1330 origin,1331 contract_address: T::AccountId,1332 enable: bool1333 ) -> DispatchResult {13341335 let sender = ensure_signed(origin)?;13361337 #[cfg(feature = "runtime-benchmarks")]1338 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13391340 let mut is_owner = false;1341 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1342 let owner = <ContractOwner<T>>::get(&contract_address);1343 is_owner = sender == owner;1344 }1345 ensure!(is_owner, Error::<T>::NoPermission);13461347 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1348 Ok(())1349 }13501351 /// Set the rate limit for contract sponsoring to specified number of blocks.1352 /// 1353 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1354 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1355 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1356 /// from contract endowment if there are at least B blocks between such transactions. 1357 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1358 /// 1359 /// # Permissions1360 /// 1361 /// * Contract Owner1362 /// 1363 /// # Arguments1364 /// 1365 /// -`contract_address`: Address of the contract to sponsor1366 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1367 /// 1368 #[weight = 0]1369 pub fn set_contract_sponsoring_rate_limit(1370 origin,1371 contract_address: T::AccountId,1372 rate_limit: T::BlockNumber1373 ) -> DispatchResult {1374 let sender = ensure_signed(origin)?;1375 let mut is_owner = false;1376 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1377 let owner = <ContractOwner<T>>::get(&contract_address);1378 is_owner = sender == owner;1379 }1380 ensure!(is_owner, Error::<T>::NoPermission);13811382 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1383 Ok(())1384 }13851386 // #[cfg(feature = "runtime-benchmarks")]1387 // #[weight = 0]1388 // pub fn add_contract_sponsoring_debug(1389 // origin,1390 // contract_address: T::AccountId, 1391 // owner: T::AccountId) -> DispatchResult {1392 // let sender = ensure_signed(origin)?;1393 // <ContractOwner<T>>::insert(contract_address.clone(), owner);1394 // Ok(())1395 // }1396 1397 }1398}13991400impl<T: Trait> Module<T> {14011402 fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {14031404 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1405 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1406 Self::check_white_list(collection_id, owner)?;1407 Self::check_white_list(collection_id, sender)?;1408 }14091410 Ok(())1411 }14121413 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1414 match target_collection.mode1415 {1416 CollectionMode::NFT => {1417 if let CreateItemData::NFT(data) = data {1418 // check sizes1419 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1420 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1421 } else {1422 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1423 }1424 },1425 CollectionMode::Fungible(_) => {1426 if let CreateItemData::Fungible(_) = data {1427 } else {1428 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1429 }1430 },1431 CollectionMode::ReFungible(_) => {1432 if let CreateItemData::ReFungible(data) = data {14331434 // check sizes1435 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1436 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1437 } else {1438 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1439 }1440 },1441 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1442 };14431444 Ok(())1445 }14461447 fn create_item_no_validation(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1448 match data1449 {1450 CreateItemData::NFT(data) => {1451 let item = NftItemType {1452 collection: collection_id,1453 owner,1454 const_data: data.const_data,1455 variable_data: data.variable_data1456 };14571458 Self::add_nft_item(item)?;1459 },1460 CreateItemData::Fungible(_) => {1461 let item = FungibleItemType {1462 collection: collection_id,1463 owner,1464 value: (10 as u128).pow(collection.decimal_points)1465 };14661467 Self::add_fungible_item(item)?;1468 },1469 CreateItemData::ReFungible(data) => {1470 let mut owner_list = Vec::new();1471 let value = (10 as u128).pow(collection.decimal_points);1472 owner_list.push(Ownership {owner: owner.clone(), fraction: value});14731474 let item = ReFungibleItemType {1475 collection: collection_id,1476 owner: owner_list,1477 const_data: data.const_data,1478 variable_data: data.variable_data1479 };14801481 Self::add_refungible_item(item)?;1482 }1483 };148414851486 // call event1487 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));14881489 Ok(())1490 }14911492 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {1493 let current_index = <ItemListIndex>::get(item.collection)1494 .checked_add(1)1495 .ok_or(Error::<T>::NumOverflow)?;1496 let itemcopy = item.clone();1497 let owner = item.owner.clone();1498 let value = item.value as u64;14991500 Self::add_token_index(item.collection, current_index, owner.clone())?;15011502 <ItemListIndex>::insert(item.collection, current_index);1503 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);15041505 // Add current block1506 let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1507 <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);1508 1509 // Update balance1510 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1511 .checked_add(value)1512 .ok_or(Error::<T>::NumOverflow)?;1513 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);15141515 Ok(())1516 }15171518 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1519 let current_index = <ItemListIndex>::get(item.collection)1520 .checked_add(1)1521 .ok_or(Error::<T>::NumOverflow)?;1522 let itemcopy = item.clone();15231524 let value = item.owner.first().unwrap().fraction as u64;1525 let owner = item.owner.first().unwrap().owner.clone();15261527 Self::add_token_index(item.collection, current_index, owner.clone())?;15281529 <ItemListIndex>::insert(item.collection, current_index);1530 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);15311532 // Add current block1533 let block_number: T::BlockNumber = 0.into();1534 <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);15351536 // Update balance1537 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1538 .checked_add(value)1539 .ok_or(Error::<T>::NumOverflow)?;1540 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);15411542 Ok(())1543 }15441545 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1546 let current_index = <ItemListIndex>::get(item.collection)1547 .checked_add(1)1548 .ok_or(Error::<T>::NumOverflow)?;15491550 let item_owner = item.owner.clone();1551 let collection_id = item.collection.clone();1552 Self::add_token_index(collection_id, current_index, item.owner.clone())?;15531554 <ItemListIndex>::insert(collection_id, current_index);1555 <NftItemList<T>>::insert(collection_id, current_index, item);15561557 // Add current block1558 let block_number: T::BlockNumber = 0.into();1559 <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);15601561 // Update balance1562 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1563 .checked_add(1)1564 .ok_or(Error::<T>::NumOverflow)?;1565 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);15661567 Ok(())1568 }15691570 fn burn_refungible_item(1571 collection_id: CollectionId,1572 item_id: TokenId,1573 owner: T::AccountId,1574 ) -> DispatchResult {1575 ensure!(1576 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1577 Error::<T>::TokenNotFound1578 );1579 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1580 let item = collection1581 .owner1582 .iter()1583 .filter(|&i| i.owner == owner)1584 .next()1585 .unwrap();1586 Self::remove_token_index(collection_id, item_id, owner.clone())?;15871588 // remove approve list1589 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));15901591 // update balance1592 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1593 .checked_sub(item.fraction as u64)1594 .ok_or(Error::<T>::NumOverflow)?;1595 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);15961597 <ReFungibleItemList<T>>::remove(collection_id, item_id);15981599 Ok(())1600 }16011602 fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1603 ensure!(1604 <NftItemList<T>>::contains_key(collection_id, item_id),1605 Error::<T>::TokenNotFound1606 );1607 let item = <NftItemList<T>>::get(collection_id, item_id);1608 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;16091610 // remove approve list1611 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));16121613 // update balance1614 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1615 .checked_sub(1)1616 .ok_or(Error::<T>::NumOverflow)?;1617 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1618 <NftItemList<T>>::remove(collection_id, item_id);16191620 Ok(())1621 }16221623 fn burn_fungible_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1624 ensure!(1625 <FungibleItemList<T>>::contains_key(collection_id, item_id),1626 Error::<T>::TokenNotFound1627 );1628 let item = <FungibleItemList<T>>::get(collection_id, item_id);1629 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;16301631 // remove approve list1632 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));16331634 // update balance1635 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1636 .checked_sub(item.value as u64)1637 .ok_or(Error::<T>::NumOverflow)?;1638 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);16391640 <FungibleItemList<T>>::remove(collection_id, item_id);16411642 Ok(())1643 }16441645 fn collection_exists(collection_id: CollectionId) -> DispatchResult {1646 ensure!(1647 <Collection<T>>::contains_key(collection_id),1648 Error::<T>::CollectionNotFound1649 );1650 Ok(())1651 }16521653 fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1654 Self::collection_exists(collection_id)?;16551656 let target_collection = <Collection<T>>::get(collection_id);1657 ensure!(1658 subject == target_collection.owner,1659 Error::<T>::NoPermission1660 );16611662 Ok(())1663 }16641665 fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1666 let target_collection = <Collection<T>>::get(collection_id);1667 let mut result: bool = subject == target_collection.owner;1668 let exists = <AdminList<T>>::contains_key(collection_id);16691670 if !result & exists {1671 if <AdminList<T>>::get(collection_id).contains(&subject) {1672 result = true1673 }1674 }16751676 result1677 }16781679 fn check_owner_or_admin_permissions(1680 collection_id: CollectionId,1681 subject: T::AccountId,1682 ) -> DispatchResult {1683 Self::collection_exists(collection_id)?;1684 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());16851686 ensure!(1687 result,1688 Error::<T>::NoPermission1689 );1690 Ok(())1691 }16921693 fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1694 let target_collection = <Collection<T>>::get(collection_id);16951696 match target_collection.mode {1697 CollectionMode::NFT => {1698 <NftItemList<T>>::get(collection_id, item_id).owner == subject1699 }1700 CollectionMode::Fungible(_) => {1701 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1702 }1703 CollectionMode::ReFungible(_) => {1704 <ReFungibleItemList<T>>::get(collection_id, item_id)1705 .owner1706 .iter()1707 .any(|i| i.owner == subject)1708 }1709 CollectionMode::Invalid => false,1710 }1711 }17121713 fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1714 let mes = Error::<T>::AddresNotInWhiteList;1715 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1716 let wl = <WhiteList<T>>::get(collection_id);1717 ensure!(wl.contains(address), mes);17181719 Ok(())1720 }17211722 fn transfer_fungible(1723 collection_id: CollectionId,1724 item_id: TokenId,1725 value: u64,1726 owner: T::AccountId,1727 new_owner: T::AccountId,1728 ) -> DispatchResult {1729 ensure!(1730 <FungibleItemList<T>>::contains_key(collection_id, item_id),1731 Error::<T>::TokenNotFound1732 );17331734 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1735 let amount = full_item.value;17361737 ensure!(amount >= value.into(), Error::<T>::TokenValueTooLow);17381739 // update balance1740 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1741 .checked_sub(value)1742 .ok_or(Error::<T>::NumOverflow)?;1743 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);17441745 let mut new_owner_account_id = 0;1746 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1747 if new_owner_items.len() > 0 {1748 new_owner_account_id = new_owner_items[0];1749 }17501751 let val64 = value.into();17521753 // transfer1754 if amount == val64 && new_owner_account_id == 0 {1755 // change owner1756 // new owner do not have account1757 let mut new_full_item = full_item.clone();1758 new_full_item.owner = new_owner.clone();1759 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);17601761 // update balance1762 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1763 .checked_add(value)1764 .ok_or(Error::<T>::NumOverflow)?;1765 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);17661767 // update index collection1768 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1769 } else {1770 let mut new_full_item = full_item.clone();1771 new_full_item.value -= val64;17721773 // separate amount1774 if new_owner_account_id > 0 {1775 // new owner has account1776 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1777 item.value += val64;17781779 // update balance1780 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1781 .checked_add(value)1782 .ok_or(Error::<T>::NumOverflow)?;1783 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);17841785 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1786 } else {1787 // new owner do not have account1788 let item = FungibleItemType {1789 collection: collection_id,1790 owner: new_owner.clone(),1791 value: val64,1792 };17931794 Self::add_fungible_item(item)?;1795 }17961797 if amount == val64 {1798 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;17991800 // remove approve list1801 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1802 <FungibleItemList<T>>::remove(collection_id, item_id);1803 }18041805 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1806 }18071808 Ok(())1809 }18101811 fn transfer_refungible(1812 collection_id: CollectionId,1813 item_id: TokenId,1814 value: u64,1815 owner: T::AccountId,1816 new_owner: T::AccountId,1817 ) -> DispatchResult {1818 ensure!(1819 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1820 Error::<T>::TokenNotFound1821 );18221823 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1824 let item = full_item1825 .owner1826 .iter()1827 .filter(|i| i.owner == owner)1828 .next()1829 .ok_or(Error::<T>::NumOverflow)?;1830 let amount = item.fraction;18311832 ensure!(amount >= value.into(), Error::<T>::TokenValueTooLow);18331834 // update balance1835 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1836 .checked_sub(value)1837 .ok_or(Error::<T>::NumOverflow)?;1838 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);18391840 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1841 .checked_add(value)1842 .ok_or(Error::<T>::NumOverflow)?;1843 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);18441845 let old_owner = item.owner.clone();1846 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);1847 let val64 = value.into();18481849 // transfer1850 if amount == val64 && !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 -= val64;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 += val64;1882 } else {1883 // new owner do not have account1884 new_full_item.owner.push(Ownership {1885 owner: new_owner.clone(),1886 fraction: val64,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 <= 4,1986 "decimal_points parameter must be lower than 4"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();2032 let value = item.value as u64;20332034 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();20352036 <ItemListIndex>::insert(item.collection, current_index);20372038 // Update balance2039 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2040 .checked_add(value)2041 .unwrap();2042 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2043 }20442045 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {2046 let current_index = <ItemListIndex>::get(item.collection)2047 .checked_add(1)2048 .unwrap();20492050 let value = item.owner.first().unwrap().fraction as u64;2051 let owner = item.owner.first().unwrap().owner.clone();20522053 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();20542055 <ItemListIndex>::insert(item.collection, current_index);20562057 // Update balance2058 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2059 .checked_add(value)2060 .unwrap();2061 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2062 }20632064 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {20652066 // add to account limit2067 if <AccountItemCount<T>>::contains_key(owner.clone()) {20682069 // bound Owned tokens by a single address2070 let count = <AccountItemCount<T>>::get(owner.clone());2071 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);20722073 <AccountItemCount<T>>::insert(owner.clone(), count2074 .checked_add(1)2075 .ok_or(Error::<T>::NumOverflow)?);2076 }2077 else {2078 <AccountItemCount<T>>::insert(owner.clone(), 1);2079 }20802081 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2082 if list_exists {2083 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2084 let item_contains = list.contains(&item_index.clone());20852086 if !item_contains {2087 list.push(item_index.clone());2088 }20892090 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2091 } else {2092 let mut itm = Vec::new();2093 itm.push(item_index.clone());2094 <AddressTokens<T>>::insert(collection_id, owner, itm);2095 2096 }20972098 Ok(())2099 }21002101 fn remove_token_index(2102 collection_id: CollectionId,2103 item_index: TokenId,2104 owner: T::AccountId,2105 ) -> DispatchResult {21062107 // update counter2108 <AccountItemCount<T>>::insert(owner.clone(), 2109 <AccountItemCount<T>>::get(owner.clone())2110 .checked_sub(1)2111 .ok_or(Error::<T>::NumOverflow)?);211221132114 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2115 if list_exists {2116 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2117 let item_contains = list.contains(&item_index.clone());21182119 if item_contains {2120 list.retain(|&item| item != item_index);2121 <AddressTokens<T>>::insert(collection_id, owner, list);2122 }2123 }21242125 Ok(())2126 }21272128 fn move_token_index(2129 collection_id: CollectionId,2130 item_index: TokenId,2131 old_owner: T::AccountId,2132 new_owner: T::AccountId,2133 ) -> DispatchResult {2134 Self::remove_token_index(collection_id, item_index, old_owner)?;2135 Self::add_token_index(collection_id, item_index, new_owner)?;21362137 Ok(())2138 }2139}21402141////////////////////////////////////////////////////////////////////////////////////////////////////2142// Economic models2143// #region21442145/// Fee multiplier.2146pub type Multiplier = FixedU128;21472148type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2149 <T as system::Trait>::AccountId,2150>>::Balance;2151type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2152 <T as system::Trait>::AccountId,2153>>::NegativeImbalance;21542155/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2156/// in the queue.2157#[derive(Encode, Decode, Clone, Eq, PartialEq)]2158pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2159 #[codec(compact)] BalanceOf<T>2160);21612162impl<T: Trait + Send + Sync> sp_std::fmt::Debug2163 for ChargeTransactionPayment<T>2164{2165 #[cfg(feature = "std")]2166 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2167 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2168 }2169 #[cfg(not(feature = "std"))]2170 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2171 Ok(())2172 }2173}21742175impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2176where2177 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2178 BalanceOf<T>: Send + Sync + FixedPointOperand,2179{2180 /// utility constructor. Used only in client/factory code.2181 pub fn from(fee: BalanceOf<T>) -> Self {2182 Self(fee)2183 }21842185 pub fn traditional_fee(2186 len: usize,2187 info: &DispatchInfoOf<T::Call>,2188 tip: BalanceOf<T>,2189 ) -> BalanceOf<T>2190 where2191 T::Call: Dispatchable<Info = DispatchInfo>,2192 {2193 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2194 }21952196 fn withdraw_fee(2197 &self,2198 who: &T::AccountId,2199 call: &T::Call,2200 info: &DispatchInfoOf<T::Call>,2201 len: usize,2202 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2203 let tip = self.0;22042205 // Set fee based on call type. Creating collection costs 1 Unique.2206 // All other transactions have traditional fees so far2207 // let fee = match call.is_sub_type() {2208 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2209 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2210 // // _ => <BalanceOf<T>>::from(100)2211 // };2212 let fee = Self::traditional_fee(len, info, tip);22132214 // Determine who is paying transaction fee based on ecnomic model2215 // Parse call to extract collection ID and access collection sponsor2216 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2217 Some(Call::create_item(collection_id, _properties, _owner)) => {2218 <Collection<T>>::get(collection_id).sponsor2219 }2220 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {2221 let _collection_mode = <Collection<T>>::get(collection_id).mode;22222223 // sponsor timeout2224 let sponsor_transfer = match _collection_mode {2225 CollectionMode::NFT => {2226 let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);2227 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2228 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();2229 if block_number >= limit_time {2230 <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);2231 true2232 }2233 else {2234 false2235 }2236 }2237 CollectionMode::Fungible(_) => {2238 let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);2239 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2240 if basket.iter().any(|i| i.address == _new_owner.clone())2241 {2242 let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();2243 let limit_time = item.start_block + ChainLimit::get().fungible_sponsor_transfer_timeout.into();2244 if block_number >= limit_time {2245 basket.retain(|x| x.address == item.address);2246 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });2247 <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);2248 true2249 }2250 else {2251 false2252 }2253 }2254 else {2255 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});2256 true2257 }2258 }2259 CollectionMode::ReFungible(_) => {2260 let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);2261 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2262 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();2263 if block_number >= limit_time {2264 <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);2265 true2266 } else {2267 false2268 }2269 }2270 _ => {2271 false2272 },2273 };22742275 if !sponsor_transfer {2276 T::AccountId::default()2277 } else {2278 <Collection<T>>::get(collection_id).sponsor2279 }2280 }22812282 _ => T::AccountId::default(),2283 };22842285 // Sponsor smart contracts2286 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {22872288 // On instantiation: set the contract owner2289 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {22902291 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2292 code_hash,2293 &data,2294 &who,2295 );2296 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());22972298 T::AccountId::default()2299 },23002301 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2302 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {23032304 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());23052306 let mut sponsor_transfer = false;2307 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2308 let last_tx_block = <ContractSponsorBasket<T>>::get(&called_contract);2309 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2310 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2311 let limit_time = last_tx_block + rate_limit;23122313 if block_number >= limit_time {2314 <ContractSponsorBasket<T>>::insert(called_contract.clone(), block_number);2315 sponsor_transfer = true;2316 }2317 } else {2318 sponsor_transfer = false;2319 }2320 2321 2322 let mut sp = T::AccountId::default();2323 if sponsor_transfer {2324 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2325 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2326 sp = called_contract;2327 }2328 }2329 }23302331 sp2332 },23332334 _ => sponsor,2335 };23362337 let mut who_pays_fee: T::AccountId = sponsor.clone();2338 if sponsor == T::AccountId::default() {2339 who_pays_fee = who.clone();2340 }23412342 // Only mess with balances if fee is not zero.2343 if fee.is_zero() {2344 return Ok((fee, None));2345 }23462347 match <T as transaction_payment::Trait>::Currency::withdraw(2348 &who_pays_fee,2349 fee,2350 if tip.is_zero() {2351 WithdrawReason::TransactionPayment.into()2352 } else {2353 WithdrawReason::TransactionPayment | WithdrawReason::Tip2354 },2355 ExistenceRequirement::KeepAlive,2356 ) {2357 Ok(imbalance) => Ok((fee, Some(imbalance))),2358 Err(_) => Err(InvalidTransaction::Payment.into()),2359 }2360 }2361}236223632364impl<T: Trait + Send + Sync> SignedExtension2365 for ChargeTransactionPayment<T>2366where2367 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2368 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2369{2370 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2371 type AccountId = T::AccountId;2372 type Call = T::Call;2373 type AdditionalSigned = ();2374 type Pre = (2375 BalanceOf<T>,2376 Self::AccountId,2377 Option<NegativeImbalanceOf<T>>,2378 BalanceOf<T>,2379 );2380 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2381 Ok(())2382 }23832384 fn validate(2385 &self,2386 _who: &Self::AccountId,2387 _call: &Self::Call,2388 _info: &DispatchInfoOf<Self::Call>,2389 _len: usize,2390 ) -> TransactionValidity {2391 Ok(ValidTransaction::default())2392 }23932394 fn pre_dispatch(2395 self,2396 who: &Self::AccountId,2397 call: &Self::Call,2398 info: &DispatchInfoOf<Self::Call>,2399 len: usize,2400 ) -> Result<Self::Pre, TransactionValidityError> {2401 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2402 Ok((self.0, who.clone(), imbalance, fee))2403 }24042405 fn post_dispatch(2406 pre: Self::Pre,2407 info: &DispatchInfoOf<Self::Call>,2408 post_info: &PostDispatchInfoOf<Self::Call>,2409 len: usize,2410 _result: &DispatchResult,2411 ) -> Result<(), TransactionValidityError> {2412 let (tip, who, imbalance, fee) = pre;2413 if let Some(payed) = imbalance {2414 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2415 len as u32, info, post_info, tip,2416 );2417 let refund = fee.saturating_sub(actual_fee);2418 let actual_payment =2419 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2420 &who, refund,2421 ) {2422 Ok(refund_imbalance) => {2423 // The refund cannot be larger than the up front payed max weight.2424 // `PostDispatchInfo::calc_unspent` guards against such a case.2425 match payed.offset(refund_imbalance) {2426 Ok(actual_payment) => actual_payment,2427 Err(_) => return Err(InvalidTransaction::Payment.into()),2428 }2429 }2430 // We do not recreate the account using the refund. The up front payment2431 // is gone in that case.2432 Err(_) => payed,2433 };2434 let imbalances = actual_payment.split(tip);2435 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2436 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2437 );2438 }2439 Ok(())2440 }2441}24422443// #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: u64,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(blake2_128_concat) 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(identity) 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(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => u64;370371 /// second parameter: item id + owner account id372 pub ApprovedList get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;373374 /// Item collections375 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => NftItemType<T::AccountId>;376 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => FungibleItemType<T::AccountId>;377 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => ReFungibleItemType<T::AccountId>;378379 /// Index list380 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;381382 /// Tokens transfer baskets383 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;384 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Vec<BasketItem<T::AccountId, T::BlockNumber>>;385 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) 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: u64) -> 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: u64 ) -> 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();1502 let value = item.value as u64;15031504 Self::add_token_index(item.collection, current_index, owner.clone())?;15051506 <ItemListIndex>::insert(item.collection, current_index);1507 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);15081509 // Add current block1510 let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1511 <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);1512 1513 // Update balance1514 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1515 .checked_add(value)1516 .ok_or(Error::<T>::NumOverflow)?;1517 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);15181519 Ok(())1520 }15211522 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1523 let current_index = <ItemListIndex>::get(item.collection)1524 .checked_add(1)1525 .ok_or(Error::<T>::NumOverflow)?;1526 let itemcopy = item.clone();15271528 let value = item.owner.first().unwrap().fraction as u64;1529 let owner = item.owner.first().unwrap().owner.clone();15301531 Self::add_token_index(item.collection, current_index, owner.clone())?;15321533 <ItemListIndex>::insert(item.collection, current_index);1534 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);15351536 // Add current block1537 let block_number: T::BlockNumber = 0.into();1538 <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);15391540 // Update balance1541 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1542 .checked_add(value)1543 .ok_or(Error::<T>::NumOverflow)?;1544 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);15451546 Ok(())1547 }15481549 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1550 let current_index = <ItemListIndex>::get(item.collection)1551 .checked_add(1)1552 .ok_or(Error::<T>::NumOverflow)?;15531554 let item_owner = item.owner.clone();1555 let collection_id = item.collection.clone();1556 Self::add_token_index(collection_id, current_index, item.owner.clone())?;15571558 <ItemListIndex>::insert(collection_id, current_index);1559 <NftItemList<T>>::insert(collection_id, current_index, item);15601561 // Add current block1562 let block_number: T::BlockNumber = 0.into();1563 <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);15641565 // Update balance1566 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1567 .checked_add(1)1568 .ok_or(Error::<T>::NumOverflow)?;1569 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);15701571 Ok(())1572 }15731574 fn burn_refungible_item(1575 collection_id: CollectionId,1576 item_id: TokenId,1577 owner: T::AccountId,1578 ) -> DispatchResult {1579 ensure!(1580 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1581 Error::<T>::TokenNotFound1582 );1583 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1584 let item = collection1585 .owner1586 .iter()1587 .filter(|&i| i.owner == owner)1588 .next()1589 .unwrap();1590 Self::remove_token_index(collection_id, item_id, owner.clone())?;15911592 // remove approve list1593 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));15941595 // update balance1596 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1597 .checked_sub(item.fraction as u64)1598 .ok_or(Error::<T>::NumOverflow)?;1599 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);16001601 <ReFungibleItemList<T>>::remove(collection_id, item_id);16021603 Ok(())1604 }16051606 fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1607 ensure!(1608 <NftItemList<T>>::contains_key(collection_id, item_id),1609 Error::<T>::TokenNotFound1610 );1611 let item = <NftItemList<T>>::get(collection_id, item_id);1612 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;16131614 // remove approve list1615 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));16161617 // update balance1618 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1619 .checked_sub(1)1620 .ok_or(Error::<T>::NumOverflow)?;1621 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1622 <NftItemList<T>>::remove(collection_id, item_id);16231624 Ok(())1625 }16261627 fn burn_fungible_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1628 ensure!(1629 <FungibleItemList<T>>::contains_key(collection_id, item_id),1630 Error::<T>::TokenNotFound1631 );1632 let item = <FungibleItemList<T>>::get(collection_id, item_id);1633 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;16341635 // remove approve list1636 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));16371638 // update balance1639 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1640 .checked_sub(item.value as u64)1641 .ok_or(Error::<T>::NumOverflow)?;1642 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);16431644 <FungibleItemList<T>>::remove(collection_id, item_id);16451646 Ok(())1647 }16481649 fn collection_exists(collection_id: CollectionId) -> DispatchResult {1650 ensure!(1651 <Collection<T>>::contains_key(collection_id),1652 Error::<T>::CollectionNotFound1653 );1654 Ok(())1655 }16561657 fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1658 Self::collection_exists(collection_id)?;16591660 let target_collection = <Collection<T>>::get(collection_id);1661 ensure!(1662 subject == target_collection.owner,1663 Error::<T>::NoPermission1664 );16651666 Ok(())1667 }16681669 fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1670 let target_collection = <Collection<T>>::get(collection_id);1671 let mut result: bool = subject == target_collection.owner;1672 let exists = <AdminList<T>>::contains_key(collection_id);16731674 if !result & exists {1675 if <AdminList<T>>::get(collection_id).contains(&subject) {1676 result = true1677 }1678 }16791680 result1681 }16821683 fn check_owner_or_admin_permissions(1684 collection_id: CollectionId,1685 subject: T::AccountId,1686 ) -> DispatchResult {1687 Self::collection_exists(collection_id)?;1688 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());16891690 ensure!(1691 result,1692 Error::<T>::NoPermission1693 );1694 Ok(())1695 }16961697 fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1698 let target_collection = <Collection<T>>::get(collection_id);16991700 match target_collection.mode {1701 CollectionMode::NFT => {1702 <NftItemList<T>>::get(collection_id, item_id).owner == subject1703 }1704 CollectionMode::Fungible(_) => {1705 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1706 }1707 CollectionMode::ReFungible(_) => {1708 <ReFungibleItemList<T>>::get(collection_id, item_id)1709 .owner1710 .iter()1711 .any(|i| i.owner == subject)1712 }1713 CollectionMode::Invalid => false,1714 }1715 }17161717 fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1718 let mes = Error::<T>::AddresNotInWhiteList;1719 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1720 let wl = <WhiteList<T>>::get(collection_id);1721 ensure!(wl.contains(address), mes);17221723 Ok(())1724 }17251726 fn transfer_fungible(1727 collection_id: CollectionId,1728 item_id: TokenId,1729 value: u64,1730 owner: T::AccountId,1731 new_owner: T::AccountId,1732 ) -> DispatchResult {1733 ensure!(1734 <FungibleItemList<T>>::contains_key(collection_id, item_id),1735 Error::<T>::TokenNotFound1736 );17371738 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1739 let amount = full_item.value;17401741 ensure!(amount >= value.into(), Error::<T>::TokenValueTooLow);17421743 // update balance1744 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1745 .checked_sub(value)1746 .ok_or(Error::<T>::NumOverflow)?;1747 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);17481749 let mut new_owner_account_id = 0;1750 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1751 if new_owner_items.len() > 0 {1752 new_owner_account_id = new_owner_items[0];1753 }17541755 let val64 = value.into();17561757 // transfer1758 if amount == val64 && new_owner_account_id == 0 {1759 // change owner1760 // new owner do not have account1761 let mut new_full_item = full_item.clone();1762 new_full_item.owner = new_owner.clone();1763 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);17641765 // update balance1766 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1767 .checked_add(value)1768 .ok_or(Error::<T>::NumOverflow)?;1769 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);17701771 // update index collection1772 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1773 } else {1774 let mut new_full_item = full_item.clone();1775 new_full_item.value -= val64;17761777 // separate amount1778 if new_owner_account_id > 0 {1779 // new owner has account1780 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1781 item.value += val64;17821783 // update balance1784 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1785 .checked_add(value)1786 .ok_or(Error::<T>::NumOverflow)?;1787 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);17881789 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1790 } else {1791 // new owner do not have account1792 let item = FungibleItemType {1793 collection: collection_id,1794 owner: new_owner.clone(),1795 value: val64,1796 };17971798 Self::add_fungible_item(item)?;1799 }18001801 if amount == val64 {1802 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;18031804 // remove approve list1805 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1806 <FungibleItemList<T>>::remove(collection_id, item_id);1807 }18081809 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1810 }18111812 Ok(())1813 }18141815 fn transfer_refungible(1816 collection_id: CollectionId,1817 item_id: TokenId,1818 value: u64,1819 owner: T::AccountId,1820 new_owner: T::AccountId,1821 ) -> DispatchResult {1822 ensure!(1823 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1824 Error::<T>::TokenNotFound1825 );18261827 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1828 let item = full_item1829 .owner1830 .iter()1831 .filter(|i| i.owner == owner)1832 .next()1833 .ok_or(Error::<T>::NumOverflow)?;1834 let amount = item.fraction;18351836 ensure!(amount >= value.into(), Error::<T>::TokenValueTooLow);18371838 // update balance1839 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1840 .checked_sub(value)1841 .ok_or(Error::<T>::NumOverflow)?;1842 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);18431844 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1845 .checked_add(value)1846 .ok_or(Error::<T>::NumOverflow)?;1847 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);18481849 let old_owner = item.owner.clone();1850 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);1851 let val64 = value.into();18521853 // transfer1854 if amount == val64 && !new_owner_has_account {1855 // change owner1856 // new owner do not have account1857 let mut new_full_item = full_item.clone();1858 new_full_item1859 .owner1860 .iter_mut()1861 .find(|i| i.owner == owner)1862 .unwrap()1863 .owner = new_owner.clone();1864 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);18651866 // update index collection1867 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1868 } else {1869 let mut new_full_item = full_item.clone();1870 new_full_item1871 .owner1872 .iter_mut()1873 .find(|i| i.owner == owner)1874 .unwrap()1875 .fraction -= val64;18761877 // separate amount1878 if new_owner_has_account {1879 // new owner has account1880 new_full_item1881 .owner1882 .iter_mut()1883 .find(|i| i.owner == new_owner)1884 .unwrap()1885 .fraction += val64;1886 } else {1887 // new owner do not have account1888 new_full_item.owner.push(Ownership {1889 owner: new_owner.clone(),1890 fraction: val64,1891 });1892 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1893 }18941895 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1896 }18971898 Ok(())1899 }19001901 fn transfer_nft(1902 collection_id: CollectionId,1903 item_id: TokenId,1904 sender: T::AccountId,1905 new_owner: T::AccountId,1906 ) -> DispatchResult {1907 ensure!(1908 <NftItemList<T>>::contains_key(collection_id, item_id),1909 Error::<T>::TokenNotFound1910 );19111912 let mut item = <NftItemList<T>>::get(collection_id, item_id);19131914 ensure!(1915 sender == item.owner,1916 Error::<T>::MustBeTokenOwner1917 );19181919 // update balance1920 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1921 .checked_sub(1)1922 .ok_or(Error::<T>::NumOverflow)?;1923 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19241925 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1926 .checked_add(1)1927 .ok_or(Error::<T>::NumOverflow)?;1928 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);19291930 // change owner1931 let old_owner = item.owner.clone();1932 item.owner = new_owner.clone();1933 <NftItemList<T>>::insert(collection_id, item_id, item);19341935 // update index collection1936 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;19371938 // reset approved list1939 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));1940 Ok(())1941 }1942 1943 fn item_exists(1944 collection_id: CollectionId,1945 item_id: TokenId,1946 mode: &CollectionMode1947 ) -> DispatchResult {1948 match mode {1949 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1950 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1951 CollectionMode::Fungible(_) => ensure!(<FungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1952 _ => ()1953 };1954 1955 Ok(())1956 }19571958 fn set_re_fungible_variable_data(1959 collection_id: CollectionId,1960 item_id: TokenId,1961 data: Vec<u8>1962 ) -> DispatchResult {1963 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);19641965 item.variable_data = data;19661967 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);19681969 Ok(())1970 }19711972 fn set_nft_variable_data(1973 collection_id: CollectionId,1974 item_id: TokenId,1975 data: Vec<u8>1976 ) -> DispatchResult {1977 let mut item = <NftItemList<T>>::get(collection_id, item_id);1978 1979 item.variable_data = data;19801981 <NftItemList<T>>::insert(collection_id, item_id, item);1982 1983 Ok(())1984 }19851986 fn init_collection(item: &CollectionType<T::AccountId>) {1987 // check params1988 assert!(1989 item.decimal_points <= MAX_DECIMAL_POINTS,1990 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"1991 );1992 assert!(1993 item.name.len() <= 64,1994 "Collection name can not be longer than 63 char"1995 );1996 assert!(1997 item.name.len() <= 256,1998 "Collection description can not be longer than 255 char"1999 );2000 assert!(2001 item.token_prefix.len() <= 16,2002 "Token prefix can not be longer than 15 char"2003 );20042005 // Generate next collection ID2006 let next_id = CreatedCollectionCount::get()2007 .checked_add(1)2008 .unwrap();20092010 CreatedCollectionCount::put(next_id);2011 }20122013 fn init_nft_token(item: &NftItemType<T::AccountId>) {2014 let current_index = <ItemListIndex>::get(item.collection)2015 .checked_add(1)2016 .unwrap();20172018 let item_owner = item.owner.clone();2019 let collection_id = item.collection.clone();2020 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();20212022 <ItemListIndex>::insert(collection_id, current_index);20232024 // Update balance2025 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())2026 .checked_add(1)2027 .unwrap();2028 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2029 }20302031 fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {2032 let current_index = <ItemListIndex>::get(item.collection)2033 .checked_add(1)2034 .unwrap();2035 let owner = item.owner.clone();2036 let value = item.value as u64;20372038 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();20392040 <ItemListIndex>::insert(item.collection, current_index);20412042 // Update balance2043 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2044 .checked_add(value)2045 .unwrap();2046 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2047 }20482049 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {2050 let current_index = <ItemListIndex>::get(item.collection)2051 .checked_add(1)2052 .unwrap();20532054 let value = item.owner.first().unwrap().fraction as u64;2055 let owner = item.owner.first().unwrap().owner.clone();20562057 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();20582059 <ItemListIndex>::insert(item.collection, current_index);20602061 // Update balance2062 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2063 .checked_add(value)2064 .unwrap();2065 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2066 }20672068 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {20692070 // add to account limit2071 if <AccountItemCount<T>>::contains_key(owner.clone()) {20722073 // bound Owned tokens by a single address2074 let count = <AccountItemCount<T>>::get(owner.clone());2075 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);20762077 <AccountItemCount<T>>::insert(owner.clone(), count2078 .checked_add(1)2079 .ok_or(Error::<T>::NumOverflow)?);2080 }2081 else {2082 <AccountItemCount<T>>::insert(owner.clone(), 1);2083 }20842085 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2086 if list_exists {2087 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2088 let item_contains = list.contains(&item_index.clone());20892090 if !item_contains {2091 list.push(item_index.clone());2092 }20932094 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2095 } else {2096 let mut itm = Vec::new();2097 itm.push(item_index.clone());2098 <AddressTokens<T>>::insert(collection_id, owner, itm);2099 2100 }21012102 Ok(())2103 }21042105 fn remove_token_index(2106 collection_id: CollectionId,2107 item_index: TokenId,2108 owner: T::AccountId,2109 ) -> DispatchResult {21102111 // update counter2112 <AccountItemCount<T>>::insert(owner.clone(), 2113 <AccountItemCount<T>>::get(owner.clone())2114 .checked_sub(1)2115 .ok_or(Error::<T>::NumOverflow)?);211621172118 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2119 if list_exists {2120 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2121 let item_contains = list.contains(&item_index.clone());21222123 if item_contains {2124 list.retain(|&item| item != item_index);2125 <AddressTokens<T>>::insert(collection_id, owner, list);2126 }2127 }21282129 Ok(())2130 }21312132 fn move_token_index(2133 collection_id: CollectionId,2134 item_index: TokenId,2135 old_owner: T::AccountId,2136 new_owner: T::AccountId,2137 ) -> DispatchResult {2138 Self::remove_token_index(collection_id, item_index, old_owner)?;2139 Self::add_token_index(collection_id, item_index, new_owner)?;21402141 Ok(())2142 }2143}21442145////////////////////////////////////////////////////////////////////////////////////////////////////2146// Economic models2147// #region21482149/// Fee multiplier.2150pub type Multiplier = FixedU128;21512152type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2153 <T as system::Trait>::AccountId,2154>>::Balance;2155type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2156 <T as system::Trait>::AccountId,2157>>::NegativeImbalance;21582159/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2160/// in the queue.2161#[derive(Encode, Decode, Clone, Eq, PartialEq)]2162pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2163 #[codec(compact)] BalanceOf<T>2164);21652166impl<T: Trait + Send + Sync> sp_std::fmt::Debug2167 for ChargeTransactionPayment<T>2168{2169 #[cfg(feature = "std")]2170 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2171 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2172 }2173 #[cfg(not(feature = "std"))]2174 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2175 Ok(())2176 }2177}21782179impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2180where2181 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2182 BalanceOf<T>: Send + Sync + FixedPointOperand,2183{2184 /// utility constructor. Used only in client/factory code.2185 pub fn from(fee: BalanceOf<T>) -> Self {2186 Self(fee)2187 }21882189 pub fn traditional_fee(2190 len: usize,2191 info: &DispatchInfoOf<T::Call>,2192 tip: BalanceOf<T>,2193 ) -> BalanceOf<T>2194 where2195 T::Call: Dispatchable<Info = DispatchInfo>,2196 {2197 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2198 }21992200 fn withdraw_fee(2201 &self,2202 who: &T::AccountId,2203 call: &T::Call,2204 info: &DispatchInfoOf<T::Call>,2205 len: usize,2206 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2207 let tip = self.0;22082209 // Set fee based on call type. Creating collection costs 1 Unique.2210 // All other transactions have traditional fees so far2211 // let fee = match call.is_sub_type() {2212 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2213 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2214 // // _ => <BalanceOf<T>>::from(100)2215 // };2216 let fee = Self::traditional_fee(len, info, tip);22172218 // Determine who is paying transaction fee based on ecnomic model2219 // Parse call to extract collection ID and access collection sponsor2220 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2221 Some(Call::create_item(collection_id, _properties, _owner)) => {2222 <Collection<T>>::get(collection_id).sponsor2223 }2224 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {2225 let _collection_mode = <Collection<T>>::get(collection_id).mode;22262227 // sponsor timeout2228 let sponsor_transfer = match _collection_mode {2229 CollectionMode::NFT => {2230 let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);2231 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2232 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();2233 if block_number >= limit_time {2234 <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);2235 true2236 }2237 else {2238 false2239 }2240 }2241 CollectionMode::Fungible(_) => {2242 let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);2243 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2244 if basket.iter().any(|i| i.address == _new_owner.clone())2245 {2246 let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();2247 let limit_time = item.start_block + ChainLimit::get().fungible_sponsor_transfer_timeout.into();2248 if block_number >= limit_time {2249 basket.retain(|x| x.address == item.address);2250 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });2251 <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);2252 true2253 }2254 else {2255 false2256 }2257 }2258 else {2259 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});2260 true2261 }2262 }2263 CollectionMode::ReFungible(_) => {2264 let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);2265 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2266 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();2267 if block_number >= limit_time {2268 <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);2269 true2270 } else {2271 false2272 }2273 }2274 _ => {2275 false2276 },2277 };22782279 if !sponsor_transfer {2280 T::AccountId::default()2281 } else {2282 <Collection<T>>::get(collection_id).sponsor2283 }2284 }22852286 _ => T::AccountId::default(),2287 };22882289 // Sponsor smart contracts2290 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {22912292 // On instantiation: set the contract owner2293 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {22942295 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2296 code_hash,2297 &data,2298 &who,2299 );2300 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());23012302 T::AccountId::default()2303 },23042305 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2306 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {23072308 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());23092310 let mut sponsor_transfer = false;2311 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2312 let last_tx_block = <ContractSponsorBasket<T>>::get(&called_contract);2313 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2314 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2315 let limit_time = last_tx_block + rate_limit;23162317 if block_number >= limit_time {2318 <ContractSponsorBasket<T>>::insert(called_contract.clone(), block_number);2319 sponsor_transfer = true;2320 }2321 } else {2322 sponsor_transfer = false;2323 }2324 2325 2326 let mut sp = T::AccountId::default();2327 if sponsor_transfer {2328 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2329 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2330 sp = called_contract;2331 }2332 }2333 }23342335 sp2336 },23372338 _ => sponsor,2339 };23402341 let mut who_pays_fee: T::AccountId = sponsor.clone();2342 if sponsor == T::AccountId::default() {2343 who_pays_fee = who.clone();2344 }23452346 // Only mess with balances if fee is not zero.2347 if fee.is_zero() {2348 return Ok((fee, None));2349 }23502351 match <T as transaction_payment::Trait>::Currency::withdraw(2352 &who_pays_fee,2353 fee,2354 if tip.is_zero() {2355 WithdrawReason::TransactionPayment.into()2356 } else {2357 WithdrawReason::TransactionPayment | WithdrawReason::Tip2358 },2359 ExistenceRequirement::KeepAlive,2360 ) {2361 Ok(imbalance) => Ok((fee, Some(imbalance))),2362 Err(_) => Err(InvalidTransaction::Payment.into()),2363 }2364 }2365}236623672368impl<T: Trait + Send + Sync> SignedExtension2369 for ChargeTransactionPayment<T>2370where2371 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2372 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2373{2374 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2375 type AccountId = T::AccountId;2376 type Call = T::Call;2377 type AdditionalSigned = ();2378 type Pre = (2379 BalanceOf<T>,2380 Self::AccountId,2381 Option<NegativeImbalanceOf<T>>,2382 BalanceOf<T>,2383 );2384 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2385 Ok(())2386 }23872388 fn validate(2389 &self,2390 _who: &Self::AccountId,2391 _call: &Self::Call,2392 _info: &DispatchInfoOf<Self::Call>,2393 _len: usize,2394 ) -> TransactionValidity {2395 Ok(ValidTransaction::default())2396 }23972398 fn pre_dispatch(2399 self,2400 who: &Self::AccountId,2401 call: &Self::Call,2402 info: &DispatchInfoOf<Self::Call>,2403 len: usize,2404 ) -> Result<Self::Pre, TransactionValidityError> {2405 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2406 Ok((self.0, who.clone(), imbalance, fee))2407 }24082409 fn post_dispatch(2410 pre: Self::Pre,2411 info: &DispatchInfoOf<Self::Call>,2412 post_info: &PostDispatchInfoOf<Self::Call>,2413 len: usize,2414 _result: &DispatchResult,2415 ) -> Result<(), TransactionValidityError> {2416 let (tip, who, imbalance, fee) = pre;2417 if let Some(payed) = imbalance {2418 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2419 len as u32, info, post_info, tip,2420 );2421 let refund = fee.saturating_sub(actual_fee);2422 let actual_payment =2423 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2424 &who, refund,2425 ) {2426 Ok(refund_imbalance) => {2427 // The refund cannot be larger than the up front payed max weight.2428 // `PostDispatchInfo::calc_unspent` guards against such a case.2429 match payed.offset(refund_imbalance) {2430 Ok(actual_payment) => actual_payment,2431 Err(_) => return Err(InvalidTransaction::Payment.into()),2432 }2433 }2434 // We do not recreate the account using the refund. The up front payment2435 // is gone in that case.2436 Err(_) => payed,2437 };2438 let imbalances = actual_payment.split(tip);2439 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2440 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2441 );2442 }2443 Ok(())2444 }2445}24462447// #endregionpallets/nft/src/tests.rsdiffbeforeafterboth--- a/pallets/nft/src/tests.rs
+++ b/pallets/nft/src/tests.rs
@@ -3,7 +3,7 @@
use crate::mock::*;
use crate::{AccessMode, ApprovePermissions, CollectionMode,
Ownership, ChainLimits, CreateItemData, CreateNftData, CreateFungibleData, CreateReFungibleData,
- CollectionId, TokenId}; //Err
+ CollectionId, TokenId, MAX_DECIMAL_POINTS}; //Err
use frame_support::{assert_noop, assert_ok};
use frame_system::{ RawOrigin };
@@ -78,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();