difftreelog
Merge pull request #23 from usetech-llc/feature/nftpar-184
in: master
Errors messages
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,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}262263pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {264 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;265266 /// Weight information for extrinsics in this pallet.267 type WeightInfo: WeightInfo;268}269270#[cfg(feature = "runtime-benchmarks")]271mod benchmarking;272273// #endregion274275decl_storage! {276 trait Store for Module<T: Trait> as Nft {277278 // Private members279 NextCollectionID: u64;280 CreatedCollectionCount: u64;281 ChainVersion: u64;282 ItemListIndex: map hasher(blake2_128_concat) u64 => u64;283284 // Chain limits struct285 pub ChainLimit get(fn chain_limit) config(): ChainLimits;286287 // Bound counters288 CollectionCount: u64;289 pub AccountItemCount get(fn account_item_count): map hasher(identity) T::AccountId => u64;290291 // Basic collections292 pub Collection get(fn collection) config(): map hasher(identity) u64 => CollectionType<T::AccountId>;293 pub AdminList get(fn admin_list_collection): map hasher(identity) u64 => Vec<T::AccountId>;294 pub WhiteList get(fn white_list): map hasher(identity) u64 => Vec<T::AccountId>;295296 /// Balance owner per collection map297 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => u64;298299 /// second parameter: item id + owner account id300 pub ApprovedList get(fn approved): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) (u64, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;301302 /// Item collections303 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => NftItemType<T::AccountId>;304 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => FungibleItemType<T::AccountId>;305 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => ReFungibleItemType<T::AccountId>;306307 /// Index list308 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => Vec<u64>;309310 /// Tokens transfer baskets311 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => T::BlockNumber;312 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>>;313 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => T::BlockNumber;314315 // Contract Sponsorship and Ownership316 pub ContractOwner get(fn contract_owner): map hasher(identity) T::AccountId => T::AccountId;317 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(identity) T::AccountId => bool;318 }319 add_extra_genesis {320 build(|config: &GenesisConfig<T>| {321 // Modification of storage322 for (_num, _c) in &config.collection {323 <Module<T>>::init_collection(_c);324 }325326 for (_num, _q, _i) in &config.nft_item_id {327 <Module<T>>::init_nft_token(_i);328 }329330 for (_num, _q, _i) in &config.fungible_item_id {331 <Module<T>>::init_fungible_token(_i);332 }333334 for (_num, _q, _i) in &config.refungible_item_id {335 <Module<T>>::init_refungible_token(_i);336 }337 })338 }339}340341decl_event!(342 pub enum Event<T>343 where344 AccountId = <T as system::Trait>::AccountId,345 {346 /// New collection was created347 /// 348 /// # Arguments349 /// 350 /// * collection_id: Globally unique identifier of newly created collection.351 /// 352 /// * mode: [CollectionMode] converted into u8.353 /// 354 /// * account_id: Collection owner.355 Created(u64, u8, AccountId),356357 /// New item was created.358 /// 359 /// # Arguments360 /// 361 /// * collection_id: Id of the collection where item was created.362 /// 363 /// * item_id: Id of an item. Unique within the collection.364 ItemCreated(u64, u64),365366 /// Collection item was burned.367 /// 368 /// # Arguments369 /// 370 /// collection_id.371 /// 372 /// item_id: Identifier of burned NFT.373 ItemDestroyed(u64, u64),374 }375);376377decl_module! {378 pub struct Module<T: Trait> for enum Call where origin: T::Origin {379380 fn deposit_event() = default;381382 fn on_initialize(now: T::BlockNumber) -> Weight {383384 if ChainVersion::get() < 2385 {386 let value = NextCollectionID::get();387 CreatedCollectionCount::put(value);388 ChainVersion::put(2);389 }390391 0392 }393394 /// 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.395 /// 396 /// # Permissions397 /// 398 /// * Anyone.399 /// 400 /// # Arguments401 /// 402 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.403 /// 404 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.405 /// 406 /// * token_prefix: UTF-8 string with token prefix.407 /// 408 /// * mode: [CollectionMode] collection type and type dependent data.409 // returns collection ID410 #[weight = T::WeightInfo::create_collection()]411 pub fn create_collection(origin,412 collection_name: Vec<u16>,413 collection_description: Vec<u16>,414 token_prefix: Vec<u8>,415 mode: CollectionMode) -> DispatchResult {416417 // Anyone can create a collection418 let who = ensure_signed(origin)?;419420 let decimal_points = match mode {421 CollectionMode::Fungible(points) => points,422 CollectionMode::ReFungible(points) => points,423 _ => 0424 };425426 // bound Total number of collections427 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, "Total collections bound exceeded");428429 // check params430 ensure!(decimal_points <= 4, "decimal_points parameter must be lower than 4");431432 let mut name = collection_name.to_vec();433 name.push(0);434 ensure!(name.len() <= 64, "Collection name can not be longer than 63 char");435436 let mut description = collection_description.to_vec();437 description.push(0);438 ensure!(name.len() <= 256, "Collection description can not be longer than 255 char");439440 let mut prefix = token_prefix.to_vec();441 prefix.push(0);442 ensure!(prefix.len() <= 16, "Token prefix can not be longer than 15 char");443444 // Generate next collection ID445 let next_id = CreatedCollectionCount::get()446 .checked_add(1)447 .expect("collection id error");448449 // bound counter450 let total = CollectionCount::get()451 .checked_add(1)452 .expect("collection counter error");453454 CreatedCollectionCount::put(next_id);455 CollectionCount::put(total);456457 // Create new collection458 let new_collection = CollectionType {459 owner: who.clone(),460 name: name,461 mode: mode.clone(),462 mint_mode: false,463 access: AccessMode::Normal,464 description: description,465 decimal_points: decimal_points,466 token_prefix: prefix,467 offchain_schema: Vec::new(),468 sponsor: T::AccountId::default(),469 unconfirmed_sponsor: T::AccountId::default(),470 variable_on_chain_schema: Vec::new(),471 const_on_chain_schema: Vec::new(),472 };473474 // Add new collection to map475 <Collection<T>>::insert(next_id, new_collection);476477 // call event478 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));479480 Ok(())481 }482483 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.484 /// 485 /// # Permissions486 /// 487 /// * Collection Owner.488 /// 489 /// # Arguments490 /// 491 /// * collection_id: collection to destroy.492 #[weight = T::WeightInfo::destroy_collection()]493 pub fn destroy_collection(origin, collection_id: u64) -> DispatchResult {494495 let sender = ensure_signed(origin)?;496 Self::check_owner_permissions(collection_id, sender)?;497498 <AddressTokens<T>>::remove_prefix(collection_id);499 <ApprovedList<T>>::remove_prefix(collection_id);500 <Balance<T>>::remove_prefix(collection_id);501 <ItemListIndex>::remove(collection_id);502 <AdminList<T>>::remove(collection_id);503 <Collection<T>>::remove(collection_id);504 <WhiteList<T>>::remove(collection_id);505506 <NftItemList<T>>::remove_prefix(collection_id);507 <FungibleItemList<T>>::remove_prefix(collection_id);508 <ReFungibleItemList<T>>::remove_prefix(collection_id);509510 <NftTransferBasket<T>>::remove_prefix(collection_id);511 <FungibleTransferBasket<T>>::remove_prefix(collection_id);512 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);513514 if CollectionCount::get() > 0515 {516 // bound couter517 let total = CollectionCount::get()518 .checked_sub(1)519 .expect("collection counter error");520521 CollectionCount::put(total);522 }523524 Ok(())525 }526527 /// Add an address to white list.528 /// 529 /// # Permissions530 /// 531 /// * Collection Owner532 /// * Collection Admin533 /// 534 /// # Arguments535 /// 536 /// * collection_id.537 /// 538 /// * address.539 #[weight = T::WeightInfo::add_to_white_list()]540 pub fn add_to_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{541542 let sender = ensure_signed(origin)?;543 Self::check_owner_or_admin_permissions(collection_id, sender)?;544545 let mut white_list_collection: Vec<T::AccountId>;546 if <WhiteList<T>>::contains_key(collection_id) {547 white_list_collection = <WhiteList<T>>::get(collection_id);548 if !white_list_collection.contains(&address.clone())549 {550 white_list_collection.push(address.clone());551 }552 }553 else {554 white_list_collection = Vec::new();555 white_list_collection.push(address.clone());556 }557558 <WhiteList<T>>::insert(collection_id, white_list_collection);559 Ok(())560 }561562 /// Remove an address from white list.563 /// 564 /// # Permissions565 /// 566 /// * Collection Owner567 /// * Collection Admin568 /// 569 /// # Arguments570 /// 571 /// * collection_id.572 /// 573 /// * address.574 #[weight = T::WeightInfo::remove_from_white_list()]575 pub fn remove_from_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{576577 let sender = ensure_signed(origin)?;578 Self::check_owner_or_admin_permissions(collection_id, sender)?;579580 if <WhiteList<T>>::contains_key(collection_id) {581 let mut white_list_collection = <WhiteList<T>>::get(collection_id);582 if white_list_collection.contains(&address.clone())583 {584 white_list_collection.retain(|i| *i != address.clone());585 <WhiteList<T>>::insert(collection_id, white_list_collection);586 }587 }588589 Ok(())590 }591592 /// Toggle between normal and white list access for the methods with access for `Anyone`.593 /// 594 /// # Permissions595 /// 596 /// * Collection Owner.597 /// 598 /// # Arguments599 /// 600 /// * collection_id.601 /// 602 /// * mode: [AccessMode]603 #[weight = T::WeightInfo::set_public_access_mode()]604 pub fn set_public_access_mode(origin, collection_id: u64, mode: AccessMode) -> DispatchResult605 {606 let sender = ensure_signed(origin)?;607608 Self::check_owner_permissions(collection_id, sender)?;609 let mut target_collection = <Collection<T>>::get(collection_id);610 target_collection.access = mode;611 <Collection<T>>::insert(collection_id, target_collection);612613 Ok(())614 }615616 /// Allows Anyone to create tokens if:617 /// * White List is enabled, and618 /// * Address is added to white list, and619 /// * This method was called with True parameter620 /// 621 /// # Permissions622 /// * Collection Owner623 ///624 /// # Arguments625 /// 626 /// * collection_id.627 /// 628 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.629 #[weight = T::WeightInfo::set_mint_permission()]630 pub fn set_mint_permission(origin, collection_id: u64, mint_permission: bool) -> DispatchResult631 {632 let sender = ensure_signed(origin)?;633634 Self::check_owner_permissions(collection_id, sender)?;635 let mut target_collection = <Collection<T>>::get(collection_id);636 target_collection.mint_mode = mint_permission;637 <Collection<T>>::insert(collection_id, target_collection);638639 Ok(())640 }641642 /// Change the owner of the collection.643 /// 644 /// # Permissions645 /// 646 /// * Collection Owner.647 /// 648 /// # Arguments649 /// 650 /// * collection_id.651 /// 652 /// * new_owner.653 #[weight = T::WeightInfo::change_collection_owner()]654 pub fn change_collection_owner(origin, collection_id: u64, new_owner: T::AccountId) -> DispatchResult {655656 let sender = ensure_signed(origin)?;657 Self::check_owner_permissions(collection_id, sender)?;658 let mut target_collection = <Collection<T>>::get(collection_id);659 target_collection.owner = new_owner;660 <Collection<T>>::insert(collection_id, target_collection);661662 Ok(())663 }664665 /// Adds an admin of the Collection.666 /// 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. 667 /// 668 /// # Permissions669 /// 670 /// * Collection Owner.671 /// * Collection Admin.672 /// 673 /// # Arguments674 /// 675 /// * collection_id: ID of the Collection to add admin for.676 /// 677 /// * new_admin_id: Address of new admin to add.678 #[weight = T::WeightInfo::add_collection_admin()]679 pub fn add_collection_admin(origin, collection_id: u64, new_admin_id: T::AccountId) -> DispatchResult {680681 let sender = ensure_signed(origin)?;682 Self::check_owner_or_admin_permissions(collection_id, sender)?;683 let mut admin_arr: Vec<T::AccountId> = Vec::new();684685 if <AdminList<T>>::contains_key(collection_id)686 {687 admin_arr = <AdminList<T>>::get(collection_id);688 ensure!(!admin_arr.contains(&new_admin_id), "Account already has admin role");689 }690691 // Number of collection admins692 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, "Number of collection admins bound exceeded");693694 admin_arr.push(new_admin_id);695 <AdminList<T>>::insert(collection_id, admin_arr);696697 Ok(())698 }699700 /// 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.701 ///702 /// # Permissions703 /// 704 /// * Collection Owner.705 /// * Collection Admin.706 /// 707 /// # Arguments708 /// 709 /// * collection_id: ID of the Collection to remove admin for.710 /// 711 /// * account_id: Address of admin to remove.712 #[weight = T::WeightInfo::remove_collection_admin()]713 pub fn remove_collection_admin(origin, collection_id: u64, account_id: T::AccountId) -> DispatchResult {714715 let sender = ensure_signed(origin)?;716 Self::check_owner_or_admin_permissions(collection_id, sender)?;717718 if <AdminList<T>>::contains_key(collection_id)719 {720 let mut admin_arr = <AdminList<T>>::get(collection_id);721 admin_arr.retain(|i| *i != account_id);722 <AdminList<T>>::insert(collection_id, admin_arr);723 }724725 Ok(())726 }727728 /// # Permissions729 /// 730 /// * Collection Owner731 /// 732 /// # Arguments733 /// 734 /// * collection_id.735 /// 736 /// * new_sponsor.737 #[weight = T::WeightInfo::set_collection_sponsor()]738 pub fn set_collection_sponsor(origin, collection_id: u64, new_sponsor: T::AccountId) -> DispatchResult {739740 let sender = ensure_signed(origin)?;741 ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");742743 let mut target_collection = <Collection<T>>::get(collection_id);744 ensure!(sender == target_collection.owner, "You do not own this collection");745746 target_collection.unconfirmed_sponsor = new_sponsor;747 <Collection<T>>::insert(collection_id, target_collection);748749 Ok(())750 }751752 /// # Permissions753 /// 754 /// * Sponsor.755 /// 756 /// # Arguments757 /// 758 /// * collection_id.759 #[weight = T::WeightInfo::confirm_sponsorship()]760 pub fn confirm_sponsorship(origin, collection_id: u64) -> DispatchResult {761762 let sender = ensure_signed(origin)?;763 ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");764765 let mut target_collection = <Collection<T>>::get(collection_id);766 ensure!(sender == target_collection.unconfirmed_sponsor, "This address is not set as sponsor, use setCollectionSponsor first");767768 target_collection.sponsor = target_collection.unconfirmed_sponsor;769 target_collection.unconfirmed_sponsor = T::AccountId::default();770 <Collection<T>>::insert(collection_id, target_collection);771772 Ok(())773 }774775 /// Switch back to pay-per-own-transaction model.776 ///777 /// # Permissions778 ///779 /// * Collection owner.780 /// 781 /// # Arguments782 /// 783 /// * collection_id.784 #[weight = T::WeightInfo::remove_collection_sponsor()]785 pub fn remove_collection_sponsor(origin, collection_id: u64) -> DispatchResult {786787 let sender = ensure_signed(origin)?;788 ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");789790 let mut target_collection = <Collection<T>>::get(collection_id);791 ensure!(sender == target_collection.owner, "You do not own this collection");792793 target_collection.sponsor = T::AccountId::default();794 <Collection<T>>::insert(collection_id, target_collection);795796 Ok(())797 }798799 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.800 /// 801 /// # Permissions802 /// 803 /// * Collection Owner.804 /// * Collection Admin.805 /// * Anyone if806 /// * White List is enabled, and807 /// * Address is added to white list, and808 /// * MintPermission is enabled (see SetMintPermission method)809 /// 810 /// # Arguments811 /// 812 /// * collection_id: ID of the collection.813 /// 814 /// * owner: Address, initial owner of the NFT.815 ///816 /// * data: Token data to store on chain.817 // #[weight =818 // (130_000_000 as Weight)819 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))820 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))821 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]822823 #[weight = T::WeightInfo::create_item(data.len())]824 pub fn create_item(origin, collection_id: u64, owner: T::AccountId, data: CreateItemData) -> DispatchResult {825826 let sender = ensure_signed(origin)?;827828 Self::collection_exists(collection_id)?;829830 let target_collection = <Collection<T>>::get(collection_id);831832 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;833 Self::validate_create_item_args(&target_collection, &data)?;834 Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;835836 Ok(())837 }838839 /// This method creates multiple instances of NFT Collection created with CreateCollection method.840 /// 841 /// # Permissions842 /// 843 /// * Collection Owner.844 /// * Collection Admin.845 /// * Anyone if846 /// * White List is enabled, and847 /// * Address is added to white list, and848 /// * MintPermission is enabled (see SetMintPermission method)849 /// 850 /// # Arguments851 /// 852 /// * collection_id: ID of the collection.853 /// 854 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].855 /// 856 /// * owner: Address, initial owner of the NFT.857 #[weight = T::WeightInfo::create_item(items_data.into_iter()858 .map(|data| { data.len() })859 .sum())]860 pub fn create_multiple_items(origin, collection_id: u64, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {861862 ensure!(items_data.len() > 0, "Length of items properties must be greater than 0.");863 let sender = ensure_signed(origin)?;864865 Self::collection_exists(collection_id)?;866 let target_collection = <Collection<T>>::get(collection_id);867868 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;869870 for data in &items_data {871 Self::validate_create_item_args(&target_collection, data)?;872 }873 for data in &items_data {874 Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;875 }876877 Ok(())878 }879880 /// Destroys a concrete instance of NFT.881 /// 882 /// # Permissions883 /// 884 /// * Collection Owner.885 /// * Collection Admin.886 /// * Current NFT Owner.887 /// 888 /// # Arguments889 /// 890 /// * collection_id: ID of the collection.891 /// 892 /// * item_id: ID of NFT to burn.893 #[weight = T::WeightInfo::burn_item()]894 pub fn burn_item(origin, collection_id: u64, item_id: u64) -> DispatchResult {895896 let sender = ensure_signed(origin)?;897 Self::collection_exists(collection_id)?;898899 // Transfer permissions check900 let target_collection = <Collection<T>>::get(collection_id);901 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||902 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),903 "Only item owner, collection owner and admins can modify item");904905 if target_collection.access == AccessMode::WhiteList {906 Self::check_white_list(collection_id, &sender)?;907 }908909 match target_collection.mode910 {911 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,912 CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?,913 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,914 _ => ()915 };916917 // call event918 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));919920 Ok(())921 }922923 /// Change ownership of the token.924 /// 925 /// # Permissions926 /// 927 /// * Collection Owner928 /// * Collection Admin929 /// * Current NFT owner930 ///931 /// # Arguments932 /// 933 /// * recipient: Address of token recipient.934 /// 935 /// * collection_id.936 /// 937 /// * item_id: ID of the item938 /// * Non-Fungible Mode: Required.939 /// * Fungible Mode: Ignored.940 /// * Re-Fungible Mode: Required.941 /// 942 /// * value: Amount to transfer.943 /// * Non-Fungible Mode: Ignored944 /// * Fungible Mode: Must specify transferred amount945 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)946 #[weight = T::WeightInfo::transfer()]947 pub fn transfer(origin, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64) -> DispatchResult {948949 let sender = ensure_signed(origin)?;950951 // Transfer permissions check952 let target_collection = <Collection<T>>::get(collection_id);953 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||954 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),955 "Only item owner, collection owner and admins can modify item");956957 if target_collection.access == AccessMode::WhiteList {958 Self::check_white_list(collection_id, &sender)?;959 Self::check_white_list(collection_id, &recipient)?;960 }961962 match target_collection.mode963 {964 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,965 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,966 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,967 _ => ()968 };969970 Ok(())971 }972973 /// Set, change, or remove approved address to transfer the ownership of the NFT.974 /// 975 /// # Permissions976 /// 977 /// * Collection Owner978 /// * Collection Admin979 /// * Current NFT owner980 /// 981 /// # Arguments982 /// 983 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).984 /// 985 /// * collection_id.986 /// 987 /// * item_id: ID of the item.988 #[weight = T::WeightInfo::approve()]989 pub fn approve(origin, approved: T::AccountId, collection_id: u64, item_id: u64) -> DispatchResult {990991 let sender = ensure_signed(origin)?;992993 // Transfer permissions check994 let target_collection = <Collection<T>>::get(collection_id);995 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||996 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),997 "Only item owner, collection owner and admins can approve");998999 if target_collection.access == AccessMode::WhiteList {1000 Self::check_white_list(collection_id, &sender)?;1001 Self::check_white_list(collection_id, &approved)?;1002 }10031004 // amount param stub1005 let amount = 100000000;10061007 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));1008 if list_exists {10091010 let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));1011 let item_contains = list.iter().any(|i| i.approved == approved);10121013 if !item_contains {1014 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1015 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1016 }1017 } else {10181019 let mut list = Vec::new();1020 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1021 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1022 }10231024 Ok(())1025 }1026 1027 /// 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.1028 /// 1029 /// # Permissions1030 /// * Collection Owner1031 /// * Collection Admin1032 /// * Current NFT owner1033 /// * Address approved by current NFT owner1034 /// 1035 /// # Arguments1036 /// 1037 /// * from: Address that owns token.1038 /// 1039 /// * recipient: Address of token recipient.1040 /// 1041 /// * collection_id.1042 /// 1043 /// * item_id: ID of the item.1044 /// 1045 /// * value: Amount to transfer.1046 #[weight = T::WeightInfo::transfer_from()]1047 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64 ) -> DispatchResult {10481049 let sender = ensure_signed(origin)?;1050 let mut appoved_transfer = false;10511052 // Check approve1053 if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {1054 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));1055 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());1056 if opt_item.is_some()1057 {1058 appoved_transfer = true;1059 ensure!(opt_item.unwrap().amount >= value, "Requested value more than approved");1060 }1061 }10621063 // Transfer permissions check1064 let target_collection = <Collection<T>>::get(collection_id);1065 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1066 "Only item owner, collection owner and admins can modify items");10671068 if target_collection.access == AccessMode::WhiteList {1069 Self::check_white_list(collection_id, &sender)?;1070 Self::check_white_list(collection_id, &recipient)?;1071 }10721073 // remove approve1074 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))1075 .into_iter().filter(|i| i.approved != sender.clone()).collect();1076 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);107710781079 match target_collection.mode1080 {1081 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1082 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,1083 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1084 _ => ()1085 };10861087 Ok(())1088 }10891090 ///1091 #[weight = 0]1092 pub fn safe_transfer_from(origin, collection_id: u64, item_id: u64, new_owner: T::AccountId) -> DispatchResult {10931094 // let no_perm_mes = "You do not have permissions to modify this collection";1095 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1096 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1097 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);10981099 // // on_nft_received call11001101 // Self::transfer(origin, collection_id, item_id, new_owner)?;11021103 Ok(())1104 }11051106 /// Set off-chain data schema.1107 /// 1108 /// # Permissions1109 /// 1110 /// * Collection Owner1111 /// * Collection Admin1112 /// 1113 /// # Arguments1114 /// 1115 /// * collection_id.1116 /// 1117 /// * schema: String representing the offchain data schema.1118 #[weight = T::WeightInfo::set_variable_meta_data()]1119 pub fn set_variable_meta_data (1120 origin,1121 collection_id: u64,1122 item_id: u64,1123 data: Vec<u8>1124 ) -> DispatchResult {1125 let sender = ensure_signed(origin)?;1126 1127 Self::collection_exists(collection_id)?;11281129 // Modify permissions check1130 let target_collection = <Collection<T>>::get(collection_id);1131 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1132 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1133 "Only item owner, collection owner and admins can modify item");11341135 Self::item_exists(collection_id, item_id, &target_collection.mode)?;11361137 match target_collection.mode1138 {1139 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1140 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1141 _ => ()1142 };11431144 Ok(())1145 }1146 11471148 /// Set off-chain data schema.1149 /// 1150 /// # Permissions1151 /// 1152 /// * Collection Owner1153 /// * Collection Admin1154 /// 1155 /// # Arguments1156 /// 1157 /// * collection_id.1158 /// 1159 /// * schema: String representing the offchain data schema.1160 #[weight = T::WeightInfo::set_offchain_schema()]1161 pub fn set_offchain_schema(1162 origin,1163 collection_id: u64,1164 schema: Vec<u8>1165 ) -> DispatchResult {1166 let sender = ensure_signed(origin)?;1167 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;11681169 let mut target_collection = <Collection<T>>::get(collection_id);1170 target_collection.offchain_schema = schema;1171 <Collection<T>>::insert(collection_id, target_collection);11721173 Ok(())1174 }11751176 /// Set const on-chain data schema.1177 /// 1178 /// # Permissions1179 /// 1180 /// * Collection Owner1181 /// * Collection Admin1182 /// 1183 /// # Arguments1184 /// 1185 /// * collection_id.1186 /// 1187 /// * schema: String representing the const on-chain data schema.1188 #[weight = T::WeightInfo::set_const_on_chain_schema()]1189 pub fn set_const_on_chain_schema (1190 origin,1191 collection_id: u64,1192 schema: Vec<u8>1193 ) -> DispatchResult {1194 let sender = ensure_signed(origin)?;1195 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;11961197 let mut target_collection = <Collection<T>>::get(collection_id);1198 target_collection.const_on_chain_schema = schema;1199 <Collection<T>>::insert(collection_id, target_collection);12001201 Ok(())1202 }12031204 /// Set variable on-chain data schema.1205 /// 1206 /// # Permissions1207 /// 1208 /// * Collection Owner1209 /// * Collection Admin1210 /// 1211 /// # Arguments1212 /// 1213 /// * collection_id.1214 /// 1215 /// * schema: String representing the variable on-chain data schema.1216 #[weight = T::WeightInfo::set_const_on_chain_schema()]1217 pub fn set_variable_on_chain_schema (1218 origin,1219 collection_id: u64,1220 schema: Vec<u8>1221 ) -> DispatchResult {1222 let sender = ensure_signed(origin)?;1223 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12241225 let mut target_collection = <Collection<T>>::get(collection_id);1226 target_collection.variable_on_chain_schema = schema;1227 <Collection<T>>::insert(collection_id, target_collection);12281229 Ok(())1230 }12311232 // Sudo permissions function1233 #[weight = 0]1234 pub fn set_chain_limits(1235 origin,1236 limits: ChainLimits1237 ) -> DispatchResult {1238 ensure_root(origin)?;1239 <ChainLimit>::put(limits);1240 Ok(())1241 }12421243 /// Enable smart contract self-sponsoring.1244 /// 1245 /// # Permissions1246 /// 1247 /// * Contract Owner1248 /// 1249 /// # Arguments1250 /// 1251 /// * contract address1252 /// * enable flag1253 /// 1254 #[weight = 0]1255 pub fn enable_contract_sponsoring(1256 origin,1257 contract_address: T::AccountId,1258 enable: bool1259 ) -> DispatchResult {1260 let sender = ensure_signed(origin)?;1261 let mut is_owner = false;1262 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1263 let owner = <ContractOwner<T>>::get(&contract_address);1264 is_owner = sender == owner;1265 }1266 ensure!(is_owner, "Only contract owner may call this method");12671268 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1269 Ok(())1270 }12711272 }1273}12741275impl<T: Trait> Module<T> {12761277 fn can_create_items_in_collection(collection_id: u64, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {12781279 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1280 ensure!(collection.mint_mode == true, "Public minting is not allowed for this collection");1281 Self::check_white_list(collection_id, owner)?;1282 Self::check_white_list(collection_id, sender)?;1283 }12841285 Ok(())1286 }12871288 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1289 match target_collection.mode1290 {1291 CollectionMode::NFT => {1292 if let CreateItemData::NFT(data) = data {1293 // check sizes1294 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, "const_data exceeded data limit.");1295 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, "variable_data exceeded data limit.");1296 } else {1297 fail!("Not NFT item data used to mint in NFT collection.");1298 }1299 },1300 CollectionMode::Fungible(_) => {1301 if let CreateItemData::Fungible(_) = data {1302 } else {1303 fail!("Not Fungible item data used to mint in Fungible collection.");1304 }1305 },1306 CollectionMode::ReFungible(_) => {1307 if let CreateItemData::ReFungible(data) = data {13081309 // check sizes1310 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, "const_data exceeded data limit.");1311 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, "variable_data exceeded data limit.");1312 } else {1313 fail!("Not Re Fungible item data used to mint in Re Fungible collection.");1314 }1315 },1316 _ => { fail!("Unexpected collection type."); }1317 };13181319 Ok(())1320 }13211322 fn create_item_no_validation(collection_id: u64, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1323 match data1324 {1325 CreateItemData::NFT(data) => {1326 let item = NftItemType {1327 collection: collection_id,1328 owner,1329 const_data: data.const_data,1330 variable_data: data.variable_data1331 };13321333 Self::add_nft_item(item)?;1334 },1335 CreateItemData::Fungible(_) => {1336 let item = FungibleItemType {1337 collection: collection_id,1338 owner,1339 value: (10 as u128).pow(collection.decimal_points)1340 };13411342 Self::add_fungible_item(item)?;1343 },1344 CreateItemData::ReFungible(data) => {1345 let mut owner_list = Vec::new();1346 let value = (10 as u128).pow(collection.decimal_points);1347 owner_list.push(Ownership {owner: owner.clone(), fraction: value});13481349 let item = ReFungibleItemType {1350 collection: collection_id,1351 owner: owner_list,1352 const_data: data.const_data,1353 variable_data: data.variable_data1354 };13551356 Self::add_refungible_item(item)?;1357 }1358 };135913601361 // call event1362 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));13631364 Ok(())1365 }13661367 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {1368 let current_index = <ItemListIndex>::get(item.collection)1369 .checked_add(1)1370 .expect("Item list index id error");1371 let itemcopy = item.clone();1372 let owner = item.owner.clone();1373 let value = item.value as u64;13741375 Self::add_token_index(item.collection, current_index, owner.clone())?;13761377 <ItemListIndex>::insert(item.collection, current_index);1378 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);13791380 // Add current block1381 let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1382 <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);1383 1384 // Update balance1385 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1386 .checked_add(value)1387 .unwrap();1388 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);13891390 Ok(())1391 }13921393 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1394 let current_index = <ItemListIndex>::get(item.collection)1395 .checked_add(1)1396 .expect("Item list index id error");1397 let itemcopy = item.clone();13981399 let value = item.owner.first().unwrap().fraction as u64;1400 let owner = item.owner.first().unwrap().owner.clone();14011402 Self::add_token_index(item.collection, current_index, owner.clone())?;14031404 <ItemListIndex>::insert(item.collection, current_index);1405 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);14061407 // Add current block1408 let block_number: T::BlockNumber = 0.into();1409 <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);14101411 // Update balance1412 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1413 .checked_add(value)1414 .unwrap();1415 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);14161417 Ok(())1418 }14191420 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1421 let current_index = <ItemListIndex>::get(item.collection)1422 .checked_add(1)1423 .expect("Item list index id error");14241425 let item_owner = item.owner.clone();1426 let collection_id = item.collection.clone();1427 Self::add_token_index(collection_id, current_index, item.owner.clone())?;14281429 <ItemListIndex>::insert(collection_id, current_index);1430 <NftItemList<T>>::insert(collection_id, current_index, item);14311432 // Add current block1433 let block_number: T::BlockNumber = 0.into();1434 <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);14351436 // Update balance1437 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1438 .checked_add(1)1439 .unwrap();1440 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);14411442 Ok(())1443 }14441445 fn burn_refungible_item(1446 collection_id: u64,1447 item_id: u64,1448 owner: T::AccountId,1449 ) -> DispatchResult {1450 ensure!(1451 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1452 "Item does not exists"1453 );1454 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1455 let item = collection1456 .owner1457 .iter()1458 .filter(|&i| i.owner == owner)1459 .next()1460 .unwrap();1461 Self::remove_token_index(collection_id, item_id, owner.clone())?;14621463 // remove approve list1464 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));14651466 // update balance1467 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1468 .checked_sub(item.fraction as u64)1469 .unwrap();1470 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);14711472 <ReFungibleItemList<T>>::remove(collection_id, item_id);14731474 Ok(())1475 }14761477 fn burn_nft_item(collection_id: u64, item_id: u64) -> DispatchResult {1478 ensure!(1479 <NftItemList<T>>::contains_key(collection_id, item_id),1480 "Item does not exists"1481 );1482 let item = <NftItemList<T>>::get(collection_id, item_id);1483 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;14841485 // remove approve list1486 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));14871488 // update balance1489 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1490 .checked_sub(1)1491 .unwrap();1492 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1493 <NftItemList<T>>::remove(collection_id, item_id);14941495 Ok(())1496 }14971498 fn burn_fungible_item(collection_id: u64, item_id: u64) -> DispatchResult {1499 ensure!(1500 <FungibleItemList<T>>::contains_key(collection_id, item_id),1501 "Item does not exists"1502 );1503 let item = <FungibleItemList<T>>::get(collection_id, item_id);1504 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;15051506 // remove approve list1507 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));15081509 // update balance1510 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1511 .checked_sub(item.value as u64)1512 .unwrap();1513 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);15141515 <FungibleItemList<T>>::remove(collection_id, item_id);15161517 Ok(())1518 }15191520 fn collection_exists(collection_id: u64) -> DispatchResult {1521 ensure!(1522 <Collection<T>>::contains_key(collection_id),1523 "This collection does not exist"1524 );1525 Ok(())1526 }15271528 fn check_owner_permissions(collection_id: u64, subject: T::AccountId) -> DispatchResult {1529 Self::collection_exists(collection_id)?;15301531 let target_collection = <Collection<T>>::get(collection_id);1532 ensure!(1533 subject == target_collection.owner,1534 "You do not own this collection"1535 );15361537 Ok(())1538 }15391540 fn is_owner_or_admin_permissions(collection_id: u64, subject: T::AccountId) -> bool {1541 let target_collection = <Collection<T>>::get(collection_id);1542 let mut result: bool = subject == target_collection.owner;1543 let exists = <AdminList<T>>::contains_key(collection_id);15441545 if !result & exists {1546 if <AdminList<T>>::get(collection_id).contains(&subject) {1547 result = true1548 }1549 }15501551 result1552 }15531554 fn check_owner_or_admin_permissions(1555 collection_id: u64,1556 subject: T::AccountId,1557 ) -> DispatchResult {1558 Self::collection_exists(collection_id)?;1559 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());15601561 ensure!(1562 result,1563 "You do not have permissions to modify this collection"1564 );1565 Ok(())1566 }15671568 fn is_item_owner(subject: T::AccountId, collection_id: u64, item_id: u64) -> bool {1569 let target_collection = <Collection<T>>::get(collection_id);15701571 match target_collection.mode {1572 CollectionMode::NFT => {1573 <NftItemList<T>>::get(collection_id, item_id).owner == subject1574 }1575 CollectionMode::Fungible(_) => {1576 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1577 }1578 CollectionMode::ReFungible(_) => {1579 <ReFungibleItemList<T>>::get(collection_id, item_id)1580 .owner1581 .iter()1582 .any(|i| i.owner == subject)1583 }1584 CollectionMode::Invalid => false,1585 }1586 }15871588 fn check_white_list(collection_id: u64, address: &T::AccountId) -> DispatchResult {1589 let mes = "Address is not in white list";1590 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1591 let wl = <WhiteList<T>>::get(collection_id);1592 ensure!(wl.contains(address), mes);15931594 Ok(())1595 }15961597 fn transfer_fungible(1598 collection_id: u64,1599 item_id: u64,1600 value: u64,1601 owner: T::AccountId,1602 new_owner: T::AccountId,1603 ) -> DispatchResult {1604 ensure!(1605 <FungibleItemList<T>>::contains_key(collection_id, item_id),1606 "Item not exists"1607 );16081609 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1610 let amount = full_item.value;16111612 ensure!(amount >= value.into(), "Item balance not enouth");16131614 // update balance1615 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1616 .checked_sub(value)1617 .unwrap();1618 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);16191620 let mut new_owner_account_id = 0;1621 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1622 if new_owner_items.len() > 0 {1623 new_owner_account_id = new_owner_items[0];1624 }16251626 let val64 = value.into();16271628 // transfer1629 if amount == val64 && new_owner_account_id == 0 {1630 // change owner1631 // new owner do not have account1632 let mut new_full_item = full_item.clone();1633 new_full_item.owner = new_owner.clone();1634 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);16351636 // update balance1637 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1638 .checked_add(value)1639 .unwrap();1640 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);16411642 // update index collection1643 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1644 } else {1645 let mut new_full_item = full_item.clone();1646 new_full_item.value -= val64;16471648 // separate amount1649 if new_owner_account_id > 0 {1650 // new owner has account1651 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1652 item.value += val64;16531654 // update balance1655 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1656 .checked_add(value)1657 .unwrap();1658 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);16591660 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1661 } else {1662 // new owner do not have account1663 let item = FungibleItemType {1664 collection: collection_id,1665 owner: new_owner.clone(),1666 value: val64,1667 };16681669 Self::add_fungible_item(item)?;1670 }16711672 if amount == val64 {1673 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;16741675 // remove approve list1676 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1677 <FungibleItemList<T>>::remove(collection_id, item_id);1678 }16791680 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1681 }16821683 Ok(())1684 }16851686 fn transfer_refungible(1687 collection_id: u64,1688 item_id: u64,1689 value: u64,1690 owner: T::AccountId,1691 new_owner: T::AccountId,1692 ) -> DispatchResult {1693 ensure!(1694 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1695 "Item not exists"1696 );16971698 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1699 let item = full_item1700 .owner1701 .iter()1702 .filter(|i| i.owner == owner)1703 .next()1704 .unwrap();1705 let amount = item.fraction;17061707 ensure!(amount >= value.into(), "Item balance not enouth");17081709 // update balance1710 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1711 .checked_sub(value)1712 .unwrap();1713 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);17141715 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1716 .checked_add(value)1717 .unwrap();1718 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);17191720 let old_owner = item.owner.clone();1721 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);1722 let val64 = value.into();17231724 // transfer1725 if amount == val64 && !new_owner_has_account {1726 // change owner1727 // new owner do not have account1728 let mut new_full_item = full_item.clone();1729 new_full_item1730 .owner1731 .iter_mut()1732 .find(|i| i.owner == owner)1733 .unwrap()1734 .owner = new_owner.clone();1735 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);17361737 // update index collection1738 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1739 } else {1740 let mut new_full_item = full_item.clone();1741 new_full_item1742 .owner1743 .iter_mut()1744 .find(|i| i.owner == owner)1745 .unwrap()1746 .fraction -= val64;17471748 // separate amount1749 if new_owner_has_account {1750 // new owner has account1751 new_full_item1752 .owner1753 .iter_mut()1754 .find(|i| i.owner == new_owner)1755 .unwrap()1756 .fraction += val64;1757 } else {1758 // new owner do not have account1759 new_full_item.owner.push(Ownership {1760 owner: new_owner.clone(),1761 fraction: val64,1762 });1763 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1764 }17651766 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1767 }17681769 Ok(())1770 }17711772 fn transfer_nft(1773 collection_id: u64,1774 item_id: u64,1775 sender: T::AccountId,1776 new_owner: T::AccountId,1777 ) -> DispatchResult {1778 ensure!(1779 <NftItemList<T>>::contains_key(collection_id, item_id),1780 "Item not exists"1781 );17821783 let mut item = <NftItemList<T>>::get(collection_id, item_id);17841785 ensure!(1786 sender == item.owner,1787 "sender parameter and item owner must be equal"1788 );17891790 // update balance1791 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1792 .checked_sub(1)1793 .unwrap();1794 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);17951796 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1797 .checked_add(1)1798 .unwrap();1799 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);18001801 // change owner1802 let old_owner = item.owner.clone();1803 item.owner = new_owner.clone();1804 <NftItemList<T>>::insert(collection_id, item_id, item);18051806 // update index collection1807 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;18081809 // reset approved list1810 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));1811 Ok(())1812 }1813 1814 fn item_exists(1815 collection_id: u64,1816 item_id: u64,1817 mode: &CollectionMode1818 ) -> DispatchResult {1819 match mode {1820 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), "Item does not exists"),1821 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), "Item does not exists"),1822 CollectionMode::Fungible(_) => ensure!(<FungibleItemList<T>>::contains_key(collection_id, item_id), "Item does not exists"),1823 _ => ()1824 };1825 1826 Ok(())1827 }18281829 fn set_re_fungible_variable_data(1830 collection_id: u64,1831 item_id: u64,1832 data: Vec<u8>1833 ) -> DispatchResult {1834 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);18351836 item.variable_data = data;18371838 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);18391840 Ok(())1841 }18421843 fn set_nft_variable_data(1844 collection_id: u64,1845 item_id: u64,1846 data: Vec<u8>1847 ) -> DispatchResult {1848 let mut item = <NftItemList<T>>::get(collection_id, item_id);1849 1850 item.variable_data = data;18511852 <NftItemList<T>>::insert(collection_id, item_id, item);1853 1854 Ok(())1855 }18561857 fn init_collection(item: &CollectionType<T::AccountId>) {1858 // check params1859 assert!(1860 item.decimal_points <= 4,1861 "decimal_points parameter must be lower than 4"1862 );1863 assert!(1864 item.name.len() <= 64,1865 "Collection name can not be longer than 63 char"1866 );1867 assert!(1868 item.name.len() <= 256,1869 "Collection description can not be longer than 255 char"1870 );1871 assert!(1872 item.token_prefix.len() <= 16,1873 "Token prefix can not be longer than 15 char"1874 );18751876 // Generate next collection ID1877 let next_id = CreatedCollectionCount::get()1878 .checked_add(1)1879 .expect("collection id error");18801881 CreatedCollectionCount::put(next_id);1882 }18831884 fn init_nft_token(item: &NftItemType<T::AccountId>) {1885 let current_index = <ItemListIndex>::get(item.collection)1886 .checked_add(1)1887 .expect("Item list index id error");18881889 let item_owner = item.owner.clone();1890 let collection_id = item.collection.clone();1891 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();18921893 <ItemListIndex>::insert(collection_id, current_index);18941895 // Update balance1896 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1897 .checked_add(1)1898 .unwrap();1899 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);1900 }19011902 fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {1903 let current_index = <ItemListIndex>::get(item.collection)1904 .checked_add(1)1905 .expect("Item list index id error");1906 let owner = item.owner.clone();1907 let value = item.value as u64;19081909 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();19101911 <ItemListIndex>::insert(item.collection, current_index);19121913 // Update balance1914 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1915 .checked_add(value)1916 .unwrap();1917 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1918 }19191920 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {1921 let current_index = <ItemListIndex>::get(item.collection)1922 .checked_add(1)1923 .expect("Item list index id error");19241925 let value = item.owner.first().unwrap().fraction as u64;1926 let owner = item.owner.first().unwrap().owner.clone();19271928 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();19291930 <ItemListIndex>::insert(item.collection, current_index);19311932 // Update balance1933 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1934 .checked_add(value)1935 .unwrap();1936 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1937 }19381939 fn add_token_index(collection_id: u64, item_index: u64, owner: T::AccountId) -> DispatchResult {19401941 // add to account limit1942 if <AccountItemCount<T>>::contains_key(owner.clone()) {19431944 // bound Owned tokens by a single address1945 let count = <AccountItemCount<T>>::get(owner.clone());1946 ensure!(count < ChainLimit::get().account_token_ownership_limit, "Owned tokens by a single address bound exceeded");19471948 <AccountItemCount<T>>::insert(owner.clone(), 1949 count.checked_add(1).unwrap());1950 }1951 else {1952 <AccountItemCount<T>>::insert(owner.clone(), 1);1953 }19541955 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());1956 if list_exists {1957 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());1958 let item_contains = list.contains(&item_index.clone());19591960 if !item_contains {1961 list.push(item_index.clone());1962 }19631964 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);1965 } else {1966 let mut itm = Vec::new();1967 itm.push(item_index.clone());1968 <AddressTokens<T>>::insert(collection_id, owner, itm);1969 1970 }19711972 Ok(())1973 }19741975 fn remove_token_index(1976 collection_id: u64,1977 item_index: u64,1978 owner: T::AccountId,1979 ) -> DispatchResult {19801981 // update counter1982 <AccountItemCount<T>>::insert(owner.clone(), 1983 <AccountItemCount<T>>::get(owner.clone()).checked_sub(1).unwrap());198419851986 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());1987 if list_exists {1988 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());1989 let item_contains = list.contains(&item_index.clone());19901991 if item_contains {1992 list.retain(|&item| item != item_index);1993 <AddressTokens<T>>::insert(collection_id, owner, list);1994 }1995 }19961997 Ok(())1998 }19992000 fn move_token_index(2001 collection_id: u64,2002 item_index: u64,2003 old_owner: T::AccountId,2004 new_owner: T::AccountId,2005 ) -> DispatchResult {2006 Self::remove_token_index(collection_id, item_index, old_owner)?;2007 Self::add_token_index(collection_id, item_index, new_owner)?;20082009 Ok(())2010 }2011}20122013////////////////////////////////////////////////////////////////////////////////////////////////////2014// Economic models2015// #region20162017/// Fee multiplier.2018pub type Multiplier = FixedU128;20192020type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2021 <T as system::Trait>::AccountId,2022>>::Balance;2023type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2024 <T as system::Trait>::AccountId,2025>>::NegativeImbalance;20262027/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2028/// in the queue.2029#[derive(Encode, Decode, Clone, Eq, PartialEq)]2030pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2031 #[codec(compact)] BalanceOf<T>2032);20332034impl<T: Trait + Send + Sync> sp_std::fmt::Debug2035 for ChargeTransactionPayment<T>2036{2037 #[cfg(feature = "std")]2038 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2039 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2040 }2041 #[cfg(not(feature = "std"))]2042 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2043 Ok(())2044 }2045}20462047impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2048where2049 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2050 BalanceOf<T>: Send + Sync + FixedPointOperand,2051{2052 /// utility constructor. Used only in client/factory code.2053 pub fn from(fee: BalanceOf<T>) -> Self {2054 Self(fee)2055 }20562057 pub fn traditional_fee(2058 len: usize,2059 info: &DispatchInfoOf<T::Call>,2060 tip: BalanceOf<T>,2061 ) -> BalanceOf<T>2062 where2063 T::Call: Dispatchable<Info = DispatchInfo>,2064 {2065 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2066 }20672068 fn withdraw_fee(2069 &self,2070 who: &T::AccountId,2071 call: &T::Call,2072 info: &DispatchInfoOf<T::Call>,2073 len: usize,2074 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2075 let tip = self.0;20762077 // Set fee based on call type. Creating collection costs 1 Unique.2078 // All other transactions have traditional fees so far2079 // let fee = match call.is_sub_type() {2080 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2081 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2082 // // _ => <BalanceOf<T>>::from(100)2083 // };2084 let fee = Self::traditional_fee(len, info, tip);20852086 // Determine who is paying transaction fee based on ecnomic model2087 // Parse call to extract collection ID and access collection sponsor2088 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2089 Some(Call::create_item(collection_id, _properties, _owner)) => {2090 <Collection<T>>::get(collection_id).sponsor2091 }2092 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {2093 let _collection_mode = <Collection<T>>::get(collection_id).mode;20942095 // sponsor timeout2096 let sponsor_transfer = match _collection_mode {2097 CollectionMode::NFT => {2098 let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);2099 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2100 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();2101 if block_number >= limit_time {2102 <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);2103 true2104 }2105 else {2106 false2107 }2108 }2109 CollectionMode::Fungible(_) => {2110 let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);2111 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2112 if basket.iter().any(|i| i.address == _new_owner.clone())2113 {2114 let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();2115 let limit_time = item.start_block + ChainLimit::get().fungible_sponsor_transfer_timeout.into();2116 if block_number >= limit_time {2117 basket.retain(|x| x.address == item.address);2118 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });2119 <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);2120 true2121 }2122 else {2123 false2124 }2125 }2126 else {2127 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});2128 true2129 }2130 }2131 CollectionMode::ReFungible(_) => {2132 let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);2133 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2134 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();2135 if block_number >= limit_time {2136 <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);2137 true2138 } else {2139 false2140 }2141 }2142 _ => {2143 false2144 },2145 };21462147 if !sponsor_transfer {2148 T::AccountId::default()2149 } else {2150 <Collection<T>>::get(collection_id).sponsor2151 }2152 }21532154 _ => T::AccountId::default(),2155 };21562157 // Sponsor smart contracts2158 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {21592160 // On instantiation: set the contract owner2161 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {21622163 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2164 code_hash,2165 &data,2166 &who,2167 );2168 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());21692170 T::AccountId::default()2171 },21722173 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2174 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {21752176 let mut sp = T::AccountId::default();2177 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());2178 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2179 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2180 sp = called_contract;2181 }2182 }21832184 sp2185 },21862187 _ => sponsor,2188 };21892190 let mut who_pays_fee: T::AccountId = sponsor.clone();2191 if sponsor == T::AccountId::default() {2192 who_pays_fee = who.clone();2193 }21942195 // Only mess with balances if fee is not zero.2196 if fee.is_zero() {2197 return Ok((fee, None));2198 }21992200 match <T as transaction_payment::Trait>::Currency::withdraw(2201 &who_pays_fee,2202 fee,2203 if tip.is_zero() {2204 WithdrawReason::TransactionPayment.into()2205 } else {2206 WithdrawReason::TransactionPayment | WithdrawReason::Tip2207 },2208 ExistenceRequirement::KeepAlive,2209 ) {2210 Ok(imbalance) => Ok((fee, Some(imbalance))),2211 Err(_) => Err(InvalidTransaction::Payment.into()),2212 }2213 }2214}221522162217impl<T: Trait + Send + Sync> SignedExtension2218 for ChargeTransactionPayment<T>2219where2220 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2221 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2222{2223 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2224 type AccountId = T::AccountId;2225 type Call = T::Call;2226 type AdditionalSigned = ();2227 type Pre = (2228 BalanceOf<T>,2229 Self::AccountId,2230 Option<NegativeImbalanceOf<T>>,2231 BalanceOf<T>,2232 );2233 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2234 Ok(())2235 }22362237 fn validate(2238 &self,2239 _who: &Self::AccountId,2240 _call: &Self::Call,2241 _info: &DispatchInfoOf<Self::Call>,2242 _len: usize,2243 ) -> TransactionValidity {2244 Ok(ValidTransaction::default())2245 }22462247 fn pre_dispatch(2248 self,2249 who: &Self::AccountId,2250 call: &Self::Call,2251 info: &DispatchInfoOf<Self::Call>,2252 len: usize,2253 ) -> Result<Self::Pre, TransactionValidityError> {2254 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2255 Ok((self.0, who.clone(), imbalance, fee))2256 }22572258 fn post_dispatch(2259 pre: Self::Pre,2260 info: &DispatchInfoOf<Self::Call>,2261 post_info: &PostDispatchInfoOf<Self::Call>,2262 len: usize,2263 _result: &DispatchResult,2264 ) -> Result<(), TransactionValidityError> {2265 let (tip, who, imbalance, fee) = pre;2266 if let Some(payed) = imbalance {2267 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2268 len as u32, info, post_info, tip,2269 );2270 let refund = fee.saturating_sub(actual_fee);2271 let actual_payment =2272 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2273 &who, refund,2274 ) {2275 Ok(refund_imbalance) => {2276 // The refund cannot be larger than the up front payed max weight.2277 // `PostDispatchInfo::calc_unspent` guards against such a case.2278 match payed.offset(refund_imbalance) {2279 Ok(actual_payment) => actual_payment,2280 Err(_) => return Err(InvalidTransaction::Payment.into()),2281 }2282 }2283 // We do not recreate the account using the refund. The up front payment2284 // is gone in that case.2285 Err(_) => payed,2286 };2287 let imbalances = actual_payment.split(tip);2288 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2289 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2290 );2291 }2292 Ok(())2293 }2294}22952296// #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 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 _ => ()1196 };11971198 Ok(())1199 }1200 12011202 /// Set off-chain data schema.1203 /// 1204 /// # Permissions1205 /// 1206 /// * Collection Owner1207 /// * Collection Admin1208 /// 1209 /// # Arguments1210 /// 1211 /// * collection_id.1212 /// 1213 /// * schema: String representing the offchain data schema.1214 #[weight = T::WeightInfo::set_offchain_schema()]1215 pub fn set_offchain_schema(1216 origin,1217 collection_id: u64,1218 schema: Vec<u8>1219 ) -> DispatchResult {1220 let sender = ensure_signed(origin)?;1221 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12221223 let mut target_collection = <Collection<T>>::get(collection_id);1224 target_collection.offchain_schema = schema;1225 <Collection<T>>::insert(collection_id, target_collection);12261227 Ok(())1228 }12291230 /// Set const on-chain data schema.1231 /// 1232 /// # Permissions1233 /// 1234 /// * Collection Owner1235 /// * Collection Admin1236 /// 1237 /// # Arguments1238 /// 1239 /// * collection_id.1240 /// 1241 /// * schema: String representing the const on-chain data schema.1242 #[weight = T::WeightInfo::set_const_on_chain_schema()]1243 pub fn set_const_on_chain_schema (1244 origin,1245 collection_id: u64,1246 schema: Vec<u8>1247 ) -> DispatchResult {1248 let sender = ensure_signed(origin)?;1249 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12501251 let mut target_collection = <Collection<T>>::get(collection_id);1252 target_collection.const_on_chain_schema = schema;1253 <Collection<T>>::insert(collection_id, target_collection);12541255 Ok(())1256 }12571258 /// Set variable on-chain data schema.1259 /// 1260 /// # Permissions1261 /// 1262 /// * Collection Owner1263 /// * Collection Admin1264 /// 1265 /// # Arguments1266 /// 1267 /// * collection_id.1268 /// 1269 /// * schema: String representing the variable on-chain data schema.1270 #[weight = T::WeightInfo::set_const_on_chain_schema()]1271 pub fn set_variable_on_chain_schema (1272 origin,1273 collection_id: u64,1274 schema: Vec<u8>1275 ) -> DispatchResult {1276 let sender = ensure_signed(origin)?;1277 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12781279 let mut target_collection = <Collection<T>>::get(collection_id);1280 target_collection.variable_on_chain_schema = schema;1281 <Collection<T>>::insert(collection_id, target_collection);12821283 Ok(())1284 }12851286 // Sudo permissions function1287 #[weight = 0]1288 pub fn set_chain_limits(1289 origin,1290 limits: ChainLimits1291 ) -> DispatchResult {1292 ensure_root(origin)?;1293 <ChainLimit>::put(limits);1294 Ok(())1295 }12961297 /// Enable smart contract self-sponsoring.1298 /// 1299 /// # Permissions1300 /// 1301 /// * Contract Owner1302 /// 1303 /// # Arguments1304 /// 1305 /// * contract address1306 /// * enable flag1307 /// 1308 #[weight = 0]1309 pub fn enable_contract_sponsoring(1310 origin,1311 contract_address: T::AccountId,1312 enable: bool1313 ) -> DispatchResult {1314 let sender = ensure_signed(origin)?;1315 let mut is_owner = false;1316 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1317 let owner = <ContractOwner<T>>::get(&contract_address);1318 is_owner = sender == owner;1319 }1320 ensure!(is_owner, Error::<T>::NoPermission);13211322 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1323 Ok(())1324 }13251326 }1327}13281329impl<T: Trait> Module<T> {13301331 fn can_create_items_in_collection(collection_id: u64, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {13321333 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1334 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1335 Self::check_white_list(collection_id, owner)?;1336 Self::check_white_list(collection_id, sender)?;1337 }13381339 Ok(())1340 }13411342 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1343 match target_collection.mode1344 {1345 CollectionMode::NFT => {1346 if let CreateItemData::NFT(data) = data {1347 // check sizes1348 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, "const_data exceeded data limit.");1349 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, "variable_data exceeded data limit.");1350 } else {1351 fail!("Not NFT item data used to mint in NFT collection.");1352 }1353 },1354 CollectionMode::Fungible(_) => {1355 if let CreateItemData::Fungible(_) = data {1356 } else {1357 fail!("Not Fungible item data used to mint in Fungible collection.");1358 }1359 },1360 CollectionMode::ReFungible(_) => {1361 if let CreateItemData::ReFungible(data) = data {13621363 // check sizes1364 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, "const_data exceeded data limit.");1365 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, "variable_data exceeded data limit.");1366 } else {1367 fail!("Not Re Fungible item data used to mint in Re Fungible collection.");1368 }1369 },1370 _ => { fail!("Unexpected collection type."); }1371 };13721373 Ok(())1374 }13751376 fn create_item_no_validation(collection_id: u64, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1377 match data1378 {1379 CreateItemData::NFT(data) => {1380 let item = NftItemType {1381 collection: collection_id,1382 owner,1383 const_data: data.const_data,1384 variable_data: data.variable_data1385 };13861387 Self::add_nft_item(item)?;1388 },1389 CreateItemData::Fungible(_) => {1390 let item = FungibleItemType {1391 collection: collection_id,1392 owner,1393 value: (10 as u128).pow(collection.decimal_points)1394 };13951396 Self::add_fungible_item(item)?;1397 },1398 CreateItemData::ReFungible(data) => {1399 let mut owner_list = Vec::new();1400 let value = (10 as u128).pow(collection.decimal_points);1401 owner_list.push(Ownership {owner: owner.clone(), fraction: value});14021403 let item = ReFungibleItemType {1404 collection: collection_id,1405 owner: owner_list,1406 const_data: data.const_data,1407 variable_data: data.variable_data1408 };14091410 Self::add_refungible_item(item)?;1411 }1412 };141314141415 // call event1416 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));14171418 Ok(())1419 }14201421 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {1422 let current_index = <ItemListIndex>::get(item.collection)1423 .checked_add(1)1424 .ok_or(Error::<T>::NumOverflow)?;1425 let itemcopy = item.clone();1426 let owner = item.owner.clone();1427 let value = item.value as u64;14281429 Self::add_token_index(item.collection, current_index, owner.clone())?;14301431 <ItemListIndex>::insert(item.collection, current_index);1432 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);14331434 // Add current block1435 let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1436 <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);1437 1438 // Update balance1439 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1440 .checked_add(value)1441 .ok_or(Error::<T>::NumOverflow)?;1442 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);14431444 Ok(())1445 }14461447 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1448 let current_index = <ItemListIndex>::get(item.collection)1449 .checked_add(1)1450 .ok_or(Error::<T>::NumOverflow)?;1451 let itemcopy = item.clone();14521453 let value = item.owner.first().unwrap().fraction as u64;1454 let owner = item.owner.first().unwrap().owner.clone();14551456 Self::add_token_index(item.collection, current_index, owner.clone())?;14571458 <ItemListIndex>::insert(item.collection, current_index);1459 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);14601461 // Add current block1462 let block_number: T::BlockNumber = 0.into();1463 <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);14641465 // Update balance1466 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1467 .checked_add(value)1468 .ok_or(Error::<T>::NumOverflow)?;1469 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);14701471 Ok(())1472 }14731474 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1475 let current_index = <ItemListIndex>::get(item.collection)1476 .checked_add(1)1477 .ok_or(Error::<T>::NumOverflow)?;14781479 let item_owner = item.owner.clone();1480 let collection_id = item.collection.clone();1481 Self::add_token_index(collection_id, current_index, item.owner.clone())?;14821483 <ItemListIndex>::insert(collection_id, current_index);1484 <NftItemList<T>>::insert(collection_id, current_index, item);14851486 // Add current block1487 let block_number: T::BlockNumber = 0.into();1488 <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);14891490 // Update balance1491 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1492 .checked_add(1)1493 .ok_or(Error::<T>::NumOverflow)?;1494 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);14951496 Ok(())1497 }14981499 fn burn_refungible_item(1500 collection_id: u64,1501 item_id: u64,1502 owner: T::AccountId,1503 ) -> DispatchResult {1504 ensure!(1505 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1506 Error::<T>::TokenNotFound1507 );1508 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1509 let item = collection1510 .owner1511 .iter()1512 .filter(|&i| i.owner == owner)1513 .next()1514 .unwrap();1515 Self::remove_token_index(collection_id, item_id, owner.clone())?;15161517 // remove approve list1518 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));15191520 // update balance1521 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1522 .checked_sub(item.fraction as u64)1523 .ok_or(Error::<T>::NumOverflow)?;1524 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);15251526 <ReFungibleItemList<T>>::remove(collection_id, item_id);15271528 Ok(())1529 }15301531 fn burn_nft_item(collection_id: u64, item_id: u64) -> DispatchResult {1532 ensure!(1533 <NftItemList<T>>::contains_key(collection_id, item_id),1534 Error::<T>::TokenNotFound1535 );1536 let item = <NftItemList<T>>::get(collection_id, item_id);1537 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;15381539 // remove approve list1540 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));15411542 // update balance1543 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1544 .checked_sub(1)1545 .ok_or(Error::<T>::NumOverflow)?;1546 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1547 <NftItemList<T>>::remove(collection_id, item_id);15481549 Ok(())1550 }15511552 fn burn_fungible_item(collection_id: u64, item_id: u64) -> DispatchResult {1553 ensure!(1554 <FungibleItemList<T>>::contains_key(collection_id, item_id),1555 Error::<T>::TokenNotFound1556 );1557 let item = <FungibleItemList<T>>::get(collection_id, item_id);1558 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;15591560 // remove approve list1561 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));15621563 // update balance1564 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1565 .checked_sub(item.value as u64)1566 .ok_or(Error::<T>::NumOverflow)?;1567 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);15681569 <FungibleItemList<T>>::remove(collection_id, item_id);15701571 Ok(())1572 }15731574 fn collection_exists(collection_id: u64) -> DispatchResult {1575 ensure!(1576 <Collection<T>>::contains_key(collection_id),1577 Error::<T>::CollectionNotFound1578 );1579 Ok(())1580 }15811582 fn check_owner_permissions(collection_id: u64, subject: T::AccountId) -> DispatchResult {1583 Self::collection_exists(collection_id)?;15841585 let target_collection = <Collection<T>>::get(collection_id);1586 ensure!(1587 subject == target_collection.owner,1588 Error::<T>::NoPermission1589 );15901591 Ok(())1592 }15931594 fn is_owner_or_admin_permissions(collection_id: u64, subject: T::AccountId) -> bool {1595 let target_collection = <Collection<T>>::get(collection_id);1596 let mut result: bool = subject == target_collection.owner;1597 let exists = <AdminList<T>>::contains_key(collection_id);15981599 if !result & exists {1600 if <AdminList<T>>::get(collection_id).contains(&subject) {1601 result = true1602 }1603 }16041605 result1606 }16071608 fn check_owner_or_admin_permissions(1609 collection_id: u64,1610 subject: T::AccountId,1611 ) -> DispatchResult {1612 Self::collection_exists(collection_id)?;1613 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());16141615 ensure!(1616 result,1617 Error::<T>::NoPermission1618 );1619 Ok(())1620 }16211622 fn is_item_owner(subject: T::AccountId, collection_id: u64, item_id: u64) -> bool {1623 let target_collection = <Collection<T>>::get(collection_id);16241625 match target_collection.mode {1626 CollectionMode::NFT => {1627 <NftItemList<T>>::get(collection_id, item_id).owner == subject1628 }1629 CollectionMode::Fungible(_) => {1630 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1631 }1632 CollectionMode::ReFungible(_) => {1633 <ReFungibleItemList<T>>::get(collection_id, item_id)1634 .owner1635 .iter()1636 .any(|i| i.owner == subject)1637 }1638 CollectionMode::Invalid => false,1639 }1640 }16411642 fn check_white_list(collection_id: u64, address: &T::AccountId) -> DispatchResult {1643 let mes = Error::<T>::AddresNotInWhiteList;1644 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1645 let wl = <WhiteList<T>>::get(collection_id);1646 ensure!(wl.contains(address), mes);16471648 Ok(())1649 }16501651 fn transfer_fungible(1652 collection_id: u64,1653 item_id: u64,1654 value: u64,1655 owner: T::AccountId,1656 new_owner: T::AccountId,1657 ) -> DispatchResult {1658 ensure!(1659 <FungibleItemList<T>>::contains_key(collection_id, item_id),1660 Error::<T>::TokenNotFound1661 );16621663 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1664 let amount = full_item.value;16651666 ensure!(amount >= value.into(), Error::<T>::TokenValueTooLow);16671668 // update balance1669 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1670 .checked_sub(value)1671 .ok_or(Error::<T>::NumOverflow)?;1672 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);16731674 let mut new_owner_account_id = 0;1675 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1676 if new_owner_items.len() > 0 {1677 new_owner_account_id = new_owner_items[0];1678 }16791680 let val64 = value.into();16811682 // transfer1683 if amount == val64 && new_owner_account_id == 0 {1684 // change owner1685 // new owner do not have account1686 let mut new_full_item = full_item.clone();1687 new_full_item.owner = new_owner.clone();1688 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);16891690 // update balance1691 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1692 .checked_add(value)1693 .ok_or(Error::<T>::NumOverflow)?;1694 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);16951696 // update index collection1697 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1698 } else {1699 let mut new_full_item = full_item.clone();1700 new_full_item.value -= val64;17011702 // separate amount1703 if new_owner_account_id > 0 {1704 // new owner has account1705 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1706 item.value += val64;17071708 // update balance1709 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1710 .checked_add(value)1711 .ok_or(Error::<T>::NumOverflow)?;1712 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);17131714 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1715 } else {1716 // new owner do not have account1717 let item = FungibleItemType {1718 collection: collection_id,1719 owner: new_owner.clone(),1720 value: val64,1721 };17221723 Self::add_fungible_item(item)?;1724 }17251726 if amount == val64 {1727 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;17281729 // remove approve list1730 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1731 <FungibleItemList<T>>::remove(collection_id, item_id);1732 }17331734 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1735 }17361737 Ok(())1738 }17391740 fn transfer_refungible(1741 collection_id: u64,1742 item_id: u64,1743 value: u64,1744 owner: T::AccountId,1745 new_owner: T::AccountId,1746 ) -> DispatchResult {1747 ensure!(1748 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1749 Error::<T>::TokenNotFound1750 );17511752 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1753 let item = full_item1754 .owner1755 .iter()1756 .filter(|i| i.owner == owner)1757 .next()1758 .ok_or(Error::<T>::NumOverflow)?;1759 let amount = item.fraction;17601761 ensure!(amount >= value.into(), Error::<T>::TokenValueTooLow);17621763 // update balance1764 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1765 .checked_sub(value)1766 .ok_or(Error::<T>::NumOverflow)?;1767 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);17681769 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1770 .checked_add(value)1771 .ok_or(Error::<T>::NumOverflow)?;1772 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);17731774 let old_owner = item.owner.clone();1775 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);1776 let val64 = value.into();17771778 // transfer1779 if amount == val64 && !new_owner_has_account {1780 // change owner1781 // new owner do not have account1782 let mut new_full_item = full_item.clone();1783 new_full_item1784 .owner1785 .iter_mut()1786 .find(|i| i.owner == owner)1787 .unwrap()1788 .owner = new_owner.clone();1789 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);17901791 // update index collection1792 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1793 } else {1794 let mut new_full_item = full_item.clone();1795 new_full_item1796 .owner1797 .iter_mut()1798 .find(|i| i.owner == owner)1799 .unwrap()1800 .fraction -= val64;18011802 // separate amount1803 if new_owner_has_account {1804 // new owner has account1805 new_full_item1806 .owner1807 .iter_mut()1808 .find(|i| i.owner == new_owner)1809 .unwrap()1810 .fraction += val64;1811 } else {1812 // new owner do not have account1813 new_full_item.owner.push(Ownership {1814 owner: new_owner.clone(),1815 fraction: val64,1816 });1817 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1818 }18191820 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1821 }18221823 Ok(())1824 }18251826 fn transfer_nft(1827 collection_id: u64,1828 item_id: u64,1829 sender: T::AccountId,1830 new_owner: T::AccountId,1831 ) -> DispatchResult {1832 ensure!(1833 <NftItemList<T>>::contains_key(collection_id, item_id),1834 Error::<T>::TokenNotFound1835 );18361837 let mut item = <NftItemList<T>>::get(collection_id, item_id);18381839 ensure!(1840 sender == item.owner,1841 Error::<T>::MustBeTokenOwner1842 );18431844 // update balance1845 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1846 .checked_sub(1)1847 .ok_or(Error::<T>::NumOverflow)?;1848 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);18491850 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1851 .checked_add(1)1852 .ok_or(Error::<T>::NumOverflow)?;1853 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);18541855 // change owner1856 let old_owner = item.owner.clone();1857 item.owner = new_owner.clone();1858 <NftItemList<T>>::insert(collection_id, item_id, item);18591860 // update index collection1861 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;18621863 // reset approved list1864 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));1865 Ok(())1866 }1867 1868 fn item_exists(1869 collection_id: u64,1870 item_id: u64,1871 mode: &CollectionMode1872 ) -> DispatchResult {1873 match mode {1874 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1875 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1876 CollectionMode::Fungible(_) => ensure!(<FungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1877 _ => ()1878 };1879 1880 Ok(())1881 }18821883 fn set_re_fungible_variable_data(1884 collection_id: u64,1885 item_id: u64,1886 data: Vec<u8>1887 ) -> DispatchResult {1888 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);18891890 item.variable_data = data;18911892 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);18931894 Ok(())1895 }18961897 fn set_nft_variable_data(1898 collection_id: u64,1899 item_id: u64,1900 data: Vec<u8>1901 ) -> DispatchResult {1902 let mut item = <NftItemList<T>>::get(collection_id, item_id);1903 1904 item.variable_data = data;19051906 <NftItemList<T>>::insert(collection_id, item_id, item);1907 1908 Ok(())1909 }19101911 fn init_collection(item: &CollectionType<T::AccountId>) {1912 // check params1913 assert!(1914 item.decimal_points <= 4,1915 "decimal_points parameter must be lower than 4"1916 );1917 assert!(1918 item.name.len() <= 64,1919 "Collection name can not be longer than 63 char"1920 );1921 assert!(1922 item.name.len() <= 256,1923 "Collection description can not be longer than 255 char"1924 );1925 assert!(1926 item.token_prefix.len() <= 16,1927 "Token prefix can not be longer than 15 char"1928 );19291930 // Generate next collection ID1931 let next_id = CreatedCollectionCount::get()1932 .checked_add(1)1933 .unwrap();19341935 CreatedCollectionCount::put(next_id);1936 }19371938 fn init_nft_token(item: &NftItemType<T::AccountId>) {1939 let current_index = <ItemListIndex>::get(item.collection)1940 .checked_add(1)1941 .unwrap();19421943 let item_owner = item.owner.clone();1944 let collection_id = item.collection.clone();1945 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();19461947 <ItemListIndex>::insert(collection_id, current_index);19481949 // Update balance1950 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1951 .checked_add(1)1952 .unwrap();1953 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);1954 }19551956 fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {1957 let current_index = <ItemListIndex>::get(item.collection)1958 .checked_add(1)1959 .unwrap();1960 let owner = item.owner.clone();1961 let value = item.value as u64;19621963 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();19641965 <ItemListIndex>::insert(item.collection, current_index);19661967 // Update balance1968 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1969 .checked_add(value)1970 .unwrap();1971 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1972 }19731974 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {1975 let current_index = <ItemListIndex>::get(item.collection)1976 .checked_add(1)1977 .unwrap();19781979 let value = item.owner.first().unwrap().fraction as u64;1980 let owner = item.owner.first().unwrap().owner.clone();19811982 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();19831984 <ItemListIndex>::insert(item.collection, current_index);19851986 // Update balance1987 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1988 .checked_add(value)1989 .unwrap();1990 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1991 }19921993 fn add_token_index(collection_id: u64, item_index: u64, owner: T::AccountId) -> DispatchResult {19941995 // add to account limit1996 if <AccountItemCount<T>>::contains_key(owner.clone()) {19971998 // bound Owned tokens by a single address1999 let count = <AccountItemCount<T>>::get(owner.clone());2000 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);20012002 <AccountItemCount<T>>::insert(owner.clone(), count2003 .checked_add(1)2004 .ok_or(Error::<T>::NumOverflow)?);2005 }2006 else {2007 <AccountItemCount<T>>::insert(owner.clone(), 1);2008 }20092010 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2011 if list_exists {2012 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2013 let item_contains = list.contains(&item_index.clone());20142015 if !item_contains {2016 list.push(item_index.clone());2017 }20182019 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2020 } else {2021 let mut itm = Vec::new();2022 itm.push(item_index.clone());2023 <AddressTokens<T>>::insert(collection_id, owner, itm);2024 2025 }20262027 Ok(())2028 }20292030 fn remove_token_index(2031 collection_id: u64,2032 item_index: u64,2033 owner: T::AccountId,2034 ) -> DispatchResult {20352036 // update counter2037 <AccountItemCount<T>>::insert(owner.clone(), 2038 <AccountItemCount<T>>::get(owner.clone())2039 .checked_sub(1)2040 .ok_or(Error::<T>::NumOverflow)?);204120422043 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2044 if list_exists {2045 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2046 let item_contains = list.contains(&item_index.clone());20472048 if item_contains {2049 list.retain(|&item| item != item_index);2050 <AddressTokens<T>>::insert(collection_id, owner, list);2051 }2052 }20532054 Ok(())2055 }20562057 fn move_token_index(2058 collection_id: u64,2059 item_index: u64,2060 old_owner: T::AccountId,2061 new_owner: T::AccountId,2062 ) -> DispatchResult {2063 Self::remove_token_index(collection_id, item_index, old_owner)?;2064 Self::add_token_index(collection_id, item_index, new_owner)?;20652066 Ok(())2067 }2068}20692070////////////////////////////////////////////////////////////////////////////////////////////////////2071// Economic models2072// #region20732074/// Fee multiplier.2075pub type Multiplier = FixedU128;20762077type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2078 <T as system::Trait>::AccountId,2079>>::Balance;2080type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2081 <T as system::Trait>::AccountId,2082>>::NegativeImbalance;20832084/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2085/// in the queue.2086#[derive(Encode, Decode, Clone, Eq, PartialEq)]2087pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2088 #[codec(compact)] BalanceOf<T>2089);20902091impl<T: Trait + Send + Sync> sp_std::fmt::Debug2092 for ChargeTransactionPayment<T>2093{2094 #[cfg(feature = "std")]2095 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2096 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2097 }2098 #[cfg(not(feature = "std"))]2099 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2100 Ok(())2101 }2102}21032104impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2105where2106 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2107 BalanceOf<T>: Send + Sync + FixedPointOperand,2108{2109 /// utility constructor. Used only in client/factory code.2110 pub fn from(fee: BalanceOf<T>) -> Self {2111 Self(fee)2112 }21132114 pub fn traditional_fee(2115 len: usize,2116 info: &DispatchInfoOf<T::Call>,2117 tip: BalanceOf<T>,2118 ) -> BalanceOf<T>2119 where2120 T::Call: Dispatchable<Info = DispatchInfo>,2121 {2122 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2123 }21242125 fn withdraw_fee(2126 &self,2127 who: &T::AccountId,2128 call: &T::Call,2129 info: &DispatchInfoOf<T::Call>,2130 len: usize,2131 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2132 let tip = self.0;21332134 // Set fee based on call type. Creating collection costs 1 Unique.2135 // All other transactions have traditional fees so far2136 // let fee = match call.is_sub_type() {2137 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2138 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2139 // // _ => <BalanceOf<T>>::from(100)2140 // };2141 let fee = Self::traditional_fee(len, info, tip);21422143 // Determine who is paying transaction fee based on ecnomic model2144 // Parse call to extract collection ID and access collection sponsor2145 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2146 Some(Call::create_item(collection_id, _properties, _owner)) => {2147 <Collection<T>>::get(collection_id).sponsor2148 }2149 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {2150 let _collection_mode = <Collection<T>>::get(collection_id).mode;21512152 // sponsor timeout2153 let sponsor_transfer = match _collection_mode {2154 CollectionMode::NFT => {2155 let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);2156 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2157 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();2158 if block_number >= limit_time {2159 <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);2160 true2161 }2162 else {2163 false2164 }2165 }2166 CollectionMode::Fungible(_) => {2167 let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);2168 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2169 if basket.iter().any(|i| i.address == _new_owner.clone())2170 {2171 let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();2172 let limit_time = item.start_block + ChainLimit::get().fungible_sponsor_transfer_timeout.into();2173 if block_number >= limit_time {2174 basket.retain(|x| x.address == item.address);2175 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });2176 <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);2177 true2178 }2179 else {2180 false2181 }2182 }2183 else {2184 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});2185 true2186 }2187 }2188 CollectionMode::ReFungible(_) => {2189 let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);2190 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2191 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();2192 if block_number >= limit_time {2193 <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);2194 true2195 } else {2196 false2197 }2198 }2199 _ => {2200 false2201 },2202 };22032204 if !sponsor_transfer {2205 T::AccountId::default()2206 } else {2207 <Collection<T>>::get(collection_id).sponsor2208 }2209 }22102211 _ => T::AccountId::default(),2212 };22132214 // Sponsor smart contracts2215 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {22162217 // On instantiation: set the contract owner2218 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {22192220 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2221 code_hash,2222 &data,2223 &who,2224 );2225 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());22262227 T::AccountId::default()2228 },22292230 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2231 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {22322233 let mut sp = T::AccountId::default();2234 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());2235 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2236 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2237 sp = called_contract;2238 }2239 }22402241 sp2242 },22432244 _ => sponsor,2245 };22462247 let mut who_pays_fee: T::AccountId = sponsor.clone();2248 if sponsor == T::AccountId::default() {2249 who_pays_fee = who.clone();2250 }22512252 // Only mess with balances if fee is not zero.2253 if fee.is_zero() {2254 return Ok((fee, None));2255 }22562257 match <T as transaction_payment::Trait>::Currency::withdraw(2258 &who_pays_fee,2259 fee,2260 if tip.is_zero() {2261 WithdrawReason::TransactionPayment.into()2262 } else {2263 WithdrawReason::TransactionPayment | WithdrawReason::Tip2264 },2265 ExistenceRequirement::KeepAlive,2266 ) {2267 Ok(imbalance) => Ok((fee, Some(imbalance))),2268 Err(_) => Err(InvalidTransaction::Payment.into()),2269 }2270 }2271}227222732274impl<T: Trait + Send + Sync> SignedExtension2275 for ChargeTransactionPayment<T>2276where2277 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2278 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2279{2280 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2281 type AccountId = T::AccountId;2282 type Call = T::Call;2283 type AdditionalSigned = ();2284 type Pre = (2285 BalanceOf<T>,2286 Self::AccountId,2287 Option<NegativeImbalanceOf<T>>,2288 BalanceOf<T>,2289 );2290 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2291 Ok(())2292 }22932294 fn validate(2295 &self,2296 _who: &Self::AccountId,2297 _call: &Self::Call,2298 _info: &DispatchInfoOf<Self::Call>,2299 _len: usize,2300 ) -> TransactionValidity {2301 Ok(ValidTransaction::default())2302 }23032304 fn pre_dispatch(2305 self,2306 who: &Self::AccountId,2307 call: &Self::Call,2308 info: &DispatchInfoOf<Self::Call>,2309 len: usize,2310 ) -> Result<Self::Pre, TransactionValidityError> {2311 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2312 Ok((self.0, who.clone(), imbalance, fee))2313 }23142315 fn post_dispatch(2316 pre: Self::Pre,2317 info: &DispatchInfoOf<Self::Call>,2318 post_info: &PostDispatchInfoOf<Self::Call>,2319 len: usize,2320 _result: &DispatchResult,2321 ) -> Result<(), TransactionValidityError> {2322 let (tip, who, imbalance, fee) = pre;2323 if let Some(payed) = imbalance {2324 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2325 len as u32, info, post_info, tip,2326 );2327 let refund = fee.saturating_sub(actual_fee);2328 let actual_payment =2329 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2330 &who, refund,2331 ) {2332 Ok(refund_imbalance) => {2333 // The refund cannot be larger than the up front payed max weight.2334 // `PostDispatchInfo::calc_unspent` guards against such a case.2335 match payed.offset(refund_imbalance) {2336 Ok(actual_payment) => actual_payment,2337 Err(_) => return Err(InvalidTransaction::Payment.into()),2338 }2339 }2340 // We do not recreate the account using the refund. The up front payment2341 // is gone in that case.2342 Err(_) => payed,2343 };2344 let imbalances = actual_payment.split(tip);2345 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2346 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2347 );2348 }2349 Ok(())2350 }2351}23522353// #endregion