difftreelog
Merge pull request #10 from usetech-llc/feature/NFTPAR-96
in: master
Feature/nftpar-96
3 files changed
pallets/nft/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/nft/src/benchmarking.rs
+++ b/pallets/nft/src/benchmarking.rs
@@ -165,7 +165,7 @@
let token_prefix1: Vec<u8> = b"token_prefix1".to_vec();
let mode: CollectionMode = CollectionMode::NFT;
let caller: T::AccountId = T::AccountId::from(whitelisted_caller());
- let nft_data = CreateNftData {
+ let mut nft_data = CreateNftData {
const_data: vec![],
variable_data: vec![]
};
@@ -173,7 +173,7 @@
nft_data.const_data.push(10);
nft_data.variable_data.push(10);
}
- let mut data = CreateItemData::NFT(nft_data);
+ let data = CreateItemData::NFT(nft_data);
Nft::<T>::create_collection(RawOrigin::Signed(caller.clone()).into(), col_name1.clone(), col_desc1.clone(), token_prefix1.clone(), mode.clone())?;
}: create_item(RawOrigin::Signed(caller.clone()), 2, caller.clone(), data)
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, parameter_types, fail,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};25// use frame_support::weights::{Weight, constants::RocksDbWeight as DbWeight};2627use frame_system::{self as system, ensure_signed, ensure_root};28use sp_runtime::sp_std::prelude::Vec;29use sp_runtime::{30 traits::{31 DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SignedExtension, Zero,32 },33 transaction_validity::{34 InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,35 },36 FixedPointOperand, FixedU128,37};38use pallet_contracts::ContractAddressFor;39use sp_runtime::traits::StaticLookup;4041#[cfg(test)]42mod mock;4344#[cfg(test)]45mod tests;4647mod default_weights;4849// Structs50// #region5152#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]53#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]54pub enum CollectionMode {55 Invalid,56 NFT,57 // decimal points58 Fungible(u32),59 // decimal points60 ReFungible(u32),61}6263impl Into<u8> for CollectionMode {64 fn into(self) -> u8 {65 match self {66 CollectionMode::Invalid => 0,67 CollectionMode::NFT => 1,68 CollectionMode::Fungible(_) => 2,69 CollectionMode::ReFungible(_) => 3,70 }71 }72}7374#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]75#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]76pub enum AccessMode {77 Normal,78 WhiteList,79}80impl Default for AccessMode {81 fn default() -> Self {82 Self::Normal83 }84}8586impl Default for CollectionMode {87 fn default() -> Self {88 Self::Invalid89 }90}9192#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]93#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]94pub struct Ownership<AccountId> {95 pub owner: AccountId,96 pub fraction: u128,97}9899#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]100#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]101pub struct CollectionType<AccountId> {102 pub owner: AccountId,103 pub mode: CollectionMode,104 pub access: AccessMode,105 pub decimal_points: u32,106 pub name: Vec<u16>, // 64 include null escape char107 pub description: Vec<u16>, // 256 include null escape char108 pub token_prefix: Vec<u8>, // 16 include null escape char109 pub mint_mode: bool,110 pub offchain_schema: Vec<u8>,111 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender112 pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship113 pub variable_on_chain_schema: Vec<u8>, //114 pub const_on_chain_schema: Vec<u8>, //115}116117#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]118#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]119pub struct CollectionAdminsType<AccountId> {120 pub admin: AccountId,121 pub collection_id: u64,122}123124#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]125#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]126pub struct NftItemType<AccountId> {127 pub collection: u64,128 pub owner: AccountId,129 pub const_data: Vec<u8>,130 pub variable_data: Vec<u8>,131}132133#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]134#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]135pub struct FungibleItemType<AccountId> {136 pub collection: u64,137 pub owner: AccountId,138 pub value: u128,139}140141#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]142#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]143pub struct ReFungibleItemType<AccountId> {144 pub collection: u64,145 pub owner: Vec<Ownership<AccountId>>,146 pub const_data: Vec<u8>,147 pub variable_data: Vec<u8>,148}149150#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]151#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]152pub struct ApprovePermissions<AccountId> {153 pub approved: AccountId,154 pub amount: u64,155}156157#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]158#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]159pub struct VestingItem<AccountId, Moment> {160 pub sender: AccountId,161 pub recipient: AccountId,162 pub collection_id: u64,163 pub item_id: u64,164 pub amount: u64,165 pub vesting_date: Moment,166}167168#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]169#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]170pub struct BasketItem<AccountId, BlockNumber> {171 pub address: AccountId,172 pub start_block: BlockNumber,173}174175#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]176#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]177pub struct ChainLimits {178 pub collection_numbers_limit: u64,179 pub account_token_ownership_limit: u64,180 pub collections_admins_limit: u64,181 pub custom_data_limit: u32,182183 // Timeouts for item types in passed blocks184 pub nft_sponsor_transfer_timeout: u32,185 pub fungible_sponsor_transfer_timeout: u32,186 pub refungible_sponsor_transfer_timeout: u32,187}188189pub trait WeightInfo {190 fn create_collection() -> Weight;191 fn destroy_collection() -> Weight;192 fn add_to_white_list() -> Weight;193 fn remove_from_white_list() -> Weight;194 fn set_public_access_mode() -> Weight;195 fn set_mint_permission() -> Weight;196 fn change_collection_owner() -> Weight;197 fn add_collection_admin() -> Weight;198 fn remove_collection_admin() -> Weight;199 fn set_collection_sponsor() -> Weight;200 fn confirm_sponsorship() -> Weight;201 fn remove_collection_sponsor() -> Weight;202 fn create_item(s: usize) -> Weight;203 fn burn_item() -> Weight;204 fn transfer() -> Weight;205 fn approve() -> Weight;206 fn transfer_from() -> Weight;207 fn set_offchain_schema() -> Weight;208 fn set_const_on_chain_schema() -> Weight;209 fn set_variable_on_chain_schema() -> Weight;210 fn set_variable_meta_data() -> Weight;211 // fn enable_contract_sponsoring() -> Weight;212}213214#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]215#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]216pub struct CreateNftData {217 pub const_data: Vec<u8>,218 pub variable_data: Vec<u8>,219}220221#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]222#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]223pub struct CreateFungibleData {224}225226#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]227#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]228pub struct CreateReFungibleData {229 pub const_data: Vec<u8>,230 pub variable_data: Vec<u8>,231}232233#[derive(Encode, Decode, Debug, Clone, PartialEq)]234#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]235pub enum CreateItemData {236 NFT(CreateNftData),237 Fungible(CreateFungibleData),238 ReFungible(CreateReFungibleData)239}240241impl CreateItemData {242 pub fn len(&self) -> usize {243 let len = match self {244 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),245 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),246 _ => 0247 };248 249 return len;250 }251}252253impl From<CreateNftData> for CreateItemData {254 fn from(item: CreateNftData) -> Self {255 CreateItemData::NFT(item)256 }257}258259impl From<CreateReFungibleData> for CreateItemData {260 fn from(item: CreateReFungibleData) -> Self {261 CreateItemData::ReFungible(item)262 }263}264265impl From<CreateFungibleData> for CreateItemData {266 fn from(item: CreateFungibleData) -> Self {267 CreateItemData::Fungible(item)268 }269}270271pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {272 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;273274 /// Weight information for extrinsics in this pallet.275 type WeightInfo: WeightInfo;276}277278#[cfg(feature = "runtime-benchmarks")]279mod benchmarking;280281// #endregion282283decl_storage! {284 trait Store for Module<T: Trait> as Nft {285286 // Private members287 NextCollectionID: u64;288 CreatedCollectionCount: u64;289 ChainVersion: u64;290 ItemListIndex: map hasher(blake2_128_concat) u64 => u64;291292 // Chain limits struct293 pub ChainLimit get(fn chain_limit) config(): ChainLimits;294295 // Bound counters296 CollectionCount: u64;297 pub AccountItemCount get(fn account_item_count): map hasher(identity) T::AccountId => u64;298299 // Basic collections300 pub Collection get(fn collection) config(): map hasher(identity) u64 => CollectionType<T::AccountId>;301 pub AdminList get(fn admin_list_collection): map hasher(identity) u64 => Vec<T::AccountId>;302 pub WhiteList get(fn white_list): map hasher(identity) u64 => Vec<T::AccountId>;303304 /// Balance owner per collection map305 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => u64;306307 /// second parameter: item id + owner account id308 pub ApprovedList get(fn approved): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) (u64, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;309310 /// Item collections311 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => NftItemType<T::AccountId>;312 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => FungibleItemType<T::AccountId>;313 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => ReFungibleItemType<T::AccountId>;314315 /// Index list316 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => Vec<u64>;317318 /// Tokens transfer baskets319 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => T::BlockNumber;320 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>>;321 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => T::BlockNumber;322323 // Contract Sponsorship and Ownership324 pub ContractOwner get(fn contract_owner): map hasher(identity) T::AccountId => T::AccountId;325 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(identity) T::AccountId => bool;326 }327 add_extra_genesis {328 build(|config: &GenesisConfig<T>| {329 // Modification of storage330 for (_num, _c) in &config.collection {331 <Module<T>>::init_collection(_c);332 }333334 for (_num, _q, _i) in &config.nft_item_id {335 <Module<T>>::init_nft_token(_i);336 }337338 for (_num, _q, _i) in &config.fungible_item_id {339 <Module<T>>::init_fungible_token(_i);340 }341342 for (_num, _q, _i) in &config.refungible_item_id {343 <Module<T>>::init_refungible_token(_i);344 }345 })346 }347}348349decl_event!(350 pub enum Event<T>351 where352 AccountId = <T as system::Trait>::AccountId,353 {354 /// New collection was created355 /// 356 /// # Arguments357 /// 358 /// * collection_id: Globally unique identifier of newly created collection.359 /// 360 /// * mode: [CollectionMode] converted into u8.361 /// 362 /// * account_id: Collection owner.363 Created(u64, u8, AccountId),364365 /// New item was created.366 /// 367 /// # Arguments368 /// 369 /// * collection_id: Id of the collection where item was created.370 /// 371 /// * item_id: Id of an item. Unique within the collection.372 ItemCreated(u64, u64),373374 /// Collection item was burned.375 /// 376 /// # Arguments377 /// 378 /// collection_id.379 /// 380 /// item_id: Identifier of burned NFT.381 ItemDestroyed(u64, u64),382 }383);384385decl_module! {386 pub struct Module<T: Trait> for enum Call where origin: T::Origin {387388 fn deposit_event() = default;389390 fn on_initialize(now: T::BlockNumber) -> Weight {391392 if ChainVersion::get() < 2393 {394 let value = NextCollectionID::get();395 CreatedCollectionCount::put(value);396 ChainVersion::put(2);397 }398399 0400 }401402 /// 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.403 /// 404 /// # Permissions405 /// 406 /// * Anyone.407 /// 408 /// # Arguments409 /// 410 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.411 /// 412 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.413 /// 414 /// * token_prefix: UTF-8 string with token prefix.415 /// 416 /// * mode: [CollectionMode] collection type and type dependent data.417 // returns collection ID418 #[weight = T::WeightInfo::create_collection()]419 pub fn create_collection(origin,420 collection_name: Vec<u16>,421 collection_description: Vec<u16>,422 token_prefix: Vec<u8>,423 mode: CollectionMode) -> DispatchResult {424425 // Anyone can create a collection426 let who = ensure_signed(origin)?;427428 let decimal_points = match mode {429 CollectionMode::Fungible(points) => points,430 CollectionMode::ReFungible(points) => points,431 _ => 0432 };433434 // bound Total number of collections435 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, "Total collections bound exceeded");436437 // check params438 ensure!(decimal_points <= 4, "decimal_points parameter must be lower than 4");439440 let mut name = collection_name.to_vec();441 name.push(0);442 ensure!(name.len() <= 64, "Collection name can not be longer than 63 char");443444 let mut description = collection_description.to_vec();445 description.push(0);446 ensure!(name.len() <= 256, "Collection description can not be longer than 255 char");447448 let mut prefix = token_prefix.to_vec();449 prefix.push(0);450 ensure!(prefix.len() <= 16, "Token prefix can not be longer than 15 char");451452 // Generate next collection ID453 let next_id = CreatedCollectionCount::get()454 .checked_add(1)455 .expect("collection id error");456457 // bound counter458 let total = CollectionCount::get()459 .checked_add(1)460 .expect("collection counter error");461462 CreatedCollectionCount::put(next_id);463 CollectionCount::put(total);464465 // Create new collection466 let new_collection = CollectionType {467 owner: who.clone(),468 name: name,469 mode: mode.clone(),470 mint_mode: false,471 access: AccessMode::Normal,472 description: description,473 decimal_points: decimal_points,474 token_prefix: prefix,475 offchain_schema: Vec::new(),476 sponsor: T::AccountId::default(),477 unconfirmed_sponsor: T::AccountId::default(),478 variable_on_chain_schema: Vec::new(),479 const_on_chain_schema: Vec::new(),480 };481482 // Add new collection to map483 <Collection<T>>::insert(next_id, new_collection);484485 // call event486 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));487488 Ok(())489 }490491 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.492 /// 493 /// # Permissions494 /// 495 /// * Collection Owner.496 /// 497 /// # Arguments498 /// 499 /// * collection_id: collection to destroy.500 #[weight = T::WeightInfo::destroy_collection()]501 pub fn destroy_collection(origin, collection_id: u64) -> DispatchResult {502503 let sender = ensure_signed(origin)?;504 Self::check_owner_permissions(collection_id, sender)?;505506 <AddressTokens<T>>::remove_prefix(collection_id);507 <ApprovedList<T>>::remove_prefix(collection_id);508 <Balance<T>>::remove_prefix(collection_id);509 <ItemListIndex>::remove(collection_id);510 <AdminList<T>>::remove(collection_id);511 <Collection<T>>::remove(collection_id);512 <WhiteList<T>>::remove(collection_id);513514 <NftItemList<T>>::remove_prefix(collection_id);515 <FungibleItemList<T>>::remove_prefix(collection_id);516 <ReFungibleItemList<T>>::remove_prefix(collection_id);517518 <NftTransferBasket<T>>::remove_prefix(collection_id);519 <FungibleTransferBasket<T>>::remove_prefix(collection_id);520 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);521522 if CollectionCount::get() > 0523 {524 // bound couter525 let total = CollectionCount::get()526 .checked_sub(1)527 .expect("collection counter error");528529 CollectionCount::put(total);530 }531532 Ok(())533 }534535 /// Add an address to white list.536 /// 537 /// # Permissions538 /// 539 /// * Collection Owner540 /// * Collection Admin541 /// 542 /// # Arguments543 /// 544 /// * collection_id.545 /// 546 /// * address.547 #[weight = T::WeightInfo::add_to_white_list()]548 pub fn add_to_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{549550 let sender = ensure_signed(origin)?;551 Self::check_owner_or_admin_permissions(collection_id, sender)?;552553 let mut white_list_collection: Vec<T::AccountId>;554 if <WhiteList<T>>::contains_key(collection_id) {555 white_list_collection = <WhiteList<T>>::get(collection_id);556 if !white_list_collection.contains(&address.clone())557 {558 white_list_collection.push(address.clone());559 }560 }561 else {562 white_list_collection = Vec::new();563 white_list_collection.push(address.clone());564 }565566 <WhiteList<T>>::insert(collection_id, white_list_collection);567 Ok(())568 }569570 /// Remove an address from white list.571 /// 572 /// # Permissions573 /// 574 /// * Collection Owner575 /// * Collection Admin576 /// 577 /// # Arguments578 /// 579 /// * collection_id.580 /// 581 /// * address.582 #[weight = T::WeightInfo::remove_from_white_list()]583 pub fn remove_from_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{584585 let sender = ensure_signed(origin)?;586 Self::check_owner_or_admin_permissions(collection_id, sender)?;587588 if <WhiteList<T>>::contains_key(collection_id) {589 let mut white_list_collection = <WhiteList<T>>::get(collection_id);590 if white_list_collection.contains(&address.clone())591 {592 white_list_collection.retain(|i| *i != address.clone());593 <WhiteList<T>>::insert(collection_id, white_list_collection);594 }595 }596597 Ok(())598 }599600 /// Toggle between normal and white list access for the methods with access for `Anyone`.601 /// 602 /// # Permissions603 /// 604 /// * Collection Owner.605 /// 606 /// # Arguments607 /// 608 /// * collection_id.609 /// 610 /// * mode: [AccessMode]611 #[weight = T::WeightInfo::set_public_access_mode()]612 pub fn set_public_access_mode(origin, collection_id: u64, mode: AccessMode) -> DispatchResult613 {614 let sender = ensure_signed(origin)?;615616 Self::check_owner_permissions(collection_id, sender)?;617 let mut target_collection = <Collection<T>>::get(collection_id);618 target_collection.access = mode;619 <Collection<T>>::insert(collection_id, target_collection);620621 Ok(())622 }623624 /// Allows Anyone to create tokens if:625 /// * White List is enabled, and626 /// * Address is added to white list, and627 /// * This method was called with True parameter628 /// 629 /// # Permissions630 /// * Collection Owner631 ///632 /// # Arguments633 /// 634 /// * collection_id.635 /// 636 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.637 #[weight = T::WeightInfo::set_mint_permission()]638 pub fn set_mint_permission(origin, collection_id: u64, mint_permission: bool) -> DispatchResult639 {640 let sender = ensure_signed(origin)?;641642 Self::check_owner_permissions(collection_id, sender)?;643 let mut target_collection = <Collection<T>>::get(collection_id);644 target_collection.mint_mode = mint_permission;645 <Collection<T>>::insert(collection_id, target_collection);646647 Ok(())648 }649650 /// Change the owner of the collection.651 /// 652 /// # Permissions653 /// 654 /// * Collection Owner.655 /// 656 /// # Arguments657 /// 658 /// * collection_id.659 /// 660 /// * new_owner.661 #[weight = T::WeightInfo::change_collection_owner()]662 pub fn change_collection_owner(origin, collection_id: u64, new_owner: T::AccountId) -> DispatchResult {663664 let sender = ensure_signed(origin)?;665 Self::check_owner_permissions(collection_id, sender)?;666 let mut target_collection = <Collection<T>>::get(collection_id);667 target_collection.owner = new_owner;668 <Collection<T>>::insert(collection_id, target_collection);669670 Ok(())671 }672673 /// Adds an admin of the Collection.674 /// 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. 675 /// 676 /// # Permissions677 /// 678 /// * Collection Owner.679 /// * Collection Admin.680 /// 681 /// # Arguments682 /// 683 /// * collection_id: ID of the Collection to add admin for.684 /// 685 /// * new_admin_id: Address of new admin to add.686 #[weight = T::WeightInfo::add_collection_admin()]687 pub fn add_collection_admin(origin, collection_id: u64, new_admin_id: T::AccountId) -> DispatchResult {688689 let sender = ensure_signed(origin)?;690 Self::check_owner_or_admin_permissions(collection_id, sender)?;691 let mut admin_arr: Vec<T::AccountId> = Vec::new();692693 if <AdminList<T>>::contains_key(collection_id)694 {695 admin_arr = <AdminList<T>>::get(collection_id);696 ensure!(!admin_arr.contains(&new_admin_id), "Account already has admin role");697 }698699 // Number of collection admins700 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, "Number of collection admins bound exceeded");701702 admin_arr.push(new_admin_id);703 <AdminList<T>>::insert(collection_id, admin_arr);704705 Ok(())706 }707708 /// 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.709 ///710 /// # Permissions711 /// 712 /// * Collection Owner.713 /// * Collection Admin.714 /// 715 /// # Arguments716 /// 717 /// * collection_id: ID of the Collection to remove admin for.718 /// 719 /// * account_id: Address of admin to remove.720 #[weight = T::WeightInfo::remove_collection_admin()]721 pub fn remove_collection_admin(origin, collection_id: u64, account_id: T::AccountId) -> DispatchResult {722723 let sender = ensure_signed(origin)?;724 Self::check_owner_or_admin_permissions(collection_id, sender)?;725726 if <AdminList<T>>::contains_key(collection_id)727 {728 let mut admin_arr = <AdminList<T>>::get(collection_id);729 admin_arr.retain(|i| *i != account_id);730 <AdminList<T>>::insert(collection_id, admin_arr);731 }732733 Ok(())734 }735736 /// # Permissions737 /// 738 /// * Collection Owner739 /// 740 /// # Arguments741 /// 742 /// * collection_id.743 /// 744 /// * new_sponsor.745 #[weight = T::WeightInfo::set_collection_sponsor()]746 pub fn set_collection_sponsor(origin, collection_id: u64, new_sponsor: T::AccountId) -> DispatchResult {747748 let sender = ensure_signed(origin)?;749 ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");750751 let mut target_collection = <Collection<T>>::get(collection_id);752 ensure!(sender == target_collection.owner, "You do not own this collection");753754 target_collection.unconfirmed_sponsor = new_sponsor;755 <Collection<T>>::insert(collection_id, target_collection);756757 Ok(())758 }759760 /// # Permissions761 /// 762 /// * Sponsor.763 /// 764 /// # Arguments765 /// 766 /// * collection_id.767 #[weight = T::WeightInfo::confirm_sponsorship()]768 pub fn confirm_sponsorship(origin, collection_id: u64) -> DispatchResult {769770 let sender = ensure_signed(origin)?;771 ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");772773 let mut target_collection = <Collection<T>>::get(collection_id);774 ensure!(sender == target_collection.unconfirmed_sponsor, "This address is not set as sponsor, use setCollectionSponsor first");775776 target_collection.sponsor = target_collection.unconfirmed_sponsor;777 target_collection.unconfirmed_sponsor = T::AccountId::default();778 <Collection<T>>::insert(collection_id, target_collection);779780 Ok(())781 }782783 /// Switch back to pay-per-own-transaction model.784 ///785 /// # Permissions786 ///787 /// * Collection owner.788 /// 789 /// # Arguments790 /// 791 /// * collection_id.792 #[weight = T::WeightInfo::remove_collection_sponsor()]793 pub fn remove_collection_sponsor(origin, collection_id: u64) -> DispatchResult {794795 let sender = ensure_signed(origin)?;796 ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");797798 let mut target_collection = <Collection<T>>::get(collection_id);799 ensure!(sender == target_collection.owner, "You do not own this collection");800801 target_collection.sponsor = T::AccountId::default();802 <Collection<T>>::insert(collection_id, target_collection);803804 Ok(())805 }806807 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.808 /// 809 /// # Permissions810 /// 811 /// * Collection Owner.812 /// * Collection Admin.813 /// * Anyone if814 /// * White List is enabled, and815 /// * Address is added to white list, and816 /// * MintPermission is enabled (see SetMintPermission method)817 /// 818 /// # Arguments819 /// 820 /// * collection_id: ID of the collection.821 /// 822 /// * owner: Address, initial owner of the NFT.823 ///824 /// * data: Token data to store on chain.825 // #[weight =826 // (130_000_000 as Weight)827 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))828 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))829 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]830831 #[weight = T::WeightInfo::create_item(data.len())]832 pub fn create_item(origin, collection_id: u64, owner: T::AccountId, data: CreateItemData) -> DispatchResult {833834 let sender = ensure_signed(origin)?;835 Self::collection_exists(collection_id)?;836 let target_collection = <Collection<T>>::get(collection_id);837838 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {839 ensure!(target_collection.mint_mode == true, "Public minting is not allowed for this collection.");840 Self::check_white_list(collection_id, &owner)?;841 Self::check_white_list(collection_id, &sender)?;842 }843844 match target_collection.mode845 {846 CollectionMode::NFT => {847 if let CreateItemData::NFT(data) = data {848 // check sizes849 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, "const_data exceeded data limit.");850 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, "variable_data exceeded data limit.");851 852 // Create nft item853 let item = NftItemType {854 collection: collection_id,855 owner: owner,856 const_data: data.const_data.clone(),857 variable_data: data.variable_data.clone() 858 };859 860 Self::add_nft_item(item)?;861 862 } else {863 fail!("Not NFT item data used to mint in NFT collection.");864 }865 },866 CollectionMode::Fungible(_) => {867 if let CreateItemData::Fungible(_) = data {868 869 let item = FungibleItemType {870 collection: collection_id,871 owner: owner,872 value: (10 as u128).pow(target_collection.decimal_points)873 };874 875 Self::add_fungible_item(item)?;876 } else {877 fail!("Not Fungible item data used to mint in Fungible collection.");878 }879 },880 CollectionMode::ReFungible(_) => {881 if let CreateItemData::ReFungible(data) = data {882 883 // check sizes884 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, "const_data exceeded data limit.");885 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, "variable_data exceeded data limit.");886 887 let mut owner_list = Vec::new();888 let value = (10 as u128).pow(target_collection.decimal_points);889 owner_list.push(Ownership {owner: owner.clone(), fraction: value});890 891 let item = ReFungibleItemType {892 collection: collection_id,893 owner: owner_list,894 const_data: data.const_data.clone(),895 variable_data: data.variable_data.clone() 896 };897 898 Self::add_refungible_item(item)?;899 } else {900 fail!("Not Re Fungible item data used to mint in Re Fungible collection.");901 }902 },903 _ => { ensure!(1 == 0,"Unexpected collection type."); }904 };905906 // call event907 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));908909 Ok(())910 }911912 /// Destroys a concrete instance of NFT.913 /// 914 /// # Permissions915 /// 916 /// * Collection Owner.917 /// * Collection Admin.918 /// * Current NFT Owner.919 /// 920 /// # Arguments921 /// 922 /// * collection_id: ID of the collection.923 /// 924 /// * item_id: ID of NFT to burn.925 #[weight = T::WeightInfo::burn_item()]926 pub fn burn_item(origin, collection_id: u64, item_id: u64) -> DispatchResult {927928 let sender = ensure_signed(origin)?;929 Self::collection_exists(collection_id)?;930931 // Transfer permissions check932 let target_collection = <Collection<T>>::get(collection_id);933 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||934 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),935 "Only item owner, collection owner and admins can modify item");936937 if target_collection.access == AccessMode::WhiteList {938 Self::check_white_list(collection_id, &sender)?;939 }940941 match target_collection.mode942 {943 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,944 CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?,945 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,946 _ => ()947 };948949 // call event950 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));951952 Ok(())953 }954955 /// Change ownership of the token.956 /// 957 /// # Permissions958 /// 959 /// * Collection Owner960 /// * Collection Admin961 /// * Current NFT owner962 ///963 /// # Arguments964 /// 965 /// * recipient: Address of token recipient.966 /// 967 /// * collection_id.968 /// 969 /// * item_id: ID of the item970 /// * Non-Fungible Mode: Required.971 /// * Fungible Mode: Ignored.972 /// * Re-Fungible Mode: Required.973 /// 974 /// * value: Amount to transfer.975 /// * Non-Fungible Mode: Ignored976 /// * Fungible Mode: Must specify transferred amount977 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)978 #[weight = T::WeightInfo::transfer()]979 pub fn transfer(origin, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64) -> DispatchResult {980981 let sender = ensure_signed(origin)?;982983 // Transfer permissions check984 let target_collection = <Collection<T>>::get(collection_id);985 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||986 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),987 "Only item owner, collection owner and admins can modify item");988989 if target_collection.access == AccessMode::WhiteList {990 Self::check_white_list(collection_id, &sender)?;991 Self::check_white_list(collection_id, &recipient)?;992 }993994 match target_collection.mode995 {996 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,997 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,998 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,999 _ => ()1000 };10011002 Ok(())1003 }10041005 /// Set, change, or remove approved address to transfer the ownership of the NFT.1006 /// 1007 /// # Permissions1008 /// 1009 /// * Collection Owner1010 /// * Collection Admin1011 /// * Current NFT owner1012 /// 1013 /// # Arguments1014 /// 1015 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1016 /// 1017 /// * collection_id.1018 /// 1019 /// * item_id: ID of the item.1020 #[weight = T::WeightInfo::approve()]1021 pub fn approve(origin, approved: T::AccountId, collection_id: u64, item_id: u64) -> DispatchResult {10221023 let sender = ensure_signed(origin)?;10241025 // Transfer permissions check1026 let target_collection = <Collection<T>>::get(collection_id);1027 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1028 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1029 "Only item owner, collection owner and admins can approve");10301031 if target_collection.access == AccessMode::WhiteList {1032 Self::check_white_list(collection_id, &sender)?;1033 Self::check_white_list(collection_id, &approved)?;1034 }10351036 // amount param stub1037 let amount = 100000000;10381039 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));1040 if list_exists {10411042 let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));1043 let item_contains = list.iter().any(|i| i.approved == approved);10441045 if !item_contains {1046 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1047 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1048 }1049 } else {10501051 let mut list = Vec::new();1052 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1053 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1054 }10551056 Ok(())1057 }1058 1059 /// 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.1060 /// 1061 /// # Permissions1062 /// * Collection Owner1063 /// * Collection Admin1064 /// * Current NFT owner1065 /// * Address approved by current NFT owner1066 /// 1067 /// # Arguments1068 /// 1069 /// * from: Address that owns token.1070 /// 1071 /// * recipient: Address of token recipient.1072 /// 1073 /// * collection_id.1074 /// 1075 /// * item_id: ID of the item.1076 /// 1077 /// * value: Amount to transfer.1078 #[weight = T::WeightInfo::transfer_from()]1079 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64 ) -> DispatchResult {10801081 let sender = ensure_signed(origin)?;1082 let mut appoved_transfer = false;10831084 // Check approve1085 if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {1086 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));1087 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());1088 if opt_item.is_some()1089 {1090 appoved_transfer = true;1091 ensure!(opt_item.unwrap().amount >= value, "Requested value more than approved");1092 }1093 }10941095 // Transfer permissions check1096 let target_collection = <Collection<T>>::get(collection_id);1097 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1098 "Only item owner, collection owner and admins can modify items");10991100 if target_collection.access == AccessMode::WhiteList {1101 Self::check_white_list(collection_id, &sender)?;1102 Self::check_white_list(collection_id, &recipient)?;1103 }11041105 // remove approve1106 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))1107 .into_iter().filter(|i| i.approved != sender.clone()).collect();1108 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);110911101111 match target_collection.mode1112 {1113 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1114 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,1115 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1116 _ => ()1117 };11181119 Ok(())1120 }11211122 ///1123 #[weight = 0]1124 pub fn safe_transfer_from(origin, collection_id: u64, item_id: u64, new_owner: T::AccountId) -> DispatchResult {11251126 // let no_perm_mes = "You do not have permissions to modify this collection";1127 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1128 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1129 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);11301131 // // on_nft_received call11321133 // Self::transfer(origin, collection_id, item_id, new_owner)?;11341135 Ok(())1136 }1137 1138 /// Set off-chain data schema.1139 /// 1140 /// # Permissions1141 /// 1142 /// * Collection Owner1143 /// * Collection Admin1144 /// 1145 /// # Arguments1146 /// 1147 /// * collection_id.1148 /// 1149 /// * schema: String representing the offchain data schema.1150 #[weight = T::WeightInfo::set_variable_meta_data()]1151 pub fn set_variable_meta_data (1152 origin,1153 collection_id: u64,1154 item_id: u64,1155 data: Vec<u8>1156 ) -> DispatchResult {1157 let sender = ensure_signed(origin)?;1158 1159 Self::collection_exists(collection_id)?;11601161 // Modify permissions check1162 let target_collection = <Collection<T>>::get(collection_id);1163 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1164 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1165 "Only item owner, collection owner and admins can modify item");11661167 Self::item_exists(collection_id, item_id, &target_collection.mode)?;11681169 match target_collection.mode1170 {1171 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1172 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1173 _ => ()1174 };11751176 Ok(())1177 }1178 11791180 /// Set off-chain data schema.1181 /// 1182 /// # Permissions1183 /// 1184 /// * Collection Owner1185 /// * Collection Admin1186 /// 1187 /// # Arguments1188 /// 1189 /// * collection_id.1190 /// 1191 /// * schema: String representing the offchain data schema.1192 #[weight = T::WeightInfo::set_offchain_schema()]1193 pub fn set_offchain_schema(1194 origin,1195 collection_id: u64,1196 schema: Vec<u8>1197 ) -> DispatchResult {1198 let sender = ensure_signed(origin)?;1199 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12001201 let mut target_collection = <Collection<T>>::get(collection_id);1202 target_collection.offchain_schema = schema;1203 <Collection<T>>::insert(collection_id, target_collection);12041205 Ok(())1206 }12071208 /// Set const on-chain data schema.1209 /// 1210 /// # Permissions1211 /// 1212 /// * Collection Owner1213 /// * Collection Admin1214 /// 1215 /// # Arguments1216 /// 1217 /// * collection_id.1218 /// 1219 /// * schema: String representing the const on-chain data schema.1220 #[weight = T::WeightInfo::set_const_on_chain_schema()]1221 pub fn set_const_on_chain_schema (1222 origin,1223 collection_id: u64,1224 schema: Vec<u8>1225 ) -> DispatchResult {1226 let sender = ensure_signed(origin)?;1227 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12281229 let mut target_collection = <Collection<T>>::get(collection_id);1230 target_collection.const_on_chain_schema = schema;1231 <Collection<T>>::insert(collection_id, target_collection);12321233 Ok(())1234 }12351236 /// Set variable on-chain data schema.1237 /// 1238 /// # Permissions1239 /// 1240 /// * Collection Owner1241 /// * Collection Admin1242 /// 1243 /// # Arguments1244 /// 1245 /// * collection_id.1246 /// 1247 /// * schema: String representing the variable on-chain data schema.1248 #[weight = T::WeightInfo::set_const_on_chain_schema()]1249 pub fn set_variable_on_chain_schema (1250 origin,1251 collection_id: u64,1252 schema: Vec<u8>1253 ) -> DispatchResult {1254 let sender = ensure_signed(origin)?;1255 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12561257 let mut target_collection = <Collection<T>>::get(collection_id);1258 target_collection.variable_on_chain_schema = schema;1259 <Collection<T>>::insert(collection_id, target_collection);12601261 Ok(())1262 }12631264 // Sudo permissions function1265 #[weight = 0]1266 pub fn set_chain_limits(1267 origin,1268 limits: ChainLimits1269 ) -> DispatchResult {1270 ensure_root(origin)?;1271 <ChainLimit>::put(limits);1272 Ok(())1273 }12741275 /// Enable smart contract self-sponsoring.1276 /// 1277 /// # Permissions1278 /// 1279 /// * Contract Owner1280 /// 1281 /// # Arguments1282 /// 1283 /// * contract address1284 /// * enable flag1285 /// 1286 #[weight = 0]1287 pub fn enable_contract_sponsoring(1288 origin,1289 contract_address: T::AccountId,1290 enable: bool1291 ) -> DispatchResult {1292 let sender = ensure_signed(origin)?;1293 let mut is_owner = false;1294 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1295 let owner = <ContractOwner<T>>::get(&contract_address);1296 is_owner = sender == owner;1297 }1298 ensure!(is_owner, "Only contract owner may call this method");12991300 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1301 Ok(())1302 }13031304 }1305}13061307impl<T: Trait> Module<T> {1308 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {1309 let current_index = <ItemListIndex>::get(item.collection)1310 .checked_add(1)1311 .expect("Item list index id error");1312 let itemcopy = item.clone();1313 let owner = item.owner.clone();1314 let value = item.value as u64;13151316 Self::add_token_index(item.collection, current_index, owner.clone())?;13171318 <ItemListIndex>::insert(item.collection, current_index);1319 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);13201321 // Add current block1322 let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1323 <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);1324 1325 // Update balance1326 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1327 .checked_add(value)1328 .unwrap();1329 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);13301331 Ok(())1332 }13331334 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1335 let current_index = <ItemListIndex>::get(item.collection)1336 .checked_add(1)1337 .expect("Item list index id error");1338 let itemcopy = item.clone();13391340 let value = item.owner.first().unwrap().fraction as u64;1341 let owner = item.owner.first().unwrap().owner.clone();13421343 Self::add_token_index(item.collection, current_index, owner.clone())?;13441345 <ItemListIndex>::insert(item.collection, current_index);1346 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);13471348 // Add current block1349 let block_number: T::BlockNumber = 0.into();1350 <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);13511352 // Update balance1353 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1354 .checked_add(value)1355 .unwrap();1356 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);13571358 Ok(())1359 }13601361 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1362 let current_index = <ItemListIndex>::get(item.collection)1363 .checked_add(1)1364 .expect("Item list index id error");13651366 let item_owner = item.owner.clone();1367 let collection_id = item.collection.clone();1368 Self::add_token_index(collection_id, current_index, item.owner.clone())?;13691370 <ItemListIndex>::insert(collection_id, current_index);1371 <NftItemList<T>>::insert(collection_id, current_index, item);13721373 // Add current block1374 let block_number: T::BlockNumber = 0.into();1375 <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);13761377 // Update balance1378 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1379 .checked_add(1)1380 .unwrap();1381 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);13821383 Ok(())1384 }13851386 fn burn_refungible_item(1387 collection_id: u64,1388 item_id: u64,1389 owner: T::AccountId,1390 ) -> DispatchResult {1391 ensure!(1392 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1393 "Item does not exists"1394 );1395 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1396 let item = collection1397 .owner1398 .iter()1399 .filter(|&i| i.owner == owner)1400 .next()1401 .unwrap();1402 Self::remove_token_index(collection_id, item_id, owner.clone())?;14031404 // remove approve list1405 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));14061407 // update balance1408 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1409 .checked_sub(item.fraction as u64)1410 .unwrap();1411 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);14121413 <ReFungibleItemList<T>>::remove(collection_id, item_id);14141415 Ok(())1416 }14171418 fn burn_nft_item(collection_id: u64, item_id: u64) -> DispatchResult {1419 ensure!(1420 <NftItemList<T>>::contains_key(collection_id, item_id),1421 "Item does not exists"1422 );1423 let item = <NftItemList<T>>::get(collection_id, item_id);1424 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;14251426 // remove approve list1427 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));14281429 // update balance1430 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1431 .checked_sub(1)1432 .unwrap();1433 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1434 <NftItemList<T>>::remove(collection_id, item_id);14351436 Ok(())1437 }14381439 fn burn_fungible_item(collection_id: u64, item_id: u64) -> DispatchResult {1440 ensure!(1441 <FungibleItemList<T>>::contains_key(collection_id, item_id),1442 "Item does not exists"1443 );1444 let item = <FungibleItemList<T>>::get(collection_id, item_id);1445 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;14461447 // remove approve list1448 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));14491450 // update balance1451 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1452 .checked_sub(item.value as u64)1453 .unwrap();1454 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);14551456 <FungibleItemList<T>>::remove(collection_id, item_id);14571458 Ok(())1459 }14601461 fn collection_exists(collection_id: u64) -> DispatchResult {1462 ensure!(1463 <Collection<T>>::contains_key(collection_id),1464 "This collection does not exist"1465 );1466 Ok(())1467 }14681469 fn check_owner_permissions(collection_id: u64, subject: T::AccountId) -> DispatchResult {1470 Self::collection_exists(collection_id)?;14711472 let target_collection = <Collection<T>>::get(collection_id);1473 ensure!(1474 subject == target_collection.owner,1475 "You do not own this collection"1476 );14771478 Ok(())1479 }14801481 fn is_owner_or_admin_permissions(collection_id: u64, subject: T::AccountId) -> bool {1482 let target_collection = <Collection<T>>::get(collection_id);1483 let mut result: bool = subject == target_collection.owner;1484 let exists = <AdminList<T>>::contains_key(collection_id);14851486 if !result & exists {1487 if <AdminList<T>>::get(collection_id).contains(&subject) {1488 result = true1489 }1490 }14911492 result1493 }14941495 fn check_owner_or_admin_permissions(1496 collection_id: u64,1497 subject: T::AccountId,1498 ) -> DispatchResult {1499 Self::collection_exists(collection_id)?;1500 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());15011502 ensure!(1503 result,1504 "You do not have permissions to modify this collection"1505 );1506 Ok(())1507 }15081509 fn is_item_owner(subject: T::AccountId, collection_id: u64, item_id: u64) -> bool {1510 let target_collection = <Collection<T>>::get(collection_id);15111512 match target_collection.mode {1513 CollectionMode::NFT => {1514 <NftItemList<T>>::get(collection_id, item_id).owner == subject1515 }1516 CollectionMode::Fungible(_) => {1517 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1518 }1519 CollectionMode::ReFungible(_) => {1520 <ReFungibleItemList<T>>::get(collection_id, item_id)1521 .owner1522 .iter()1523 .any(|i| i.owner == subject)1524 }1525 CollectionMode::Invalid => false,1526 }1527 }15281529 fn check_white_list(collection_id: u64, address: &T::AccountId) -> DispatchResult {1530 let mes = "Address is not in white list";1531 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1532 let wl = <WhiteList<T>>::get(collection_id);1533 ensure!(wl.contains(address), mes);15341535 Ok(())1536 }15371538 fn transfer_fungible(1539 collection_id: u64,1540 item_id: u64,1541 value: u64,1542 owner: T::AccountId,1543 new_owner: T::AccountId,1544 ) -> DispatchResult {1545 ensure!(1546 <FungibleItemList<T>>::contains_key(collection_id, item_id),1547 "Item not exists"1548 );15491550 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1551 let amount = full_item.value;15521553 ensure!(amount >= value.into(), "Item balance not enouth");15541555 // update balance1556 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1557 .checked_sub(value)1558 .unwrap();1559 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);15601561 let mut new_owner_account_id = 0;1562 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1563 if new_owner_items.len() > 0 {1564 new_owner_account_id = new_owner_items[0];1565 }15661567 let val64 = value.into();15681569 // transfer1570 if amount == val64 && new_owner_account_id == 0 {1571 // change owner1572 // new owner do not have account1573 let mut new_full_item = full_item.clone();1574 new_full_item.owner = new_owner.clone();1575 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);15761577 // update balance1578 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1579 .checked_add(value)1580 .unwrap();1581 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);15821583 // update index collection1584 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1585 } else {1586 let mut new_full_item = full_item.clone();1587 new_full_item.value -= val64;15881589 // separate amount1590 if new_owner_account_id > 0 {1591 // new owner has account1592 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1593 item.value += val64;15941595 // update balance1596 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1597 .checked_add(value)1598 .unwrap();1599 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);16001601 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1602 } else {1603 // new owner do not have account1604 let item = FungibleItemType {1605 collection: collection_id,1606 owner: new_owner.clone(),1607 value: val64,1608 };16091610 Self::add_fungible_item(item)?;1611 }16121613 if amount == val64 {1614 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;16151616 // remove approve list1617 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1618 <FungibleItemList<T>>::remove(collection_id, item_id);1619 }16201621 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1622 }16231624 Ok(())1625 }16261627 fn transfer_refungible(1628 collection_id: u64,1629 item_id: u64,1630 value: u64,1631 owner: T::AccountId,1632 new_owner: T::AccountId,1633 ) -> DispatchResult {1634 ensure!(1635 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1636 "Item not exists"1637 );16381639 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1640 let item = full_item1641 .owner1642 .iter()1643 .filter(|i| i.owner == owner)1644 .next()1645 .unwrap();1646 let amount = item.fraction;16471648 ensure!(amount >= value.into(), "Item balance not enouth");16491650 // update balance1651 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1652 .checked_sub(value)1653 .unwrap();1654 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);16551656 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1657 .checked_add(value)1658 .unwrap();1659 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);16601661 let old_owner = item.owner.clone();1662 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);1663 let val64 = value.into();16641665 // transfer1666 if amount == val64 && !new_owner_has_account {1667 // change owner1668 // new owner do not have account1669 let mut new_full_item = full_item.clone();1670 new_full_item1671 .owner1672 .iter_mut()1673 .find(|i| i.owner == owner)1674 .unwrap()1675 .owner = new_owner.clone();1676 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);16771678 // update index collection1679 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1680 } else {1681 let mut new_full_item = full_item.clone();1682 new_full_item1683 .owner1684 .iter_mut()1685 .find(|i| i.owner == owner)1686 .unwrap()1687 .fraction -= val64;16881689 // separate amount1690 if new_owner_has_account {1691 // new owner has account1692 new_full_item1693 .owner1694 .iter_mut()1695 .find(|i| i.owner == new_owner)1696 .unwrap()1697 .fraction += val64;1698 } else {1699 // new owner do not have account1700 new_full_item.owner.push(Ownership {1701 owner: new_owner.clone(),1702 fraction: val64,1703 });1704 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1705 }17061707 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1708 }17091710 Ok(())1711 }17121713 fn transfer_nft(1714 collection_id: u64,1715 item_id: u64,1716 sender: T::AccountId,1717 new_owner: T::AccountId,1718 ) -> DispatchResult {1719 ensure!(1720 <NftItemList<T>>::contains_key(collection_id, item_id),1721 "Item not exists"1722 );17231724 let mut item = <NftItemList<T>>::get(collection_id, item_id);17251726 ensure!(1727 sender == item.owner,1728 "sender parameter and item owner must be equal"1729 );17301731 // update balance1732 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1733 .checked_sub(1)1734 .unwrap();1735 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);17361737 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1738 .checked_add(1)1739 .unwrap();1740 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);17411742 // change owner1743 let old_owner = item.owner.clone();1744 item.owner = new_owner.clone();1745 <NftItemList<T>>::insert(collection_id, item_id, item);17461747 // update index collection1748 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;17491750 // reset approved list1751 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));1752 Ok(())1753 }1754 1755 fn item_exists(1756 collection_id: u64,1757 item_id: u64,1758 mode: &CollectionMode1759 ) -> DispatchResult {1760 match mode {1761 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), "Item does not exists"),1762 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), "Item does not exists"),1763 CollectionMode::Fungible(_) => ensure!(<FungibleItemList<T>>::contains_key(collection_id, item_id), "Item does not exists"),1764 _ => ()1765 };1766 1767 Ok(())1768 }17691770 fn set_re_fungible_variable_data(1771 collection_id: u64,1772 item_id: u64,1773 data: Vec<u8>1774 ) -> DispatchResult {1775 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);17761777 item.variable_data = data;17781779 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);17801781 Ok(())1782 }17831784 fn set_nft_variable_data(1785 collection_id: u64,1786 item_id: u64,1787 data: Vec<u8>1788 ) -> DispatchResult {1789 let mut item = <NftItemList<T>>::get(collection_id, item_id);1790 1791 item.variable_data = data;17921793 <NftItemList<T>>::insert(collection_id, item_id, item);1794 1795 Ok(())1796 }17971798 fn init_collection(item: &CollectionType<T::AccountId>) {1799 // check params1800 assert!(1801 item.decimal_points <= 4,1802 "decimal_points parameter must be lower than 4"1803 );1804 assert!(1805 item.name.len() <= 64,1806 "Collection name can not be longer than 63 char"1807 );1808 assert!(1809 item.name.len() <= 256,1810 "Collection description can not be longer than 255 char"1811 );1812 assert!(1813 item.token_prefix.len() <= 16,1814 "Token prefix can not be longer than 15 char"1815 );18161817 // Generate next collection ID1818 let next_id = CreatedCollectionCount::get()1819 .checked_add(1)1820 .expect("collection id error");18211822 CreatedCollectionCount::put(next_id);1823 }18241825 fn init_nft_token(item: &NftItemType<T::AccountId>) {1826 let current_index = <ItemListIndex>::get(item.collection)1827 .checked_add(1)1828 .expect("Item list index id error");18291830 let item_owner = item.owner.clone();1831 let collection_id = item.collection.clone();1832 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();18331834 <ItemListIndex>::insert(collection_id, current_index);18351836 // Update balance1837 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1838 .checked_add(1)1839 .unwrap();1840 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);1841 }18421843 fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {1844 let current_index = <ItemListIndex>::get(item.collection)1845 .checked_add(1)1846 .expect("Item list index id error");1847 let owner = item.owner.clone();1848 let value = item.value as u64;18491850 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();18511852 <ItemListIndex>::insert(item.collection, current_index);18531854 // Update balance1855 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1856 .checked_add(value)1857 .unwrap();1858 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1859 }18601861 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {1862 let current_index = <ItemListIndex>::get(item.collection)1863 .checked_add(1)1864 .expect("Item list index id error");18651866 let value = item.owner.first().unwrap().fraction as u64;1867 let owner = item.owner.first().unwrap().owner.clone();18681869 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();18701871 <ItemListIndex>::insert(item.collection, current_index);18721873 // Update balance1874 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1875 .checked_add(value)1876 .unwrap();1877 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1878 }18791880 fn add_token_index(collection_id: u64, item_index: u64, owner: T::AccountId) -> DispatchResult {18811882 // add to account limit1883 if <AccountItemCount<T>>::contains_key(owner.clone()) {18841885 // bound Owned tokens by a single address1886 let count = <AccountItemCount<T>>::get(owner.clone());1887 ensure!(count < ChainLimit::get().account_token_ownership_limit, "Owned tokens by a single address bound exceeded");18881889 <AccountItemCount<T>>::insert(owner.clone(), 1890 count.checked_add(1).unwrap());1891 }1892 else {1893 <AccountItemCount<T>>::insert(owner.clone(), 1);1894 }18951896 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());1897 if list_exists {1898 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());1899 let item_contains = list.contains(&item_index.clone());19001901 if !item_contains {1902 list.push(item_index.clone());1903 }19041905 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);1906 } else {1907 let mut itm = Vec::new();1908 itm.push(item_index.clone());1909 <AddressTokens<T>>::insert(collection_id, owner, itm);1910 1911 }19121913 Ok(())1914 }19151916 fn remove_token_index(1917 collection_id: u64,1918 item_index: u64,1919 owner: T::AccountId,1920 ) -> DispatchResult {19211922 // update counter1923 <AccountItemCount<T>>::insert(owner.clone(), 1924 <AccountItemCount<T>>::get(owner.clone()).checked_sub(1).unwrap());192519261927 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());1928 if list_exists {1929 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());1930 let item_contains = list.contains(&item_index.clone());19311932 if item_contains {1933 list.retain(|&item| item != item_index);1934 <AddressTokens<T>>::insert(collection_id, owner, list);1935 }1936 }19371938 Ok(())1939 }19401941 fn move_token_index(1942 collection_id: u64,1943 item_index: u64,1944 old_owner: T::AccountId,1945 new_owner: T::AccountId,1946 ) -> DispatchResult {1947 Self::remove_token_index(collection_id, item_index, old_owner)?;1948 Self::add_token_index(collection_id, item_index, new_owner)?;19491950 Ok(())1951 }1952}19531954////////////////////////////////////////////////////////////////////////////////////////////////////1955// Economic models1956// #region19571958/// Fee multiplier.1959pub type Multiplier = FixedU128;19601961type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<1962 <T as system::Trait>::AccountId,1963>>::Balance;1964type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<1965 <T as system::Trait>::AccountId,1966>>::NegativeImbalance;19671968/// Require the transactor pay for themselves and maybe include a tip to gain additional priority1969/// in the queue.1970#[derive(Encode, Decode, Clone, Eq, PartialEq)]1971pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(1972 #[codec(compact)] BalanceOf<T>1973);19741975impl<T: Trait + Send + Sync> sp_std::fmt::Debug1976 for ChargeTransactionPayment<T>1977{1978 #[cfg(feature = "std")]1979 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {1980 write!(f, "ChargeTransactionPayment<{:?}>", self.0)1981 }1982 #[cfg(not(feature = "std"))]1983 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {1984 Ok(())1985 }1986}19871988impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>1989where1990 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,1991 BalanceOf<T>: Send + Sync + FixedPointOperand,1992{1993 /// utility constructor. Used only in client/factory code.1994 pub fn from(fee: BalanceOf<T>) -> Self {1995 Self(fee)1996 }19971998 pub fn traditional_fee(1999 len: usize,2000 info: &DispatchInfoOf<T::Call>,2001 tip: BalanceOf<T>,2002 ) -> BalanceOf<T>2003 where2004 T::Call: Dispatchable<Info = DispatchInfo>,2005 {2006 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2007 }20082009 fn withdraw_fee(2010 &self,2011 who: &T::AccountId,2012 call: &T::Call,2013 info: &DispatchInfoOf<T::Call>,2014 len: usize,2015 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2016 let tip = self.0;20172018 // Set fee based on call type. Creating collection costs 1 Unique.2019 // All other transactions have traditional fees so far2020 // let fee = match call.is_sub_type() {2021 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2022 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2023 // // _ => <BalanceOf<T>>::from(100)2024 // };2025 let fee = Self::traditional_fee(len, info, tip);20262027 // Determine who is paying transaction fee based on ecnomic model2028 // Parse call to extract collection ID and access collection sponsor2029 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2030 Some(Call::create_item(collection_id, _properties, _owner)) => {2031 <Collection<T>>::get(collection_id).sponsor2032 }2033 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {2034 let _collection_mode = <Collection<T>>::get(collection_id).mode;20352036 // sponsor timeout2037 let sponsor_transfer = match _collection_mode {2038 CollectionMode::NFT => {2039 let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);2040 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2041 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();2042 if block_number >= limit_time {2043 <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);2044 true2045 }2046 else {2047 false2048 }2049 }2050 CollectionMode::Fungible(_) => {2051 let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);2052 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2053 if basket.iter().any(|i| i.address == _new_owner.clone())2054 {2055 let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();2056 let limit_time = item.start_block + ChainLimit::get().fungible_sponsor_transfer_timeout.into();2057 if block_number >= limit_time {2058 basket.retain(|x| x.address == item.address);2059 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });2060 <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);2061 true2062 }2063 else {2064 false2065 }2066 }2067 else {2068 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});2069 true2070 }2071 }2072 CollectionMode::ReFungible(_) => {2073 let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);2074 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2075 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();2076 if block_number >= limit_time {2077 <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);2078 true2079 } else {2080 false2081 }2082 }2083 _ => {2084 false2085 },2086 };20872088 if !sponsor_transfer {2089 T::AccountId::default()2090 } else {2091 <Collection<T>>::get(collection_id).sponsor2092 }2093 }20942095 _ => T::AccountId::default(),2096 };20972098 // Sponsor smart contracts2099 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {21002101 // On instantiation: set the contract owner2102 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {21032104 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2105 code_hash,2106 &data,2107 &who,2108 );2109 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());21102111 T::AccountId::default()2112 },21132114 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2115 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {21162117 let mut sp = T::AccountId::default();2118 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());2119 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2120 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2121 sp = called_contract;2122 }2123 }21242125 sp2126 },21272128 _ => sponsor,2129 };21302131 let mut who_pays_fee: T::AccountId = sponsor.clone();2132 if sponsor == T::AccountId::default() {2133 who_pays_fee = who.clone();2134 }21352136 // Only mess with balances if fee is not zero.2137 if fee.is_zero() {2138 return Ok((fee, None));2139 }21402141 match <T as transaction_payment::Trait>::Currency::withdraw(2142 &who_pays_fee,2143 fee,2144 if tip.is_zero() {2145 WithdrawReason::TransactionPayment.into()2146 } else {2147 WithdrawReason::TransactionPayment | WithdrawReason::Tip2148 },2149 ExistenceRequirement::KeepAlive,2150 ) {2151 Ok(imbalance) => Ok((fee, Some(imbalance))),2152 Err(_) => Err(InvalidTransaction::Payment.into()),2153 }2154 }2155}215621572158impl<T: Trait + Send + Sync> SignedExtension2159 for ChargeTransactionPayment<T>2160where2161 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2162 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2163{2164 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2165 type AccountId = T::AccountId;2166 type Call = T::Call;2167 type AdditionalSigned = ();2168 type Pre = (2169 BalanceOf<T>,2170 Self::AccountId,2171 Option<NegativeImbalanceOf<T>>,2172 BalanceOf<T>,2173 );2174 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2175 Ok(())2176 }21772178 fn validate(2179 &self,2180 _who: &Self::AccountId,2181 _call: &Self::Call,2182 _info: &DispatchInfoOf<Self::Call>,2183 _len: usize,2184 ) -> TransactionValidity {2185 Ok(ValidTransaction::default())2186 }21872188 fn pre_dispatch(2189 self,2190 who: &Self::AccountId,2191 call: &Self::Call,2192 info: &DispatchInfoOf<Self::Call>,2193 len: usize,2194 ) -> Result<Self::Pre, TransactionValidityError> {2195 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2196 Ok((self.0, who.clone(), imbalance, fee))2197 }21982199 fn post_dispatch(2200 pre: Self::Pre,2201 info: &DispatchInfoOf<Self::Call>,2202 post_info: &PostDispatchInfoOf<Self::Call>,2203 len: usize,2204 _result: &DispatchResult,2205 ) -> Result<(), TransactionValidityError> {2206 let (tip, who, imbalance, fee) = pre;2207 if let Some(payed) = imbalance {2208 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2209 len as u32, info, post_info, tip,2210 );2211 let refund = fee.saturating_sub(actual_fee);2212 let actual_payment =2213 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2214 &who, refund,2215 ) {2216 Ok(refund_imbalance) => {2217 // The refund cannot be larger than the up front payed max weight.2218 // `PostDispatchInfo::calc_unspent` guards against such a case.2219 match payed.offset(refund_imbalance) {2220 Ok(actual_payment) => actual_payment,2221 Err(_) => return Err(InvalidTransaction::Payment.into()),2222 }2223 }2224 // We do not recreate the account using the refund. The up front payment2225 // is gone in that case.2226 Err(_) => payed,2227 };2228 let imbalances = actual_payment.split(tip);2229 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2230 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2231 );2232 }2233 Ok(())2234 }2235}22362237// #endregion223822391#![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 CollectionAdminsType<AccountId> {119 pub admin: AccountId,120 pub collection_id: u64,121}122123#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]124#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]125pub struct NftItemType<AccountId> {126 pub collection: u64,127 pub owner: AccountId,128 pub const_data: Vec<u8>,129 pub variable_data: Vec<u8>,130}131132#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]133#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]134pub struct FungibleItemType<AccountId> {135 pub collection: u64,136 pub owner: AccountId,137 pub value: u128,138}139140#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]141#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]142pub struct ReFungibleItemType<AccountId> {143 pub collection: u64,144 pub owner: Vec<Ownership<AccountId>>,145 pub const_data: Vec<u8>,146 pub variable_data: Vec<u8>,147}148149#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]150#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]151pub struct ApprovePermissions<AccountId> {152 pub approved: AccountId,153 pub amount: u64,154}155156#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]157#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]158pub struct VestingItem<AccountId, Moment> {159 pub sender: AccountId,160 pub recipient: AccountId,161 pub collection_id: u64,162 pub item_id: u64,163 pub amount: u64,164 pub vesting_date: Moment,165}166167#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]168#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]169pub struct BasketItem<AccountId, BlockNumber> {170 pub address: AccountId,171 pub start_block: BlockNumber,172}173174#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]175#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]176pub struct ChainLimits {177 pub collection_numbers_limit: u64,178 pub account_token_ownership_limit: u64,179 pub collections_admins_limit: u64,180 pub custom_data_limit: u32,181182 // Timeouts for item types in passed blocks183 pub nft_sponsor_transfer_timeout: u32,184 pub fungible_sponsor_transfer_timeout: u32,185 pub refungible_sponsor_transfer_timeout: u32,186}187188pub trait WeightInfo {189 fn create_collection() -> Weight;190 fn destroy_collection() -> Weight;191 fn add_to_white_list() -> Weight;192 fn remove_from_white_list() -> Weight;193 fn set_public_access_mode() -> Weight;194 fn set_mint_permission() -> Weight;195 fn change_collection_owner() -> Weight;196 fn add_collection_admin() -> Weight;197 fn remove_collection_admin() -> Weight;198 fn set_collection_sponsor() -> Weight;199 fn confirm_sponsorship() -> Weight;200 fn remove_collection_sponsor() -> Weight;201 fn create_item(s: usize) -> Weight;202 fn burn_item() -> Weight;203 fn transfer() -> Weight;204 fn approve() -> Weight;205 fn transfer_from() -> Weight;206 fn set_offchain_schema() -> Weight;207 fn set_const_on_chain_schema() -> Weight;208 fn set_variable_on_chain_schema() -> Weight;209 fn set_variable_meta_data() -> Weight;210 // fn enable_contract_sponsoring() -> Weight;211}212213#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]214#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]215pub struct CreateNftData {216 pub const_data: Vec<u8>,217 pub variable_data: Vec<u8>,218}219220#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]221#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]222pub struct CreateFungibleData {223}224225#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]226#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]227pub struct CreateReFungibleData {228 pub const_data: Vec<u8>,229 pub variable_data: Vec<u8>,230}231232#[derive(Encode, Decode, Debug, Clone, PartialEq)]233#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]234pub enum CreateItemData {235 NFT(CreateNftData),236 Fungible(CreateFungibleData),237 ReFungible(CreateReFungibleData)238}239240impl CreateItemData {241 pub fn len(&self) -> usize {242 let len = match self {243 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),244 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),245 _ => 0246 };247 248 return len;249 }250}251252impl From<CreateNftData> for CreateItemData {253 fn from(item: CreateNftData) -> Self {254 CreateItemData::NFT(item)255 }256}257258impl From<CreateReFungibleData> for CreateItemData {259 fn from(item: CreateReFungibleData) -> Self {260 CreateItemData::ReFungible(item)261 }262}263264impl From<CreateFungibleData> for CreateItemData {265 fn from(item: CreateFungibleData) -> Self {266 CreateItemData::Fungible(item)267 }268}269270pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {271 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;272273 /// Weight information for extrinsics in this pallet.274 type WeightInfo: WeightInfo;275}276277#[cfg(feature = "runtime-benchmarks")]278mod benchmarking;279280// #endregion281282decl_storage! {283 trait Store for Module<T: Trait> as Nft {284285 // Private members286 NextCollectionID: u64;287 CreatedCollectionCount: u64;288 ChainVersion: u64;289 ItemListIndex: map hasher(blake2_128_concat) u64 => u64;290291 // Chain limits struct292 pub ChainLimit get(fn chain_limit) config(): ChainLimits;293294 // Bound counters295 CollectionCount: u64;296 pub AccountItemCount get(fn account_item_count): map hasher(identity) T::AccountId => u64;297298 // Basic collections299 pub Collection get(fn collection) config(): map hasher(identity) u64 => CollectionType<T::AccountId>;300 pub AdminList get(fn admin_list_collection): map hasher(identity) u64 => Vec<T::AccountId>;301 pub WhiteList get(fn white_list): map hasher(identity) u64 => Vec<T::AccountId>;302303 /// Balance owner per collection map304 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => u64;305306 /// second parameter: item id + owner account id307 pub ApprovedList get(fn approved): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) (u64, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;308309 /// Item collections310 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => NftItemType<T::AccountId>;311 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => FungibleItemType<T::AccountId>;312 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => ReFungibleItemType<T::AccountId>;313314 /// Index list315 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => Vec<u64>;316317 /// Tokens transfer baskets318 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => T::BlockNumber;319 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>>;320 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => T::BlockNumber;321322 // Contract Sponsorship and Ownership323 pub ContractOwner get(fn contract_owner): map hasher(identity) T::AccountId => T::AccountId;324 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(identity) T::AccountId => bool;325 }326 add_extra_genesis {327 build(|config: &GenesisConfig<T>| {328 // Modification of storage329 for (_num, _c) in &config.collection {330 <Module<T>>::init_collection(_c);331 }332333 for (_num, _q, _i) in &config.nft_item_id {334 <Module<T>>::init_nft_token(_i);335 }336337 for (_num, _q, _i) in &config.fungible_item_id {338 <Module<T>>::init_fungible_token(_i);339 }340341 for (_num, _q, _i) in &config.refungible_item_id {342 <Module<T>>::init_refungible_token(_i);343 }344 })345 }346}347348decl_event!(349 pub enum Event<T>350 where351 AccountId = <T as system::Trait>::AccountId,352 {353 /// New collection was created354 /// 355 /// # Arguments356 /// 357 /// * collection_id: Globally unique identifier of newly created collection.358 /// 359 /// * mode: [CollectionMode] converted into u8.360 /// 361 /// * account_id: Collection owner.362 Created(u64, u8, AccountId),363364 /// New item was created.365 /// 366 /// # Arguments367 /// 368 /// * collection_id: Id of the collection where item was created.369 /// 370 /// * item_id: Id of an item. Unique within the collection.371 ItemCreated(u64, u64),372373 /// Collection item was burned.374 /// 375 /// # Arguments376 /// 377 /// collection_id.378 /// 379 /// item_id: Identifier of burned NFT.380 ItemDestroyed(u64, u64),381 }382);383384decl_module! {385 pub struct Module<T: Trait> for enum Call where origin: T::Origin {386387 fn deposit_event() = default;388389 fn on_initialize(now: T::BlockNumber) -> Weight {390391 if ChainVersion::get() < 2392 {393 let value = NextCollectionID::get();394 CreatedCollectionCount::put(value);395 ChainVersion::put(2);396 }397398 0399 }400401 /// 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.402 /// 403 /// # Permissions404 /// 405 /// * Anyone.406 /// 407 /// # Arguments408 /// 409 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.410 /// 411 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.412 /// 413 /// * token_prefix: UTF-8 string with token prefix.414 /// 415 /// * mode: [CollectionMode] collection type and type dependent data.416 // returns collection ID417 #[weight = T::WeightInfo::create_collection()]418 pub fn create_collection(origin,419 collection_name: Vec<u16>,420 collection_description: Vec<u16>,421 token_prefix: Vec<u8>,422 mode: CollectionMode) -> DispatchResult {423424 // Anyone can create a collection425 let who = ensure_signed(origin)?;426427 let decimal_points = match mode {428 CollectionMode::Fungible(points) => points,429 CollectionMode::ReFungible(points) => points,430 _ => 0431 };432433 // bound Total number of collections434 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, "Total collections bound exceeded");435436 // check params437 ensure!(decimal_points <= 4, "decimal_points parameter must be lower than 4");438439 let mut name = collection_name.to_vec();440 name.push(0);441 ensure!(name.len() <= 64, "Collection name can not be longer than 63 char");442443 let mut description = collection_description.to_vec();444 description.push(0);445 ensure!(name.len() <= 256, "Collection description can not be longer than 255 char");446447 let mut prefix = token_prefix.to_vec();448 prefix.push(0);449 ensure!(prefix.len() <= 16, "Token prefix can not be longer than 15 char");450451 // Generate next collection ID452 let next_id = CreatedCollectionCount::get()453 .checked_add(1)454 .expect("collection id error");455456 // bound counter457 let total = CollectionCount::get()458 .checked_add(1)459 .expect("collection counter error");460461 CreatedCollectionCount::put(next_id);462 CollectionCount::put(total);463464 // Create new collection465 let new_collection = CollectionType {466 owner: who.clone(),467 name: name,468 mode: mode.clone(),469 mint_mode: false,470 access: AccessMode::Normal,471 description: description,472 decimal_points: decimal_points,473 token_prefix: prefix,474 offchain_schema: Vec::new(),475 sponsor: T::AccountId::default(),476 unconfirmed_sponsor: T::AccountId::default(),477 variable_on_chain_schema: Vec::new(),478 const_on_chain_schema: Vec::new(),479 };480481 // Add new collection to map482 <Collection<T>>::insert(next_id, new_collection);483484 // call event485 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));486487 Ok(())488 }489490 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.491 /// 492 /// # Permissions493 /// 494 /// * Collection Owner.495 /// 496 /// # Arguments497 /// 498 /// * collection_id: collection to destroy.499 #[weight = T::WeightInfo::destroy_collection()]500 pub fn destroy_collection(origin, collection_id: u64) -> DispatchResult {501502 let sender = ensure_signed(origin)?;503 Self::check_owner_permissions(collection_id, sender)?;504505 <AddressTokens<T>>::remove_prefix(collection_id);506 <ApprovedList<T>>::remove_prefix(collection_id);507 <Balance<T>>::remove_prefix(collection_id);508 <ItemListIndex>::remove(collection_id);509 <AdminList<T>>::remove(collection_id);510 <Collection<T>>::remove(collection_id);511 <WhiteList<T>>::remove(collection_id);512513 <NftItemList<T>>::remove_prefix(collection_id);514 <FungibleItemList<T>>::remove_prefix(collection_id);515 <ReFungibleItemList<T>>::remove_prefix(collection_id);516517 <NftTransferBasket<T>>::remove_prefix(collection_id);518 <FungibleTransferBasket<T>>::remove_prefix(collection_id);519 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);520521 if CollectionCount::get() > 0522 {523 // bound couter524 let total = CollectionCount::get()525 .checked_sub(1)526 .expect("collection counter error");527528 CollectionCount::put(total);529 }530531 Ok(())532 }533534 /// Add an address to white list.535 /// 536 /// # Permissions537 /// 538 /// * Collection Owner539 /// * Collection Admin540 /// 541 /// # Arguments542 /// 543 /// * collection_id.544 /// 545 /// * address.546 #[weight = T::WeightInfo::add_to_white_list()]547 pub fn add_to_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{548549 let sender = ensure_signed(origin)?;550 Self::check_owner_or_admin_permissions(collection_id, sender)?;551552 let mut white_list_collection: Vec<T::AccountId>;553 if <WhiteList<T>>::contains_key(collection_id) {554 white_list_collection = <WhiteList<T>>::get(collection_id);555 if !white_list_collection.contains(&address.clone())556 {557 white_list_collection.push(address.clone());558 }559 }560 else {561 white_list_collection = Vec::new();562 white_list_collection.push(address.clone());563 }564565 <WhiteList<T>>::insert(collection_id, white_list_collection);566 Ok(())567 }568569 /// Remove an address from white list.570 /// 571 /// # Permissions572 /// 573 /// * Collection Owner574 /// * Collection Admin575 /// 576 /// # Arguments577 /// 578 /// * collection_id.579 /// 580 /// * address.581 #[weight = T::WeightInfo::remove_from_white_list()]582 pub fn remove_from_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{583584 let sender = ensure_signed(origin)?;585 Self::check_owner_or_admin_permissions(collection_id, sender)?;586587 if <WhiteList<T>>::contains_key(collection_id) {588 let mut white_list_collection = <WhiteList<T>>::get(collection_id);589 if white_list_collection.contains(&address.clone())590 {591 white_list_collection.retain(|i| *i != address.clone());592 <WhiteList<T>>::insert(collection_id, white_list_collection);593 }594 }595596 Ok(())597 }598599 /// Toggle between normal and white list access for the methods with access for `Anyone`.600 /// 601 /// # Permissions602 /// 603 /// * Collection Owner.604 /// 605 /// # Arguments606 /// 607 /// * collection_id.608 /// 609 /// * mode: [AccessMode]610 #[weight = T::WeightInfo::set_public_access_mode()]611 pub fn set_public_access_mode(origin, collection_id: u64, mode: AccessMode) -> DispatchResult612 {613 let sender = ensure_signed(origin)?;614615 Self::check_owner_permissions(collection_id, sender)?;616 let mut target_collection = <Collection<T>>::get(collection_id);617 target_collection.access = mode;618 <Collection<T>>::insert(collection_id, target_collection);619620 Ok(())621 }622623 /// Allows Anyone to create tokens if:624 /// * White List is enabled, and625 /// * Address is added to white list, and626 /// * This method was called with True parameter627 /// 628 /// # Permissions629 /// * Collection Owner630 ///631 /// # Arguments632 /// 633 /// * collection_id.634 /// 635 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.636 #[weight = T::WeightInfo::set_mint_permission()]637 pub fn set_mint_permission(origin, collection_id: u64, mint_permission: bool) -> DispatchResult638 {639 let sender = ensure_signed(origin)?;640641 Self::check_owner_permissions(collection_id, sender)?;642 let mut target_collection = <Collection<T>>::get(collection_id);643 target_collection.mint_mode = mint_permission;644 <Collection<T>>::insert(collection_id, target_collection);645646 Ok(())647 }648649 /// Change the owner of the collection.650 /// 651 /// # Permissions652 /// 653 /// * Collection Owner.654 /// 655 /// # Arguments656 /// 657 /// * collection_id.658 /// 659 /// * new_owner.660 #[weight = T::WeightInfo::change_collection_owner()]661 pub fn change_collection_owner(origin, collection_id: u64, new_owner: T::AccountId) -> DispatchResult {662663 let sender = ensure_signed(origin)?;664 Self::check_owner_permissions(collection_id, sender)?;665 let mut target_collection = <Collection<T>>::get(collection_id);666 target_collection.owner = new_owner;667 <Collection<T>>::insert(collection_id, target_collection);668669 Ok(())670 }671672 /// Adds an admin of the Collection.673 /// 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. 674 /// 675 /// # Permissions676 /// 677 /// * Collection Owner.678 /// * Collection Admin.679 /// 680 /// # Arguments681 /// 682 /// * collection_id: ID of the Collection to add admin for.683 /// 684 /// * new_admin_id: Address of new admin to add.685 #[weight = T::WeightInfo::add_collection_admin()]686 pub fn add_collection_admin(origin, collection_id: u64, new_admin_id: T::AccountId) -> DispatchResult {687688 let sender = ensure_signed(origin)?;689 Self::check_owner_or_admin_permissions(collection_id, sender)?;690 let mut admin_arr: Vec<T::AccountId> = Vec::new();691692 if <AdminList<T>>::contains_key(collection_id)693 {694 admin_arr = <AdminList<T>>::get(collection_id);695 ensure!(!admin_arr.contains(&new_admin_id), "Account already has admin role");696 }697698 // Number of collection admins699 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, "Number of collection admins bound exceeded");700701 admin_arr.push(new_admin_id);702 <AdminList<T>>::insert(collection_id, admin_arr);703704 Ok(())705 }706707 /// 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.708 ///709 /// # Permissions710 /// 711 /// * Collection Owner.712 /// * Collection Admin.713 /// 714 /// # Arguments715 /// 716 /// * collection_id: ID of the Collection to remove admin for.717 /// 718 /// * account_id: Address of admin to remove.719 #[weight = T::WeightInfo::remove_collection_admin()]720 pub fn remove_collection_admin(origin, collection_id: u64, account_id: T::AccountId) -> DispatchResult {721722 let sender = ensure_signed(origin)?;723 Self::check_owner_or_admin_permissions(collection_id, sender)?;724725 if <AdminList<T>>::contains_key(collection_id)726 {727 let mut admin_arr = <AdminList<T>>::get(collection_id);728 admin_arr.retain(|i| *i != account_id);729 <AdminList<T>>::insert(collection_id, admin_arr);730 }731732 Ok(())733 }734735 /// # Permissions736 /// 737 /// * Collection Owner738 /// 739 /// # Arguments740 /// 741 /// * collection_id.742 /// 743 /// * new_sponsor.744 #[weight = T::WeightInfo::set_collection_sponsor()]745 pub fn set_collection_sponsor(origin, collection_id: u64, new_sponsor: T::AccountId) -> DispatchResult {746747 let sender = ensure_signed(origin)?;748 ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");749750 let mut target_collection = <Collection<T>>::get(collection_id);751 ensure!(sender == target_collection.owner, "You do not own this collection");752753 target_collection.unconfirmed_sponsor = new_sponsor;754 <Collection<T>>::insert(collection_id, target_collection);755756 Ok(())757 }758759 /// # Permissions760 /// 761 /// * Sponsor.762 /// 763 /// # Arguments764 /// 765 /// * collection_id.766 #[weight = T::WeightInfo::confirm_sponsorship()]767 pub fn confirm_sponsorship(origin, collection_id: u64) -> DispatchResult {768769 let sender = ensure_signed(origin)?;770 ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");771772 let mut target_collection = <Collection<T>>::get(collection_id);773 ensure!(sender == target_collection.unconfirmed_sponsor, "This address is not set as sponsor, use setCollectionSponsor first");774775 target_collection.sponsor = target_collection.unconfirmed_sponsor;776 target_collection.unconfirmed_sponsor = T::AccountId::default();777 <Collection<T>>::insert(collection_id, target_collection);778779 Ok(())780 }781782 /// Switch back to pay-per-own-transaction model.783 ///784 /// # Permissions785 ///786 /// * Collection owner.787 /// 788 /// # Arguments789 /// 790 /// * collection_id.791 #[weight = T::WeightInfo::remove_collection_sponsor()]792 pub fn remove_collection_sponsor(origin, collection_id: u64) -> DispatchResult {793794 let sender = ensure_signed(origin)?;795 ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");796797 let mut target_collection = <Collection<T>>::get(collection_id);798 ensure!(sender == target_collection.owner, "You do not own this collection");799800 target_collection.sponsor = T::AccountId::default();801 <Collection<T>>::insert(collection_id, target_collection);802803 Ok(())804 }805806 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.807 /// 808 /// # Permissions809 /// 810 /// * Collection Owner.811 /// * Collection Admin.812 /// * Anyone if813 /// * White List is enabled, and814 /// * Address is added to white list, and815 /// * MintPermission is enabled (see SetMintPermission method)816 /// 817 /// # Arguments818 /// 819 /// * collection_id: ID of the collection.820 /// 821 /// * owner: Address, initial owner of the NFT.822 ///823 /// * data: Token data to store on chain.824 // #[weight =825 // (130_000_000 as Weight)826 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))827 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))828 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]829830 #[weight = T::WeightInfo::create_item(data.len())]831 pub fn create_item(origin, collection_id: u64, owner: T::AccountId, data: CreateItemData) -> DispatchResult {832833 let sender = ensure_signed(origin)?;834835 Self::collection_exists(collection_id)?;836837 let target_collection = <Collection<T>>::get(collection_id);838839 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;840 Self::validate_create_item_args(&target_collection, &data)?;841 Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;842843 Ok(())844 }845846 /// This method creates multiple instances of NFT Collection created with CreateCollection method.847 /// 848 /// # Permissions849 /// 850 /// * Collection Owner.851 /// * Collection Admin.852 /// * Anyone if853 /// * White List is enabled, and854 /// * Address is added to white list, and855 /// * MintPermission is enabled (see SetMintPermission method)856 /// 857 /// # Arguments858 /// 859 /// * collection_id: ID of the collection.860 /// 861 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].862 /// 863 /// * owner: Address, initial owner of the NFT.864 #[weight = T::WeightInfo::create_item(items_data.into_iter()865 .map(|data| { data.len() })866 .sum())]867 pub fn create_multiple_items(origin, collection_id: u64, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {868869 ensure!(items_data.len() > 0, "Length of items properties must be greater than 0.");870 let sender = ensure_signed(origin)?;871872 Self::collection_exists(collection_id)?;873 let target_collection = <Collection<T>>::get(collection_id);874875 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;876877 for data in &items_data {878 Self::validate_create_item_args(&target_collection, data)?;879 }880 for data in &items_data {881 Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;882 }883884 Ok(())885 }886887 /// Destroys a concrete instance of NFT.888 /// 889 /// # Permissions890 /// 891 /// * Collection Owner.892 /// * Collection Admin.893 /// * Current NFT Owner.894 /// 895 /// # Arguments896 /// 897 /// * collection_id: ID of the collection.898 /// 899 /// * item_id: ID of NFT to burn.900 #[weight = T::WeightInfo::burn_item()]901 pub fn burn_item(origin, collection_id: u64, item_id: u64) -> DispatchResult {902903 let sender = ensure_signed(origin)?;904 Self::collection_exists(collection_id)?;905906 // Transfer permissions check907 let target_collection = <Collection<T>>::get(collection_id);908 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||909 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),910 "Only item owner, collection owner and admins can modify item");911912 if target_collection.access == AccessMode::WhiteList {913 Self::check_white_list(collection_id, &sender)?;914 }915916 match target_collection.mode917 {918 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,919 CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?,920 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,921 _ => ()922 };923924 // call event925 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));926927 Ok(())928 }929930 /// Change ownership of the token.931 /// 932 /// # Permissions933 /// 934 /// * Collection Owner935 /// * Collection Admin936 /// * Current NFT owner937 ///938 /// # Arguments939 /// 940 /// * recipient: Address of token recipient.941 /// 942 /// * collection_id.943 /// 944 /// * item_id: ID of the item945 /// * Non-Fungible Mode: Required.946 /// * Fungible Mode: Ignored.947 /// * Re-Fungible Mode: Required.948 /// 949 /// * value: Amount to transfer.950 /// * Non-Fungible Mode: Ignored951 /// * Fungible Mode: Must specify transferred amount952 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)953 #[weight = T::WeightInfo::transfer()]954 pub fn transfer(origin, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64) -> DispatchResult {955956 let sender = ensure_signed(origin)?;957958 // Transfer permissions check959 let target_collection = <Collection<T>>::get(collection_id);960 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||961 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),962 "Only item owner, collection owner and admins can modify item");963964 if target_collection.access == AccessMode::WhiteList {965 Self::check_white_list(collection_id, &sender)?;966 Self::check_white_list(collection_id, &recipient)?;967 }968969 match target_collection.mode970 {971 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,972 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,973 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,974 _ => ()975 };976977 Ok(())978 }979980 /// Set, change, or remove approved address to transfer the ownership of the NFT.981 /// 982 /// # Permissions983 /// 984 /// * Collection Owner985 /// * Collection Admin986 /// * Current NFT owner987 /// 988 /// # Arguments989 /// 990 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).991 /// 992 /// * collection_id.993 /// 994 /// * item_id: ID of the item.995 #[weight = T::WeightInfo::approve()]996 pub fn approve(origin, approved: T::AccountId, collection_id: u64, item_id: u64) -> DispatchResult {997998 let sender = ensure_signed(origin)?;9991000 // Transfer permissions check1001 let target_collection = <Collection<T>>::get(collection_id);1002 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1003 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1004 "Only item owner, collection owner and admins can approve");10051006 if target_collection.access == AccessMode::WhiteList {1007 Self::check_white_list(collection_id, &sender)?;1008 Self::check_white_list(collection_id, &approved)?;1009 }10101011 // amount param stub1012 let amount = 100000000;10131014 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));1015 if list_exists {10161017 let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));1018 let item_contains = list.iter().any(|i| i.approved == approved);10191020 if !item_contains {1021 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1022 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1023 }1024 } else {10251026 let mut list = Vec::new();1027 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1028 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1029 }10301031 Ok(())1032 }1033 1034 /// 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.1035 /// 1036 /// # Permissions1037 /// * Collection Owner1038 /// * Collection Admin1039 /// * Current NFT owner1040 /// * Address approved by current NFT owner1041 /// 1042 /// # Arguments1043 /// 1044 /// * from: Address that owns token.1045 /// 1046 /// * recipient: Address of token recipient.1047 /// 1048 /// * collection_id.1049 /// 1050 /// * item_id: ID of the item.1051 /// 1052 /// * value: Amount to transfer.1053 #[weight = T::WeightInfo::transfer_from()]1054 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64 ) -> DispatchResult {10551056 let sender = ensure_signed(origin)?;1057 let mut appoved_transfer = false;10581059 // Check approve1060 if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {1061 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));1062 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());1063 if opt_item.is_some()1064 {1065 appoved_transfer = true;1066 ensure!(opt_item.unwrap().amount >= value, "Requested value more than approved");1067 }1068 }10691070 // Transfer permissions check1071 let target_collection = <Collection<T>>::get(collection_id);1072 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1073 "Only item owner, collection owner and admins can modify items");10741075 if target_collection.access == AccessMode::WhiteList {1076 Self::check_white_list(collection_id, &sender)?;1077 Self::check_white_list(collection_id, &recipient)?;1078 }10791080 // remove approve1081 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))1082 .into_iter().filter(|i| i.approved != sender.clone()).collect();1083 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);108410851086 match target_collection.mode1087 {1088 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1089 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,1090 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1091 _ => ()1092 };10931094 Ok(())1095 }10961097 ///1098 #[weight = 0]1099 pub fn safe_transfer_from(origin, collection_id: u64, item_id: u64, new_owner: T::AccountId) -> DispatchResult {11001101 // let no_perm_mes = "You do not have permissions to modify this collection";1102 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1103 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1104 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);11051106 // // on_nft_received call11071108 // Self::transfer(origin, collection_id, item_id, new_owner)?;11091110 Ok(())1111 }11121113 /// Set off-chain data schema.1114 /// 1115 /// # Permissions1116 /// 1117 /// * Collection Owner1118 /// * Collection Admin1119 /// 1120 /// # Arguments1121 /// 1122 /// * collection_id.1123 /// 1124 /// * schema: String representing the offchain data schema.1125 #[weight = T::WeightInfo::set_variable_meta_data()]1126 pub fn set_variable_meta_data (1127 origin,1128 collection_id: u64,1129 item_id: u64,1130 data: Vec<u8>1131 ) -> DispatchResult {1132 let sender = ensure_signed(origin)?;1133 1134 Self::collection_exists(collection_id)?;11351136 // Modify permissions check1137 let target_collection = <Collection<T>>::get(collection_id);1138 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1139 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1140 "Only item owner, collection owner and admins can modify item");11411142 Self::item_exists(collection_id, item_id, &target_collection.mode)?;11431144 match target_collection.mode1145 {1146 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1147 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1148 _ => ()1149 };11501151 Ok(())1152 }1153 11541155 /// Set off-chain data schema.1156 /// 1157 /// # Permissions1158 /// 1159 /// * Collection Owner1160 /// * Collection Admin1161 /// 1162 /// # Arguments1163 /// 1164 /// * collection_id.1165 /// 1166 /// * schema: String representing the offchain data schema.1167 #[weight = T::WeightInfo::set_offchain_schema()]1168 pub fn set_offchain_schema(1169 origin,1170 collection_id: u64,1171 schema: Vec<u8>1172 ) -> DispatchResult {1173 let sender = ensure_signed(origin)?;1174 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;11751176 let mut target_collection = <Collection<T>>::get(collection_id);1177 target_collection.offchain_schema = schema;1178 <Collection<T>>::insert(collection_id, target_collection);11791180 Ok(())1181 }11821183 /// Set const on-chain data schema.1184 /// 1185 /// # Permissions1186 /// 1187 /// * Collection Owner1188 /// * Collection Admin1189 /// 1190 /// # Arguments1191 /// 1192 /// * collection_id.1193 /// 1194 /// * schema: String representing the const on-chain data schema.1195 #[weight = T::WeightInfo::set_const_on_chain_schema()]1196 pub fn set_const_on_chain_schema (1197 origin,1198 collection_id: u64,1199 schema: Vec<u8>1200 ) -> DispatchResult {1201 let sender = ensure_signed(origin)?;1202 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12031204 let mut target_collection = <Collection<T>>::get(collection_id);1205 target_collection.const_on_chain_schema = schema;1206 <Collection<T>>::insert(collection_id, target_collection);12071208 Ok(())1209 }12101211 /// Set variable on-chain data schema.1212 /// 1213 /// # Permissions1214 /// 1215 /// * Collection Owner1216 /// * Collection Admin1217 /// 1218 /// # Arguments1219 /// 1220 /// * collection_id.1221 /// 1222 /// * schema: String representing the variable on-chain data schema.1223 #[weight = T::WeightInfo::set_const_on_chain_schema()]1224 pub fn set_variable_on_chain_schema (1225 origin,1226 collection_id: u64,1227 schema: Vec<u8>1228 ) -> DispatchResult {1229 let sender = ensure_signed(origin)?;1230 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12311232 let mut target_collection = <Collection<T>>::get(collection_id);1233 target_collection.variable_on_chain_schema = schema;1234 <Collection<T>>::insert(collection_id, target_collection);12351236 Ok(())1237 }12381239 // Sudo permissions function1240 #[weight = 0]1241 pub fn set_chain_limits(1242 origin,1243 limits: ChainLimits1244 ) -> DispatchResult {1245 ensure_root(origin)?;1246 <ChainLimit>::put(limits);1247 Ok(())1248 }12491250 /// Enable smart contract self-sponsoring.1251 /// 1252 /// # Permissions1253 /// 1254 /// * Contract Owner1255 /// 1256 /// # Arguments1257 /// 1258 /// * contract address1259 /// * enable flag1260 /// 1261 #[weight = 0]1262 pub fn enable_contract_sponsoring(1263 origin,1264 contract_address: T::AccountId,1265 enable: bool1266 ) -> DispatchResult {1267 let sender = ensure_signed(origin)?;1268 let mut is_owner = false;1269 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1270 let owner = <ContractOwner<T>>::get(&contract_address);1271 is_owner = sender == owner;1272 }1273 ensure!(is_owner, "Only contract owner may call this method");12741275 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1276 Ok(())1277 }12781279 }1280}12811282impl<T: Trait> Module<T> {12831284 fn can_create_items_in_collection(collection_id: u64, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {12851286 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1287 ensure!(collection.mint_mode == true, "Public minting is not allowed for this collection");1288 Self::check_white_list(collection_id, owner)?;1289 Self::check_white_list(collection_id, sender)?;1290 }12911292 Ok(())1293 }12941295 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1296 match target_collection.mode1297 {1298 CollectionMode::NFT => {1299 if let CreateItemData::NFT(data) = data {1300 // check sizes1301 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, "const_data exceeded data limit.");1302 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, "variable_data exceeded data limit.");1303 } else {1304 fail!("Not NFT item data used to mint in NFT collection.");1305 }1306 },1307 CollectionMode::Fungible(_) => {1308 if let CreateItemData::Fungible(_) = data {1309 } else {1310 fail!("Not Fungible item data used to mint in Fungible collection.");1311 }1312 },1313 CollectionMode::ReFungible(_) => {1314 if let CreateItemData::ReFungible(data) = data {13151316 // check sizes1317 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, "const_data exceeded data limit.");1318 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, "variable_data exceeded data limit.");1319 } else {1320 fail!("Not Re Fungible item data used to mint in Re Fungible collection.");1321 }1322 },1323 _ => { fail!("Unexpected collection type."); }1324 };13251326 Ok(())1327 }13281329 fn create_item_no_validation(collection_id: u64, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1330 match data1331 {1332 CreateItemData::NFT(data) => {1333 let item = NftItemType {1334 collection: collection_id,1335 owner,1336 const_data: data.const_data,1337 variable_data: data.variable_data1338 };13391340 Self::add_nft_item(item)?;1341 },1342 CreateItemData::Fungible(_) => {1343 let item = FungibleItemType {1344 collection: collection_id,1345 owner,1346 value: (10 as u128).pow(collection.decimal_points)1347 };13481349 Self::add_fungible_item(item)?;1350 },1351 CreateItemData::ReFungible(data) => {1352 let mut owner_list = Vec::new();1353 let value = (10 as u128).pow(collection.decimal_points);1354 owner_list.push(Ownership {owner: owner.clone(), fraction: value});13551356 let item = ReFungibleItemType {1357 collection: collection_id,1358 owner: owner_list,1359 const_data: data.const_data,1360 variable_data: data.variable_data1361 };13621363 Self::add_refungible_item(item)?;1364 }1365 };136613671368 // call event1369 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));13701371 Ok(())1372 }13731374 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {1375 let current_index = <ItemListIndex>::get(item.collection)1376 .checked_add(1)1377 .expect("Item list index id error");1378 let itemcopy = item.clone();1379 let owner = item.owner.clone();1380 let value = item.value as u64;13811382 Self::add_token_index(item.collection, current_index, owner.clone())?;13831384 <ItemListIndex>::insert(item.collection, current_index);1385 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);13861387 // Add current block1388 let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1389 <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);1390 1391 // Update balance1392 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1393 .checked_add(value)1394 .unwrap();1395 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);13961397 Ok(())1398 }13991400 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1401 let current_index = <ItemListIndex>::get(item.collection)1402 .checked_add(1)1403 .expect("Item list index id error");1404 let itemcopy = item.clone();14051406 let value = item.owner.first().unwrap().fraction as u64;1407 let owner = item.owner.first().unwrap().owner.clone();14081409 Self::add_token_index(item.collection, current_index, owner.clone())?;14101411 <ItemListIndex>::insert(item.collection, current_index);1412 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);14131414 // Add current block1415 let block_number: T::BlockNumber = 0.into();1416 <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);14171418 // Update balance1419 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1420 .checked_add(value)1421 .unwrap();1422 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);14231424 Ok(())1425 }14261427 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1428 let current_index = <ItemListIndex>::get(item.collection)1429 .checked_add(1)1430 .expect("Item list index id error");14311432 let item_owner = item.owner.clone();1433 let collection_id = item.collection.clone();1434 Self::add_token_index(collection_id, current_index, item.owner.clone())?;14351436 <ItemListIndex>::insert(collection_id, current_index);1437 <NftItemList<T>>::insert(collection_id, current_index, item);14381439 // Add current block1440 let block_number: T::BlockNumber = 0.into();1441 <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);14421443 // Update balance1444 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1445 .checked_add(1)1446 .unwrap();1447 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);14481449 Ok(())1450 }14511452 fn burn_refungible_item(1453 collection_id: u64,1454 item_id: u64,1455 owner: T::AccountId,1456 ) -> DispatchResult {1457 ensure!(1458 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1459 "Item does not exists"1460 );1461 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1462 let item = collection1463 .owner1464 .iter()1465 .filter(|&i| i.owner == owner)1466 .next()1467 .unwrap();1468 Self::remove_token_index(collection_id, item_id, owner.clone())?;14691470 // remove approve list1471 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));14721473 // update balance1474 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1475 .checked_sub(item.fraction as u64)1476 .unwrap();1477 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);14781479 <ReFungibleItemList<T>>::remove(collection_id, item_id);14801481 Ok(())1482 }14831484 fn burn_nft_item(collection_id: u64, item_id: u64) -> DispatchResult {1485 ensure!(1486 <NftItemList<T>>::contains_key(collection_id, item_id),1487 "Item does not exists"1488 );1489 let item = <NftItemList<T>>::get(collection_id, item_id);1490 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;14911492 // remove approve list1493 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));14941495 // update balance1496 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1497 .checked_sub(1)1498 .unwrap();1499 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1500 <NftItemList<T>>::remove(collection_id, item_id);15011502 Ok(())1503 }15041505 fn burn_fungible_item(collection_id: u64, item_id: u64) -> DispatchResult {1506 ensure!(1507 <FungibleItemList<T>>::contains_key(collection_id, item_id),1508 "Item does not exists"1509 );1510 let item = <FungibleItemList<T>>::get(collection_id, item_id);1511 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;15121513 // remove approve list1514 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));15151516 // update balance1517 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1518 .checked_sub(item.value as u64)1519 .unwrap();1520 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);15211522 <FungibleItemList<T>>::remove(collection_id, item_id);15231524 Ok(())1525 }15261527 fn collection_exists(collection_id: u64) -> DispatchResult {1528 ensure!(1529 <Collection<T>>::contains_key(collection_id),1530 "This collection does not exist"1531 );1532 Ok(())1533 }15341535 fn check_owner_permissions(collection_id: u64, subject: T::AccountId) -> DispatchResult {1536 Self::collection_exists(collection_id)?;15371538 let target_collection = <Collection<T>>::get(collection_id);1539 ensure!(1540 subject == target_collection.owner,1541 "You do not own this collection"1542 );15431544 Ok(())1545 }15461547 fn is_owner_or_admin_permissions(collection_id: u64, subject: T::AccountId) -> bool {1548 let target_collection = <Collection<T>>::get(collection_id);1549 let mut result: bool = subject == target_collection.owner;1550 let exists = <AdminList<T>>::contains_key(collection_id);15511552 if !result & exists {1553 if <AdminList<T>>::get(collection_id).contains(&subject) {1554 result = true1555 }1556 }15571558 result1559 }15601561 fn check_owner_or_admin_permissions(1562 collection_id: u64,1563 subject: T::AccountId,1564 ) -> DispatchResult {1565 Self::collection_exists(collection_id)?;1566 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());15671568 ensure!(1569 result,1570 "You do not have permissions to modify this collection"1571 );1572 Ok(())1573 }15741575 fn is_item_owner(subject: T::AccountId, collection_id: u64, item_id: u64) -> bool {1576 let target_collection = <Collection<T>>::get(collection_id);15771578 match target_collection.mode {1579 CollectionMode::NFT => {1580 <NftItemList<T>>::get(collection_id, item_id).owner == subject1581 }1582 CollectionMode::Fungible(_) => {1583 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1584 }1585 CollectionMode::ReFungible(_) => {1586 <ReFungibleItemList<T>>::get(collection_id, item_id)1587 .owner1588 .iter()1589 .any(|i| i.owner == subject)1590 }1591 CollectionMode::Invalid => false,1592 }1593 }15941595 fn check_white_list(collection_id: u64, address: &T::AccountId) -> DispatchResult {1596 let mes = "Address is not in white list";1597 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1598 let wl = <WhiteList<T>>::get(collection_id);1599 ensure!(wl.contains(address), mes);16001601 Ok(())1602 }16031604 fn transfer_fungible(1605 collection_id: u64,1606 item_id: u64,1607 value: u64,1608 owner: T::AccountId,1609 new_owner: T::AccountId,1610 ) -> DispatchResult {1611 ensure!(1612 <FungibleItemList<T>>::contains_key(collection_id, item_id),1613 "Item not exists"1614 );16151616 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1617 let amount = full_item.value;16181619 ensure!(amount >= value.into(), "Item balance not enouth");16201621 // update balance1622 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1623 .checked_sub(value)1624 .unwrap();1625 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);16261627 let mut new_owner_account_id = 0;1628 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1629 if new_owner_items.len() > 0 {1630 new_owner_account_id = new_owner_items[0];1631 }16321633 let val64 = value.into();16341635 // transfer1636 if amount == val64 && new_owner_account_id == 0 {1637 // change owner1638 // new owner do not have account1639 let mut new_full_item = full_item.clone();1640 new_full_item.owner = new_owner.clone();1641 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);16421643 // update balance1644 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1645 .checked_add(value)1646 .unwrap();1647 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);16481649 // update index collection1650 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1651 } else {1652 let mut new_full_item = full_item.clone();1653 new_full_item.value -= val64;16541655 // separate amount1656 if new_owner_account_id > 0 {1657 // new owner has account1658 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1659 item.value += val64;16601661 // update balance1662 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1663 .checked_add(value)1664 .unwrap();1665 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);16661667 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1668 } else {1669 // new owner do not have account1670 let item = FungibleItemType {1671 collection: collection_id,1672 owner: new_owner.clone(),1673 value: val64,1674 };16751676 Self::add_fungible_item(item)?;1677 }16781679 if amount == val64 {1680 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;16811682 // remove approve list1683 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1684 <FungibleItemList<T>>::remove(collection_id, item_id);1685 }16861687 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1688 }16891690 Ok(())1691 }16921693 fn transfer_refungible(1694 collection_id: u64,1695 item_id: u64,1696 value: u64,1697 owner: T::AccountId,1698 new_owner: T::AccountId,1699 ) -> DispatchResult {1700 ensure!(1701 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1702 "Item not exists"1703 );17041705 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1706 let item = full_item1707 .owner1708 .iter()1709 .filter(|i| i.owner == owner)1710 .next()1711 .unwrap();1712 let amount = item.fraction;17131714 ensure!(amount >= value.into(), "Item balance not enouth");17151716 // update balance1717 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1718 .checked_sub(value)1719 .unwrap();1720 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);17211722 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1723 .checked_add(value)1724 .unwrap();1725 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);17261727 let old_owner = item.owner.clone();1728 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);1729 let val64 = value.into();17301731 // transfer1732 if amount == val64 && !new_owner_has_account {1733 // change owner1734 // new owner do not have account1735 let mut new_full_item = full_item.clone();1736 new_full_item1737 .owner1738 .iter_mut()1739 .find(|i| i.owner == owner)1740 .unwrap()1741 .owner = new_owner.clone();1742 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);17431744 // update index collection1745 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1746 } else {1747 let mut new_full_item = full_item.clone();1748 new_full_item1749 .owner1750 .iter_mut()1751 .find(|i| i.owner == owner)1752 .unwrap()1753 .fraction -= val64;17541755 // separate amount1756 if new_owner_has_account {1757 // new owner has account1758 new_full_item1759 .owner1760 .iter_mut()1761 .find(|i| i.owner == new_owner)1762 .unwrap()1763 .fraction += val64;1764 } else {1765 // new owner do not have account1766 new_full_item.owner.push(Ownership {1767 owner: new_owner.clone(),1768 fraction: val64,1769 });1770 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1771 }17721773 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1774 }17751776 Ok(())1777 }17781779 fn transfer_nft(1780 collection_id: u64,1781 item_id: u64,1782 sender: T::AccountId,1783 new_owner: T::AccountId,1784 ) -> DispatchResult {1785 ensure!(1786 <NftItemList<T>>::contains_key(collection_id, item_id),1787 "Item not exists"1788 );17891790 let mut item = <NftItemList<T>>::get(collection_id, item_id);17911792 ensure!(1793 sender == item.owner,1794 "sender parameter and item owner must be equal"1795 );17961797 // update balance1798 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1799 .checked_sub(1)1800 .unwrap();1801 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);18021803 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1804 .checked_add(1)1805 .unwrap();1806 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);18071808 // change owner1809 let old_owner = item.owner.clone();1810 item.owner = new_owner.clone();1811 <NftItemList<T>>::insert(collection_id, item_id, item);18121813 // update index collection1814 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;18151816 // reset approved list1817 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));1818 Ok(())1819 }1820 1821 fn item_exists(1822 collection_id: u64,1823 item_id: u64,1824 mode: &CollectionMode1825 ) -> DispatchResult {1826 match mode {1827 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), "Item does not exists"),1828 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), "Item does not exists"),1829 CollectionMode::Fungible(_) => ensure!(<FungibleItemList<T>>::contains_key(collection_id, item_id), "Item does not exists"),1830 _ => ()1831 };1832 1833 Ok(())1834 }18351836 fn set_re_fungible_variable_data(1837 collection_id: u64,1838 item_id: u64,1839 data: Vec<u8>1840 ) -> DispatchResult {1841 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);18421843 item.variable_data = data;18441845 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);18461847 Ok(())1848 }18491850 fn set_nft_variable_data(1851 collection_id: u64,1852 item_id: u64,1853 data: Vec<u8>1854 ) -> DispatchResult {1855 let mut item = <NftItemList<T>>::get(collection_id, item_id);1856 1857 item.variable_data = data;18581859 <NftItemList<T>>::insert(collection_id, item_id, item);1860 1861 Ok(())1862 }18631864 fn init_collection(item: &CollectionType<T::AccountId>) {1865 // check params1866 assert!(1867 item.decimal_points <= 4,1868 "decimal_points parameter must be lower than 4"1869 );1870 assert!(1871 item.name.len() <= 64,1872 "Collection name can not be longer than 63 char"1873 );1874 assert!(1875 item.name.len() <= 256,1876 "Collection description can not be longer than 255 char"1877 );1878 assert!(1879 item.token_prefix.len() <= 16,1880 "Token prefix can not be longer than 15 char"1881 );18821883 // Generate next collection ID1884 let next_id = CreatedCollectionCount::get()1885 .checked_add(1)1886 .expect("collection id error");18871888 CreatedCollectionCount::put(next_id);1889 }18901891 fn init_nft_token(item: &NftItemType<T::AccountId>) {1892 let current_index = <ItemListIndex>::get(item.collection)1893 .checked_add(1)1894 .expect("Item list index id error");18951896 let item_owner = item.owner.clone();1897 let collection_id = item.collection.clone();1898 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();18991900 <ItemListIndex>::insert(collection_id, current_index);19011902 // Update balance1903 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1904 .checked_add(1)1905 .unwrap();1906 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);1907 }19081909 fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {1910 let current_index = <ItemListIndex>::get(item.collection)1911 .checked_add(1)1912 .expect("Item list index id error");1913 let owner = item.owner.clone();1914 let value = item.value as u64;19151916 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();19171918 <ItemListIndex>::insert(item.collection, current_index);19191920 // Update balance1921 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1922 .checked_add(value)1923 .unwrap();1924 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1925 }19261927 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {1928 let current_index = <ItemListIndex>::get(item.collection)1929 .checked_add(1)1930 .expect("Item list index id error");19311932 let value = item.owner.first().unwrap().fraction as u64;1933 let owner = item.owner.first().unwrap().owner.clone();19341935 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();19361937 <ItemListIndex>::insert(item.collection, current_index);19381939 // Update balance1940 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1941 .checked_add(value)1942 .unwrap();1943 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1944 }19451946 fn add_token_index(collection_id: u64, item_index: u64, owner: T::AccountId) -> DispatchResult {19471948 // add to account limit1949 if <AccountItemCount<T>>::contains_key(owner.clone()) {19501951 // bound Owned tokens by a single address1952 let count = <AccountItemCount<T>>::get(owner.clone());1953 ensure!(count < ChainLimit::get().account_token_ownership_limit, "Owned tokens by a single address bound exceeded");19541955 <AccountItemCount<T>>::insert(owner.clone(), 1956 count.checked_add(1).unwrap());1957 }1958 else {1959 <AccountItemCount<T>>::insert(owner.clone(), 1);1960 }19611962 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());1963 if list_exists {1964 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());1965 let item_contains = list.contains(&item_index.clone());19661967 if !item_contains {1968 list.push(item_index.clone());1969 }19701971 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);1972 } else {1973 let mut itm = Vec::new();1974 itm.push(item_index.clone());1975 <AddressTokens<T>>::insert(collection_id, owner, itm);1976 1977 }19781979 Ok(())1980 }19811982 fn remove_token_index(1983 collection_id: u64,1984 item_index: u64,1985 owner: T::AccountId,1986 ) -> DispatchResult {19871988 // update counter1989 <AccountItemCount<T>>::insert(owner.clone(), 1990 <AccountItemCount<T>>::get(owner.clone()).checked_sub(1).unwrap());199119921993 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());1994 if list_exists {1995 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());1996 let item_contains = list.contains(&item_index.clone());19971998 if item_contains {1999 list.retain(|&item| item != item_index);2000 <AddressTokens<T>>::insert(collection_id, owner, list);2001 }2002 }20032004 Ok(())2005 }20062007 fn move_token_index(2008 collection_id: u64,2009 item_index: u64,2010 old_owner: T::AccountId,2011 new_owner: T::AccountId,2012 ) -> DispatchResult {2013 Self::remove_token_index(collection_id, item_index, old_owner)?;2014 Self::add_token_index(collection_id, item_index, new_owner)?;20152016 Ok(())2017 }2018}20192020////////////////////////////////////////////////////////////////////////////////////////////////////2021// Economic models2022// #region20232024/// Fee multiplier.2025pub type Multiplier = FixedU128;20262027type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2028 <T as system::Trait>::AccountId,2029>>::Balance;2030type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2031 <T as system::Trait>::AccountId,2032>>::NegativeImbalance;20332034/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2035/// in the queue.2036#[derive(Encode, Decode, Clone, Eq, PartialEq)]2037pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2038 #[codec(compact)] BalanceOf<T>2039);20402041impl<T: Trait + Send + Sync> sp_std::fmt::Debug2042 for ChargeTransactionPayment<T>2043{2044 #[cfg(feature = "std")]2045 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2046 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2047 }2048 #[cfg(not(feature = "std"))]2049 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2050 Ok(())2051 }2052}20532054impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2055where2056 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2057 BalanceOf<T>: Send + Sync + FixedPointOperand,2058{2059 /// utility constructor. Used only in client/factory code.2060 pub fn from(fee: BalanceOf<T>) -> Self {2061 Self(fee)2062 }20632064 pub fn traditional_fee(2065 len: usize,2066 info: &DispatchInfoOf<T::Call>,2067 tip: BalanceOf<T>,2068 ) -> BalanceOf<T>2069 where2070 T::Call: Dispatchable<Info = DispatchInfo>,2071 {2072 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2073 }20742075 fn withdraw_fee(2076 &self,2077 who: &T::AccountId,2078 call: &T::Call,2079 info: &DispatchInfoOf<T::Call>,2080 len: usize,2081 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2082 let tip = self.0;20832084 // Set fee based on call type. Creating collection costs 1 Unique.2085 // All other transactions have traditional fees so far2086 // let fee = match call.is_sub_type() {2087 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2088 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2089 // // _ => <BalanceOf<T>>::from(100)2090 // };2091 let fee = Self::traditional_fee(len, info, tip);20922093 // Determine who is paying transaction fee based on ecnomic model2094 // Parse call to extract collection ID and access collection sponsor2095 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2096 Some(Call::create_item(collection_id, _properties, _owner)) => {2097 <Collection<T>>::get(collection_id).sponsor2098 }2099 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {2100 let _collection_mode = <Collection<T>>::get(collection_id).mode;21012102 // sponsor timeout2103 let sponsor_transfer = match _collection_mode {2104 CollectionMode::NFT => {2105 let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);2106 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2107 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();2108 if block_number >= limit_time {2109 <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);2110 true2111 }2112 else {2113 false2114 }2115 }2116 CollectionMode::Fungible(_) => {2117 let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);2118 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2119 if basket.iter().any(|i| i.address == _new_owner.clone())2120 {2121 let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();2122 let limit_time = item.start_block + ChainLimit::get().fungible_sponsor_transfer_timeout.into();2123 if block_number >= limit_time {2124 basket.retain(|x| x.address == item.address);2125 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });2126 <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);2127 true2128 }2129 else {2130 false2131 }2132 }2133 else {2134 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});2135 true2136 }2137 }2138 CollectionMode::ReFungible(_) => {2139 let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);2140 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2141 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();2142 if block_number >= limit_time {2143 <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);2144 true2145 } else {2146 false2147 }2148 }2149 _ => {2150 false2151 },2152 };21532154 if !sponsor_transfer {2155 T::AccountId::default()2156 } else {2157 <Collection<T>>::get(collection_id).sponsor2158 }2159 }21602161 _ => T::AccountId::default(),2162 };21632164 // Sponsor smart contracts2165 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {21662167 // On instantiation: set the contract owner2168 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {21692170 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2171 code_hash,2172 &data,2173 &who,2174 );2175 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());21762177 T::AccountId::default()2178 },21792180 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2181 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {21822183 let mut sp = T::AccountId::default();2184 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());2185 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2186 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2187 sp = called_contract;2188 }2189 }21902191 sp2192 },21932194 _ => sponsor,2195 };21962197 let mut who_pays_fee: T::AccountId = sponsor.clone();2198 if sponsor == T::AccountId::default() {2199 who_pays_fee = who.clone();2200 }22012202 // Only mess with balances if fee is not zero.2203 if fee.is_zero() {2204 return Ok((fee, None));2205 }22062207 match <T as transaction_payment::Trait>::Currency::withdraw(2208 &who_pays_fee,2209 fee,2210 if tip.is_zero() {2211 WithdrawReason::TransactionPayment.into()2212 } else {2213 WithdrawReason::TransactionPayment | WithdrawReason::Tip2214 },2215 ExistenceRequirement::KeepAlive,2216 ) {2217 Ok(imbalance) => Ok((fee, Some(imbalance))),2218 Err(_) => Err(InvalidTransaction::Payment.into()),2219 }2220 }2221}222222232224impl<T: Trait + Send + Sync> SignedExtension2225 for ChargeTransactionPayment<T>2226where2227 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2228 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2229{2230 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2231 type AccountId = T::AccountId;2232 type Call = T::Call;2233 type AdditionalSigned = ();2234 type Pre = (2235 BalanceOf<T>,2236 Self::AccountId,2237 Option<NegativeImbalanceOf<T>>,2238 BalanceOf<T>,2239 );2240 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2241 Ok(())2242 }22432244 fn validate(2245 &self,2246 _who: &Self::AccountId,2247 _call: &Self::Call,2248 _info: &DispatchInfoOf<Self::Call>,2249 _len: usize,2250 ) -> TransactionValidity {2251 Ok(ValidTransaction::default())2252 }22532254 fn pre_dispatch(2255 self,2256 who: &Self::AccountId,2257 call: &Self::Call,2258 info: &DispatchInfoOf<Self::Call>,2259 len: usize,2260 ) -> Result<Self::Pre, TransactionValidityError> {2261 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2262 Ok((self.0, who.clone(), imbalance, fee))2263 }22642265 fn post_dispatch(2266 pre: Self::Pre,2267 info: &DispatchInfoOf<Self::Call>,2268 post_info: &PostDispatchInfoOf<Self::Call>,2269 len: usize,2270 _result: &DispatchResult,2271 ) -> Result<(), TransactionValidityError> {2272 let (tip, who, imbalance, fee) = pre;2273 if let Some(payed) = imbalance {2274 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2275 len as u32, info, post_info, tip,2276 );2277 let refund = fee.saturating_sub(actual_fee);2278 let actual_payment =2279 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2280 &who, refund,2281 ) {2282 Ok(refund_imbalance) => {2283 // The refund cannot be larger than the up front payed max weight.2284 // `PostDispatchInfo::calc_unspent` guards against such a case.2285 match payed.offset(refund_imbalance) {2286 Ok(actual_payment) => actual_payment,2287 Err(_) => return Err(InvalidTransaction::Payment.into()),2288 }2289 }2290 // We do not recreate the account using the refund. The up front payment2291 // is gone in that case.2292 Err(_) => payed,2293 };2294 let imbalances = actual_payment.split(tip);2295 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2296 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2297 );2298 }2299 Ok(())2300 }2301}23022303// #endregionpallets/nft/src/tests.rsdiffbeforeafterboth--- a/pallets/nft/src/tests.rs
+++ b/pallets/nft/src/tests.rs
@@ -87,6 +87,32 @@
});
}
+// Use cases tests region
+// #region
+#[test]
+fn create_nft_multiple_items() {
+ new_test_ext().execute_with(|| {
+ default_limits();
+
+ create_test_collection(&CollectionMode::NFT, 1);
+
+ let origin1 = Origin::signed(1);
+
+ let items_data = vec![default_nft_data(), default_nft_data(), default_nft_data()];
+
+ assert_ok!(TemplateModule::create_multiple_items(
+ origin1.clone(),
+ 1,
+ 1,
+ items_data.clone().into_iter().map(|d| { d.into() }).collect()
+ ));
+ for (index, data) in items_data.iter().enumerate() {
+ assert_eq!(TemplateModule::nft_item_id(1, (index + 1) as u64).const_data.to_vec(), data.const_data);
+ assert_eq!(TemplateModule::nft_item_id(1, (index + 1) as u64).variable_data.to_vec(), data.variable_data);
+ }
+ });
+}
+
#[test]
fn create_refungible_item() {
new_test_ext().execute_with(|| {
@@ -114,6 +140,39 @@
}
#[test]
+fn create_multiple_refungible_items() {
+ new_test_ext().execute_with(|| {
+ default_limits();
+
+ create_test_collection(&CollectionMode::ReFungible(3), 1);
+
+ let origin1 = Origin::signed(1);
+
+ let items_data = vec![default_re_fungible_data(), default_re_fungible_data(), default_re_fungible_data()];
+
+ assert_ok!(TemplateModule::create_multiple_items(
+ origin1.clone(),
+ 1,
+ 1,
+ items_data.clone().into_iter().map(|d| { d.into() }).collect()
+ ));
+ for (index, data) in items_data.iter().enumerate() {
+
+ let item = TemplateModule::refungible_item_id(1, (index + 1) as u64);
+ assert_eq!(item.const_data.to_vec(), data.const_data);
+ assert_eq!(item.variable_data.to_vec(), data.variable_data);
+ assert_eq!(
+ item.owner[0],
+ Ownership {
+ owner: 1,
+ fraction: 1000
+ }
+ );
+ }
+ });
+}
+
+#[test]
fn create_fungible_item() {
new_test_ext().execute_with(|| {
default_limits();
@@ -124,12 +183,36 @@
create_test_item(collection_id, &data.into());
assert_eq!(TemplateModule::fungible_item_id(collection_id, 1).owner, 1);
- assert_eq!(TemplateModule::balance_count(1, 1), 1000);
- assert_eq!(TemplateModule::address_tokens(1, 1), [1]);
});
}
#[test]
+fn create_multiple_fungible_items() {
+ new_test_ext().execute_with(|| {
+ default_limits();
+
+ create_test_collection(&CollectionMode::Fungible(3), 1);
+
+ let origin1 = Origin::signed(1);
+
+ let items_data = vec![default_fungible_data(), default_fungible_data(), default_fungible_data()];
+
+ assert_ok!(TemplateModule::create_multiple_items(
+ origin1.clone(),
+ 1,
+ 1,
+ items_data.clone().into_iter().map(|d| { d.into() }).collect()
+ ));
+
+ for (index, _) in items_data.iter().enumerate() {
+ assert_eq!(TemplateModule::fungible_item_id(1, (index + 1) as u64).owner, 1);
+ }
+ assert_eq!(TemplateModule::balance_count(1, 1), 3000);
+ assert_eq!(TemplateModule::address_tokens(1, 1), [1, 2, 3]);
+ });
+}
+
+#[test]
fn transfer_fungible_item() {
new_test_ext().execute_with(|| {
default_limits();
@@ -1333,7 +1416,7 @@
assert_noop!(
TemplateModule::create_item(origin2.clone(), 1, 2, default_nft_data().into()),
- "Public minting is not allowed for this collection."
+ "Public minting is not allowed for this collection"
);
});
}
@@ -1362,7 +1445,7 @@
assert_noop!(
TemplateModule::create_item(origin2.clone(), 1, 2, default_nft_data().into()),
- "Public minting is not allowed for this collection."
+ "Public minting is not allowed for this collection"
);
});
}