difftreelog
Merge branch 'develop' into feature/NFTPAR-142
in: master
# Conflicts: # pallets/nft/src/lib.rs
5 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/default_weights.rsdiffbeforeafterboth--- a/pallets/nft/src/default_weights.rs
+++ b/pallets/nft/src/default_weights.rs
@@ -63,7 +63,7 @@
}
fn create_item(s: usize, ) -> Weight {
(130_000_000 as Weight)
- .saturating_add((2135 as Weight).saturating_mul(s as Weight))
+ .saturating_add((2135 as Weight).saturating_mul(s as Weight).saturating_mul(500 as Weight)) // 500 is temporary multiplier, fee for storage
.saturating_add(DbWeight::get().reads(10 as Weight))
.saturating_add(DbWeight::get().writes(8 as Weight))
}
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 CollectionMode::Fungible(_) => fail!("Can't store metadata in fungible tokens."),1174 _ => fail!("Unexpected collection type.")1175 };11761177 Ok(())1178 }1179 11801181 /// Set off-chain data schema.1182 /// 1183 /// # Permissions1184 /// 1185 /// * Collection Owner1186 /// * Collection Admin1187 /// 1188 /// # Arguments1189 /// 1190 /// * collection_id.1191 /// 1192 /// * schema: String representing the offchain data schema.1193 #[weight = T::WeightInfo::set_offchain_schema()]1194 pub fn set_offchain_schema(1195 origin,1196 collection_id: u64,1197 schema: Vec<u8>1198 ) -> DispatchResult {1199 let sender = ensure_signed(origin)?;1200 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12011202 let mut target_collection = <Collection<T>>::get(collection_id);1203 target_collection.offchain_schema = schema;1204 <Collection<T>>::insert(collection_id, target_collection);12051206 Ok(())1207 }12081209 /// Set const on-chain data schema.1210 /// 1211 /// # Permissions1212 /// 1213 /// * Collection Owner1214 /// * Collection Admin1215 /// 1216 /// # Arguments1217 /// 1218 /// * collection_id.1219 /// 1220 /// * schema: String representing the const on-chain data schema.1221 #[weight = T::WeightInfo::set_const_on_chain_schema()]1222 pub fn set_const_on_chain_schema (1223 origin,1224 collection_id: u64,1225 schema: Vec<u8>1226 ) -> DispatchResult {1227 let sender = ensure_signed(origin)?;1228 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12291230 let mut target_collection = <Collection<T>>::get(collection_id);1231 target_collection.const_on_chain_schema = schema;1232 <Collection<T>>::insert(collection_id, target_collection);12331234 Ok(())1235 }12361237 /// Set variable on-chain data schema.1238 /// 1239 /// # Permissions1240 /// 1241 /// * Collection Owner1242 /// * Collection Admin1243 /// 1244 /// # Arguments1245 /// 1246 /// * collection_id.1247 /// 1248 /// * schema: String representing the variable on-chain data schema.1249 #[weight = T::WeightInfo::set_const_on_chain_schema()]1250 pub fn set_variable_on_chain_schema (1251 origin,1252 collection_id: u64,1253 schema: Vec<u8>1254 ) -> DispatchResult {1255 let sender = ensure_signed(origin)?;1256 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12571258 let mut target_collection = <Collection<T>>::get(collection_id);1259 target_collection.variable_on_chain_schema = schema;1260 <Collection<T>>::insert(collection_id, target_collection);12611262 Ok(())1263 }12641265 // Sudo permissions function1266 #[weight = 0]1267 pub fn set_chain_limits(1268 origin,1269 limits: ChainLimits1270 ) -> DispatchResult {1271 ensure_root(origin)?;1272 <ChainLimit>::put(limits);1273 Ok(())1274 }12751276 /// Enable smart contract self-sponsoring.1277 /// 1278 /// # Permissions1279 /// 1280 /// * Contract Owner1281 /// 1282 /// # Arguments1283 /// 1284 /// * contract address1285 /// * enable flag1286 /// 1287 #[weight = 0]1288 pub fn enable_contract_sponsoring(1289 origin,1290 contract_address: T::AccountId,1291 enable: bool1292 ) -> DispatchResult {1293 let sender = ensure_signed(origin)?;1294 let mut is_owner = false;1295 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1296 let owner = <ContractOwner<T>>::get(&contract_address);1297 is_owner = sender == owner;1298 }1299 ensure!(is_owner, "Only contract owner may call this method");13001301 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1302 Ok(())1303 }13041305 }1306}13071308impl<T: Trait> Module<T> {1309 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {1310 let current_index = <ItemListIndex>::get(item.collection)1311 .checked_add(1)1312 .expect("Item list index id error");1313 let itemcopy = item.clone();1314 let owner = item.owner.clone();1315 let value = item.value as u64;13161317 Self::add_token_index(item.collection, current_index, owner.clone())?;13181319 <ItemListIndex>::insert(item.collection, current_index);1320 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);13211322 // Add current block1323 let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1324 <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);1325 1326 // Update balance1327 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1328 .checked_add(value)1329 .unwrap();1330 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);13311332 Ok(())1333 }13341335 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1336 let current_index = <ItemListIndex>::get(item.collection)1337 .checked_add(1)1338 .expect("Item list index id error");1339 let itemcopy = item.clone();13401341 let value = item.owner.first().unwrap().fraction as u64;1342 let owner = item.owner.first().unwrap().owner.clone();13431344 Self::add_token_index(item.collection, current_index, owner.clone())?;13451346 <ItemListIndex>::insert(item.collection, current_index);1347 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);13481349 // Add current block1350 let block_number: T::BlockNumber = 0.into();1351 <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);13521353 // Update balance1354 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1355 .checked_add(value)1356 .unwrap();1357 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);13581359 Ok(())1360 }13611362 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1363 let current_index = <ItemListIndex>::get(item.collection)1364 .checked_add(1)1365 .expect("Item list index id error");13661367 let item_owner = item.owner.clone();1368 let collection_id = item.collection.clone();1369 Self::add_token_index(collection_id, current_index, item.owner.clone())?;13701371 <ItemListIndex>::insert(collection_id, current_index);1372 <NftItemList<T>>::insert(collection_id, current_index, item);13731374 // Add current block1375 let block_number: T::BlockNumber = 0.into();1376 <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);13771378 // Update balance1379 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1380 .checked_add(1)1381 .unwrap();1382 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);13831384 Ok(())1385 }13861387 fn burn_refungible_item(1388 collection_id: u64,1389 item_id: u64,1390 owner: T::AccountId,1391 ) -> DispatchResult {1392 ensure!(1393 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1394 "Item does not exists"1395 );1396 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1397 let item = collection1398 .owner1399 .iter()1400 .filter(|&i| i.owner == owner)1401 .next()1402 .unwrap();1403 Self::remove_token_index(collection_id, item_id, owner.clone())?;14041405 // remove approve list1406 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));14071408 // update balance1409 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1410 .checked_sub(item.fraction as u64)1411 .unwrap();1412 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);14131414 <ReFungibleItemList<T>>::remove(collection_id, item_id);14151416 Ok(())1417 }14181419 fn burn_nft_item(collection_id: u64, item_id: u64) -> DispatchResult {1420 ensure!(1421 <NftItemList<T>>::contains_key(collection_id, item_id),1422 "Item does not exists"1423 );1424 let item = <NftItemList<T>>::get(collection_id, item_id);1425 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;14261427 // remove approve list1428 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));14291430 // update balance1431 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1432 .checked_sub(1)1433 .unwrap();1434 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1435 <NftItemList<T>>::remove(collection_id, item_id);14361437 Ok(())1438 }14391440 fn burn_fungible_item(collection_id: u64, item_id: u64) -> DispatchResult {1441 ensure!(1442 <FungibleItemList<T>>::contains_key(collection_id, item_id),1443 "Item does not exists"1444 );1445 let item = <FungibleItemList<T>>::get(collection_id, item_id);1446 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;14471448 // remove approve list1449 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));14501451 // update balance1452 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1453 .checked_sub(item.value as u64)1454 .unwrap();1455 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);14561457 <FungibleItemList<T>>::remove(collection_id, item_id);14581459 Ok(())1460 }14611462 fn collection_exists(collection_id: u64) -> DispatchResult {1463 ensure!(1464 <Collection<T>>::contains_key(collection_id),1465 "This collection does not exist"1466 );1467 Ok(())1468 }14691470 fn check_owner_permissions(collection_id: u64, subject: T::AccountId) -> DispatchResult {1471 Self::collection_exists(collection_id)?;14721473 let target_collection = <Collection<T>>::get(collection_id);1474 ensure!(1475 subject == target_collection.owner,1476 "You do not own this collection"1477 );14781479 Ok(())1480 }14811482 fn is_owner_or_admin_permissions(collection_id: u64, subject: T::AccountId) -> bool {1483 let target_collection = <Collection<T>>::get(collection_id);1484 let mut result: bool = subject == target_collection.owner;1485 let exists = <AdminList<T>>::contains_key(collection_id);14861487 if !result & exists {1488 if <AdminList<T>>::get(collection_id).contains(&subject) {1489 result = true1490 }1491 }14921493 result1494 }14951496 fn check_owner_or_admin_permissions(1497 collection_id: u64,1498 subject: T::AccountId,1499 ) -> DispatchResult {1500 Self::collection_exists(collection_id)?;1501 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());15021503 ensure!(1504 result,1505 "You do not have permissions to modify this collection"1506 );1507 Ok(())1508 }15091510 fn is_item_owner(subject: T::AccountId, collection_id: u64, item_id: u64) -> bool {1511 let target_collection = <Collection<T>>::get(collection_id);15121513 match target_collection.mode {1514 CollectionMode::NFT => {1515 <NftItemList<T>>::get(collection_id, item_id).owner == subject1516 }1517 CollectionMode::Fungible(_) => {1518 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1519 }1520 CollectionMode::ReFungible(_) => {1521 <ReFungibleItemList<T>>::get(collection_id, item_id)1522 .owner1523 .iter()1524 .any(|i| i.owner == subject)1525 }1526 CollectionMode::Invalid => false,1527 }1528 }15291530 fn check_white_list(collection_id: u64, address: &T::AccountId) -> DispatchResult {1531 let mes = "Address is not in white list";1532 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1533 let wl = <WhiteList<T>>::get(collection_id);1534 ensure!(wl.contains(address), mes);15351536 Ok(())1537 }15381539 fn transfer_fungible(1540 collection_id: u64,1541 item_id: u64,1542 value: u64,1543 owner: T::AccountId,1544 new_owner: T::AccountId,1545 ) -> DispatchResult {1546 ensure!(1547 <FungibleItemList<T>>::contains_key(collection_id, item_id),1548 "Item not exists"1549 );15501551 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1552 let amount = full_item.value;15531554 ensure!(amount >= value.into(), "Item balance not enouth");15551556 // update balance1557 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1558 .checked_sub(value)1559 .unwrap();1560 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);15611562 let mut new_owner_account_id = 0;1563 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1564 if new_owner_items.len() > 0 {1565 new_owner_account_id = new_owner_items[0];1566 }15671568 let val64 = value.into();15691570 // transfer1571 if amount == val64 && new_owner_account_id == 0 {1572 // change owner1573 // new owner do not have account1574 let mut new_full_item = full_item.clone();1575 new_full_item.owner = new_owner.clone();1576 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);15771578 // update balance1579 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1580 .checked_add(value)1581 .unwrap();1582 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);15831584 // update index collection1585 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1586 } else {1587 let mut new_full_item = full_item.clone();1588 new_full_item.value -= val64;15891590 // separate amount1591 if new_owner_account_id > 0 {1592 // new owner has account1593 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1594 item.value += val64;15951596 // update balance1597 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1598 .checked_add(value)1599 .unwrap();1600 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);16011602 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1603 } else {1604 // new owner do not have account1605 let item = FungibleItemType {1606 collection: collection_id,1607 owner: new_owner.clone(),1608 value: val64,1609 };16101611 Self::add_fungible_item(item)?;1612 }16131614 if amount == val64 {1615 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;16161617 // remove approve list1618 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1619 <FungibleItemList<T>>::remove(collection_id, item_id);1620 }16211622 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1623 }16241625 Ok(())1626 }16271628 fn transfer_refungible(1629 collection_id: u64,1630 item_id: u64,1631 value: u64,1632 owner: T::AccountId,1633 new_owner: T::AccountId,1634 ) -> DispatchResult {1635 ensure!(1636 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1637 "Item not exists"1638 );16391640 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1641 let item = full_item1642 .owner1643 .iter()1644 .filter(|i| i.owner == owner)1645 .next()1646 .unwrap();1647 let amount = item.fraction;16481649 ensure!(amount >= value.into(), "Item balance not enouth");16501651 // update balance1652 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1653 .checked_sub(value)1654 .unwrap();1655 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);16561657 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1658 .checked_add(value)1659 .unwrap();1660 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);16611662 let old_owner = item.owner.clone();1663 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);1664 let val64 = value.into();16651666 // transfer1667 if amount == val64 && !new_owner_has_account {1668 // change owner1669 // new owner do not have account1670 let mut new_full_item = full_item.clone();1671 new_full_item1672 .owner1673 .iter_mut()1674 .find(|i| i.owner == owner)1675 .unwrap()1676 .owner = new_owner.clone();1677 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);16781679 // update index collection1680 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1681 } else {1682 let mut new_full_item = full_item.clone();1683 new_full_item1684 .owner1685 .iter_mut()1686 .find(|i| i.owner == owner)1687 .unwrap()1688 .fraction -= val64;16891690 // separate amount1691 if new_owner_has_account {1692 // new owner has account1693 new_full_item1694 .owner1695 .iter_mut()1696 .find(|i| i.owner == new_owner)1697 .unwrap()1698 .fraction += val64;1699 } else {1700 // new owner do not have account1701 new_full_item.owner.push(Ownership {1702 owner: new_owner.clone(),1703 fraction: val64,1704 });1705 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1706 }17071708 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1709 }17101711 Ok(())1712 }17131714 fn transfer_nft(1715 collection_id: u64,1716 item_id: u64,1717 sender: T::AccountId,1718 new_owner: T::AccountId,1719 ) -> DispatchResult {1720 ensure!(1721 <NftItemList<T>>::contains_key(collection_id, item_id),1722 "Item not exists"1723 );17241725 let mut item = <NftItemList<T>>::get(collection_id, item_id);17261727 ensure!(1728 sender == item.owner,1729 "sender parameter and item owner must be equal"1730 );17311732 // update balance1733 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1734 .checked_sub(1)1735 .unwrap();1736 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);17371738 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1739 .checked_add(1)1740 .unwrap();1741 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);17421743 // change owner1744 let old_owner = item.owner.clone();1745 item.owner = new_owner.clone();1746 <NftItemList<T>>::insert(collection_id, item_id, item);17471748 // update index collection1749 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;17501751 // reset approved list1752 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));1753 Ok(())1754 }1755 1756 fn item_exists(1757 collection_id: u64,1758 item_id: u64,1759 mode: &CollectionMode1760 ) -> DispatchResult {1761 match mode {1762 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), "Item does not exists"),1763 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), "Item does not exists"),1764 CollectionMode::Fungible(_) => ensure!(<FungibleItemList<T>>::contains_key(collection_id, item_id), "Item does not exists"),1765 _ => ()1766 };1767 1768 Ok(())1769 }17701771 fn set_re_fungible_variable_data(1772 collection_id: u64,1773 item_id: u64,1774 data: Vec<u8>1775 ) -> DispatchResult {1776 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);17771778 item.variable_data = data;17791780 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);17811782 Ok(())1783 }17841785 fn set_nft_variable_data(1786 collection_id: u64,1787 item_id: u64,1788 data: Vec<u8>1789 ) -> DispatchResult {1790 let mut item = <NftItemList<T>>::get(collection_id, item_id);1791 1792 item.variable_data = data;17931794 <NftItemList<T>>::insert(collection_id, item_id, item);1795 1796 Ok(())1797 }17981799 fn init_collection(item: &CollectionType<T::AccountId>) {1800 // check params1801 assert!(1802 item.decimal_points <= 4,1803 "decimal_points parameter must be lower than 4"1804 );1805 assert!(1806 item.name.len() <= 64,1807 "Collection name can not be longer than 63 char"1808 );1809 assert!(1810 item.name.len() <= 256,1811 "Collection description can not be longer than 255 char"1812 );1813 assert!(1814 item.token_prefix.len() <= 16,1815 "Token prefix can not be longer than 15 char"1816 );18171818 // Generate next collection ID1819 let next_id = CreatedCollectionCount::get()1820 .checked_add(1)1821 .expect("collection id error");18221823 CreatedCollectionCount::put(next_id);1824 }18251826 fn init_nft_token(item: &NftItemType<T::AccountId>) {1827 let current_index = <ItemListIndex>::get(item.collection)1828 .checked_add(1)1829 .expect("Item list index id error");18301831 let item_owner = item.owner.clone();1832 let collection_id = item.collection.clone();1833 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();18341835 <ItemListIndex>::insert(collection_id, current_index);18361837 // Update balance1838 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1839 .checked_add(1)1840 .unwrap();1841 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);1842 }18431844 fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {1845 let current_index = <ItemListIndex>::get(item.collection)1846 .checked_add(1)1847 .expect("Item list index id error");1848 let owner = item.owner.clone();1849 let value = item.value as u64;18501851 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();18521853 <ItemListIndex>::insert(item.collection, current_index);18541855 // Update balance1856 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1857 .checked_add(value)1858 .unwrap();1859 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1860 }18611862 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {1863 let current_index = <ItemListIndex>::get(item.collection)1864 .checked_add(1)1865 .expect("Item list index id error");18661867 let value = item.owner.first().unwrap().fraction as u64;1868 let owner = item.owner.first().unwrap().owner.clone();18691870 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();18711872 <ItemListIndex>::insert(item.collection, current_index);18731874 // Update balance1875 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1876 .checked_add(value)1877 .unwrap();1878 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1879 }18801881 fn add_token_index(collection_id: u64, item_index: u64, owner: T::AccountId) -> DispatchResult {18821883 // add to account limit1884 if <AccountItemCount<T>>::contains_key(owner.clone()) {18851886 // bound Owned tokens by a single address1887 let count = <AccountItemCount<T>>::get(owner.clone());1888 ensure!(count < ChainLimit::get().account_token_ownership_limit, "Owned tokens by a single address bound exceeded");18891890 <AccountItemCount<T>>::insert(owner.clone(), 1891 count.checked_add(1).unwrap());1892 }1893 else {1894 <AccountItemCount<T>>::insert(owner.clone(), 1);1895 }18961897 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());1898 if list_exists {1899 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());1900 let item_contains = list.contains(&item_index.clone());19011902 if !item_contains {1903 list.push(item_index.clone());1904 }19051906 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);1907 } else {1908 let mut itm = Vec::new();1909 itm.push(item_index.clone());1910 <AddressTokens<T>>::insert(collection_id, owner, itm);1911 1912 }19131914 Ok(())1915 }19161917 fn remove_token_index(1918 collection_id: u64,1919 item_index: u64,1920 owner: T::AccountId,1921 ) -> DispatchResult {19221923 // update counter1924 <AccountItemCount<T>>::insert(owner.clone(), 1925 <AccountItemCount<T>>::get(owner.clone()).checked_sub(1).unwrap());192619271928 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());1929 if list_exists {1930 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());1931 let item_contains = list.contains(&item_index.clone());19321933 if item_contains {1934 list.retain(|&item| item != item_index);1935 <AddressTokens<T>>::insert(collection_id, owner, list);1936 }1937 }19381939 Ok(())1940 }19411942 fn move_token_index(1943 collection_id: u64,1944 item_index: u64,1945 old_owner: T::AccountId,1946 new_owner: T::AccountId,1947 ) -> DispatchResult {1948 Self::remove_token_index(collection_id, item_index, old_owner)?;1949 Self::add_token_index(collection_id, item_index, new_owner)?;19501951 Ok(())1952 }1953}19541955////////////////////////////////////////////////////////////////////////////////////////////////////1956// Economic models1957// #region19581959/// Fee multiplier.1960pub type Multiplier = FixedU128;19611962type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<1963 <T as system::Trait>::AccountId,1964>>::Balance;1965type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<1966 <T as system::Trait>::AccountId,1967>>::NegativeImbalance;19681969/// Require the transactor pay for themselves and maybe include a tip to gain additional priority1970/// in the queue.1971#[derive(Encode, Decode, Clone, Eq, PartialEq)]1972pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(1973 #[codec(compact)] BalanceOf<T>1974);19751976impl<T: Trait + Send + Sync> sp_std::fmt::Debug1977 for ChargeTransactionPayment<T>1978{1979 #[cfg(feature = "std")]1980 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {1981 write!(f, "ChargeTransactionPayment<{:?}>", self.0)1982 }1983 #[cfg(not(feature = "std"))]1984 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {1985 Ok(())1986 }1987}19881989impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>1990where1991 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,1992 BalanceOf<T>: Send + Sync + FixedPointOperand,1993{1994 /// utility constructor. Used only in client/factory code.1995 pub fn from(fee: BalanceOf<T>) -> Self {1996 Self(fee)1997 }19981999 pub fn traditional_fee(2000 len: usize,2001 info: &DispatchInfoOf<T::Call>,2002 tip: BalanceOf<T>,2003 ) -> BalanceOf<T>2004 where2005 T::Call: Dispatchable<Info = DispatchInfo>,2006 {2007 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2008 }20092010 fn withdraw_fee(2011 &self,2012 who: &T::AccountId,2013 call: &T::Call,2014 info: &DispatchInfoOf<T::Call>,2015 len: usize,2016 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2017 let tip = self.0;20182019 // Set fee based on call type. Creating collection costs 1 Unique.2020 // All other transactions have traditional fees so far2021 // let fee = match call.is_sub_type() {2022 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2023 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2024 // // _ => <BalanceOf<T>>::from(100)2025 // };2026 let fee = Self::traditional_fee(len, info, tip);20272028 // Determine who is paying transaction fee based on ecnomic model2029 // Parse call to extract collection ID and access collection sponsor2030 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2031 Some(Call::create_item(collection_id, _properties, _owner)) => {2032 <Collection<T>>::get(collection_id).sponsor2033 }2034 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {2035 let _collection_mode = <Collection<T>>::get(collection_id).mode;20362037 // sponsor timeout2038 let sponsor_transfer = match _collection_mode {2039 CollectionMode::NFT => {2040 let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);2041 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2042 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();2043 if block_number >= limit_time {2044 <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);2045 true2046 }2047 else {2048 false2049 }2050 }2051 CollectionMode::Fungible(_) => {2052 let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);2053 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2054 if basket.iter().any(|i| i.address == _new_owner.clone())2055 {2056 let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();2057 let limit_time = item.start_block + ChainLimit::get().fungible_sponsor_transfer_timeout.into();2058 if block_number >= limit_time {2059 basket.retain(|x| x.address == item.address);2060 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });2061 <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);2062 true2063 }2064 else {2065 false2066 }2067 }2068 else {2069 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});2070 true2071 }2072 }2073 CollectionMode::ReFungible(_) => {2074 let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);2075 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2076 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();2077 if block_number >= limit_time {2078 <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);2079 true2080 } else {2081 false2082 }2083 }2084 _ => {2085 false2086 },2087 };20882089 if !sponsor_transfer {2090 T::AccountId::default()2091 } else {2092 <Collection<T>>::get(collection_id).sponsor2093 }2094 }20952096 _ => T::AccountId::default(),2097 };20982099 // Sponsor smart contracts2100 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {21012102 // On instantiation: set the contract owner2103 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {21042105 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2106 code_hash,2107 &data,2108 &who,2109 );2110 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());21112112 T::AccountId::default()2113 },21142115 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2116 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {21172118 let mut sp = T::AccountId::default();2119 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());2120 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2121 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2122 sp = called_contract;2123 }2124 }21252126 sp2127 },21282129 _ => sponsor,2130 };21312132 let mut who_pays_fee: T::AccountId = sponsor.clone();2133 if sponsor == T::AccountId::default() {2134 who_pays_fee = who.clone();2135 }21362137 // Only mess with balances if fee is not zero.2138 if fee.is_zero() {2139 return Ok((fee, None));2140 }21412142 match <T as transaction_payment::Trait>::Currency::withdraw(2143 &who_pays_fee,2144 fee,2145 if tip.is_zero() {2146 WithdrawReason::TransactionPayment.into()2147 } else {2148 WithdrawReason::TransactionPayment | WithdrawReason::Tip2149 },2150 ExistenceRequirement::KeepAlive,2151 ) {2152 Ok(imbalance) => Ok((fee, Some(imbalance))),2153 Err(_) => Err(InvalidTransaction::Payment.into()),2154 }2155 }2156}215721582159impl<T: Trait + Send + Sync> SignedExtension2160 for ChargeTransactionPayment<T>2161where2162 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2163 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2164{2165 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2166 type AccountId = T::AccountId;2167 type Call = T::Call;2168 type AdditionalSigned = ();2169 type Pre = (2170 BalanceOf<T>,2171 Self::AccountId,2172 Option<NegativeImbalanceOf<T>>,2173 BalanceOf<T>,2174 );2175 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2176 Ok(())2177 }21782179 fn validate(2180 &self,2181 _who: &Self::AccountId,2182 _call: &Self::Call,2183 _info: &DispatchInfoOf<Self::Call>,2184 _len: usize,2185 ) -> TransactionValidity {2186 Ok(ValidTransaction::default())2187 }21882189 fn pre_dispatch(2190 self,2191 who: &Self::AccountId,2192 call: &Self::Call,2193 info: &DispatchInfoOf<Self::Call>,2194 len: usize,2195 ) -> Result<Self::Pre, TransactionValidityError> {2196 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2197 Ok((self.0, who.clone(), imbalance, fee))2198 }21992200 fn post_dispatch(2201 pre: Self::Pre,2202 info: &DispatchInfoOf<Self::Call>,2203 post_info: &PostDispatchInfoOf<Self::Call>,2204 len: usize,2205 _result: &DispatchResult,2206 ) -> Result<(), TransactionValidityError> {2207 let (tip, who, imbalance, fee) = pre;2208 if let Some(payed) = imbalance {2209 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2210 len as u32, info, post_info, tip,2211 );2212 let refund = fee.saturating_sub(actual_fee);2213 let actual_payment =2214 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2215 &who, refund,2216 ) {2217 Ok(refund_imbalance) => {2218 // The refund cannot be larger than the up front payed max weight.2219 // `PostDispatchInfo::calc_unspent` guards against such a case.2220 match payed.offset(refund_imbalance) {2221 Ok(actual_payment) => actual_payment,2222 Err(_) => return Err(InvalidTransaction::Payment.into()),2223 }2224 }2225 // We do not recreate the account using the refund. The up front payment2226 // is gone in that case.2227 Err(_) => payed,2228 };2229 let imbalances = actual_payment.split(tip);2230 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2231 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2232 );2233 }2234 Ok(())2235 }2236}22372238// #endregion22392240pallets/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"
);
});
}
runtime/src/nft_weights.rsdiffbeforeafterboth--- a/runtime/src/nft_weights.rs
+++ b/runtime/src/nft_weights.rs
@@ -64,7 +64,7 @@
}
fn create_item(s: usize, ) -> Weight {
(130_000_000 as Weight)
- .saturating_add((2135 as Weight).saturating_mul(s as Weight))
+ .saturating_add((2135 as Weight).saturating_mul(s as Weight).saturating_mul(500 as Weight)) // 500 is temparary multiplier, fee for storage
.saturating_add(DbWeight::get().reads(10 as Weight))
.saturating_add(DbWeight::get().writes(8 as Weight))
}