difftreelog
NFTPAR-142: Off-chain and on-chain data schema. Error messages.
in: master
1 file 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// #region5051#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]52#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]53pub enum CollectionMode {54 Invalid,55 NFT,56 // decimal points57 Fungible(u32),58 // decimal points59 ReFungible(u32),60}6162impl Into<u8> for CollectionMode {63 fn into(self) -> u8 {64 match self {65 CollectionMode::Invalid => 0,66 CollectionMode::NFT => 1,67 CollectionMode::Fungible(_) => 2,68 CollectionMode::ReFungible(_) => 3,69 }70 }71}7273#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]74#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]75pub enum AccessMode {76 Normal,77 WhiteList,78}79impl Default for AccessMode {80 fn default() -> Self {81 Self::Normal82 }83}8485impl Default for CollectionMode {86 fn default() -> Self {87 Self::Invalid88 }89}9091#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]92#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]93pub struct Ownership<AccountId> {94 pub owner: AccountId,95 pub fraction: u128,96}9798#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]99#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]100pub struct CollectionType<AccountId> {101 pub owner: AccountId,102 pub mode: CollectionMode,103 pub access: AccessMode,104 pub decimal_points: u32,105 pub name: Vec<u16>, // 64 include null escape char106 pub description: Vec<u16>, // 256 include null escape char107 pub token_prefix: Vec<u8>, // 16 include null escape char108 pub mint_mode: bool,109 pub offchain_schema: Vec<u8>,110 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender111 pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship112 pub variable_on_chain_schema: Vec<u8>, //113 pub const_on_chain_schema: Vec<u8>, //114}115116#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]117#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]118pub struct NftItemType<AccountId> {119 pub collection: u64,120 pub owner: AccountId,121 pub const_data: Vec<u8>,122 pub variable_data: Vec<u8>,123}124125#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]126#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]127pub struct FungibleItemType<AccountId> {128 pub collection: u64,129 pub owner: AccountId,130 pub value: u128,131}132133#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]134#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]135pub struct ReFungibleItemType<AccountId> {136 pub collection: u64,137 pub owner: Vec<Ownership<AccountId>>,138 pub const_data: Vec<u8>,139 pub variable_data: Vec<u8>,140}141142#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]143#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]144pub struct ApprovePermissions<AccountId> {145 pub approved: AccountId,146 pub amount: u64,147}148149#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]150#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]151pub struct VestingItem<AccountId, Moment> {152 pub sender: AccountId,153 pub recipient: AccountId,154 pub collection_id: u64,155 pub item_id: u64,156 pub amount: u64,157 pub vesting_date: Moment,158}159160#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]161#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]162pub struct BasketItem<AccountId, BlockNumber> {163 pub address: AccountId,164 pub start_block: BlockNumber,165}166167#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]168#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]169pub struct ChainLimits {170 pub collection_numbers_limit: u64,171 pub account_token_ownership_limit: u64,172 pub collections_admins_limit: u64,173 pub custom_data_limit: u32,174175 // Timeouts for item types in passed blocks176 pub nft_sponsor_transfer_timeout: u32,177 pub fungible_sponsor_transfer_timeout: u32,178 pub refungible_sponsor_transfer_timeout: u32,179}180181pub trait WeightInfo {182 fn create_collection() -> Weight;183 fn destroy_collection() -> Weight;184 fn add_to_white_list() -> Weight;185 fn remove_from_white_list() -> Weight;186 fn set_public_access_mode() -> Weight;187 fn set_mint_permission() -> Weight;188 fn change_collection_owner() -> Weight;189 fn add_collection_admin() -> Weight;190 fn remove_collection_admin() -> Weight;191 fn set_collection_sponsor() -> Weight;192 fn confirm_sponsorship() -> Weight;193 fn remove_collection_sponsor() -> Weight;194 fn create_item(s: usize) -> Weight;195 fn burn_item() -> Weight;196 fn transfer() -> Weight;197 fn approve() -> Weight;198 fn transfer_from() -> Weight;199 fn set_offchain_schema() -> Weight;200 fn set_const_on_chain_schema() -> Weight;201 fn set_variable_on_chain_schema() -> Weight;202 fn set_variable_meta_data() -> Weight;203 // fn enable_contract_sponsoring() -> Weight;204}205206#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]207#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]208pub struct CreateNftData {209 pub const_data: Vec<u8>,210 pub variable_data: Vec<u8>,211}212213#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]214#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]215pub struct CreateFungibleData {216}217218#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]219#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]220pub struct CreateReFungibleData {221 pub const_data: Vec<u8>,222 pub variable_data: Vec<u8>,223}224225#[derive(Encode, Decode, Debug, Clone, PartialEq)]226#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]227pub enum CreateItemData {228 NFT(CreateNftData),229 Fungible(CreateFungibleData),230 ReFungible(CreateReFungibleData)231}232233impl CreateItemData {234 pub fn len(&self) -> usize {235 let len = match self {236 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),237 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),238 _ => 0239 };240 241 return len;242 }243}244245impl From<CreateNftData> for CreateItemData {246 fn from(item: CreateNftData) -> Self {247 CreateItemData::NFT(item)248 }249}250251impl From<CreateReFungibleData> for CreateItemData {252 fn from(item: CreateReFungibleData) -> Self {253 CreateItemData::ReFungible(item)254 }255}256257impl From<CreateFungibleData> for CreateItemData {258 fn from(item: CreateFungibleData) -> Self {259 CreateItemData::Fungible(item)260 }261}262263264decl_error! {265 /// Error for non-fungible-token module.266 pub enum Error for Module<T: Trait> {267 /// Total collections bound exceeded268 TotalCollectionsLimitExceeded,269 /// Decimal_points parameter must be lower than 4270 CollectionDecimalPointLimitExceeded, 271 /// Collection name can not be longer than 63 char272 CollectionNameLimitExceeded, 273 /// Collection description can not be longer than 255 char274 CollectionDescriptionLimitExceeded, 275 /// Token prefix can not be longer than 15 char276 CollectionTokenPrefixLimitExceeded,277 /// This collection does not exist278 CollectionNotFound,279 /// Item not exists280 TokenNotFound,281 /// Arithmetic calculation overflow282 NumOverflow, 283 /// Account already has admin role284 AlreadyAdmin, 285 /// You do not own this collection286 NoPermission,287 /// This address is not set as sponsor, use setCollectionSponsor first288 ConfirmUnsetSponsorFail,289 /// Collection is not in mint mode290 PublicMintingNotAllowed,291 /// Sender parameter and item owner must be equal292 MustBeTokenOwner,293 /// Item balance not enouth294 TokenValueTooLow,295 /// Size of item is too large296 NftSizeLimitExceeded,297 /// Size of item must be 0 with fungible type298 FungibleUnexpectedParam,299 /// No approve found300 ApproveNotFound,301 /// Requested value more than approved302 TokenValueNotEnough,303 /// Only approved addresses can call this method304 ApproveRequired,305 /// Address is not in white list306 AddresNotInWhiteList,307 /// Number of collection admins bound exceeded308 CollectionAdminsLimitExceeded,309 /// Owned tokens by a single address bound exceeded310 AddressOwnershipLimitExceeded,311 /// Length of items properties must be greater than 0312 EmptyArgument,313 }314}315316pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {317 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;318319 /// Weight information for extrinsics in this pallet.320 type WeightInfo: WeightInfo;321}322323#[cfg(feature = "runtime-benchmarks")]324mod benchmarking;325326// #endregion327328decl_storage! {329 trait Store for Module<T: Trait> as Nft {330331 // Private members332 NextCollectionID: u64;333 CreatedCollectionCount: u64;334 ChainVersion: u64;335 ItemListIndex: map hasher(blake2_128_concat) u64 => u64;336337 // Chain limits struct338 pub ChainLimit get(fn chain_limit) config(): ChainLimits;339340 // Bound counters341 CollectionCount: u64;342 pub AccountItemCount get(fn account_item_count): map hasher(identity) T::AccountId => u64;343344 // Basic collections345 pub Collection get(fn collection) config(): map hasher(identity) u64 => CollectionType<T::AccountId>;346 pub AdminList get(fn admin_list_collection): map hasher(identity) u64 => Vec<T::AccountId>;347 pub WhiteList get(fn white_list): map hasher(identity) u64 => Vec<T::AccountId>;348349 /// Balance owner per collection map350 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => u64;351352 /// second parameter: item id + owner account id353 pub ApprovedList get(fn approved): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) (u64, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;354355 /// Item collections356 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => NftItemType<T::AccountId>;357 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => FungibleItemType<T::AccountId>;358 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => ReFungibleItemType<T::AccountId>;359360 /// Index list361 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => Vec<u64>;362363 /// Tokens transfer baskets364 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => T::BlockNumber;365 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => Vec<BasketItem<T::AccountId, T::BlockNumber>>;366 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => T::BlockNumber;367368 // Contract Sponsorship and Ownership369 pub ContractOwner get(fn contract_owner): map hasher(identity) T::AccountId => T::AccountId;370 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(identity) T::AccountId => bool;371 }372 add_extra_genesis {373 build(|config: &GenesisConfig<T>| {374 // Modification of storage375 for (_num, _c) in &config.collection {376 <Module<T>>::init_collection(_c);377 }378379 for (_num, _q, _i) in &config.nft_item_id {380 <Module<T>>::init_nft_token(_i);381 }382383 for (_num, _q, _i) in &config.fungible_item_id {384 <Module<T>>::init_fungible_token(_i);385 }386387 for (_num, _q, _i) in &config.refungible_item_id {388 <Module<T>>::init_refungible_token(_i);389 }390 })391 }392}393394decl_event!(395 pub enum Event<T>396 where397 AccountId = <T as system::Trait>::AccountId,398 {399 /// New collection was created400 /// 401 /// # Arguments402 /// 403 /// * collection_id: Globally unique identifier of newly created collection.404 /// 405 /// * mode: [CollectionMode] converted into u8.406 /// 407 /// * account_id: Collection owner.408 Created(u64, u8, AccountId),409410 /// New item was created.411 /// 412 /// # Arguments413 /// 414 /// * collection_id: Id of the collection where item was created.415 /// 416 /// * item_id: Id of an item. Unique within the collection.417 ItemCreated(u64, u64),418419 /// Collection item was burned.420 /// 421 /// # Arguments422 /// 423 /// collection_id.424 /// 425 /// item_id: Identifier of burned NFT.426 ItemDestroyed(u64, u64),427 }428);429430decl_module! {431 pub struct Module<T: Trait> for enum Call where origin: T::Origin {432433 fn deposit_event() = default;434 type Error = Error<T>;435436 fn on_initialize(now: T::BlockNumber) -> Weight {437438 if ChainVersion::get() < 2439 {440 let value = NextCollectionID::get();441 CreatedCollectionCount::put(value);442 ChainVersion::put(2);443 }444445 0446 }447448 /// 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.449 /// 450 /// # Permissions451 /// 452 /// * Anyone.453 /// 454 /// # Arguments455 /// 456 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.457 /// 458 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.459 /// 460 /// * token_prefix: UTF-8 string with token prefix.461 /// 462 /// * mode: [CollectionMode] collection type and type dependent data.463 // returns collection ID464 #[weight = T::WeightInfo::create_collection()]465 pub fn create_collection(origin,466 collection_name: Vec<u16>,467 collection_description: Vec<u16>,468 token_prefix: Vec<u8>,469 mode: CollectionMode) -> DispatchResult {470471 // Anyone can create a collection472 let who = ensure_signed(origin)?;473474 let decimal_points = match mode {475 CollectionMode::Fungible(points) => points,476 CollectionMode::ReFungible(points) => points,477 _ => 0478 };479480 // bound Total number of collections481 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);482483 // check params484 ensure!(decimal_points <= 4, Error::<T>::CollectionDecimalPointLimitExceeded);485486 let mut name = collection_name.to_vec();487 name.push(0);488 ensure!(name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);489490 let mut description = collection_description.to_vec();491 description.push(0);492 ensure!(name.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);493494 let mut prefix = token_prefix.to_vec();495 prefix.push(0);496 ensure!(prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);497498 // Generate next collection ID499 let next_id = CreatedCollectionCount::get()500 .checked_add(1)501 .ok_or(Error::<T>::NumOverflow)?;502503 // bound counter504 let total = CollectionCount::get()505 .checked_add(1)506 .ok_or(Error::<T>::NumOverflow)?;507508 CreatedCollectionCount::put(next_id);509 CollectionCount::put(total);510511 // Create new collection512 let new_collection = CollectionType {513 owner: who.clone(),514 name: name,515 mode: mode.clone(),516 mint_mode: false,517 access: AccessMode::Normal,518 description: description,519 decimal_points: decimal_points,520 token_prefix: prefix,521 offchain_schema: Vec::new(),522 sponsor: T::AccountId::default(),523 unconfirmed_sponsor: T::AccountId::default(),524 variable_on_chain_schema: Vec::new(),525 const_on_chain_schema: Vec::new(),526 };527528 // Add new collection to map529 <Collection<T>>::insert(next_id, new_collection);530531 // call event532 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));533534 Ok(())535 }536537 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.538 /// 539 /// # Permissions540 /// 541 /// * Collection Owner.542 /// 543 /// # Arguments544 /// 545 /// * collection_id: collection to destroy.546 #[weight = T::WeightInfo::destroy_collection()]547 pub fn destroy_collection(origin, collection_id: u64) -> DispatchResult {548549 let sender = ensure_signed(origin)?;550 Self::check_owner_permissions(collection_id, sender)?;551552 <AddressTokens<T>>::remove_prefix(collection_id);553 <ApprovedList<T>>::remove_prefix(collection_id);554 <Balance<T>>::remove_prefix(collection_id);555 <ItemListIndex>::remove(collection_id);556 <AdminList<T>>::remove(collection_id);557 <Collection<T>>::remove(collection_id);558 <WhiteList<T>>::remove(collection_id);559560 <NftItemList<T>>::remove_prefix(collection_id);561 <FungibleItemList<T>>::remove_prefix(collection_id);562 <ReFungibleItemList<T>>::remove_prefix(collection_id);563564 <NftTransferBasket<T>>::remove_prefix(collection_id);565 <FungibleTransferBasket<T>>::remove_prefix(collection_id);566 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);567568 if CollectionCount::get() > 0569 {570 // bound couter571 let total = CollectionCount::get()572 .checked_sub(1)573 .ok_or(Error::<T>::NumOverflow)?;574575 CollectionCount::put(total);576 }577578 Ok(())579 }580581 /// Add an address to white list.582 /// 583 /// # Permissions584 /// 585 /// * Collection Owner586 /// * Collection Admin587 /// 588 /// # Arguments589 /// 590 /// * collection_id.591 /// 592 /// * address.593 #[weight = T::WeightInfo::add_to_white_list()]594 pub fn add_to_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{595596 let sender = ensure_signed(origin)?;597 Self::check_owner_or_admin_permissions(collection_id, sender)?;598599 let mut white_list_collection: Vec<T::AccountId>;600 if <WhiteList<T>>::contains_key(collection_id) {601 white_list_collection = <WhiteList<T>>::get(collection_id);602 if !white_list_collection.contains(&address.clone())603 {604 white_list_collection.push(address.clone());605 }606 }607 else {608 white_list_collection = Vec::new();609 white_list_collection.push(address.clone());610 }611612 <WhiteList<T>>::insert(collection_id, white_list_collection);613 Ok(())614 }615616 /// Remove an address from white list.617 /// 618 /// # Permissions619 /// 620 /// * Collection Owner621 /// * Collection Admin622 /// 623 /// # Arguments624 /// 625 /// * collection_id.626 /// 627 /// * address.628 #[weight = T::WeightInfo::remove_from_white_list()]629 pub fn remove_from_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{630631 let sender = ensure_signed(origin)?;632 Self::check_owner_or_admin_permissions(collection_id, sender)?;633634 if <WhiteList<T>>::contains_key(collection_id) {635 let mut white_list_collection = <WhiteList<T>>::get(collection_id);636 if white_list_collection.contains(&address.clone())637 {638 white_list_collection.retain(|i| *i != address.clone());639 <WhiteList<T>>::insert(collection_id, white_list_collection);640 }641 }642643 Ok(())644 }645646 /// Toggle between normal and white list access for the methods with access for `Anyone`.647 /// 648 /// # Permissions649 /// 650 /// * Collection Owner.651 /// 652 /// # Arguments653 /// 654 /// * collection_id.655 /// 656 /// * mode: [AccessMode]657 #[weight = T::WeightInfo::set_public_access_mode()]658 pub fn set_public_access_mode(origin, collection_id: u64, mode: AccessMode) -> DispatchResult659 {660 let sender = ensure_signed(origin)?;661662 Self::check_owner_permissions(collection_id, sender)?;663 let mut target_collection = <Collection<T>>::get(collection_id);664 target_collection.access = mode;665 <Collection<T>>::insert(collection_id, target_collection);666667 Ok(())668 }669670 /// Allows Anyone to create tokens if:671 /// * White List is enabled, and672 /// * Address is added to white list, and673 /// * This method was called with True parameter674 /// 675 /// # Permissions676 /// * Collection Owner677 ///678 /// # Arguments679 /// 680 /// * collection_id.681 /// 682 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.683 #[weight = T::WeightInfo::set_mint_permission()]684 pub fn set_mint_permission(origin, collection_id: u64, mint_permission: bool) -> DispatchResult685 {686 let sender = ensure_signed(origin)?;687688 Self::check_owner_permissions(collection_id, sender)?;689 let mut target_collection = <Collection<T>>::get(collection_id);690 target_collection.mint_mode = mint_permission;691 <Collection<T>>::insert(collection_id, target_collection);692693 Ok(())694 }695696 /// Change the owner of the collection.697 /// 698 /// # Permissions699 /// 700 /// * Collection Owner.701 /// 702 /// # Arguments703 /// 704 /// * collection_id.705 /// 706 /// * new_owner.707 #[weight = T::WeightInfo::change_collection_owner()]708 pub fn change_collection_owner(origin, collection_id: u64, new_owner: T::AccountId) -> DispatchResult {709710 let sender = ensure_signed(origin)?;711 Self::check_owner_permissions(collection_id, sender)?;712 let mut target_collection = <Collection<T>>::get(collection_id);713 target_collection.owner = new_owner;714 <Collection<T>>::insert(collection_id, target_collection);715716 Ok(())717 }718719 /// Adds an admin of the Collection.720 /// 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. 721 /// 722 /// # Permissions723 /// 724 /// * Collection Owner.725 /// * Collection Admin.726 /// 727 /// # Arguments728 /// 729 /// * collection_id: ID of the Collection to add admin for.730 /// 731 /// * new_admin_id: Address of new admin to add.732 #[weight = T::WeightInfo::add_collection_admin()]733 pub fn add_collection_admin(origin, collection_id: u64, new_admin_id: T::AccountId) -> DispatchResult {734735 let sender = ensure_signed(origin)?;736 Self::check_owner_or_admin_permissions(collection_id, sender)?;737 let mut admin_arr: Vec<T::AccountId> = Vec::new();738739 if <AdminList<T>>::contains_key(collection_id)740 {741 admin_arr = <AdminList<T>>::get(collection_id);742 ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);743 }744745 // Number of collection admins746 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);747748 admin_arr.push(new_admin_id);749 <AdminList<T>>::insert(collection_id, admin_arr);750751 Ok(())752 }753754 /// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.755 ///756 /// # Permissions757 /// 758 /// * Collection Owner.759 /// * Collection Admin.760 /// 761 /// # Arguments762 /// 763 /// * collection_id: ID of the Collection to remove admin for.764 /// 765 /// * account_id: Address of admin to remove.766 #[weight = T::WeightInfo::remove_collection_admin()]767 pub fn remove_collection_admin(origin, collection_id: u64, account_id: T::AccountId) -> DispatchResult {768769 let sender = ensure_signed(origin)?;770 Self::check_owner_or_admin_permissions(collection_id, sender)?;771772 if <AdminList<T>>::contains_key(collection_id)773 {774 let mut admin_arr = <AdminList<T>>::get(collection_id);775 admin_arr.retain(|i| *i != account_id);776 <AdminList<T>>::insert(collection_id, admin_arr);777 }778779 Ok(())780 }781782 /// # Permissions783 /// 784 /// * Collection Owner785 /// 786 /// # Arguments787 /// 788 /// * collection_id.789 /// 790 /// * new_sponsor.791 #[weight = T::WeightInfo::set_collection_sponsor()]792 pub fn set_collection_sponsor(origin, collection_id: u64, new_sponsor: T::AccountId) -> DispatchResult {793794 let sender = ensure_signed(origin)?;795 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);796797 let mut target_collection = <Collection<T>>::get(collection_id);798 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);799800 target_collection.unconfirmed_sponsor = new_sponsor;801 <Collection<T>>::insert(collection_id, target_collection);802803 Ok(())804 }805806 /// # Permissions807 /// 808 /// * Sponsor.809 /// 810 /// # Arguments811 /// 812 /// * collection_id.813 #[weight = T::WeightInfo::confirm_sponsorship()]814 pub fn confirm_sponsorship(origin, collection_id: u64) -> DispatchResult {815816 let sender = ensure_signed(origin)?;817 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);818819 let mut target_collection = <Collection<T>>::get(collection_id);820 ensure!(sender == target_collection.unconfirmed_sponsor, Error::<T>::ConfirmUnsetSponsorFail);821822 target_collection.sponsor = target_collection.unconfirmed_sponsor;823 target_collection.unconfirmed_sponsor = T::AccountId::default();824 <Collection<T>>::insert(collection_id, target_collection);825826 Ok(())827 }828829 /// Switch back to pay-per-own-transaction model.830 ///831 /// # Permissions832 ///833 /// * Collection owner.834 /// 835 /// # Arguments836 /// 837 /// * collection_id.838 #[weight = T::WeightInfo::remove_collection_sponsor()]839 pub fn remove_collection_sponsor(origin, collection_id: u64) -> DispatchResult {840841 let sender = ensure_signed(origin)?;842 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);843844 let mut target_collection = <Collection<T>>::get(collection_id);845 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);846847 target_collection.sponsor = T::AccountId::default();848 <Collection<T>>::insert(collection_id, target_collection);849850 Ok(())851 }852853 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.854 /// 855 /// # Permissions856 /// 857 /// * Collection Owner.858 /// * Collection Admin.859 /// * Anyone if860 /// * White List is enabled, and861 /// * Address is added to white list, and862 /// * MintPermission is enabled (see SetMintPermission method)863 /// 864 /// # Arguments865 /// 866 /// * collection_id: ID of the collection.867 /// 868 /// * owner: Address, initial owner of the NFT.869 ///870 /// * data: Token data to store on chain.871 // #[weight =872 // (130_000_000 as Weight)873 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))874 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))875 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]876877 #[weight = T::WeightInfo::create_item(data.len())]878 pub fn create_item(origin, collection_id: u64, owner: T::AccountId, data: CreateItemData) -> DispatchResult {879880 let sender = ensure_signed(origin)?;881882 Self::collection_exists(collection_id)?;883884 let target_collection = <Collection<T>>::get(collection_id);885886 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;887 Self::validate_create_item_args(&target_collection, &data)?;888 Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;889890 Ok(())891 }892893 /// This method creates multiple instances of NFT Collection created with CreateCollection method.894 /// 895 /// # Permissions896 /// 897 /// * Collection Owner.898 /// * Collection Admin.899 /// * Anyone if900 /// * White List is enabled, and901 /// * Address is added to white list, and902 /// * MintPermission is enabled (see SetMintPermission method)903 /// 904 /// # Arguments905 /// 906 /// * collection_id: ID of the collection.907 /// 908 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].909 /// 910 /// * owner: Address, initial owner of the NFT.911 #[weight = T::WeightInfo::create_item(items_data.into_iter()912 .map(|data| { data.len() })913 .sum())]914 pub fn create_multiple_items(origin, collection_id: u64, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {915916 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);917 let sender = ensure_signed(origin)?;918919 Self::collection_exists(collection_id)?;920 let target_collection = <Collection<T>>::get(collection_id);921922 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;923924 for data in &items_data {925 Self::validate_create_item_args(&target_collection, data)?;926 }927 for data in &items_data {928 Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;929 }930931 Ok(())932 }933934 /// Destroys a concrete instance of NFT.935 /// 936 /// # Permissions937 /// 938 /// * Collection Owner.939 /// * Collection Admin.940 /// * Current NFT Owner.941 /// 942 /// # Arguments943 /// 944 /// * collection_id: ID of the collection.945 /// 946 /// * item_id: ID of NFT to burn.947 #[weight = T::WeightInfo::burn_item()]948 pub fn burn_item(origin, collection_id: u64, item_id: u64) -> DispatchResult {949950 let sender = ensure_signed(origin)?;951 Self::collection_exists(collection_id)?;952953 // Transfer permissions check954 let target_collection = <Collection<T>>::get(collection_id);955 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||956 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),957 Error::<T>::NoPermission);958959 if target_collection.access == AccessMode::WhiteList {960 Self::check_white_list(collection_id, &sender)?;961 }962963 match target_collection.mode964 {965 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,966 CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?,967 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,968 _ => ()969 };970971 // call event972 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));973974 Ok(())975 }976977 /// Change ownership of the token.978 /// 979 /// # Permissions980 /// 981 /// * Collection Owner982 /// * Collection Admin983 /// * Current NFT owner984 ///985 /// # Arguments986 /// 987 /// * recipient: Address of token recipient.988 /// 989 /// * collection_id.990 /// 991 /// * item_id: ID of the item992 /// * Non-Fungible Mode: Required.993 /// * Fungible Mode: Ignored.994 /// * Re-Fungible Mode: Required.995 /// 996 /// * value: Amount to transfer.997 /// * Non-Fungible Mode: Ignored998 /// * Fungible Mode: Must specify transferred amount999 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1000 #[weight = T::WeightInfo::transfer()]1001 pub fn transfer(origin, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64) -> DispatchResult {10021003 let sender = ensure_signed(origin)?;10041005 // Transfer permissions check1006 let target_collection = <Collection<T>>::get(collection_id);1007 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1008 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1009 Error::<T>::NoPermission);10101011 if target_collection.access == AccessMode::WhiteList {1012 Self::check_white_list(collection_id, &sender)?;1013 Self::check_white_list(collection_id, &recipient)?;1014 }10151016 match target_collection.mode1017 {1018 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1019 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,1020 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1021 _ => ()1022 };10231024 Ok(())1025 }10261027 /// Set, change, or remove approved address to transfer the ownership of the NFT.1028 /// 1029 /// # Permissions1030 /// 1031 /// * Collection Owner1032 /// * Collection Admin1033 /// * Current NFT owner1034 /// 1035 /// # Arguments1036 /// 1037 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1038 /// 1039 /// * collection_id.1040 /// 1041 /// * item_id: ID of the item.1042 #[weight = T::WeightInfo::approve()]1043 pub fn approve(origin, approved: T::AccountId, collection_id: u64, item_id: u64) -> DispatchResult {10441045 let sender = ensure_signed(origin)?;10461047 // Transfer permissions check1048 let target_collection = <Collection<T>>::get(collection_id);1049 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1050 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1051 Error::<T>::NoPermission);10521053 if target_collection.access == AccessMode::WhiteList {1054 Self::check_white_list(collection_id, &sender)?;1055 Self::check_white_list(collection_id, &approved)?;1056 }10571058 // amount param stub1059 let amount = 100000000;10601061 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));1062 if list_exists {10631064 let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));1065 let item_contains = list.iter().any(|i| i.approved == approved);10661067 if !item_contains {1068 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1069 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1070 }1071 } else {10721073 let mut list = Vec::new();1074 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1075 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1076 }10771078 Ok(())1079 }1080 1081 /// 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.1082 /// 1083 /// # Permissions1084 /// * Collection Owner1085 /// * Collection Admin1086 /// * Current NFT owner1087 /// * Address approved by current NFT owner1088 /// 1089 /// # Arguments1090 /// 1091 /// * from: Address that owns token.1092 /// 1093 /// * recipient: Address of token recipient.1094 /// 1095 /// * collection_id.1096 /// 1097 /// * item_id: ID of the item.1098 /// 1099 /// * value: Amount to transfer.1100 #[weight = T::WeightInfo::transfer_from()]1101 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64 ) -> DispatchResult {11021103 let sender = ensure_signed(origin)?;1104 let mut appoved_transfer = false;11051106 // Check approve1107 if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {1108 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));1109 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());1110 if opt_item.is_some()1111 {1112 appoved_transfer = true;1113 ensure!(opt_item.unwrap().amount >= value, Error::<T>::TokenValueNotEnough);1114 }1115 }11161117 // Transfer permissions check1118 let target_collection = <Collection<T>>::get(collection_id);1119 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1120 Error::<T>::NoPermission);11211122 if target_collection.access == AccessMode::WhiteList {1123 Self::check_white_list(collection_id, &sender)?;1124 Self::check_white_list(collection_id, &recipient)?;1125 }11261127 // remove approve1128 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))1129 .into_iter().filter(|i| i.approved != sender.clone()).collect();1130 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);113111321133 match target_collection.mode1134 {1135 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1136 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,1137 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1138 _ => ()1139 };11401141 Ok(())1142 }11431144 ///1145 #[weight = 0]1146 pub fn safe_transfer_from(origin, collection_id: u64, item_id: u64, new_owner: T::AccountId) -> DispatchResult {11471148 // let no_perm_mes = "You do not have permissions to modify this collection";1149 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1150 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1151 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);11521153 // // on_nft_received call11541155 // Self::transfer(origin, collection_id, item_id, new_owner)?;11561157 Ok(())1158 }11591160 /// Set off-chain data schema.1161 /// 1162 /// # Permissions1163 /// 1164 /// * Collection Owner1165 /// * Collection Admin1166 /// 1167 /// # Arguments1168 /// 1169 /// * collection_id.1170 /// 1171 /// * schema: String representing the offchain data schema.1172 #[weight = T::WeightInfo::set_variable_meta_data()]1173 pub fn set_variable_meta_data (1174 origin,1175 collection_id: u64,1176 item_id: u64,1177 data: Vec<u8>1178 ) -> DispatchResult {1179 let sender = ensure_signed(origin)?;1180 1181 Self::collection_exists(collection_id)?;11821183 // Modify permissions check1184 let target_collection = <Collection<T>>::get(collection_id);1185 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1186 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1187 Error::<T>::NoPermission);11881189 Self::item_exists(collection_id, item_id, &target_collection.mode)?;11901191 match target_collection.mode1192 {1193 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1194 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1195 CollectionMode::Fungible(_) => fail!("Can't store metadata in fungible tokens."),1196 _ => fail!("Unexpected collection type.")1197 };11981199 Ok(())1200 }1201 12021203 /// Set off-chain data schema.1204 /// 1205 /// # Permissions1206 /// 1207 /// * Collection Owner1208 /// * Collection Admin1209 /// 1210 /// # Arguments1211 /// 1212 /// * collection_id.1213 /// 1214 /// * schema: String representing the offchain data schema.1215 #[weight = T::WeightInfo::set_offchain_schema()]1216 pub fn set_offchain_schema(1217 origin,1218 collection_id: u64,1219 schema: Vec<u8>1220 ) -> DispatchResult {1221 let sender = ensure_signed(origin)?;1222 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12231224 let mut target_collection = <Collection<T>>::get(collection_id);1225 target_collection.offchain_schema = schema;1226 <Collection<T>>::insert(collection_id, target_collection);12271228 Ok(())1229 }12301231 /// Set const on-chain data schema.1232 /// 1233 /// # Permissions1234 /// 1235 /// * Collection Owner1236 /// * Collection Admin1237 /// 1238 /// # Arguments1239 /// 1240 /// * collection_id.1241 /// 1242 /// * schema: String representing the const on-chain data schema.1243 #[weight = T::WeightInfo::set_const_on_chain_schema()]1244 pub fn set_const_on_chain_schema (1245 origin,1246 collection_id: u64,1247 schema: Vec<u8>1248 ) -> DispatchResult {1249 let sender = ensure_signed(origin)?;1250 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12511252 let mut target_collection = <Collection<T>>::get(collection_id);1253 target_collection.const_on_chain_schema = schema;1254 <Collection<T>>::insert(collection_id, target_collection);12551256 Ok(())1257 }12581259 /// Set variable on-chain data schema.1260 /// 1261 /// # Permissions1262 /// 1263 /// * Collection Owner1264 /// * Collection Admin1265 /// 1266 /// # Arguments1267 /// 1268 /// * collection_id.1269 /// 1270 /// * schema: String representing the variable on-chain data schema.1271 #[weight = T::WeightInfo::set_const_on_chain_schema()]1272 pub fn set_variable_on_chain_schema (1273 origin,1274 collection_id: u64,1275 schema: Vec<u8>1276 ) -> DispatchResult {1277 let sender = ensure_signed(origin)?;1278 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12791280 let mut target_collection = <Collection<T>>::get(collection_id);1281 target_collection.variable_on_chain_schema = schema;1282 <Collection<T>>::insert(collection_id, target_collection);12831284 Ok(())1285 }12861287 // Sudo permissions function1288 #[weight = 0]1289 pub fn set_chain_limits(1290 origin,1291 limits: ChainLimits1292 ) -> DispatchResult {1293 ensure_root(origin)?;1294 <ChainLimit>::put(limits);1295 Ok(())1296 }12971298 /// Enable smart contract self-sponsoring.1299 /// 1300 /// # Permissions1301 /// 1302 /// * Contract Owner1303 /// 1304 /// # Arguments1305 /// 1306 /// * contract address1307 /// * enable flag1308 /// 1309 #[weight = 0]1310 pub fn enable_contract_sponsoring(1311 origin,1312 contract_address: T::AccountId,1313 enable: bool1314 ) -> DispatchResult {1315 let sender = ensure_signed(origin)?;1316 let mut is_owner = false;1317 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1318 let owner = <ContractOwner<T>>::get(&contract_address);1319 is_owner = sender == owner;1320 }1321 ensure!(is_owner, Error::<T>::NoPermission);13221323 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1324 Ok(())1325 }13261327 }1328}13291330impl<T: Trait> Module<T> {13311332 fn can_create_items_in_collection(collection_id: u64, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {13331334 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1335 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1336 Self::check_white_list(collection_id, owner)?;1337 Self::check_white_list(collection_id, sender)?;1338 }13391340 Ok(())1341 }13421343 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1344 match target_collection.mode1345 {1346 CollectionMode::NFT => {1347 if let CreateItemData::NFT(data) = data {1348 // check sizes1349 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, "const_data exceeded data limit.");1350 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, "variable_data exceeded data limit.");1351 } else {1352 fail!("Not NFT item data used to mint in NFT collection.");1353 }1354 },1355 CollectionMode::Fungible(_) => {1356 if let CreateItemData::Fungible(_) = data {1357 } else {1358 fail!("Not Fungible item data used to mint in Fungible collection.");1359 }1360 },1361 CollectionMode::ReFungible(_) => {1362 if let CreateItemData::ReFungible(data) = data {13631364 // check sizes1365 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, "const_data exceeded data limit.");1366 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, "variable_data exceeded data limit.");1367 } else {1368 fail!("Not Re Fungible item data used to mint in Re Fungible collection.");1369 }1370 },1371 _ => { fail!("Unexpected collection type."); }1372 };13731374 Ok(())1375 }13761377 fn create_item_no_validation(collection_id: u64, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1378 match data1379 {1380 CreateItemData::NFT(data) => {1381 let item = NftItemType {1382 collection: collection_id,1383 owner,1384 const_data: data.const_data,1385 variable_data: data.variable_data1386 };13871388 Self::add_nft_item(item)?;1389 },1390 CreateItemData::Fungible(_) => {1391 let item = FungibleItemType {1392 collection: collection_id,1393 owner,1394 value: (10 as u128).pow(collection.decimal_points)1395 };13961397 Self::add_fungible_item(item)?;1398 },1399 CreateItemData::ReFungible(data) => {1400 let mut owner_list = Vec::new();1401 let value = (10 as u128).pow(collection.decimal_points);1402 owner_list.push(Ownership {owner: owner.clone(), fraction: value});14031404 let item = ReFungibleItemType {1405 collection: collection_id,1406 owner: owner_list,1407 const_data: data.const_data,1408 variable_data: data.variable_data1409 };14101411 Self::add_refungible_item(item)?;1412 }1413 };141414151416 // call event1417 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));14181419 Ok(())1420 }14211422 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {1423 let current_index = <ItemListIndex>::get(item.collection)1424 .checked_add(1)1425 .ok_or(Error::<T>::NumOverflow)?;1426 let itemcopy = item.clone();1427 let owner = item.owner.clone();1428 let value = item.value as u64;14291430 Self::add_token_index(item.collection, current_index, owner.clone())?;14311432 <ItemListIndex>::insert(item.collection, current_index);1433 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);14341435 // Add current block1436 let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1437 <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);1438 1439 // Update balance1440 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1441 .checked_add(value)1442 .ok_or(Error::<T>::NumOverflow)?;1443 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);14441445 Ok(())1446 }14471448 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1449 let current_index = <ItemListIndex>::get(item.collection)1450 .checked_add(1)1451 .ok_or(Error::<T>::NumOverflow)?;1452 let itemcopy = item.clone();14531454 let value = item.owner.first().unwrap().fraction as u64;1455 let owner = item.owner.first().unwrap().owner.clone();14561457 Self::add_token_index(item.collection, current_index, owner.clone())?;14581459 <ItemListIndex>::insert(item.collection, current_index);1460 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);14611462 // Add current block1463 let block_number: T::BlockNumber = 0.into();1464 <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);14651466 // Update balance1467 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1468 .checked_add(value)1469 .ok_or(Error::<T>::NumOverflow)?;1470 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);14711472 Ok(())1473 }14741475 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1476 let current_index = <ItemListIndex>::get(item.collection)1477 .checked_add(1)1478 .ok_or(Error::<T>::NumOverflow)?;14791480 let item_owner = item.owner.clone();1481 let collection_id = item.collection.clone();1482 Self::add_token_index(collection_id, current_index, item.owner.clone())?;14831484 <ItemListIndex>::insert(collection_id, current_index);1485 <NftItemList<T>>::insert(collection_id, current_index, item);14861487 // Add current block1488 let block_number: T::BlockNumber = 0.into();1489 <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);14901491 // Update balance1492 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1493 .checked_add(1)1494 .ok_or(Error::<T>::NumOverflow)?;1495 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);14961497 Ok(())1498 }14991500 fn burn_refungible_item(1501 collection_id: u64,1502 item_id: u64,1503 owner: T::AccountId,1504 ) -> DispatchResult {1505 ensure!(1506 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1507 Error::<T>::TokenNotFound1508 );1509 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1510 let item = collection1511 .owner1512 .iter()1513 .filter(|&i| i.owner == owner)1514 .next()1515 .unwrap();1516 Self::remove_token_index(collection_id, item_id, owner.clone())?;15171518 // remove approve list1519 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));15201521 // update balance1522 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1523 .checked_sub(item.fraction as u64)1524 .ok_or(Error::<T>::NumOverflow)?;1525 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);15261527 <ReFungibleItemList<T>>::remove(collection_id, item_id);15281529 Ok(())1530 }15311532 fn burn_nft_item(collection_id: u64, item_id: u64) -> DispatchResult {1533 ensure!(1534 <NftItemList<T>>::contains_key(collection_id, item_id),1535 Error::<T>::TokenNotFound1536 );1537 let item = <NftItemList<T>>::get(collection_id, item_id);1538 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;15391540 // remove approve list1541 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));15421543 // update balance1544 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1545 .checked_sub(1)1546 .ok_or(Error::<T>::NumOverflow)?;1547 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1548 <NftItemList<T>>::remove(collection_id, item_id);15491550 Ok(())1551 }15521553 fn burn_fungible_item(collection_id: u64, item_id: u64) -> DispatchResult {1554 ensure!(1555 <FungibleItemList<T>>::contains_key(collection_id, item_id),1556 Error::<T>::TokenNotFound1557 );1558 let item = <FungibleItemList<T>>::get(collection_id, item_id);1559 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;15601561 // remove approve list1562 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));15631564 // update balance1565 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1566 .checked_sub(item.value as u64)1567 .ok_or(Error::<T>::NumOverflow)?;1568 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);15691570 <FungibleItemList<T>>::remove(collection_id, item_id);15711572 Ok(())1573 }15741575 fn collection_exists(collection_id: u64) -> DispatchResult {1576 ensure!(1577 <Collection<T>>::contains_key(collection_id),1578 Error::<T>::CollectionNotFound1579 );1580 Ok(())1581 }15821583 fn check_owner_permissions(collection_id: u64, subject: T::AccountId) -> DispatchResult {1584 Self::collection_exists(collection_id)?;15851586 let target_collection = <Collection<T>>::get(collection_id);1587 ensure!(1588 subject == target_collection.owner,1589 Error::<T>::NoPermission1590 );15911592 Ok(())1593 }15941595 fn is_owner_or_admin_permissions(collection_id: u64, subject: T::AccountId) -> bool {1596 let target_collection = <Collection<T>>::get(collection_id);1597 let mut result: bool = subject == target_collection.owner;1598 let exists = <AdminList<T>>::contains_key(collection_id);15991600 if !result & exists {1601 if <AdminList<T>>::get(collection_id).contains(&subject) {1602 result = true1603 }1604 }16051606 result1607 }16081609 fn check_owner_or_admin_permissions(1610 collection_id: u64,1611 subject: T::AccountId,1612 ) -> DispatchResult {1613 Self::collection_exists(collection_id)?;1614 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());16151616 ensure!(1617 result,1618 Error::<T>::NoPermission1619 );1620 Ok(())1621 }16221623 fn is_item_owner(subject: T::AccountId, collection_id: u64, item_id: u64) -> bool {1624 let target_collection = <Collection<T>>::get(collection_id);16251626 match target_collection.mode {1627 CollectionMode::NFT => {1628 <NftItemList<T>>::get(collection_id, item_id).owner == subject1629 }1630 CollectionMode::Fungible(_) => {1631 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1632 }1633 CollectionMode::ReFungible(_) => {1634 <ReFungibleItemList<T>>::get(collection_id, item_id)1635 .owner1636 .iter()1637 .any(|i| i.owner == subject)1638 }1639 CollectionMode::Invalid => false,1640 }1641 }16421643 fn check_white_list(collection_id: u64, address: &T::AccountId) -> DispatchResult {1644 let mes = Error::<T>::AddresNotInWhiteList;1645 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1646 let wl = <WhiteList<T>>::get(collection_id);1647 ensure!(wl.contains(address), mes);16481649 Ok(())1650 }16511652 fn transfer_fungible(1653 collection_id: u64,1654 item_id: u64,1655 value: u64,1656 owner: T::AccountId,1657 new_owner: T::AccountId,1658 ) -> DispatchResult {1659 ensure!(1660 <FungibleItemList<T>>::contains_key(collection_id, item_id),1661 Error::<T>::TokenNotFound1662 );16631664 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1665 let amount = full_item.value;16661667 ensure!(amount >= value.into(), Error::<T>::TokenValueTooLow);16681669 // update balance1670 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1671 .checked_sub(value)1672 .ok_or(Error::<T>::NumOverflow)?;1673 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);16741675 let mut new_owner_account_id = 0;1676 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1677 if new_owner_items.len() > 0 {1678 new_owner_account_id = new_owner_items[0];1679 }16801681 let val64 = value.into();16821683 // transfer1684 if amount == val64 && new_owner_account_id == 0 {1685 // change owner1686 // new owner do not have account1687 let mut new_full_item = full_item.clone();1688 new_full_item.owner = new_owner.clone();1689 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);16901691 // update balance1692 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1693 .checked_add(value)1694 .ok_or(Error::<T>::NumOverflow)?;1695 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);16961697 // update index collection1698 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1699 } else {1700 let mut new_full_item = full_item.clone();1701 new_full_item.value -= val64;17021703 // separate amount1704 if new_owner_account_id > 0 {1705 // new owner has account1706 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1707 item.value += val64;17081709 // update balance1710 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1711 .checked_add(value)1712 .ok_or(Error::<T>::NumOverflow)?;1713 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);17141715 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1716 } else {1717 // new owner do not have account1718 let item = FungibleItemType {1719 collection: collection_id,1720 owner: new_owner.clone(),1721 value: val64,1722 };17231724 Self::add_fungible_item(item)?;1725 }17261727 if amount == val64 {1728 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;17291730 // remove approve list1731 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1732 <FungibleItemList<T>>::remove(collection_id, item_id);1733 }17341735 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1736 }17371738 Ok(())1739 }17401741 fn transfer_refungible(1742 collection_id: u64,1743 item_id: u64,1744 value: u64,1745 owner: T::AccountId,1746 new_owner: T::AccountId,1747 ) -> DispatchResult {1748 ensure!(1749 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1750 Error::<T>::TokenNotFound1751 );17521753 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1754 let item = full_item1755 .owner1756 .iter()1757 .filter(|i| i.owner == owner)1758 .next()1759 .ok_or(Error::<T>::NumOverflow)?;1760 let amount = item.fraction;17611762 ensure!(amount >= value.into(), Error::<T>::TokenValueTooLow);17631764 // update balance1765 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1766 .checked_sub(value)1767 .ok_or(Error::<T>::NumOverflow)?;1768 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);17691770 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1771 .checked_add(value)1772 .ok_or(Error::<T>::NumOverflow)?;1773 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);17741775 let old_owner = item.owner.clone();1776 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);1777 let val64 = value.into();17781779 // transfer1780 if amount == val64 && !new_owner_has_account {1781 // change owner1782 // new owner do not have account1783 let mut new_full_item = full_item.clone();1784 new_full_item1785 .owner1786 .iter_mut()1787 .find(|i| i.owner == owner)1788 .unwrap()1789 .owner = new_owner.clone();1790 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);17911792 // update index collection1793 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1794 } else {1795 let mut new_full_item = full_item.clone();1796 new_full_item1797 .owner1798 .iter_mut()1799 .find(|i| i.owner == owner)1800 .unwrap()1801 .fraction -= val64;18021803 // separate amount1804 if new_owner_has_account {1805 // new owner has account1806 new_full_item1807 .owner1808 .iter_mut()1809 .find(|i| i.owner == new_owner)1810 .unwrap()1811 .fraction += val64;1812 } else {1813 // new owner do not have account1814 new_full_item.owner.push(Ownership {1815 owner: new_owner.clone(),1816 fraction: val64,1817 });1818 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1819 }18201821 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1822 }18231824 Ok(())1825 }18261827 fn transfer_nft(1828 collection_id: u64,1829 item_id: u64,1830 sender: T::AccountId,1831 new_owner: T::AccountId,1832 ) -> DispatchResult {1833 ensure!(1834 <NftItemList<T>>::contains_key(collection_id, item_id),1835 Error::<T>::TokenNotFound1836 );18371838 let mut item = <NftItemList<T>>::get(collection_id, item_id);18391840 ensure!(1841 sender == item.owner,1842 Error::<T>::MustBeTokenOwner1843 );18441845 // update balance1846 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1847 .checked_sub(1)1848 .ok_or(Error::<T>::NumOverflow)?;1849 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);18501851 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1852 .checked_add(1)1853 .ok_or(Error::<T>::NumOverflow)?;1854 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);18551856 // change owner1857 let old_owner = item.owner.clone();1858 item.owner = new_owner.clone();1859 <NftItemList<T>>::insert(collection_id, item_id, item);18601861 // update index collection1862 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;18631864 // reset approved list1865 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));1866 Ok(())1867 }1868 1869 fn item_exists(1870 collection_id: u64,1871 item_id: u64,1872 mode: &CollectionMode1873 ) -> DispatchResult {1874 match mode {1875 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1876 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1877 CollectionMode::Fungible(_) => ensure!(<FungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1878 _ => ()1879 };1880 1881 Ok(())1882 }18831884 fn set_re_fungible_variable_data(1885 collection_id: u64,1886 item_id: u64,1887 data: Vec<u8>1888 ) -> DispatchResult {1889 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);18901891 item.variable_data = data;18921893 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);18941895 Ok(())1896 }18971898 fn set_nft_variable_data(1899 collection_id: u64,1900 item_id: u64,1901 data: Vec<u8>1902 ) -> DispatchResult {1903 let mut item = <NftItemList<T>>::get(collection_id, item_id);1904 1905 item.variable_data = data;19061907 <NftItemList<T>>::insert(collection_id, item_id, item);1908 1909 Ok(())1910 }19111912 fn init_collection(item: &CollectionType<T::AccountId>) {1913 // check params1914 assert!(1915 item.decimal_points <= 4,1916 "decimal_points parameter must be lower than 4"1917 );1918 assert!(1919 item.name.len() <= 64,1920 "Collection name can not be longer than 63 char"1921 );1922 assert!(1923 item.name.len() <= 256,1924 "Collection description can not be longer than 255 char"1925 );1926 assert!(1927 item.token_prefix.len() <= 16,1928 "Token prefix can not be longer than 15 char"1929 );19301931 // Generate next collection ID1932 let next_id = CreatedCollectionCount::get()1933 .checked_add(1)1934 .unwrap();19351936 CreatedCollectionCount::put(next_id);1937 }19381939 fn init_nft_token(item: &NftItemType<T::AccountId>) {1940 let current_index = <ItemListIndex>::get(item.collection)1941 .checked_add(1)1942 .unwrap();19431944 let item_owner = item.owner.clone();1945 let collection_id = item.collection.clone();1946 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();19471948 <ItemListIndex>::insert(collection_id, current_index);19491950 // Update balance1951 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1952 .checked_add(1)1953 .unwrap();1954 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);1955 }19561957 fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {1958 let current_index = <ItemListIndex>::get(item.collection)1959 .checked_add(1)1960 .unwrap();1961 let owner = item.owner.clone();1962 let value = item.value as u64;19631964 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();19651966 <ItemListIndex>::insert(item.collection, current_index);19671968 // Update balance1969 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1970 .checked_add(value)1971 .unwrap();1972 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1973 }19741975 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {1976 let current_index = <ItemListIndex>::get(item.collection)1977 .checked_add(1)1978 .unwrap();19791980 let value = item.owner.first().unwrap().fraction as u64;1981 let owner = item.owner.first().unwrap().owner.clone();19821983 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();19841985 <ItemListIndex>::insert(item.collection, current_index);19861987 // Update balance1988 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1989 .checked_add(value)1990 .unwrap();1991 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1992 }19931994 fn add_token_index(collection_id: u64, item_index: u64, owner: T::AccountId) -> DispatchResult {19951996 // add to account limit1997 if <AccountItemCount<T>>::contains_key(owner.clone()) {19981999 // bound Owned tokens by a single address2000 let count = <AccountItemCount<T>>::get(owner.clone());2001 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);20022003 <AccountItemCount<T>>::insert(owner.clone(), count2004 .checked_add(1)2005 .ok_or(Error::<T>::NumOverflow)?);2006 }2007 else {2008 <AccountItemCount<T>>::insert(owner.clone(), 1);2009 }20102011 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2012 if list_exists {2013 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2014 let item_contains = list.contains(&item_index.clone());20152016 if !item_contains {2017 list.push(item_index.clone());2018 }20192020 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2021 } else {2022 let mut itm = Vec::new();2023 itm.push(item_index.clone());2024 <AddressTokens<T>>::insert(collection_id, owner, itm);2025 2026 }20272028 Ok(())2029 }20302031 fn remove_token_index(2032 collection_id: u64,2033 item_index: u64,2034 owner: T::AccountId,2035 ) -> DispatchResult {20362037 // update counter2038 <AccountItemCount<T>>::insert(owner.clone(), 2039 <AccountItemCount<T>>::get(owner.clone())2040 .checked_sub(1)2041 .ok_or(Error::<T>::NumOverflow)?);204220432044 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2045 if list_exists {2046 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2047 let item_contains = list.contains(&item_index.clone());20482049 if item_contains {2050 list.retain(|&item| item != item_index);2051 <AddressTokens<T>>::insert(collection_id, owner, list);2052 }2053 }20542055 Ok(())2056 }20572058 fn move_token_index(2059 collection_id: u64,2060 item_index: u64,2061 old_owner: T::AccountId,2062 new_owner: T::AccountId,2063 ) -> DispatchResult {2064 Self::remove_token_index(collection_id, item_index, old_owner)?;2065 Self::add_token_index(collection_id, item_index, new_owner)?;20662067 Ok(())2068 }2069}20702071////////////////////////////////////////////////////////////////////////////////////////////////////2072// Economic models2073// #region20742075/// Fee multiplier.2076pub type Multiplier = FixedU128;20772078type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2079 <T as system::Trait>::AccountId,2080>>::Balance;2081type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2082 <T as system::Trait>::AccountId,2083>>::NegativeImbalance;20842085/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2086/// in the queue.2087#[derive(Encode, Decode, Clone, Eq, PartialEq)]2088pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2089 #[codec(compact)] BalanceOf<T>2090);20912092impl<T: Trait + Send + Sync> sp_std::fmt::Debug2093 for ChargeTransactionPayment<T>2094{2095 #[cfg(feature = "std")]2096 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2097 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2098 }2099 #[cfg(not(feature = "std"))]2100 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2101 Ok(())2102 }2103}21042105impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2106where2107 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2108 BalanceOf<T>: Send + Sync + FixedPointOperand,2109{2110 /// utility constructor. Used only in client/factory code.2111 pub fn from(fee: BalanceOf<T>) -> Self {2112 Self(fee)2113 }21142115 pub fn traditional_fee(2116 len: usize,2117 info: &DispatchInfoOf<T::Call>,2118 tip: BalanceOf<T>,2119 ) -> BalanceOf<T>2120 where2121 T::Call: Dispatchable<Info = DispatchInfo>,2122 {2123 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2124 }21252126 fn withdraw_fee(2127 &self,2128 who: &T::AccountId,2129 call: &T::Call,2130 info: &DispatchInfoOf<T::Call>,2131 len: usize,2132 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2133 let tip = self.0;21342135 // Set fee based on call type. Creating collection costs 1 Unique.2136 // All other transactions have traditional fees so far2137 // let fee = match call.is_sub_type() {2138 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2139 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2140 // // _ => <BalanceOf<T>>::from(100)2141 // };2142 let fee = Self::traditional_fee(len, info, tip);21432144 // Determine who is paying transaction fee based on ecnomic model2145 // Parse call to extract collection ID and access collection sponsor2146 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2147 Some(Call::create_item(collection_id, _properties, _owner)) => {2148 <Collection<T>>::get(collection_id).sponsor2149 }2150 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {2151 let _collection_mode = <Collection<T>>::get(collection_id).mode;21522153 // sponsor timeout2154 let sponsor_transfer = match _collection_mode {2155 CollectionMode::NFT => {2156 let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);2157 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2158 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();2159 if block_number >= limit_time {2160 <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);2161 true2162 }2163 else {2164 false2165 }2166 }2167 CollectionMode::Fungible(_) => {2168 let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);2169 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2170 if basket.iter().any(|i| i.address == _new_owner.clone())2171 {2172 let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();2173 let limit_time = item.start_block + ChainLimit::get().fungible_sponsor_transfer_timeout.into();2174 if block_number >= limit_time {2175 basket.retain(|x| x.address == item.address);2176 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });2177 <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);2178 true2179 }2180 else {2181 false2182 }2183 }2184 else {2185 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});2186 true2187 }2188 }2189 CollectionMode::ReFungible(_) => {2190 let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);2191 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2192 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();2193 if block_number >= limit_time {2194 <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);2195 true2196 } else {2197 false2198 }2199 }2200 _ => {2201 false2202 },2203 };22042205 if !sponsor_transfer {2206 T::AccountId::default()2207 } else {2208 <Collection<T>>::get(collection_id).sponsor2209 }2210 }22112212 _ => T::AccountId::default(),2213 };22142215 // Sponsor smart contracts2216 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {22172218 // On instantiation: set the contract owner2219 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {22202221 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2222 code_hash,2223 &data,2224 &who,2225 );2226 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());22272228 T::AccountId::default()2229 },22302231 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2232 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {22332234 let mut sp = T::AccountId::default();2235 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());2236 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2237 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2238 sp = called_contract;2239 }2240 }22412242 sp2243 },22442245 _ => sponsor,2246 };22472248 let mut who_pays_fee: T::AccountId = sponsor.clone();2249 if sponsor == T::AccountId::default() {2250 who_pays_fee = who.clone();2251 }22522253 // Only mess with balances if fee is not zero.2254 if fee.is_zero() {2255 return Ok((fee, None));2256 }22572258 match <T as transaction_payment::Trait>::Currency::withdraw(2259 &who_pays_fee,2260 fee,2261 if tip.is_zero() {2262 WithdrawReason::TransactionPayment.into()2263 } else {2264 WithdrawReason::TransactionPayment | WithdrawReason::Tip2265 },2266 ExistenceRequirement::KeepAlive,2267 ) {2268 Ok(imbalance) => Ok((fee, Some(imbalance))),2269 Err(_) => Err(InvalidTransaction::Payment.into()),2270 }2271 }2272}227322742275impl<T: Trait + Send + Sync> SignedExtension2276 for ChargeTransactionPayment<T>2277where2278 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2279 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2280{2281 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2282 type AccountId = T::AccountId;2283 type Call = T::Call;2284 type AdditionalSigned = ();2285 type Pre = (2286 BalanceOf<T>,2287 Self::AccountId,2288 Option<NegativeImbalanceOf<T>>,2289 BalanceOf<T>,2290 );2291 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2292 Ok(())2293 }22942295 fn validate(2296 &self,2297 _who: &Self::AccountId,2298 _call: &Self::Call,2299 _info: &DispatchInfoOf<Self::Call>,2300 _len: usize,2301 ) -> TransactionValidity {2302 Ok(ValidTransaction::default())2303 }23042305 fn pre_dispatch(2306 self,2307 who: &Self::AccountId,2308 call: &Self::Call,2309 info: &DispatchInfoOf<Self::Call>,2310 len: usize,2311 ) -> Result<Self::Pre, TransactionValidityError> {2312 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2313 Ok((self.0, who.clone(), imbalance, fee))2314 }23152316 fn post_dispatch(2317 pre: Self::Pre,2318 info: &DispatchInfoOf<Self::Call>,2319 post_info: &PostDispatchInfoOf<Self::Call>,2320 len: usize,2321 _result: &DispatchResult,2322 ) -> Result<(), TransactionValidityError> {2323 let (tip, who, imbalance, fee) = pre;2324 if let Some(payed) = imbalance {2325 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2326 len as u32, info, post_info, tip,2327 );2328 let refund = fee.saturating_sub(actual_fee);2329 let actual_payment =2330 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2331 &who, refund,2332 ) {2333 Ok(refund_imbalance) => {2334 // The refund cannot be larger than the up front payed max weight.2335 // `PostDispatchInfo::calc_unspent` guards against such a case.2336 match payed.offset(refund_imbalance) {2337 Ok(actual_payment) => actual_payment,2338 Err(_) => return Err(InvalidTransaction::Payment.into()),2339 }2340 }2341 // We do not recreate the account using the refund. The up front payment2342 // is gone in that case.2343 Err(_) => payed,2344 };2345 let imbalances = actual_payment.split(tip);2346 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2347 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2348 );2349 }2350 Ok(())2351 }2352}23532354// #endregion1#![cfg_attr(not(feature = "std"), no_std)]23#[cfg(feature = "std")]4pub use std::*;56#[cfg(feature = "std")]7pub use serde::*;89use codec::{Decode, Encode};10pub use frame_support::{11 construct_runtime, decl_event, decl_module, decl_storage, decl_error,12 dispatch::DispatchResult,13 ensure, fail, parameter_types,14 traits::{15 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,16 Randomness, WithdrawReason,17 },18 weights::{19 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},20 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,21 WeightToFeePolynomial,22 },23 IsSubType, StorageValue,24};2526use frame_system::{self as system, ensure_signed, ensure_root};27use sp_runtime::sp_std::prelude::Vec;28use sp_runtime::{29 traits::{30 DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SignedExtension, Zero,31 },32 transaction_validity::{33 InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,34 },35 FixedPointOperand, FixedU128,36};37use pallet_contracts::ContractAddressFor;38use sp_runtime::traits::StaticLookup;3940#[cfg(test)]41mod mock;4243#[cfg(test)]44mod tests;4546mod default_weights;4748// Structs49// #region5051#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]52#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]53pub enum CollectionMode {54 Invalid,55 NFT,56 // decimal points57 Fungible(u32),58 // decimal points59 ReFungible(u32),60}6162impl Into<u8> for CollectionMode {63 fn into(self) -> u8 {64 match self {65 CollectionMode::Invalid => 0,66 CollectionMode::NFT => 1,67 CollectionMode::Fungible(_) => 2,68 CollectionMode::ReFungible(_) => 3,69 }70 }71}7273#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]74#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]75pub enum AccessMode {76 Normal,77 WhiteList,78}79impl Default for AccessMode {80 fn default() -> Self {81 Self::Normal82 }83}8485impl Default for CollectionMode {86 fn default() -> Self {87 Self::Invalid88 }89}9091#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]92#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]93pub struct Ownership<AccountId> {94 pub owner: AccountId,95 pub fraction: u128,96}9798#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]99#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]100pub struct CollectionType<AccountId> {101 pub owner: AccountId,102 pub mode: CollectionMode,103 pub access: AccessMode,104 pub decimal_points: u32,105 pub name: Vec<u16>, // 64 include null escape char106 pub description: Vec<u16>, // 256 include null escape char107 pub token_prefix: Vec<u8>, // 16 include null escape char108 pub mint_mode: bool,109 pub offchain_schema: Vec<u8>,110 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender111 pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship112 pub variable_on_chain_schema: Vec<u8>, //113 pub const_on_chain_schema: Vec<u8>, //114}115116#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]117#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]118pub struct NftItemType<AccountId> {119 pub collection: u64,120 pub owner: AccountId,121 pub const_data: Vec<u8>,122 pub variable_data: Vec<u8>,123}124125#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]126#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]127pub struct FungibleItemType<AccountId> {128 pub collection: u64,129 pub owner: AccountId,130 pub value: u128,131}132133#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]134#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]135pub struct ReFungibleItemType<AccountId> {136 pub collection: u64,137 pub owner: Vec<Ownership<AccountId>>,138 pub const_data: Vec<u8>,139 pub variable_data: Vec<u8>,140}141142#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]143#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]144pub struct ApprovePermissions<AccountId> {145 pub approved: AccountId,146 pub amount: u64,147}148149#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]150#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]151pub struct VestingItem<AccountId, Moment> {152 pub sender: AccountId,153 pub recipient: AccountId,154 pub collection_id: u64,155 pub item_id: u64,156 pub amount: u64,157 pub vesting_date: Moment,158}159160#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]161#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]162pub struct BasketItem<AccountId, BlockNumber> {163 pub address: AccountId,164 pub start_block: BlockNumber,165}166167#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]168#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]169pub struct ChainLimits {170 pub collection_numbers_limit: u64,171 pub account_token_ownership_limit: u64,172 pub collections_admins_limit: u64,173 pub custom_data_limit: u32,174175 // Timeouts for item types in passed blocks176 pub nft_sponsor_transfer_timeout: u32,177 pub fungible_sponsor_transfer_timeout: u32,178 pub refungible_sponsor_transfer_timeout: u32,179}180181pub trait WeightInfo {182 fn create_collection() -> Weight;183 fn destroy_collection() -> Weight;184 fn add_to_white_list() -> Weight;185 fn remove_from_white_list() -> Weight;186 fn set_public_access_mode() -> Weight;187 fn set_mint_permission() -> Weight;188 fn change_collection_owner() -> Weight;189 fn add_collection_admin() -> Weight;190 fn remove_collection_admin() -> Weight;191 fn set_collection_sponsor() -> Weight;192 fn confirm_sponsorship() -> Weight;193 fn remove_collection_sponsor() -> Weight;194 fn create_item(s: usize) -> Weight;195 fn burn_item() -> Weight;196 fn transfer() -> Weight;197 fn approve() -> Weight;198 fn transfer_from() -> Weight;199 fn set_offchain_schema() -> Weight;200 fn set_const_on_chain_schema() -> Weight;201 fn set_variable_on_chain_schema() -> Weight;202 fn set_variable_meta_data() -> Weight;203 // fn enable_contract_sponsoring() -> Weight;204}205206#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]207#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]208pub struct CreateNftData {209 pub const_data: Vec<u8>,210 pub variable_data: Vec<u8>,211}212213#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]214#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]215pub struct CreateFungibleData {216}217218#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]219#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]220pub struct CreateReFungibleData {221 pub const_data: Vec<u8>,222 pub variable_data: Vec<u8>,223}224225#[derive(Encode, Decode, Debug, Clone, PartialEq)]226#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]227pub enum CreateItemData {228 NFT(CreateNftData),229 Fungible(CreateFungibleData),230 ReFungible(CreateReFungibleData)231}232233impl CreateItemData {234 pub fn len(&self) -> usize {235 let len = match self {236 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),237 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),238 _ => 0239 };240 241 return len;242 }243}244245impl From<CreateNftData> for CreateItemData {246 fn from(item: CreateNftData) -> Self {247 CreateItemData::NFT(item)248 }249}250251impl From<CreateReFungibleData> for CreateItemData {252 fn from(item: CreateReFungibleData) -> Self {253 CreateItemData::ReFungible(item)254 }255}256257impl From<CreateFungibleData> for CreateItemData {258 fn from(item: CreateFungibleData) -> Self {259 CreateItemData::Fungible(item)260 }261}262263264decl_error! {265 /// Error for non-fungible-token module.266 pub enum Error for Module<T: Trait> {267 /// Total collections bound exceeded.268 TotalCollectionsLimitExceeded,269 /// Decimal_points parameter must be lower than 4.270 CollectionDecimalPointLimitExceeded, 271 /// Collection name can not be longer than 63 char.272 CollectionNameLimitExceeded, 273 /// Collection description can not be longer than 255 char.274 CollectionDescriptionLimitExceeded, 275 /// Token prefix can not be longer than 15 char.276 CollectionTokenPrefixLimitExceeded,277 /// This collection does not exist.278 CollectionNotFound,279 /// Item not exists.280 TokenNotFound,281 /// Arithmetic calculation overflow.282 NumOverflow, 283 /// Account already has admin role.284 AlreadyAdmin, 285 /// You do not own this collection.286 NoPermission,287 /// This address is not set as sponsor, use setCollectionSponsor first.288 ConfirmUnsetSponsorFail,289 /// Collection is not in mint mode.290 PublicMintingNotAllowed,291 /// Sender parameter and item owner must be equal.292 MustBeTokenOwner,293 /// Item balance not enough.294 TokenValueTooLow,295 /// Size of item is too large.296 NftSizeLimitExceeded,297 /// No approve found298 ApproveNotFound,299 /// Requested value more than approved.300 TokenValueNotEnough,301 /// Only approved addresses can call this method.302 ApproveRequired,303 /// Address is not in white list.304 AddresNotInWhiteList,305 /// Number of collection admins bound exceeded.306 CollectionAdminsLimitExceeded,307 /// Owned tokens by a single address bound exceeded.308 AddressOwnershipLimitExceeded,309 /// Length of items properties must be greater than 0.310 EmptyArgument,311 /// const_data exceeded data limit.312 TokenConstDataLimitExceeded,313 /// variable_data exceeded data limit.314 TokenVariableDataLimitExceeded,315 /// Not NFT item data used to mint in NFT collection.316 NotNftDataUsedToMintNftCollectionToken,317 /// Not Fungible item data used to mint in Fungible collection.318 NotFungibleDataUsedToMintFungibleCollectionToken,319 /// Not Re Fungible item data used to mint in Re Fungible collection.320 NotReFungibleDataUsedToMintReFungibleCollectionToken,321 /// Unexpected collection type.322 UnexpectedCollectionType,323 /// Can't store metadata in fungible tokens.324 CantSotreMetadataInFungibleTokens325 }326}327328pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {329 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;330331 /// Weight information for extrinsics in this pallet.332 type WeightInfo: WeightInfo;333}334335#[cfg(feature = "runtime-benchmarks")]336mod benchmarking;337338// #endregion339340decl_storage! {341 trait Store for Module<T: Trait> as Nft {342343 // Private members344 NextCollectionID: u64;345 CreatedCollectionCount: u64;346 ChainVersion: u64;347 ItemListIndex: map hasher(blake2_128_concat) u64 => u64;348349 // Chain limits struct350 pub ChainLimit get(fn chain_limit) config(): ChainLimits;351352 // Bound counters353 CollectionCount: u64;354 pub AccountItemCount get(fn account_item_count): map hasher(identity) T::AccountId => u64;355356 // Basic collections357 pub Collection get(fn collection) config(): map hasher(identity) u64 => CollectionType<T::AccountId>;358 pub AdminList get(fn admin_list_collection): map hasher(identity) u64 => Vec<T::AccountId>;359 pub WhiteList get(fn white_list): map hasher(identity) u64 => Vec<T::AccountId>;360361 /// Balance owner per collection map362 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => u64;363364 /// second parameter: item id + owner account id365 pub ApprovedList get(fn approved): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) (u64, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;366367 /// Item collections368 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => NftItemType<T::AccountId>;369 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => FungibleItemType<T::AccountId>;370 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => ReFungibleItemType<T::AccountId>;371372 /// Index list373 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => Vec<u64>;374375 /// Tokens transfer baskets376 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => T::BlockNumber;377 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => Vec<BasketItem<T::AccountId, T::BlockNumber>>;378 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => T::BlockNumber;379380 // Contract Sponsorship and Ownership381 pub ContractOwner get(fn contract_owner): map hasher(identity) T::AccountId => T::AccountId;382 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(identity) T::AccountId => bool;383 }384 add_extra_genesis {385 build(|config: &GenesisConfig<T>| {386 // Modification of storage387 for (_num, _c) in &config.collection {388 <Module<T>>::init_collection(_c);389 }390391 for (_num, _q, _i) in &config.nft_item_id {392 <Module<T>>::init_nft_token(_i);393 }394395 for (_num, _q, _i) in &config.fungible_item_id {396 <Module<T>>::init_fungible_token(_i);397 }398399 for (_num, _q, _i) in &config.refungible_item_id {400 <Module<T>>::init_refungible_token(_i);401 }402 })403 }404}405406decl_event!(407 pub enum Event<T>408 where409 AccountId = <T as system::Trait>::AccountId,410 {411 /// New collection was created412 /// 413 /// # Arguments414 /// 415 /// * collection_id: Globally unique identifier of newly created collection.416 /// 417 /// * mode: [CollectionMode] converted into u8.418 /// 419 /// * account_id: Collection owner.420 Created(u64, u8, AccountId),421422 /// New item was created.423 /// 424 /// # Arguments425 /// 426 /// * collection_id: Id of the collection where item was created.427 /// 428 /// * item_id: Id of an item. Unique within the collection.429 ItemCreated(u64, u64),430431 /// Collection item was burned.432 /// 433 /// # Arguments434 /// 435 /// collection_id.436 /// 437 /// item_id: Identifier of burned NFT.438 ItemDestroyed(u64, u64),439 }440);441442decl_module! {443 pub struct Module<T: Trait> for enum Call where origin: T::Origin {444445 fn deposit_event() = default;446 type Error = Error<T>;447448 fn on_initialize(now: T::BlockNumber) -> Weight {449450 if ChainVersion::get() < 2451 {452 let value = NextCollectionID::get();453 CreatedCollectionCount::put(value);454 ChainVersion::put(2);455 }456457 0458 }459460 /// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.461 /// 462 /// # Permissions463 /// 464 /// * Anyone.465 /// 466 /// # Arguments467 /// 468 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.469 /// 470 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.471 /// 472 /// * token_prefix: UTF-8 string with token prefix.473 /// 474 /// * mode: [CollectionMode] collection type and type dependent data.475 // returns collection ID476 #[weight = T::WeightInfo::create_collection()]477 pub fn create_collection(origin,478 collection_name: Vec<u16>,479 collection_description: Vec<u16>,480 token_prefix: Vec<u8>,481 mode: CollectionMode) -> DispatchResult {482483 // Anyone can create a collection484 let who = ensure_signed(origin)?;485486 let decimal_points = match mode {487 CollectionMode::Fungible(points) => points,488 CollectionMode::ReFungible(points) => points,489 _ => 0490 };491492 // bound Total number of collections493 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);494495 // check params496 ensure!(decimal_points <= 4, Error::<T>::CollectionDecimalPointLimitExceeded);497498 let mut name = collection_name.to_vec();499 name.push(0);500 ensure!(name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);501502 let mut description = collection_description.to_vec();503 description.push(0);504 ensure!(name.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);505506 let mut prefix = token_prefix.to_vec();507 prefix.push(0);508 ensure!(prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);509510 // Generate next collection ID511 let next_id = CreatedCollectionCount::get()512 .checked_add(1)513 .ok_or(Error::<T>::NumOverflow)?;514515 // bound counter516 let total = CollectionCount::get()517 .checked_add(1)518 .ok_or(Error::<T>::NumOverflow)?;519520 CreatedCollectionCount::put(next_id);521 CollectionCount::put(total);522523 // Create new collection524 let new_collection = CollectionType {525 owner: who.clone(),526 name: name,527 mode: mode.clone(),528 mint_mode: false,529 access: AccessMode::Normal,530 description: description,531 decimal_points: decimal_points,532 token_prefix: prefix,533 offchain_schema: Vec::new(),534 sponsor: T::AccountId::default(),535 unconfirmed_sponsor: T::AccountId::default(),536 variable_on_chain_schema: Vec::new(),537 const_on_chain_schema: Vec::new(),538 };539540 // Add new collection to map541 <Collection<T>>::insert(next_id, new_collection);542543 // call event544 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));545546 Ok(())547 }548549 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.550 /// 551 /// # Permissions552 /// 553 /// * Collection Owner.554 /// 555 /// # Arguments556 /// 557 /// * collection_id: collection to destroy.558 #[weight = T::WeightInfo::destroy_collection()]559 pub fn destroy_collection(origin, collection_id: u64) -> DispatchResult {560561 let sender = ensure_signed(origin)?;562 Self::check_owner_permissions(collection_id, sender)?;563564 <AddressTokens<T>>::remove_prefix(collection_id);565 <ApprovedList<T>>::remove_prefix(collection_id);566 <Balance<T>>::remove_prefix(collection_id);567 <ItemListIndex>::remove(collection_id);568 <AdminList<T>>::remove(collection_id);569 <Collection<T>>::remove(collection_id);570 <WhiteList<T>>::remove(collection_id);571572 <NftItemList<T>>::remove_prefix(collection_id);573 <FungibleItemList<T>>::remove_prefix(collection_id);574 <ReFungibleItemList<T>>::remove_prefix(collection_id);575576 <NftTransferBasket<T>>::remove_prefix(collection_id);577 <FungibleTransferBasket<T>>::remove_prefix(collection_id);578 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);579580 if CollectionCount::get() > 0581 {582 // bound couter583 let total = CollectionCount::get()584 .checked_sub(1)585 .ok_or(Error::<T>::NumOverflow)?;586587 CollectionCount::put(total);588 }589590 Ok(())591 }592593 /// Add an address to white list.594 /// 595 /// # Permissions596 /// 597 /// * Collection Owner598 /// * Collection Admin599 /// 600 /// # Arguments601 /// 602 /// * collection_id.603 /// 604 /// * address.605 #[weight = T::WeightInfo::add_to_white_list()]606 pub fn add_to_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{607608 let sender = ensure_signed(origin)?;609 Self::check_owner_or_admin_permissions(collection_id, sender)?;610611 let mut white_list_collection: Vec<T::AccountId>;612 if <WhiteList<T>>::contains_key(collection_id) {613 white_list_collection = <WhiteList<T>>::get(collection_id);614 if !white_list_collection.contains(&address.clone())615 {616 white_list_collection.push(address.clone());617 }618 }619 else {620 white_list_collection = Vec::new();621 white_list_collection.push(address.clone());622 }623624 <WhiteList<T>>::insert(collection_id, white_list_collection);625 Ok(())626 }627628 /// Remove an address from white list.629 /// 630 /// # Permissions631 /// 632 /// * Collection Owner633 /// * Collection Admin634 /// 635 /// # Arguments636 /// 637 /// * collection_id.638 /// 639 /// * address.640 #[weight = T::WeightInfo::remove_from_white_list()]641 pub fn remove_from_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{642643 let sender = ensure_signed(origin)?;644 Self::check_owner_or_admin_permissions(collection_id, sender)?;645646 if <WhiteList<T>>::contains_key(collection_id) {647 let mut white_list_collection = <WhiteList<T>>::get(collection_id);648 if white_list_collection.contains(&address.clone())649 {650 white_list_collection.retain(|i| *i != address.clone());651 <WhiteList<T>>::insert(collection_id, white_list_collection);652 }653 }654655 Ok(())656 }657658 /// Toggle between normal and white list access for the methods with access for `Anyone`.659 /// 660 /// # Permissions661 /// 662 /// * Collection Owner.663 /// 664 /// # Arguments665 /// 666 /// * collection_id.667 /// 668 /// * mode: [AccessMode]669 #[weight = T::WeightInfo::set_public_access_mode()]670 pub fn set_public_access_mode(origin, collection_id: u64, mode: AccessMode) -> DispatchResult671 {672 let sender = ensure_signed(origin)?;673674 Self::check_owner_permissions(collection_id, sender)?;675 let mut target_collection = <Collection<T>>::get(collection_id);676 target_collection.access = mode;677 <Collection<T>>::insert(collection_id, target_collection);678679 Ok(())680 }681682 /// Allows Anyone to create tokens if:683 /// * White List is enabled, and684 /// * Address is added to white list, and685 /// * This method was called with True parameter686 /// 687 /// # Permissions688 /// * Collection Owner689 ///690 /// # Arguments691 /// 692 /// * collection_id.693 /// 694 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.695 #[weight = T::WeightInfo::set_mint_permission()]696 pub fn set_mint_permission(origin, collection_id: u64, mint_permission: bool) -> DispatchResult697 {698 let sender = ensure_signed(origin)?;699700 Self::check_owner_permissions(collection_id, sender)?;701 let mut target_collection = <Collection<T>>::get(collection_id);702 target_collection.mint_mode = mint_permission;703 <Collection<T>>::insert(collection_id, target_collection);704705 Ok(())706 }707708 /// Change the owner of the collection.709 /// 710 /// # Permissions711 /// 712 /// * Collection Owner.713 /// 714 /// # Arguments715 /// 716 /// * collection_id.717 /// 718 /// * new_owner.719 #[weight = T::WeightInfo::change_collection_owner()]720 pub fn change_collection_owner(origin, collection_id: u64, new_owner: T::AccountId) -> DispatchResult {721722 let sender = ensure_signed(origin)?;723 Self::check_owner_permissions(collection_id, sender)?;724 let mut target_collection = <Collection<T>>::get(collection_id);725 target_collection.owner = new_owner;726 <Collection<T>>::insert(collection_id, target_collection);727728 Ok(())729 }730731 /// Adds an admin of the Collection.732 /// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership. 733 /// 734 /// # Permissions735 /// 736 /// * Collection Owner.737 /// * Collection Admin.738 /// 739 /// # Arguments740 /// 741 /// * collection_id: ID of the Collection to add admin for.742 /// 743 /// * new_admin_id: Address of new admin to add.744 #[weight = T::WeightInfo::add_collection_admin()]745 pub fn add_collection_admin(origin, collection_id: u64, new_admin_id: T::AccountId) -> DispatchResult {746747 let sender = ensure_signed(origin)?;748 Self::check_owner_or_admin_permissions(collection_id, sender)?;749 let mut admin_arr: Vec<T::AccountId> = Vec::new();750751 if <AdminList<T>>::contains_key(collection_id)752 {753 admin_arr = <AdminList<T>>::get(collection_id);754 ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);755 }756757 // Number of collection admins758 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);759760 admin_arr.push(new_admin_id);761 <AdminList<T>>::insert(collection_id, admin_arr);762763 Ok(())764 }765766 /// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.767 ///768 /// # Permissions769 /// 770 /// * Collection Owner.771 /// * Collection Admin.772 /// 773 /// # Arguments774 /// 775 /// * collection_id: ID of the Collection to remove admin for.776 /// 777 /// * account_id: Address of admin to remove.778 #[weight = T::WeightInfo::remove_collection_admin()]779 pub fn remove_collection_admin(origin, collection_id: u64, account_id: T::AccountId) -> DispatchResult {780781 let sender = ensure_signed(origin)?;782 Self::check_owner_or_admin_permissions(collection_id, sender)?;783784 if <AdminList<T>>::contains_key(collection_id)785 {786 let mut admin_arr = <AdminList<T>>::get(collection_id);787 admin_arr.retain(|i| *i != account_id);788 <AdminList<T>>::insert(collection_id, admin_arr);789 }790791 Ok(())792 }793794 /// # Permissions795 /// 796 /// * Collection Owner797 /// 798 /// # Arguments799 /// 800 /// * collection_id.801 /// 802 /// * new_sponsor.803 #[weight = T::WeightInfo::set_collection_sponsor()]804 pub fn set_collection_sponsor(origin, collection_id: u64, new_sponsor: T::AccountId) -> DispatchResult {805806 let sender = ensure_signed(origin)?;807 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);808809 let mut target_collection = <Collection<T>>::get(collection_id);810 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);811812 target_collection.unconfirmed_sponsor = new_sponsor;813 <Collection<T>>::insert(collection_id, target_collection);814815 Ok(())816 }817818 /// # Permissions819 /// 820 /// * Sponsor.821 /// 822 /// # Arguments823 /// 824 /// * collection_id.825 #[weight = T::WeightInfo::confirm_sponsorship()]826 pub fn confirm_sponsorship(origin, collection_id: u64) -> DispatchResult {827828 let sender = ensure_signed(origin)?;829 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);830831 let mut target_collection = <Collection<T>>::get(collection_id);832 ensure!(sender == target_collection.unconfirmed_sponsor, Error::<T>::ConfirmUnsetSponsorFail);833834 target_collection.sponsor = target_collection.unconfirmed_sponsor;835 target_collection.unconfirmed_sponsor = T::AccountId::default();836 <Collection<T>>::insert(collection_id, target_collection);837838 Ok(())839 }840841 /// Switch back to pay-per-own-transaction model.842 ///843 /// # Permissions844 ///845 /// * Collection owner.846 /// 847 /// # Arguments848 /// 849 /// * collection_id.850 #[weight = T::WeightInfo::remove_collection_sponsor()]851 pub fn remove_collection_sponsor(origin, collection_id: u64) -> DispatchResult {852853 let sender = ensure_signed(origin)?;854 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);855856 let mut target_collection = <Collection<T>>::get(collection_id);857 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);858859 target_collection.sponsor = T::AccountId::default();860 <Collection<T>>::insert(collection_id, target_collection);861862 Ok(())863 }864865 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.866 /// 867 /// # Permissions868 /// 869 /// * Collection Owner.870 /// * Collection Admin.871 /// * Anyone if872 /// * White List is enabled, and873 /// * Address is added to white list, and874 /// * MintPermission is enabled (see SetMintPermission method)875 /// 876 /// # Arguments877 /// 878 /// * collection_id: ID of the collection.879 /// 880 /// * owner: Address, initial owner of the NFT.881 ///882 /// * data: Token data to store on chain.883 // #[weight =884 // (130_000_000 as Weight)885 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))886 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))887 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]888889 #[weight = T::WeightInfo::create_item(data.len())]890 pub fn create_item(origin, collection_id: u64, owner: T::AccountId, data: CreateItemData) -> DispatchResult {891892 let sender = ensure_signed(origin)?;893894 Self::collection_exists(collection_id)?;895896 let target_collection = <Collection<T>>::get(collection_id);897898 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;899 Self::validate_create_item_args(&target_collection, &data)?;900 Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;901902 Ok(())903 }904905 /// This method creates multiple instances of NFT Collection created with CreateCollection method.906 /// 907 /// # Permissions908 /// 909 /// * Collection Owner.910 /// * Collection Admin.911 /// * Anyone if912 /// * White List is enabled, and913 /// * Address is added to white list, and914 /// * MintPermission is enabled (see SetMintPermission method)915 /// 916 /// # Arguments917 /// 918 /// * collection_id: ID of the collection.919 /// 920 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].921 /// 922 /// * owner: Address, initial owner of the NFT.923 #[weight = T::WeightInfo::create_item(items_data.into_iter()924 .map(|data| { data.len() })925 .sum())]926 pub fn create_multiple_items(origin, collection_id: u64, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {927928 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);929 let sender = ensure_signed(origin)?;930931 Self::collection_exists(collection_id)?;932 let target_collection = <Collection<T>>::get(collection_id);933934 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;935936 for data in &items_data {937 Self::validate_create_item_args(&target_collection, data)?;938 }939 for data in &items_data {940 Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;941 }942943 Ok(())944 }945946 /// Destroys a concrete instance of NFT.947 /// 948 /// # Permissions949 /// 950 /// * Collection Owner.951 /// * Collection Admin.952 /// * Current NFT Owner.953 /// 954 /// # Arguments955 /// 956 /// * collection_id: ID of the collection.957 /// 958 /// * item_id: ID of NFT to burn.959 #[weight = T::WeightInfo::burn_item()]960 pub fn burn_item(origin, collection_id: u64, item_id: u64) -> DispatchResult {961962 let sender = ensure_signed(origin)?;963 Self::collection_exists(collection_id)?;964965 // Transfer permissions check966 let target_collection = <Collection<T>>::get(collection_id);967 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||968 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),969 Error::<T>::NoPermission);970971 if target_collection.access == AccessMode::WhiteList {972 Self::check_white_list(collection_id, &sender)?;973 }974975 match target_collection.mode976 {977 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,978 CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?,979 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,980 _ => ()981 };982983 // call event984 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));985986 Ok(())987 }988989 /// Change ownership of the token.990 /// 991 /// # Permissions992 /// 993 /// * Collection Owner994 /// * Collection Admin995 /// * Current NFT owner996 ///997 /// # Arguments998 /// 999 /// * recipient: Address of token recipient.1000 /// 1001 /// * collection_id.1002 /// 1003 /// * item_id: ID of the item1004 /// * Non-Fungible Mode: Required.1005 /// * Fungible Mode: Ignored.1006 /// * Re-Fungible Mode: Required.1007 /// 1008 /// * value: Amount to transfer.1009 /// * Non-Fungible Mode: Ignored1010 /// * Fungible Mode: Must specify transferred amount1011 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1012 #[weight = T::WeightInfo::transfer()]1013 pub fn transfer(origin, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64) -> DispatchResult {10141015 let sender = ensure_signed(origin)?;10161017 // Transfer permissions check1018 let target_collection = <Collection<T>>::get(collection_id);1019 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1020 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1021 Error::<T>::NoPermission);10221023 if target_collection.access == AccessMode::WhiteList {1024 Self::check_white_list(collection_id, &sender)?;1025 Self::check_white_list(collection_id, &recipient)?;1026 }10271028 match target_collection.mode1029 {1030 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1031 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,1032 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1033 _ => ()1034 };10351036 Ok(())1037 }10381039 /// Set, change, or remove approved address to transfer the ownership of the NFT.1040 /// 1041 /// # Permissions1042 /// 1043 /// * Collection Owner1044 /// * Collection Admin1045 /// * Current NFT owner1046 /// 1047 /// # Arguments1048 /// 1049 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1050 /// 1051 /// * collection_id.1052 /// 1053 /// * item_id: ID of the item.1054 #[weight = T::WeightInfo::approve()]1055 pub fn approve(origin, approved: T::AccountId, collection_id: u64, item_id: u64) -> DispatchResult {10561057 let sender = ensure_signed(origin)?;10581059 // Transfer permissions check1060 let target_collection = <Collection<T>>::get(collection_id);1061 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1062 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1063 Error::<T>::NoPermission);10641065 if target_collection.access == AccessMode::WhiteList {1066 Self::check_white_list(collection_id, &sender)?;1067 Self::check_white_list(collection_id, &approved)?;1068 }10691070 // amount param stub1071 let amount = 100000000;10721073 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));1074 if list_exists {10751076 let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));1077 let item_contains = list.iter().any(|i| i.approved == approved);10781079 if !item_contains {1080 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1081 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1082 }1083 } else {10841085 let mut list = Vec::new();1086 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1087 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1088 }10891090 Ok(())1091 }1092 1093 /// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.1094 /// 1095 /// # Permissions1096 /// * Collection Owner1097 /// * Collection Admin1098 /// * Current NFT owner1099 /// * Address approved by current NFT owner1100 /// 1101 /// # Arguments1102 /// 1103 /// * from: Address that owns token.1104 /// 1105 /// * recipient: Address of token recipient.1106 /// 1107 /// * collection_id.1108 /// 1109 /// * item_id: ID of the item.1110 /// 1111 /// * value: Amount to transfer.1112 #[weight = T::WeightInfo::transfer_from()]1113 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64 ) -> DispatchResult {11141115 let sender = ensure_signed(origin)?;1116 let mut appoved_transfer = false;11171118 // Check approve1119 if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {1120 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));1121 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());1122 if opt_item.is_some()1123 {1124 appoved_transfer = true;1125 ensure!(opt_item.unwrap().amount >= value, Error::<T>::TokenValueNotEnough);1126 }1127 }11281129 // Transfer permissions check1130 let target_collection = <Collection<T>>::get(collection_id);1131 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1132 Error::<T>::NoPermission);11331134 if target_collection.access == AccessMode::WhiteList {1135 Self::check_white_list(collection_id, &sender)?;1136 Self::check_white_list(collection_id, &recipient)?;1137 }11381139 // remove approve1140 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))1141 .into_iter().filter(|i| i.approved != sender.clone()).collect();1142 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);114311441145 match target_collection.mode1146 {1147 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1148 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,1149 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1150 _ => ()1151 };11521153 Ok(())1154 }11551156 ///1157 #[weight = 0]1158 pub fn safe_transfer_from(origin, collection_id: u64, item_id: u64, new_owner: T::AccountId) -> DispatchResult {11591160 // let no_perm_mes = "You do not have permissions to modify this collection";1161 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1162 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1163 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);11641165 // // on_nft_received call11661167 // Self::transfer(origin, collection_id, item_id, new_owner)?;11681169 Ok(())1170 }11711172 /// Set off-chain data schema.1173 /// 1174 /// # Permissions1175 /// 1176 /// * Collection Owner1177 /// * Collection Admin1178 /// 1179 /// # Arguments1180 /// 1181 /// * collection_id.1182 /// 1183 /// * schema: String representing the offchain data schema.1184 #[weight = T::WeightInfo::set_variable_meta_data()]1185 pub fn set_variable_meta_data (1186 origin,1187 collection_id: u64,1188 item_id: u64,1189 data: Vec<u8>1190 ) -> DispatchResult {1191 let sender = ensure_signed(origin)?;1192 1193 Self::collection_exists(collection_id)?;11941195 // Modify permissions check1196 let target_collection = <Collection<T>>::get(collection_id);1197 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1198 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1199 Error::<T>::NoPermission);12001201 Self::item_exists(collection_id, item_id, &target_collection.mode)?;12021203 match target_collection.mode1204 {1205 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1206 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1207 CollectionMode::Fungible(_) => fail!(Error::<T>::CantSotreMetadataInFungibleTokens),1208 _ => fail!(Error::<T>::UnexpectedCollectionType)1209 };12101211 Ok(())1212 }1213 12141215 /// Set off-chain data schema.1216 /// 1217 /// # Permissions1218 /// 1219 /// * Collection Owner1220 /// * Collection Admin1221 /// 1222 /// # Arguments1223 /// 1224 /// * collection_id.1225 /// 1226 /// * schema: String representing the offchain data schema.1227 #[weight = T::WeightInfo::set_offchain_schema()]1228 pub fn set_offchain_schema(1229 origin,1230 collection_id: u64,1231 schema: Vec<u8>1232 ) -> DispatchResult {1233 let sender = ensure_signed(origin)?;1234 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12351236 let mut target_collection = <Collection<T>>::get(collection_id);1237 target_collection.offchain_schema = schema;1238 <Collection<T>>::insert(collection_id, target_collection);12391240 Ok(())1241 }12421243 /// Set const on-chain data schema.1244 /// 1245 /// # Permissions1246 /// 1247 /// * Collection Owner1248 /// * Collection Admin1249 /// 1250 /// # Arguments1251 /// 1252 /// * collection_id.1253 /// 1254 /// * schema: String representing the const on-chain data schema.1255 #[weight = T::WeightInfo::set_const_on_chain_schema()]1256 pub fn set_const_on_chain_schema (1257 origin,1258 collection_id: u64,1259 schema: Vec<u8>1260 ) -> DispatchResult {1261 let sender = ensure_signed(origin)?;1262 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12631264 let mut target_collection = <Collection<T>>::get(collection_id);1265 target_collection.const_on_chain_schema = schema;1266 <Collection<T>>::insert(collection_id, target_collection);12671268 Ok(())1269 }12701271 /// Set variable on-chain data schema.1272 /// 1273 /// # Permissions1274 /// 1275 /// * Collection Owner1276 /// * Collection Admin1277 /// 1278 /// # Arguments1279 /// 1280 /// * collection_id.1281 /// 1282 /// * schema: String representing the variable on-chain data schema.1283 #[weight = T::WeightInfo::set_const_on_chain_schema()]1284 pub fn set_variable_on_chain_schema (1285 origin,1286 collection_id: u64,1287 schema: Vec<u8>1288 ) -> DispatchResult {1289 let sender = ensure_signed(origin)?;1290 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12911292 let mut target_collection = <Collection<T>>::get(collection_id);1293 target_collection.variable_on_chain_schema = schema;1294 <Collection<T>>::insert(collection_id, target_collection);12951296 Ok(())1297 }12981299 // Sudo permissions function1300 #[weight = 0]1301 pub fn set_chain_limits(1302 origin,1303 limits: ChainLimits1304 ) -> DispatchResult {1305 ensure_root(origin)?;1306 <ChainLimit>::put(limits);1307 Ok(())1308 }13091310 /// Enable smart contract self-sponsoring.1311 /// 1312 /// # Permissions1313 /// 1314 /// * Contract Owner1315 /// 1316 /// # Arguments1317 /// 1318 /// * contract address1319 /// * enable flag1320 /// 1321 #[weight = 0]1322 pub fn enable_contract_sponsoring(1323 origin,1324 contract_address: T::AccountId,1325 enable: bool1326 ) -> DispatchResult {1327 let sender = ensure_signed(origin)?;1328 let mut is_owner = false;1329 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1330 let owner = <ContractOwner<T>>::get(&contract_address);1331 is_owner = sender == owner;1332 }1333 ensure!(is_owner, Error::<T>::NoPermission);13341335 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1336 Ok(())1337 }1338 }1339}13401341impl<T: Trait> Module<T> {13421343 fn can_create_items_in_collection(collection_id: u64, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {13441345 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1346 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1347 Self::check_white_list(collection_id, owner)?;1348 Self::check_white_list(collection_id, sender)?;1349 }13501351 Ok(())1352 }13531354 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1355 match target_collection.mode1356 {1357 CollectionMode::NFT => {1358 if let CreateItemData::NFT(data) = data {1359 // check sizes1360 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1361 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1362 } else {1363 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1364 }1365 },1366 CollectionMode::Fungible(_) => {1367 if let CreateItemData::Fungible(_) = data {1368 } else {1369 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1370 }1371 },1372 CollectionMode::ReFungible(_) => {1373 if let CreateItemData::ReFungible(data) = data {13741375 // check sizes1376 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1377 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1378 } else {1379 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1380 }1381 },1382 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1383 };13841385 Ok(())1386 }13871388 fn create_item_no_validation(collection_id: u64, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1389 match data1390 {1391 CreateItemData::NFT(data) => {1392 let item = NftItemType {1393 collection: collection_id,1394 owner,1395 const_data: data.const_data,1396 variable_data: data.variable_data1397 };13981399 Self::add_nft_item(item)?;1400 },1401 CreateItemData::Fungible(_) => {1402 let item = FungibleItemType {1403 collection: collection_id,1404 owner,1405 value: (10 as u128).pow(collection.decimal_points)1406 };14071408 Self::add_fungible_item(item)?;1409 },1410 CreateItemData::ReFungible(data) => {1411 let mut owner_list = Vec::new();1412 let value = (10 as u128).pow(collection.decimal_points);1413 owner_list.push(Ownership {owner: owner.clone(), fraction: value});14141415 let item = ReFungibleItemType {1416 collection: collection_id,1417 owner: owner_list,1418 const_data: data.const_data,1419 variable_data: data.variable_data1420 };14211422 Self::add_refungible_item(item)?;1423 }1424 };142514261427 // call event1428 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));14291430 Ok(())1431 }14321433 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {1434 let current_index = <ItemListIndex>::get(item.collection)1435 .checked_add(1)1436 .ok_or(Error::<T>::NumOverflow)?;1437 let itemcopy = item.clone();1438 let owner = item.owner.clone();1439 let value = item.value as u64;14401441 Self::add_token_index(item.collection, current_index, owner.clone())?;14421443 <ItemListIndex>::insert(item.collection, current_index);1444 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);14451446 // Add current block1447 let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1448 <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);1449 1450 // Update balance1451 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1452 .checked_add(value)1453 .ok_or(Error::<T>::NumOverflow)?;1454 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);14551456 Ok(())1457 }14581459 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1460 let current_index = <ItemListIndex>::get(item.collection)1461 .checked_add(1)1462 .ok_or(Error::<T>::NumOverflow)?;1463 let itemcopy = item.clone();14641465 let value = item.owner.first().unwrap().fraction as u64;1466 let owner = item.owner.first().unwrap().owner.clone();14671468 Self::add_token_index(item.collection, current_index, owner.clone())?;14691470 <ItemListIndex>::insert(item.collection, current_index);1471 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);14721473 // Add current block1474 let block_number: T::BlockNumber = 0.into();1475 <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);14761477 // Update balance1478 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1479 .checked_add(value)1480 .ok_or(Error::<T>::NumOverflow)?;1481 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);14821483 Ok(())1484 }14851486 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1487 let current_index = <ItemListIndex>::get(item.collection)1488 .checked_add(1)1489 .ok_or(Error::<T>::NumOverflow)?;14901491 let item_owner = item.owner.clone();1492 let collection_id = item.collection.clone();1493 Self::add_token_index(collection_id, current_index, item.owner.clone())?;14941495 <ItemListIndex>::insert(collection_id, current_index);1496 <NftItemList<T>>::insert(collection_id, current_index, item);14971498 // Add current block1499 let block_number: T::BlockNumber = 0.into();1500 <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);15011502 // Update balance1503 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1504 .checked_add(1)1505 .ok_or(Error::<T>::NumOverflow)?;1506 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);15071508 Ok(())1509 }15101511 fn burn_refungible_item(1512 collection_id: u64,1513 item_id: u64,1514 owner: T::AccountId,1515 ) -> DispatchResult {1516 ensure!(1517 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1518 Error::<T>::TokenNotFound1519 );1520 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1521 let item = collection1522 .owner1523 .iter()1524 .filter(|&i| i.owner == owner)1525 .next()1526 .unwrap();1527 Self::remove_token_index(collection_id, item_id, owner.clone())?;15281529 // remove approve list1530 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));15311532 // update balance1533 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1534 .checked_sub(item.fraction as u64)1535 .ok_or(Error::<T>::NumOverflow)?;1536 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);15371538 <ReFungibleItemList<T>>::remove(collection_id, item_id);15391540 Ok(())1541 }15421543 fn burn_nft_item(collection_id: u64, item_id: u64) -> DispatchResult {1544 ensure!(1545 <NftItemList<T>>::contains_key(collection_id, item_id),1546 Error::<T>::TokenNotFound1547 );1548 let item = <NftItemList<T>>::get(collection_id, item_id);1549 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;15501551 // remove approve list1552 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));15531554 // update balance1555 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1556 .checked_sub(1)1557 .ok_or(Error::<T>::NumOverflow)?;1558 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1559 <NftItemList<T>>::remove(collection_id, item_id);15601561 Ok(())1562 }15631564 fn burn_fungible_item(collection_id: u64, item_id: u64) -> DispatchResult {1565 ensure!(1566 <FungibleItemList<T>>::contains_key(collection_id, item_id),1567 Error::<T>::TokenNotFound1568 );1569 let item = <FungibleItemList<T>>::get(collection_id, item_id);1570 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;15711572 // remove approve list1573 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));15741575 // update balance1576 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1577 .checked_sub(item.value as u64)1578 .ok_or(Error::<T>::NumOverflow)?;1579 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);15801581 <FungibleItemList<T>>::remove(collection_id, item_id);15821583 Ok(())1584 }15851586 fn collection_exists(collection_id: u64) -> DispatchResult {1587 ensure!(1588 <Collection<T>>::contains_key(collection_id),1589 Error::<T>::CollectionNotFound1590 );1591 Ok(())1592 }15931594 fn check_owner_permissions(collection_id: u64, subject: T::AccountId) -> DispatchResult {1595 Self::collection_exists(collection_id)?;15961597 let target_collection = <Collection<T>>::get(collection_id);1598 ensure!(1599 subject == target_collection.owner,1600 Error::<T>::NoPermission1601 );16021603 Ok(())1604 }16051606 fn is_owner_or_admin_permissions(collection_id: u64, subject: T::AccountId) -> bool {1607 let target_collection = <Collection<T>>::get(collection_id);1608 let mut result: bool = subject == target_collection.owner;1609 let exists = <AdminList<T>>::contains_key(collection_id);16101611 if !result & exists {1612 if <AdminList<T>>::get(collection_id).contains(&subject) {1613 result = true1614 }1615 }16161617 result1618 }16191620 fn check_owner_or_admin_permissions(1621 collection_id: u64,1622 subject: T::AccountId,1623 ) -> DispatchResult {1624 Self::collection_exists(collection_id)?;1625 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());16261627 ensure!(1628 result,1629 Error::<T>::NoPermission1630 );1631 Ok(())1632 }16331634 fn is_item_owner(subject: T::AccountId, collection_id: u64, item_id: u64) -> bool {1635 let target_collection = <Collection<T>>::get(collection_id);16361637 match target_collection.mode {1638 CollectionMode::NFT => {1639 <NftItemList<T>>::get(collection_id, item_id).owner == subject1640 }1641 CollectionMode::Fungible(_) => {1642 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1643 }1644 CollectionMode::ReFungible(_) => {1645 <ReFungibleItemList<T>>::get(collection_id, item_id)1646 .owner1647 .iter()1648 .any(|i| i.owner == subject)1649 }1650 CollectionMode::Invalid => false,1651 }1652 }16531654 fn check_white_list(collection_id: u64, address: &T::AccountId) -> DispatchResult {1655 let mes = Error::<T>::AddresNotInWhiteList;1656 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1657 let wl = <WhiteList<T>>::get(collection_id);1658 ensure!(wl.contains(address), mes);16591660 Ok(())1661 }16621663 fn transfer_fungible(1664 collection_id: u64,1665 item_id: u64,1666 value: u64,1667 owner: T::AccountId,1668 new_owner: T::AccountId,1669 ) -> DispatchResult {1670 ensure!(1671 <FungibleItemList<T>>::contains_key(collection_id, item_id),1672 Error::<T>::TokenNotFound1673 );16741675 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1676 let amount = full_item.value;16771678 ensure!(amount >= value.into(), Error::<T>::TokenValueTooLow);16791680 // update balance1681 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1682 .checked_sub(value)1683 .ok_or(Error::<T>::NumOverflow)?;1684 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);16851686 let mut new_owner_account_id = 0;1687 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1688 if new_owner_items.len() > 0 {1689 new_owner_account_id = new_owner_items[0];1690 }16911692 let val64 = value.into();16931694 // transfer1695 if amount == val64 && new_owner_account_id == 0 {1696 // change owner1697 // new owner do not have account1698 let mut new_full_item = full_item.clone();1699 new_full_item.owner = new_owner.clone();1700 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);17011702 // update balance1703 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1704 .checked_add(value)1705 .ok_or(Error::<T>::NumOverflow)?;1706 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);17071708 // update index collection1709 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1710 } else {1711 let mut new_full_item = full_item.clone();1712 new_full_item.value -= val64;17131714 // separate amount1715 if new_owner_account_id > 0 {1716 // new owner has account1717 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1718 item.value += val64;17191720 // update balance1721 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1722 .checked_add(value)1723 .ok_or(Error::<T>::NumOverflow)?;1724 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);17251726 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1727 } else {1728 // new owner do not have account1729 let item = FungibleItemType {1730 collection: collection_id,1731 owner: new_owner.clone(),1732 value: val64,1733 };17341735 Self::add_fungible_item(item)?;1736 }17371738 if amount == val64 {1739 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;17401741 // remove approve list1742 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1743 <FungibleItemList<T>>::remove(collection_id, item_id);1744 }17451746 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1747 }17481749 Ok(())1750 }17511752 fn transfer_refungible(1753 collection_id: u64,1754 item_id: u64,1755 value: u64,1756 owner: T::AccountId,1757 new_owner: T::AccountId,1758 ) -> DispatchResult {1759 ensure!(1760 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1761 Error::<T>::TokenNotFound1762 );17631764 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1765 let item = full_item1766 .owner1767 .iter()1768 .filter(|i| i.owner == owner)1769 .next()1770 .ok_or(Error::<T>::NumOverflow)?;1771 let amount = item.fraction;17721773 ensure!(amount >= value.into(), Error::<T>::TokenValueTooLow);17741775 // update balance1776 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1777 .checked_sub(value)1778 .ok_or(Error::<T>::NumOverflow)?;1779 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);17801781 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1782 .checked_add(value)1783 .ok_or(Error::<T>::NumOverflow)?;1784 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);17851786 let old_owner = item.owner.clone();1787 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);1788 let val64 = value.into();17891790 // transfer1791 if amount == val64 && !new_owner_has_account {1792 // change owner1793 // new owner do not have account1794 let mut new_full_item = full_item.clone();1795 new_full_item1796 .owner1797 .iter_mut()1798 .find(|i| i.owner == owner)1799 .unwrap()1800 .owner = new_owner.clone();1801 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);18021803 // update index collection1804 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1805 } else {1806 let mut new_full_item = full_item.clone();1807 new_full_item1808 .owner1809 .iter_mut()1810 .find(|i| i.owner == owner)1811 .unwrap()1812 .fraction -= val64;18131814 // separate amount1815 if new_owner_has_account {1816 // new owner has account1817 new_full_item1818 .owner1819 .iter_mut()1820 .find(|i| i.owner == new_owner)1821 .unwrap()1822 .fraction += val64;1823 } else {1824 // new owner do not have account1825 new_full_item.owner.push(Ownership {1826 owner: new_owner.clone(),1827 fraction: val64,1828 });1829 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1830 }18311832 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1833 }18341835 Ok(())1836 }18371838 fn transfer_nft(1839 collection_id: u64,1840 item_id: u64,1841 sender: T::AccountId,1842 new_owner: T::AccountId,1843 ) -> DispatchResult {1844 ensure!(1845 <NftItemList<T>>::contains_key(collection_id, item_id),1846 Error::<T>::TokenNotFound1847 );18481849 let mut item = <NftItemList<T>>::get(collection_id, item_id);18501851 ensure!(1852 sender == item.owner,1853 Error::<T>::MustBeTokenOwner1854 );18551856 // update balance1857 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1858 .checked_sub(1)1859 .ok_or(Error::<T>::NumOverflow)?;1860 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);18611862 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1863 .checked_add(1)1864 .ok_or(Error::<T>::NumOverflow)?;1865 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);18661867 // change owner1868 let old_owner = item.owner.clone();1869 item.owner = new_owner.clone();1870 <NftItemList<T>>::insert(collection_id, item_id, item);18711872 // update index collection1873 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;18741875 // reset approved list1876 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));1877 Ok(())1878 }1879 1880 fn item_exists(1881 collection_id: u64,1882 item_id: u64,1883 mode: &CollectionMode1884 ) -> DispatchResult {1885 match mode {1886 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1887 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1888 CollectionMode::Fungible(_) => ensure!(<FungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1889 _ => ()1890 };1891 1892 Ok(())1893 }18941895 fn set_re_fungible_variable_data(1896 collection_id: u64,1897 item_id: u64,1898 data: Vec<u8>1899 ) -> DispatchResult {1900 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);19011902 item.variable_data = data;19031904 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);19051906 Ok(())1907 }19081909 fn set_nft_variable_data(1910 collection_id: u64,1911 item_id: u64,1912 data: Vec<u8>1913 ) -> DispatchResult {1914 let mut item = <NftItemList<T>>::get(collection_id, item_id);1915 1916 item.variable_data = data;19171918 <NftItemList<T>>::insert(collection_id, item_id, item);1919 1920 Ok(())1921 }19221923 fn init_collection(item: &CollectionType<T::AccountId>) {1924 // check params1925 assert!(1926 item.decimal_points <= 4,1927 "decimal_points parameter must be lower than 4"1928 );1929 assert!(1930 item.name.len() <= 64,1931 "Collection name can not be longer than 63 char"1932 );1933 assert!(1934 item.name.len() <= 256,1935 "Collection description can not be longer than 255 char"1936 );1937 assert!(1938 item.token_prefix.len() <= 16,1939 "Token prefix can not be longer than 15 char"1940 );19411942 // Generate next collection ID1943 let next_id = CreatedCollectionCount::get()1944 .checked_add(1)1945 .unwrap();19461947 CreatedCollectionCount::put(next_id);1948 }19491950 fn init_nft_token(item: &NftItemType<T::AccountId>) {1951 let current_index = <ItemListIndex>::get(item.collection)1952 .checked_add(1)1953 .unwrap();19541955 let item_owner = item.owner.clone();1956 let collection_id = item.collection.clone();1957 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();19581959 <ItemListIndex>::insert(collection_id, current_index);19601961 // Update balance1962 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1963 .checked_add(1)1964 .unwrap();1965 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);1966 }19671968 fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {1969 let current_index = <ItemListIndex>::get(item.collection)1970 .checked_add(1)1971 .unwrap();1972 let owner = item.owner.clone();1973 let value = item.value as u64;19741975 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();19761977 <ItemListIndex>::insert(item.collection, current_index);19781979 // Update balance1980 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1981 .checked_add(value)1982 .unwrap();1983 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1984 }19851986 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {1987 let current_index = <ItemListIndex>::get(item.collection)1988 .checked_add(1)1989 .unwrap();19901991 let value = item.owner.first().unwrap().fraction as u64;1992 let owner = item.owner.first().unwrap().owner.clone();19931994 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();19951996 <ItemListIndex>::insert(item.collection, current_index);19971998 // Update balance1999 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2000 .checked_add(value)2001 .unwrap();2002 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2003 }20042005 fn add_token_index(collection_id: u64, item_index: u64, owner: T::AccountId) -> DispatchResult {20062007 // add to account limit2008 if <AccountItemCount<T>>::contains_key(owner.clone()) {20092010 // bound Owned tokens by a single address2011 let count = <AccountItemCount<T>>::get(owner.clone());2012 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);20132014 <AccountItemCount<T>>::insert(owner.clone(), count2015 .checked_add(1)2016 .ok_or(Error::<T>::NumOverflow)?);2017 }2018 else {2019 <AccountItemCount<T>>::insert(owner.clone(), 1);2020 }20212022 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2023 if list_exists {2024 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2025 let item_contains = list.contains(&item_index.clone());20262027 if !item_contains {2028 list.push(item_index.clone());2029 }20302031 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2032 } else {2033 let mut itm = Vec::new();2034 itm.push(item_index.clone());2035 <AddressTokens<T>>::insert(collection_id, owner, itm);2036 2037 }20382039 Ok(())2040 }20412042 fn remove_token_index(2043 collection_id: u64,2044 item_index: u64,2045 owner: T::AccountId,2046 ) -> DispatchResult {20472048 // update counter2049 <AccountItemCount<T>>::insert(owner.clone(), 2050 <AccountItemCount<T>>::get(owner.clone())2051 .checked_sub(1)2052 .ok_or(Error::<T>::NumOverflow)?);205320542055 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2056 if list_exists {2057 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2058 let item_contains = list.contains(&item_index.clone());20592060 if item_contains {2061 list.retain(|&item| item != item_index);2062 <AddressTokens<T>>::insert(collection_id, owner, list);2063 }2064 }20652066 Ok(())2067 }20682069 fn move_token_index(2070 collection_id: u64,2071 item_index: u64,2072 old_owner: T::AccountId,2073 new_owner: T::AccountId,2074 ) -> DispatchResult {2075 Self::remove_token_index(collection_id, item_index, old_owner)?;2076 Self::add_token_index(collection_id, item_index, new_owner)?;20772078 Ok(())2079 }2080}20812082////////////////////////////////////////////////////////////////////////////////////////////////////2083// Economic models2084// #region20852086/// Fee multiplier.2087pub type Multiplier = FixedU128;20882089type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2090 <T as system::Trait>::AccountId,2091>>::Balance;2092type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2093 <T as system::Trait>::AccountId,2094>>::NegativeImbalance;20952096/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2097/// in the queue.2098#[derive(Encode, Decode, Clone, Eq, PartialEq)]2099pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2100 #[codec(compact)] BalanceOf<T>2101);21022103impl<T: Trait + Send + Sync> sp_std::fmt::Debug2104 for ChargeTransactionPayment<T>2105{2106 #[cfg(feature = "std")]2107 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2108 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2109 }2110 #[cfg(not(feature = "std"))]2111 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2112 Ok(())2113 }2114}21152116impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2117where2118 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2119 BalanceOf<T>: Send + Sync + FixedPointOperand,2120{2121 /// utility constructor. Used only in client/factory code.2122 pub fn from(fee: BalanceOf<T>) -> Self {2123 Self(fee)2124 }21252126 pub fn traditional_fee(2127 len: usize,2128 info: &DispatchInfoOf<T::Call>,2129 tip: BalanceOf<T>,2130 ) -> BalanceOf<T>2131 where2132 T::Call: Dispatchable<Info = DispatchInfo>,2133 {2134 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2135 }21362137 fn withdraw_fee(2138 &self,2139 who: &T::AccountId,2140 call: &T::Call,2141 info: &DispatchInfoOf<T::Call>,2142 len: usize,2143 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2144 let tip = self.0;21452146 // Set fee based on call type. Creating collection costs 1 Unique.2147 // All other transactions have traditional fees so far2148 // let fee = match call.is_sub_type() {2149 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2150 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2151 // // _ => <BalanceOf<T>>::from(100)2152 // };2153 let fee = Self::traditional_fee(len, info, tip);21542155 // Determine who is paying transaction fee based on ecnomic model2156 // Parse call to extract collection ID and access collection sponsor2157 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2158 Some(Call::create_item(collection_id, _properties, _owner)) => {2159 <Collection<T>>::get(collection_id).sponsor2160 }2161 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {2162 let _collection_mode = <Collection<T>>::get(collection_id).mode;21632164 // sponsor timeout2165 let sponsor_transfer = match _collection_mode {2166 CollectionMode::NFT => {2167 let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);2168 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2169 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();2170 if block_number >= limit_time {2171 <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);2172 true2173 }2174 else {2175 false2176 }2177 }2178 CollectionMode::Fungible(_) => {2179 let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);2180 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2181 if basket.iter().any(|i| i.address == _new_owner.clone())2182 {2183 let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();2184 let limit_time = item.start_block + ChainLimit::get().fungible_sponsor_transfer_timeout.into();2185 if block_number >= limit_time {2186 basket.retain(|x| x.address == item.address);2187 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });2188 <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);2189 true2190 }2191 else {2192 false2193 }2194 }2195 else {2196 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});2197 true2198 }2199 }2200 CollectionMode::ReFungible(_) => {2201 let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);2202 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2203 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();2204 if block_number >= limit_time {2205 <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);2206 true2207 } else {2208 false2209 }2210 }2211 _ => {2212 false2213 },2214 };22152216 if !sponsor_transfer {2217 T::AccountId::default()2218 } else {2219 <Collection<T>>::get(collection_id).sponsor2220 }2221 }22222223 _ => T::AccountId::default(),2224 };22252226 // Sponsor smart contracts2227 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {22282229 // On instantiation: set the contract owner2230 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {22312232 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2233 code_hash,2234 &data,2235 &who,2236 );2237 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());22382239 T::AccountId::default()2240 },22412242 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2243 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {22442245 let mut sp = T::AccountId::default();2246 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());2247 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2248 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2249 sp = called_contract;2250 }2251 }22522253 sp2254 },22552256 _ => sponsor,2257 };22582259 let mut who_pays_fee: T::AccountId = sponsor.clone();2260 if sponsor == T::AccountId::default() {2261 who_pays_fee = who.clone();2262 }22632264 // Only mess with balances if fee is not zero.2265 if fee.is_zero() {2266 return Ok((fee, None));2267 }22682269 match <T as transaction_payment::Trait>::Currency::withdraw(2270 &who_pays_fee,2271 fee,2272 if tip.is_zero() {2273 WithdrawReason::TransactionPayment.into()2274 } else {2275 WithdrawReason::TransactionPayment | WithdrawReason::Tip2276 },2277 ExistenceRequirement::KeepAlive,2278 ) {2279 Ok(imbalance) => Ok((fee, Some(imbalance))),2280 Err(_) => Err(InvalidTransaction::Payment.into()),2281 }2282 }2283}228422852286impl<T: Trait + Send + Sync> SignedExtension2287 for ChargeTransactionPayment<T>2288where2289 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2290 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2291{2292 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2293 type AccountId = T::AccountId;2294 type Call = T::Call;2295 type AdditionalSigned = ();2296 type Pre = (2297 BalanceOf<T>,2298 Self::AccountId,2299 Option<NegativeImbalanceOf<T>>,2300 BalanceOf<T>,2301 );2302 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2303 Ok(())2304 }23052306 fn validate(2307 &self,2308 _who: &Self::AccountId,2309 _call: &Self::Call,2310 _info: &DispatchInfoOf<Self::Call>,2311 _len: usize,2312 ) -> TransactionValidity {2313 Ok(ValidTransaction::default())2314 }23152316 fn pre_dispatch(2317 self,2318 who: &Self::AccountId,2319 call: &Self::Call,2320 info: &DispatchInfoOf<Self::Call>,2321 len: usize,2322 ) -> Result<Self::Pre, TransactionValidityError> {2323 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2324 Ok((self.0, who.clone(), imbalance, fee))2325 }23262327 fn post_dispatch(2328 pre: Self::Pre,2329 info: &DispatchInfoOf<Self::Call>,2330 post_info: &PostDispatchInfoOf<Self::Call>,2331 len: usize,2332 _result: &DispatchResult,2333 ) -> Result<(), TransactionValidityError> {2334 let (tip, who, imbalance, fee) = pre;2335 if let Some(payed) = imbalance {2336 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2337 len as u32, info, post_info, tip,2338 );2339 let refund = fee.saturating_sub(actual_fee);2340 let actual_payment =2341 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2342 &who, refund,2343 ) {2344 Ok(refund_imbalance) => {2345 // The refund cannot be larger than the up front payed max weight.2346 // `PostDispatchInfo::calc_unspent` guards against such a case.2347 match payed.offset(refund_imbalance) {2348 Ok(actual_payment) => actual_payment,2349 Err(_) => return Err(InvalidTransaction::Payment.into()),2350 }2351 }2352 // We do not recreate the account using the refund. The up front payment2353 // is gone in that case.2354 Err(_) => payed,2355 };2356 let imbalances = actual_payment.split(tip);2357 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2358 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2359 );2360 }2361 Ok(())2362 }2363}23642365// #endregion