difftreelog
Merge pull request #3 from usetech-llc/feature/white_list_tests
in: master
Feature/white list tests
4 files changed
node/src/chain_spec.rsdiffbeforeafterboth--- a/node/src/chain_spec.rs
+++ b/node/src/chain_spec.rs
@@ -1,8 +1,9 @@
-use nft_runtime::{
- AccountId, AuraConfig, BalancesConfig, GenesisConfig, GrandpaConfig, Signature, SudoConfig,
- SystemConfig, WASM_BINARY,
-};
-use nft_runtime::{ContractsConfig, ContractsSchedule};
+// use nft_runtime::{
+// AccountId, AuraConfig, BalancesConfig, GenesisConfig, GrandpaConfig, Signature, SudoConfig,
+// SystemConfig, WASM_BINARY,
+// };
+// use nft_runtime::{ContractsConfig, ContractsSchedule, NftConfig, CollectionType};
+use nft_runtime::*;
use sc_service::ChainType;
use sp_consensus_aura::sr25519::AuthorityId as AuraId;
use sp_core::{sr25519, Pair, Public};
@@ -128,6 +129,25 @@
.collect(),
}),
sudo: Some(SudoConfig { key: root_key }),
+ nft: Some(NftConfig {
+ collection: vec![(1, CollectionType {
+ owner: get_account_id_from_seed::<sr25519::Public>("Alice"),
+ mode: CollectionMode::NFT(50),
+ access: AccessMode::Normal,
+ decimal_points: 0,
+ name: vec!(),
+ description: vec!(),
+ token_prefix: vec!(),
+ custom_data_size: 50,
+ mint_mode: false,
+ offchain_schema: vec!(),
+ sponsor: get_account_id_from_seed::<sr25519::Public>("Alice"),
+ unconfirmed_sponsor: get_account_id_from_seed::<sr25519::Public>("Alice"),
+ })],
+ nft_item_id: vec!(),
+ fungible_item_id: vec!(),
+ refungible_item_id: vec!(),
+ }),
contracts: Some(ContractsConfig {
current_schedule: ContractsSchedule {
enable_println,
pallets/nft/src/lib.rsdiffbeforeafterboth1#![cfg_attr(not(feature = "std"), no_std)]23/// For more guidance on Substrate FRAME, see the example pallet4/// https://github.com/paritytech/substrate/blob/master/frame/example/src/lib.rs5use codec::{Decode, Encode};6pub use frame_support::{7 construct_runtime, decl_event, decl_module, decl_storage,8 dispatch::DispatchResult,9 ensure, parameter_types,10 traits::{11 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,12 Randomness, WithdrawReason,13 },14 weights::{15 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},16 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,17 WeightToFeePolynomial,18 },19 IsSubType, StorageValue,20};2122use frame_system::{self as system, ensure_signed};23use sp_runtime::sp_std::prelude::Vec;24use sp_runtime::{25 traits::{26 DispatchInfoOf, Dispatchable, PostDispatchInfoOf, SaturatedConversion, Saturating,27 SignedExtension, Zero,28 },29 transaction_validity::{30 InvalidTransaction, TransactionPriority, TransactionValidity, TransactionValidityError,31 ValidTransaction,32 },33 FixedPointOperand, FixedU128,34};35use sp_std::prelude::*;3637#[cfg(test)]38mod mock;3940#[cfg(test)]41mod tests;4243#[derive(Encode, Decode, Debug, Eq, Clone, PartialEq)]44pub enum CollectionMode {45 Invalid,46 // custom data size47 NFT(u32),48 // decimal points49 Fungible(u32),50 // custom data size and decimal points51 ReFungible(u32, u32),52}5354impl Into<u8> for CollectionMode {55 fn into(self) -> u8 {56 match self {57 CollectionMode::Invalid => 0,58 CollectionMode::NFT(_) => 1,59 CollectionMode::Fungible(_) => 2,60 CollectionMode::ReFungible(_, _) => 3,61 }62 }63}6465#[derive(Encode, Decode, Debug, Clone, PartialEq)]66pub enum AccessMode {67 Normal,68 WhiteList,69}70impl Default for AccessMode {71 fn default() -> Self {72 Self::Normal73 }74}7576impl Default for CollectionMode {77 fn default() -> Self {78 Self::Invalid79 }80}8182#[derive(Encode, Decode, Default, Clone, PartialEq)]83#[cfg_attr(feature = "std", derive(Debug))]84pub struct Ownership<AccountId> {85 pub owner: AccountId,86 pub fraction: u128,87}8889#[derive(Encode, Decode, Default, Clone, PartialEq)]90#[cfg_attr(feature = "std", derive(Debug))]91pub struct CollectionType<AccountId> {92 pub owner: AccountId,93 pub mode: CollectionMode,94 pub access: AccessMode,95 pub decimal_points: u32,96 pub name: Vec<u16>, // 64 include null escape char97 pub description: Vec<u16>, // 256 include null escape char98 pub token_prefix: Vec<u8>, // 16 include null escape char99 pub custom_data_size: u32,100 pub mint_mode: bool,101 pub offchain_schema: Vec<u8>,102 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender103 pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship104}105106#[derive(Encode, Decode, Default, Clone, PartialEq)]107#[cfg_attr(feature = "std", derive(Debug))]108pub struct CollectionAdminsType<AccountId> {109 pub admin: AccountId,110 pub collection_id: u64,111}112113#[derive(Encode, Decode, Default, Clone, PartialEq)]114#[cfg_attr(feature = "std", derive(Debug))]115pub struct NftItemType<AccountId> {116 pub collection: u64,117 pub owner: AccountId,118 pub data: Vec<u8>,119}120121#[derive(Encode, Decode, Default, Clone, PartialEq)]122#[cfg_attr(feature = "std", derive(Debug))]123pub struct FungibleItemType<AccountId> {124 pub collection: u64,125 pub owner: AccountId,126 pub value: u128,127}128129#[derive(Encode, Decode, Default, Clone, PartialEq)]130#[cfg_attr(feature = "std", derive(Debug))]131pub struct ReFungibleItemType<AccountId> {132 pub collection: u64,133 pub owner: Vec<Ownership<AccountId>>,134 pub data: Vec<u8>,135}136137#[derive(Encode, Decode, Default, Clone, PartialEq)]138#[cfg_attr(feature = "std", derive(Debug))]139pub struct ApprovePermissions<AccountId> {140 pub approved: AccountId,141 pub amount: u64,142}143144#[derive(Encode, Decode, Default, Clone, PartialEq)]145#[cfg_attr(feature = "std", derive(Debug))]146pub struct VestingItem<AccountId, Moment> {147 pub sender: AccountId,148 pub recipient: AccountId,149 pub collection_id: u64,150 pub item_id: u64,151 pub amount: u64,152 pub vesting_date: Moment,153}154155pub trait Trait: system::Trait {156 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;157}158159decl_storage! {160 trait Store for Module<T: Trait> as Nft {161162 // Private members163 NextCollectionID: u64;164 CreatedCollectionCount: u64;165 ChainVersion: u64;166 ItemListIndex: map hasher(blake2_128_concat) u64 => u64;167168 pub Collection get(fn collection): map hasher(identity) u64 => CollectionType<T::AccountId>;169 pub AdminList get(fn admin_list_collection): map hasher(identity) u64 => Vec<T::AccountId>;170 pub WhiteList get(fn white_list): map hasher(identity) u64 => Vec<T::AccountId>;171172 /// Balance owner per collection map173 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => u64;174175 /// second parameter: item id + owner account id176 pub ApprovedList get(fn approved): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) (u64, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;177178 /// Item collections179 pub NftItemList get(fn nft_item_id): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => NftItemType<T::AccountId>;180 pub FungibleItemList get(fn fungible_item_id): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => FungibleItemType<T::AccountId>;181 pub ReFungibleItemList get(fn refungible_item_id): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => ReFungibleItemType<T::AccountId>;182183 /// Index list184 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => Vec<u64>;185186 // Sponsorship187 pub ContractSponsor get(fn contract_sponsor): map hasher(identity) T::AccountId => T::AccountId;188 pub UnconfirmedContractSponsor get(fn unconfirmed_contract_sponsor): map hasher(identity) T::AccountId => T::AccountId;189 }190}191192decl_event!(193 pub enum Event<T>194 where195 AccountId = <T as system::Trait>::AccountId,196 {197 Created(u64, u8, AccountId),198 ItemCreated(u64, u64),199 ItemDestroyed(u64, u64),200 }201);202203decl_module! {204 pub struct Module<T: Trait> for enum Call where origin: T::Origin {205206 fn deposit_event() = default;207208 fn on_initialize(now: T::BlockNumber) -> Weight {209210 if ChainVersion::get() < 2211 {212 let value = NextCollectionID::get();213 CreatedCollectionCount::put(value);214 ChainVersion::put(2);215 }216217 0218 }219220 // Create collection of NFT with given parameters221 //222 // @param customDataSz size of custom data in each collection item223 // returns collection ID224 #[weight = 0]225 pub fn create_collection(origin,226 collection_name: Vec<u16>,227 collection_description: Vec<u16>,228 token_prefix: Vec<u8>,229 mode: CollectionMode) -> DispatchResult {230231 // Anyone can create a collection232 let who = ensure_signed(origin)?;233 let custom_data_size = match mode {234 CollectionMode::NFT(size) => size,235 CollectionMode::ReFungible(size, _) => size,236 _ => 0237 };238239 let decimal_points = match mode {240 CollectionMode::Fungible(points) => points,241 CollectionMode::ReFungible(_, points) => points,242 _ => 0243 };244245 // check params246 ensure!(decimal_points <= 4, "decimal_points parameter must be lower than 4");247248 let mut name = collection_name.to_vec();249 name.push(0);250 ensure!(name.len() <= 64, "Collection name can not be longer than 63 char");251252 let mut description = collection_description.to_vec();253 description.push(0);254 ensure!(name.len() <= 256, "Collection description can not be longer than 255 char");255256 let mut prefix = token_prefix.to_vec();257 prefix.push(0);258 ensure!(prefix.len() <= 16, "Token prefix can not be longer than 15 char");259260 // Generate next collection ID261 let next_id = CreatedCollectionCount::get()262 .checked_add(1)263 .expect("collection id error");264265 CreatedCollectionCount::put(next_id);266267 // Create new collection268 let new_collection = CollectionType {269 owner: who.clone(),270 name: name,271 mode: mode.clone(),272 mint_mode: false,273 access: AccessMode::Normal,274 description: description,275 decimal_points: decimal_points,276 token_prefix: prefix,277 offchain_schema: Vec::new(),278 custom_data_size: custom_data_size,279 sponsor: T::AccountId::default(),280 unconfirmed_sponsor: T::AccountId::default(),281 };282283 // Add new collection to map284 <Collection<T>>::insert(next_id, new_collection);285286 // call event287 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));288289 Ok(())290 }291292 #[weight = 0]293 pub fn destroy_collection(origin, collection_id: u64) -> DispatchResult {294295 let sender = ensure_signed(origin)?;296 Self::check_owner_permissions(collection_id, sender)?;297298 // TODO Items remove299 <AddressTokens<T>>::remove_prefix(collection_id);300 <ApprovedList<T>>::remove_prefix(collection_id);301 <Balance<T>>::remove_prefix(collection_id);302 <ItemListIndex>::remove(collection_id);303 <AdminList<T>>::remove(collection_id);304 <Collection<T>>::remove(collection_id);305 <WhiteList<T>>::remove(collection_id);306307 Ok(())308 }309310 #[weight = 0]311 pub fn add_to_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{312313 let sender = ensure_signed(origin)?;314 Self::check_owner_or_admin_permissions(collection_id, sender)?;315316 let mut white_list_collection: Vec<T::AccountId>;317 if <WhiteList<T>>::contains_key(collection_id) {318 white_list_collection = <WhiteList<T>>::get(collection_id);319 if !white_list_collection.contains(&address.clone())320 {321 white_list_collection.push(address.clone());322 }323 }324 else {325 white_list_collection = Vec::new();326 white_list_collection.push(address.clone());327 }328329 <WhiteList<T>>::insert(collection_id, white_list_collection);330 Ok(())331 }332333 #[weight = 0]334 pub fn remove_from_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{335336 let sender = ensure_signed(origin)?;337 Self::check_owner_or_admin_permissions(collection_id, sender)?;338339 if <WhiteList<T>>::contains_key(collection_id) {340 let mut white_list_collection = <WhiteList<T>>::get(collection_id);341 if white_list_collection.contains(&address.clone())342 {343 white_list_collection.retain(|i| *i != address.clone());344 <WhiteList<T>>::insert(collection_id, white_list_collection);345 }346 }347348 Ok(())349 }350351 #[weight = 0]352 pub fn set_public_access_mode(origin, collection_id: u64, mode: AccessMode) -> DispatchResult353 {354 let sender = ensure_signed(origin)?;355356 Self::check_owner_permissions(collection_id, sender)?;357 let mut target_collection = <Collection<T>>::get(collection_id);358 target_collection.access = mode;359 <Collection<T>>::insert(collection_id, target_collection);360361 Ok(())362 }363364 #[weight = 0]365 pub fn set_mint_permission(origin, collection_id: u64, mint_permission: bool) -> DispatchResult366 {367 let sender = ensure_signed(origin)?;368369 Self::check_owner_permissions(collection_id, sender)?;370 let mut target_collection = <Collection<T>>::get(collection_id);371 target_collection.mint_mode = mint_permission;372 <Collection<T>>::insert(collection_id, target_collection);373374 Ok(())375 }376377 #[weight = 0]378 pub fn change_collection_owner(origin, collection_id: u64, new_owner: T::AccountId) -> DispatchResult {379380 let sender = ensure_signed(origin)?;381 Self::check_owner_permissions(collection_id, sender)?;382 let mut target_collection = <Collection<T>>::get(collection_id);383 target_collection.owner = new_owner;384 <Collection<T>>::insert(collection_id, target_collection);385386 Ok(())387 }388389 #[weight = 0]390 pub fn add_collection_admin(origin, collection_id: u64, new_admin_id: T::AccountId) -> DispatchResult {391392 let sender = ensure_signed(origin)?;393 Self::check_owner_or_admin_permissions(collection_id, sender)?;394 let mut admin_arr: Vec<T::AccountId> = Vec::new();395396 if <AdminList<T>>::contains_key(collection_id)397 {398 admin_arr = <AdminList<T>>::get(collection_id);399 ensure!(!admin_arr.contains(&new_admin_id), "Account already has admin role");400 }401402 admin_arr.push(new_admin_id);403 <AdminList<T>>::insert(collection_id, admin_arr);404405 Ok(())406 }407408 #[weight = 0]409 pub fn remove_collection_admin(origin, collection_id: u64, account_id: T::AccountId) -> DispatchResult {410411 let sender = ensure_signed(origin)?;412 Self::check_owner_or_admin_permissions(collection_id, sender)?;413414 if <AdminList<T>>::contains_key(collection_id)415 {416 let mut admin_arr = <AdminList<T>>::get(collection_id);417 admin_arr.retain(|i| *i != account_id);418 <AdminList<T>>::insert(collection_id, admin_arr);419 }420421 Ok(())422 }423424 #[weight = 0]425 pub fn set_collection_sponsor(origin, collection_id: u64, new_sponsor: T::AccountId) -> DispatchResult {426427 let sender = ensure_signed(origin)?;428 ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");429430 let mut target_collection = <Collection<T>>::get(collection_id);431 ensure!(sender == target_collection.owner, "You do not own this collection");432433 target_collection.unconfirmed_sponsor = new_sponsor;434 <Collection<T>>::insert(collection_id, target_collection);435436 Ok(())437 }438439 #[weight = 0]440 pub fn confirm_sponsorship(origin, collection_id: u64) -> DispatchResult {441442 let sender = ensure_signed(origin)?;443 ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");444445 let mut target_collection = <Collection<T>>::get(collection_id);446 ensure!(sender == target_collection.unconfirmed_sponsor, "This address is not set as sponsor, use setCollectionSponsor first");447448 target_collection.sponsor = target_collection.unconfirmed_sponsor;449 target_collection.unconfirmed_sponsor = T::AccountId::default();450 <Collection<T>>::insert(collection_id, target_collection);451452 Ok(())453 }454455 #[weight = 0]456 pub fn remove_collection_sponsor(origin, collection_id: u64) -> DispatchResult {457458 let sender = ensure_signed(origin)?;459 ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");460461 let mut target_collection = <Collection<T>>::get(collection_id);462 ensure!(sender == target_collection.owner, "You do not own this collection");463464 target_collection.sponsor = T::AccountId::default();465 <Collection<T>>::insert(collection_id, target_collection);466467 Ok(())468 }469470 #[weight = 0]471 pub fn create_item(origin, collection_id: u64, properties: Vec<u8>, owner: T::AccountId) -> DispatchResult {472473 let sender = ensure_signed(origin)?;474 Self::collection_exists(collection_id)?;475 let target_collection = <Collection<T>>::get(collection_id);476477 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {478 ensure!(target_collection.mint_mode == true, "Collection is not in mint mode");479 Self::check_white_list(collection_id, owner.clone())?;480 }481482 match target_collection.mode483 {484 CollectionMode::NFT(_) => {485486 // check size487 ensure!(target_collection.custom_data_size >= properties.len() as u32, "Size of item is too large");488489 // Create nft item490 let item = NftItemType {491 collection: collection_id,492 owner: owner,493 data: properties.clone(),494 };495496 Self::add_nft_item(item)?;497498 },499 CollectionMode::Fungible(_) => {500501 // check size502 ensure!(properties.len() as u32 == 0, "Size of item must be 0 with fungible type");503504 let item = FungibleItemType {505 collection: collection_id,506 owner: owner,507 value: (10 as u128).pow(target_collection.decimal_points)508 };509510 Self::add_fungible_item(item)?;511 },512 CollectionMode::ReFungible(_, _) => {513514 // check size515 ensure!(target_collection.custom_data_size >= properties.len() as u32, "Size of item is too large");516517 let mut owner_list = Vec::new();518 let value = (10 as u128).pow(target_collection.decimal_points);519 owner_list.push(Ownership {owner: owner.clone(), fraction: value});520521 let item = ReFungibleItemType {522 collection: collection_id,523 owner: owner_list,524 data: properties.clone()525 };526527 Self::add_refungible_item(item)?;528 },529 _ => { ensure!(1 == 0,"just error"); }530531 };532533 // call event534 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));535536 Ok(())537 }538539 #[weight = 0]540 pub fn burn_item(origin, collection_id: u64, item_id: u64) -> DispatchResult {541542 let sender = ensure_signed(origin)?;543 Self::collection_exists(collection_id)?;544 let item_owner = Self::is_item_owner(sender.clone(), collection_id, item_id);545 if !item_owner546 {547 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) { 548 Self::check_white_list(collection_id, sender.clone())?;549 }550 }551 let target_collection = <Collection<T>>::get(collection_id);552553 match target_collection.mode554 {555 CollectionMode::NFT(_) => Self::burn_nft_item(collection_id, item_id)?,556 CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?,557 CollectionMode::ReFungible(_, _) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,558 _ => ()559 };560561 // call event562 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));563564 Ok(())565 }566567 #[weight = 0]568 pub fn transfer(origin, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64) -> DispatchResult {569570 let sender = ensure_signed(origin)?;571 Self::check_white_list(collection_id, sender.clone())?;572 Self::check_white_list(collection_id, recipient.clone())?;573 let target_collection = <Collection<T>>::get(collection_id);574575 match target_collection.mode576 {577 CollectionMode::NFT(_) => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,578 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,579 CollectionMode::ReFungible(_, _) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,580 _ => ()581 };582583 Ok(())584 }585586 #[weight = 0]587 pub fn approve(origin, approved: T::AccountId, collection_id: u64, item_id: u64) -> DispatchResult {588589 let sender = ensure_signed(origin)?;590591 // amount param stub592 let amount = 100000000;593594 let item_owner = Self::is_item_owner(sender.clone(), collection_id, item_id);595 if !item_owner {596 Self::check_white_list(collection_id, approved.clone())?;597 }598599 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));600 if list_exists {601602 let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));603 let item_contains = list.iter().any(|i| i.approved == approved);604605 if !item_contains {606 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });607 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);608 }609 } else {610611 let mut list = Vec::new();612 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });613 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);614 }615616 Ok(())617 }618619 #[weight = 0]620 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64 ) -> DispatchResult {621622 let sender = ensure_signed(origin)?;623 let approved_list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone()));624625 ensure!(approved_list_exists, "Only approved addresses can call this method");626627 Self::check_white_list(collection_id, from.clone())?;628 Self::check_white_list(collection_id, recipient.clone())?;629630 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));631 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());632 ensure!(opt_item.is_some(), "No approve found");633 ensure!(opt_item.unwrap().amount >= value, "Requested value more than approved");634635 // remove approve636 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))637 .into_iter().filter(|i| i.approved != sender.clone()).collect();638 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);639640 let target_collection = <Collection<T>>::get(collection_id);641642 match target_collection.mode643 {644 CollectionMode::NFT(_) => Self::transfer_nft(collection_id, item_id, from, recipient)?,645 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,646 CollectionMode::ReFungible(_, _) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,647 _ => ()648 };649650 Ok(())651 }652653 #[weight = 0]654 pub fn safe_transfer_from(origin, collection_id: u64, item_id: u64, new_owner: T::AccountId) -> DispatchResult {655656 // let no_perm_mes = "You do not have permissions to modify this collection";657 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);658 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));659 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);660661 // // on_nft_received call662663 // Self::transfer(origin, collection_id, item_id, new_owner)?;664665 Ok(())666 }667668 #[weight = 0]669 pub fn set_offchain_schema(670 origin,671 collection_id: u64,672 schema: Vec<u8>673 ) -> DispatchResult {674 let sender = ensure_signed(origin)?;675 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;676677 let mut target_collection = <Collection<T>>::get(collection_id);678 target_collection.offchain_schema = schema;679 <Collection<T>>::insert(collection_id, target_collection);680681 Ok(())682 }683 }684}685686impl<T: Trait> Module<T> {687 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {688 let current_index = <ItemListIndex>::get(item.collection)689 .checked_add(1)690 .expect("Item list index id error");691 let itemcopy = item.clone();692 let owner = item.owner.clone();693 let value = item.value as u64;694695 Self::add_token_index(item.collection, current_index, owner.clone())?;696697 <ItemListIndex>::insert(item.collection, current_index);698 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);699700 // Update balance701 let new_balance = <Balance<T>>::get(item.collection, owner.clone())702 .checked_add(value)703 .unwrap();704 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);705706 Ok(())707 }708709 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {710 let current_index = <ItemListIndex>::get(item.collection)711 .checked_add(1)712 .expect("Item list index id error");713 let itemcopy = item.clone();714715 let value = item.owner.first().unwrap().fraction as u64;716 let owner = item.owner.first().unwrap().owner.clone();717718 Self::add_token_index(item.collection, current_index, owner.clone())?;719720 <ItemListIndex>::insert(item.collection, current_index);721 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);722723 // Update balance724 let new_balance = <Balance<T>>::get(item.collection, owner.clone())725 .checked_add(value)726 .unwrap();727 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);728729 Ok(())730 }731732 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {733 let current_index = <ItemListIndex>::get(item.collection)734 .checked_add(1)735 .expect("Item list index id error");736737 let item_owner = item.owner.clone();738 let collection_id = item.collection.clone();739 Self::add_token_index(collection_id, current_index, item.owner.clone())?;740741 <ItemListIndex>::insert(collection_id, current_index);742 <NftItemList<T>>::insert(collection_id, current_index, item);743744 // Update balance745 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())746 .checked_add(1)747 .unwrap();748 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);749750 Ok(())751 }752753 fn burn_refungible_item(collection_id: u64, item_id: u64, owner: T::AccountId) -> DispatchResult {754 ensure!(755 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),756 "Item does not exists"757 );758 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);759 let item = collection760 .owner761 .iter()762 .filter(|&i| i.owner == owner)763 .next()764 .unwrap();765 Self::remove_token_index(collection_id, item_id, owner.clone())?;766767 // remove approve list768 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));769770 // update balance771 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())772 .checked_sub(item.fraction as u64)773 .unwrap();774 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);775776 <ReFungibleItemList<T>>::remove(collection_id, item_id);777778 Ok(())779 }780781 fn burn_nft_item(collection_id: u64, item_id: u64) -> DispatchResult {782 ensure!(783 <NftItemList<T>>::contains_key(collection_id, item_id),784 "Item does not exists"785 );786 let item = <NftItemList<T>>::get(collection_id, item_id);787 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;788789 // remove approve list790 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));791792 // update balance793 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())794 .checked_sub(1)795 .unwrap();796 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);797 <NftItemList<T>>::remove(collection_id, item_id);798799 Ok(())800 }801802 fn burn_fungible_item(collection_id: u64, item_id: u64) -> DispatchResult {803 ensure!(804 <FungibleItemList<T>>::contains_key(collection_id, item_id),805 "Item does not exists"806 );807 let item = <FungibleItemList<T>>::get(collection_id, item_id);808 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;809810 // remove approve list811 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));812813 // update balance814 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())815 .checked_sub(item.value as u64)816 .unwrap();817 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);818819 <FungibleItemList<T>>::remove(collection_id, item_id);820821 Ok(())822 }823824 fn collection_exists(collection_id: u64) -> DispatchResult {825 ensure!(826 <Collection<T>>::contains_key(collection_id),827 "This collection does not exist"828 );829 Ok(())830 }831832 fn check_owner_permissions(collection_id: u64, subject: T::AccountId) -> DispatchResult {833 Self::collection_exists(collection_id)?;834835 let target_collection = <Collection<T>>::get(collection_id);836 ensure!(837 subject == target_collection.owner,838 "You do not own this collection"839 );840841 Ok(())842 }843844 fn is_owner_or_admin_permissions(collection_id: u64, subject: T::AccountId) -> bool {845846 let target_collection = <Collection<T>>::get(collection_id);847 let mut result: bool = subject == target_collection.owner;848 let exists = <AdminList<T>>::contains_key(collection_id);849850 if !result & exists {851 if <AdminList<T>>::get(collection_id).contains(&subject) {852 result = true853 }854 }855856 result857 }858859 fn check_owner_or_admin_permissions(collection_id: u64, subject: T::AccountId) -> DispatchResult {860 861 Self::collection_exists(collection_id)?;862 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());863864 ensure!(result, "You do not have permissions to modify this collection");865 Ok(())866 }867868 fn is_item_owner(subject: T::AccountId, collection_id: u64, item_id: u64) -> bool {869 let target_collection = <Collection<T>>::get(collection_id);870871 match target_collection.mode {872 CollectionMode::NFT(_) => {873 <NftItemList<T>>::get(collection_id, item_id).owner == subject874 }875 CollectionMode::Fungible(_) => {876 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject877 }878 CollectionMode::ReFungible(_, _) => {879 <ReFungibleItemList<T>>::get(collection_id, item_id)880 .owner881 .iter()882 .any(|i| i.owner == subject)883 }884 CollectionMode::Invalid => false,885 }886 }887888 fn check_white_list(collection_id: u64, address: T::AccountId) -> DispatchResult {889890 let mes = "Address is not in white list";891 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);892 let wl = <WhiteList<T>>::get(collection_id);893 ensure!(wl.contains(&address.clone()), mes);894895 Ok(())896 }897898 fn transfer_fungible(899 collection_id: u64,900 item_id: u64,901 value: u64,902 owner: T::AccountId,903 new_owner: T::AccountId,904 ) -> DispatchResult {905906 ensure!(907 <FungibleItemList<T>>::contains_key(collection_id, item_id),908 "Item not exists"909 );910911 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);912 let amount = full_item.value;913914 ensure!(amount >= value.into(), "Item balance not enouth");915916 // update balance917 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())918 .checked_sub(value)919 .unwrap();920 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);921922 let mut new_owner_account_id = 0;923 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());924 if new_owner_items.len() > 0 {925 new_owner_account_id = new_owner_items[0];926 }927928 let val64 = value.into();929930 // transfer931 if amount == val64 && new_owner_account_id == 0 {932 // change owner933 // new owner do not have account934 let mut new_full_item = full_item.clone();935 new_full_item.owner = new_owner.clone();936 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);937938 // update balance939 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())940 .checked_add(value)941 .unwrap();942 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);943944 // update index collection945 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;946 } else {947 let mut new_full_item = full_item.clone();948 new_full_item.value -= val64;949950 // separate amount951 if new_owner_account_id > 0 {952 // new owner has account953 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);954 item.value += val64;955956 // update balance957 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())958 .checked_add(value)959 .unwrap();960 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);961962 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);963 } else {964 // new owner do not have account965 let item = FungibleItemType {966 collection: collection_id,967 owner: new_owner.clone(),968 value: val64,969 };970971 Self::add_fungible_item(item)?;972 }973974 if amount == val64 {975 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;976977 // remove approve list978 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));979 <FungibleItemList<T>>::remove(collection_id, item_id);980 }981982 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);983 }984985 Ok(())986 }987988 fn transfer_refungible(989 collection_id: u64,990 item_id: u64,991 value: u64,992 owner: T::AccountId,993 new_owner: T::AccountId,994 ) -> DispatchResult {995996 ensure!(997 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),998 "Item not exists"999 );10001001 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1002 let item = full_item1003 .owner1004 .iter()1005 .filter(|i| i.owner == owner)1006 .next()1007 .unwrap();1008 let amount = item.fraction;10091010 ensure!(amount >= value.into(), "Item balance not enouth");10111012 // update balance1013 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1014 .checked_sub(value)1015 .unwrap();1016 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);10171018 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1019 .checked_add(value)1020 .unwrap();1021 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);10221023 let old_owner = item.owner.clone();1024 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);1025 let val64 = value.into();10261027 // transfer1028 if amount == val64 && !new_owner_has_account {1029 // change owner1030 // new owner do not have account1031 let mut new_full_item = full_item.clone();1032 new_full_item1033 .owner1034 .iter_mut()1035 .find(|i| i.owner == owner)1036 .unwrap()1037 .owner = new_owner.clone();1038 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);10391040 // update index collection1041 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1042 } else {1043 let mut new_full_item = full_item.clone();1044 new_full_item1045 .owner1046 .iter_mut()1047 .find(|i| i.owner == owner)1048 .unwrap()1049 .fraction -= val64;10501051 // separate amount1052 if new_owner_has_account {1053 // new owner has account1054 new_full_item1055 .owner1056 .iter_mut()1057 .find(|i| i.owner == new_owner)1058 .unwrap()1059 .fraction += val64;1060 } else {1061 // new owner do not have account1062 new_full_item.owner.push(Ownership {1063 owner: new_owner.clone(),1064 fraction: val64,1065 });1066 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1067 }10681069 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1070 }10711072 Ok(())1073 }10741075 fn transfer_nft(1076 collection_id: u64,1077 item_id: u64,1078 sender: T::AccountId,1079 new_owner: T::AccountId,1080 ) -> DispatchResult {1081 1082 ensure!(1083 <NftItemList<T>>::contains_key(collection_id, item_id),1084 "Item not exists"1085 );10861087 let mut item = <NftItemList<T>>::get(collection_id, item_id);10881089 ensure!(1090 sender == item.owner,1091 "sender parameter and item owner must be equal"1092 );10931094 // update balance1095 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1096 .checked_sub(1)1097 .unwrap();1098 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);10991100 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1101 .checked_add(1)1102 .unwrap();1103 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);11041105 // change owner1106 let old_owner = item.owner.clone();1107 item.owner = new_owner.clone();1108 <NftItemList<T>>::insert(collection_id, item_id, item);11091110 // update index collection1111 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;11121113 // reset approved list1114 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));1115 Ok(())1116 }11171118 fn add_token_index(collection_id: u64, item_index: u64, owner: T::AccountId) -> DispatchResult {1119 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());1120 if list_exists {1121 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());1122 let item_contains = list.contains(&item_index.clone());11231124 if !item_contains {1125 list.push(item_index.clone());1126 }11271128 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);1129 } else {1130 let mut itm = Vec::new();1131 itm.push(item_index.clone());1132 <AddressTokens<T>>::insert(collection_id, owner, itm);1133 }11341135 Ok(())1136 }11371138 fn remove_token_index(1139 collection_id: u64,1140 item_index: u64,1141 owner: T::AccountId,1142 ) -> DispatchResult {1143 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());1144 if list_exists {1145 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());1146 let item_contains = list.contains(&item_index.clone());11471148 if item_contains {1149 list.retain(|&item| item != item_index);1150 <AddressTokens<T>>::insert(collection_id, owner, list);1151 }1152 }11531154 Ok(())1155 }11561157 fn move_token_index(1158 collection_id: u64,1159 item_index: u64,1160 old_owner: T::AccountId,1161 new_owner: T::AccountId,1162 ) -> DispatchResult {1163 Self::remove_token_index(collection_id, item_index, old_owner)?;1164 Self::add_token_index(collection_id, item_index, new_owner)?;11651166 Ok(())1167 }1168}11691170////////////////////////////////////////////////////////////////////////////////////////////////////1171// Economic models11721173/// Fee multiplier.1174pub type Multiplier = FixedU128;11751176type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<1177 <T as system::Trait>::AccountId,1178>>::Balance;1179type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<1180 <T as system::Trait>::AccountId,1181>>::NegativeImbalance;11821183/// Require the transactor pay for themselves and maybe include a tip to gain additional priority1184/// in the queue.1185#[derive(Encode, Decode, Clone, Eq, PartialEq)]1186pub struct ChargeTransactionPayment<T: transaction_payment::Trait + Send + Sync>(1187 #[codec(compact)] BalanceOf<T>,1188);11891190impl<T: Trait + transaction_payment::Trait + Send + Sync> sp_std::fmt::Debug1191 for ChargeTransactionPayment<T>1192{1193 #[cfg(feature = "std")]1194 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {1195 write!(f, "ChargeTransactionPayment<{:?}>", self.0)1196 }1197 #[cfg(not(feature = "std"))]1198 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {1199 Ok(())1200 }1201}12021203impl<T: Trait + transaction_payment::Trait + Send + Sync> ChargeTransactionPayment<T>1204where1205 T::Call:1206 Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Module<T>, T>,1207 BalanceOf<T>: Send + Sync + FixedPointOperand,1208{1209 /// utility constructor. Used only in client/factory code.1210 pub fn from(fee: BalanceOf<T>) -> Self {1211 Self(fee)1212 }12131214 pub fn traditional_fee(1215 len: usize,1216 info: &DispatchInfoOf<T::Call>,1217 tip: BalanceOf<T>,1218 ) -> BalanceOf<T>1219 where1220 T::Call: Dispatchable<Info = DispatchInfo>,1221 {1222 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)1223 }12241225 fn withdraw_fee(1226 &self,1227 who: &T::AccountId,1228 call: &T::Call,1229 info: &DispatchInfoOf<T::Call>,1230 len: usize,1231 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {1232 let tip = self.0;12331234 // Set fee based on call type. Creating collection costs 1 Unique.1235 // All other transactions have traditional fees so far1236 let fee = match call.is_sub_type() {1237 Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),1238 _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes1239 // _ => <BalanceOf<T>>::from(100)1240 };12411242 // Determine who is paying transaction fee based on ecnomic model1243 // Parse call to extract collection ID and access collection sponsor1244 let sponsor: T::AccountId = match call.is_sub_type() {1245 Some(Call::create_item(collection_id, _properties, _owner)) => {1246 <Collection<T>>::get(collection_id).sponsor1247 }1248 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {1249 <Collection<T>>::get(collection_id).sponsor1250 }12511252 _ => T::AccountId::default(),1253 };12541255 let mut who_pays_fee: T::AccountId = sponsor.clone();1256 if sponsor == T::AccountId::default() {1257 who_pays_fee = who.clone();1258 }12591260 // Only mess with balances if fee is not zero.1261 if fee.is_zero() {1262 return Ok((fee, None));1263 }12641265 match <T as transaction_payment::Trait>::Currency::withdraw(1266 &who_pays_fee,1267 fee,1268 if tip.is_zero() {1269 WithdrawReason::TransactionPayment.into()1270 } else {1271 WithdrawReason::TransactionPayment | WithdrawReason::Tip1272 },1273 ExistenceRequirement::KeepAlive,1274 ) {1275 Ok(imbalance) => Ok((fee, Some(imbalance))),1276 Err(_) => Err(InvalidTransaction::Payment.into()),1277 }1278 }1279}12801281impl<T: Trait + transaction_payment::Trait + Send + Sync> SignedExtension1282 for ChargeTransactionPayment<T>1283where1284 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,1285 T::Call:1286 Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Module<T>, T>,1287{1288 const IDENTIFIER: &'static str = "ChargeTransactionPayment";1289 type AccountId = T::AccountId;1290 type Call = T::Call;1291 type AdditionalSigned = ();1292 type Pre = (1293 BalanceOf<T>,1294 Self::AccountId,1295 Option<NegativeImbalanceOf<T>>,1296 BalanceOf<T>,1297 );1298 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {1299 Ok(())1300 }13011302 fn validate(1303 &self,1304 who: &Self::AccountId,1305 call: &Self::Call,1306 info: &DispatchInfoOf<Self::Call>,1307 len: usize,1308 ) -> TransactionValidity {1309 let (fee, _) = self.withdraw_fee(who, call, info, len)?;13101311 let mut r = ValidTransaction::default();1312 // NOTE: we probably want to maximize the _fee (of any type) per weight unit_ here, which1313 // will be a bit more than setting the priority to tip. For now, this is enough.1314 r.priority = fee.saturated_into::<TransactionPriority>();1315 Ok(r)1316 }13171318 fn pre_dispatch(1319 self,1320 who: &Self::AccountId,1321 call: &Self::Call,1322 info: &DispatchInfoOf<Self::Call>,1323 len: usize,1324 ) -> Result<Self::Pre, TransactionValidityError> {1325 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;1326 Ok((self.0, who.clone(), imbalance, fee))1327 }13281329 fn post_dispatch(1330 pre: Self::Pre,1331 info: &DispatchInfoOf<Self::Call>,1332 post_info: &PostDispatchInfoOf<Self::Call>,1333 len: usize,1334 _result: &DispatchResult,1335 ) -> Result<(), TransactionValidityError> {1336 let (tip, who, imbalance, fee) = pre;1337 if let Some(payed) = imbalance {1338 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(1339 len as u32, info, post_info, tip,1340 );1341 let refund = fee.saturating_sub(actual_fee);1342 let actual_payment =1343 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(1344 &who, refund,1345 ) {1346 Ok(refund_imbalance) => {1347 // The refund cannot be larger than the up front payed max weight.1348 // `PostDispatchInfo::calc_unspent` guards against such a case.1349 match payed.offset(refund_imbalance) {1350 Ok(actual_payment) => actual_payment,1351 Err(_) => return Err(InvalidTransaction::Payment.into()),1352 }1353 }1354 // We do not recreate the account using the refund. The up front payment1355 // is gone in that case.1356 Err(_) => payed,1357 };1358 let imbalances = actual_payment.split(tip);1359 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(1360 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),1361 );1362 }1363 Ok(())1364 }1365}1#![cfg_attr(not(feature = "std"), no_std)]23#[cfg(feature = "std")]4pub use serde::*;56use codec::{Decode, Encode};7pub use frame_support::{8 construct_runtime, decl_event, decl_module, decl_storage,9 dispatch::DispatchResult,10 ensure, parameter_types,11 traits::{12 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,13 Randomness, WithdrawReason,14 },15 weights::{16 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},17 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,18 WeightToFeePolynomial,19 },20 IsSubType, StorageValue,21};2223use frame_system::{self as system, ensure_signed};24use sp_runtime::sp_std::prelude::Vec;25use sp_runtime::{26 traits::{27 DispatchInfoOf, Dispatchable, PostDispatchInfoOf, SaturatedConversion, Saturating,28 SignedExtension, Zero,29 },30 transaction_validity::{31 InvalidTransaction, TransactionPriority, TransactionValidity, TransactionValidityError,32 ValidTransaction,33 },34 FixedPointOperand, FixedU128,35};3637#[cfg(test)]38mod mock;3940#[cfg(test)]41mod tests;4243// Structs44// #region4546#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]47#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]48pub enum CollectionMode {49 Invalid,50 // custom data size51 NFT(u32),52 // decimal points53 Fungible(u32),54 // custom data size and decimal points55 ReFungible(u32, u32),56}5758impl Into<u8> for CollectionMode {59 fn into(self) -> u8 {60 match self {61 CollectionMode::Invalid => 0,62 CollectionMode::NFT(_) => 1,63 CollectionMode::Fungible(_) => 2,64 CollectionMode::ReFungible(_, _) => 3,65 }66 }67}6869#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]70#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]71pub enum AccessMode {72 Normal,73 WhiteList,74}75impl Default for AccessMode {76 fn default() -> Self {77 Self::Normal78 }79}8081impl Default for CollectionMode {82 fn default() -> Self {83 Self::Invalid84 }85}8687#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]88#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]89pub struct Ownership<AccountId> {90 pub owner: AccountId,91 pub fraction: u128,92}9394#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]95#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]96pub struct CollectionType<AccountId> {97 pub owner: AccountId,98 pub mode: CollectionMode,99 pub access: AccessMode,100 pub decimal_points: u32,101 pub name: Vec<u16>, // 64 include null escape char102 pub description: Vec<u16>, // 256 include null escape char103 pub token_prefix: Vec<u8>, // 16 include null escape char104 pub custom_data_size: u32,105 pub mint_mode: bool,106 pub offchain_schema: Vec<u8>,107 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender108 pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship109}110111#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]112#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]113pub struct CollectionAdminsType<AccountId> {114 pub admin: AccountId,115 pub collection_id: u64,116}117118#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]119#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]120pub struct NftItemType<AccountId> {121 pub collection: u64,122 pub owner: AccountId,123 pub data: Vec<u8>,124}125126#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]127#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]128pub struct FungibleItemType<AccountId> {129 pub collection: u64,130 pub owner: AccountId,131 pub value: u128,132}133134#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]135#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]136pub struct ReFungibleItemType<AccountId> {137 pub collection: u64,138 pub owner: Vec<Ownership<AccountId>>,139 pub data: Vec<u8>,140}141142#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]143#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]144pub struct ApprovePermissions<AccountId> {145 pub approved: AccountId,146 pub amount: u64,147}148149#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]150#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]151pub struct VestingItem<AccountId, Moment> {152 pub sender: AccountId,153 pub recipient: AccountId,154 pub collection_id: u64,155 pub item_id: u64,156 pub amount: u64,157 pub vesting_date: Moment,158}159160pub trait Trait: system::Trait {161 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;162}163164// #endregion165166decl_storage! {167 trait Store for Module<T: Trait> as Nft {168169 // Private members170 NextCollectionID: u64;171 CreatedCollectionCount: u64;172 ChainVersion: u64;173 ItemListIndex: map hasher(blake2_128_concat) u64 => u64;174175 pub Collection get(fn collection) config(): map hasher(identity) u64 => CollectionType<T::AccountId>;176 pub AdminList get(fn admin_list_collection): map hasher(identity) u64 => Vec<T::AccountId>;177 pub WhiteList get(fn white_list): map hasher(identity) u64 => Vec<T::AccountId>;178179 /// Balance owner per collection map180 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => u64;181182 /// second parameter: item id + owner account id183 pub ApprovedList get(fn approved): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) (u64, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;184185 /// Item collections186 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => NftItemType<T::AccountId>;187 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => FungibleItemType<T::AccountId>;188 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => ReFungibleItemType<T::AccountId>;189190 /// Index list191 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => Vec<u64>;192193 // Sponsorship194 pub ContractSponsor get(fn contract_sponsor): map hasher(identity) T::AccountId => T::AccountId;195 pub UnconfirmedContractSponsor get(fn unconfirmed_contract_sponsor): map hasher(identity) T::AccountId => T::AccountId;196 }197 add_extra_genesis {198 build(|config: &GenesisConfig<T>| {199 // Modification of storage200 for (_num, _c) in &config.collection {201 <Module<T>>::init_collection(_c);202 }203204 for (_num, _q, _i) in &config.nft_item_id {205 <Module<T>>::init_nft_token(_i);206 }207208 for (_num, _q, _i) in &config.fungible_item_id {209 <Module<T>>::init_fungible_token(_i);210 }211212 for (_num, _q, _i) in &config.refungible_item_id {213 <Module<T>>::init_refungible_token(_i);214 }215 })216 }217}218219decl_event!(220 pub enum Event<T>221 where222 AccountId = <T as system::Trait>::AccountId,223 {224 Created(u64, u8, AccountId),225 ItemCreated(u64, u64),226 ItemDestroyed(u64, u64),227 }228);229230decl_module! {231 pub struct Module<T: Trait> for enum Call where origin: T::Origin {232233 fn deposit_event() = default;234235 fn on_initialize(now: T::BlockNumber) -> Weight {236237 if ChainVersion::get() < 2238 {239 let value = NextCollectionID::get();240 CreatedCollectionCount::put(value);241 ChainVersion::put(2);242 }243244 0245 }246247 // Create collection of NFT with given parameters248 //249 // @param customDataSz size of custom data in each collection item250 // returns collection ID251 #[weight = 0]252 pub fn create_collection(origin,253 collection_name: Vec<u16>,254 collection_description: Vec<u16>,255 token_prefix: Vec<u8>,256 mode: CollectionMode) -> DispatchResult {257258 // Anyone can create a collection259 let who = ensure_signed(origin)?;260 let custom_data_size = match mode {261 CollectionMode::NFT(size) => size,262 CollectionMode::ReFungible(size, _) => size,263 _ => 0264 };265266 let decimal_points = match mode {267 CollectionMode::Fungible(points) => points,268 CollectionMode::ReFungible(_, points) => points,269 _ => 0270 };271272 // check params273 ensure!(decimal_points <= 4, "decimal_points parameter must be lower than 4");274275 let mut name = collection_name.to_vec();276 name.push(0);277 ensure!(name.len() <= 64, "Collection name can not be longer than 63 char");278279 let mut description = collection_description.to_vec();280 description.push(0);281 ensure!(name.len() <= 256, "Collection description can not be longer than 255 char");282283 let mut prefix = token_prefix.to_vec();284 prefix.push(0);285 ensure!(prefix.len() <= 16, "Token prefix can not be longer than 15 char");286287 // Generate next collection ID288 let next_id = CreatedCollectionCount::get()289 .checked_add(1)290 .expect("collection id error");291292 CreatedCollectionCount::put(next_id);293294 // Create new collection295 let new_collection = CollectionType {296 owner: who.clone(),297 name: name,298 mode: mode.clone(),299 mint_mode: false,300 access: AccessMode::Normal,301 description: description,302 decimal_points: decimal_points,303 token_prefix: prefix,304 offchain_schema: Vec::new(),305 custom_data_size: custom_data_size,306 sponsor: T::AccountId::default(),307 unconfirmed_sponsor: T::AccountId::default(),308 };309310 // Add new collection to map311 <Collection<T>>::insert(next_id, new_collection);312313 // call event314 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));315316 Ok(())317 }318319 #[weight = 0]320 pub fn destroy_collection(origin, collection_id: u64) -> DispatchResult {321322 let sender = ensure_signed(origin)?;323 Self::check_owner_permissions(collection_id, sender)?;324325 // TODO Items remove326 <AddressTokens<T>>::remove_prefix(collection_id);327 <ApprovedList<T>>::remove_prefix(collection_id);328 <Balance<T>>::remove_prefix(collection_id);329 <ItemListIndex>::remove(collection_id);330 <AdminList<T>>::remove(collection_id);331 <Collection<T>>::remove(collection_id);332 <WhiteList<T>>::remove(collection_id);333334 Ok(())335 }336337 #[weight = 0]338 pub fn add_to_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{339340 let sender = ensure_signed(origin)?;341 Self::check_owner_or_admin_permissions(collection_id, sender)?;342343 let mut white_list_collection: Vec<T::AccountId>;344 if <WhiteList<T>>::contains_key(collection_id) {345 white_list_collection = <WhiteList<T>>::get(collection_id);346 if !white_list_collection.contains(&address.clone())347 {348 white_list_collection.push(address.clone());349 }350 }351 else {352 white_list_collection = Vec::new();353 white_list_collection.push(address.clone());354 }355356 <WhiteList<T>>::insert(collection_id, white_list_collection);357 Ok(())358 }359360 #[weight = 0]361 pub fn remove_from_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{362363 let sender = ensure_signed(origin)?;364 Self::check_owner_or_admin_permissions(collection_id, sender)?;365366 if <WhiteList<T>>::contains_key(collection_id) {367 let mut white_list_collection = <WhiteList<T>>::get(collection_id);368 if white_list_collection.contains(&address.clone())369 {370 white_list_collection.retain(|i| *i != address.clone());371 <WhiteList<T>>::insert(collection_id, white_list_collection);372 }373 }374375 Ok(())376 }377378 #[weight = 0]379 pub fn set_public_access_mode(origin, collection_id: u64, mode: AccessMode) -> DispatchResult380 {381 let sender = ensure_signed(origin)?;382383 Self::check_owner_permissions(collection_id, sender)?;384 let mut target_collection = <Collection<T>>::get(collection_id);385 target_collection.access = mode;386 <Collection<T>>::insert(collection_id, target_collection);387388 Ok(())389 }390391 #[weight = 0]392 pub fn set_mint_permission(origin, collection_id: u64, mint_permission: bool) -> DispatchResult393 {394 let sender = ensure_signed(origin)?;395396 Self::check_owner_permissions(collection_id, sender)?;397 let mut target_collection = <Collection<T>>::get(collection_id);398 target_collection.mint_mode = mint_permission;399 <Collection<T>>::insert(collection_id, target_collection);400401 Ok(())402 }403404 #[weight = 0]405 pub fn change_collection_owner(origin, collection_id: u64, new_owner: T::AccountId) -> DispatchResult {406407 let sender = ensure_signed(origin)?;408 Self::check_owner_permissions(collection_id, sender)?;409 let mut target_collection = <Collection<T>>::get(collection_id);410 target_collection.owner = new_owner;411 <Collection<T>>::insert(collection_id, target_collection);412413 Ok(())414 }415416 #[weight = 0]417 pub fn add_collection_admin(origin, collection_id: u64, new_admin_id: T::AccountId) -> DispatchResult {418419 let sender = ensure_signed(origin)?;420 Self::check_owner_or_admin_permissions(collection_id, sender)?;421 let mut admin_arr: Vec<T::AccountId> = Vec::new();422423 if <AdminList<T>>::contains_key(collection_id)424 {425 admin_arr = <AdminList<T>>::get(collection_id);426 ensure!(!admin_arr.contains(&new_admin_id), "Account already has admin role");427 }428429 admin_arr.push(new_admin_id);430 <AdminList<T>>::insert(collection_id, admin_arr);431432 Ok(())433 }434435 #[weight = 0]436 pub fn remove_collection_admin(origin, collection_id: u64, account_id: T::AccountId) -> DispatchResult {437438 let sender = ensure_signed(origin)?;439 Self::check_owner_or_admin_permissions(collection_id, sender)?;440441 if <AdminList<T>>::contains_key(collection_id)442 {443 let mut admin_arr = <AdminList<T>>::get(collection_id);444 admin_arr.retain(|i| *i != account_id);445 <AdminList<T>>::insert(collection_id, admin_arr);446 }447448 Ok(())449 }450451 #[weight = 0]452 pub fn set_collection_sponsor(origin, collection_id: u64, new_sponsor: T::AccountId) -> DispatchResult {453454 let sender = ensure_signed(origin)?;455 ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");456457 let mut target_collection = <Collection<T>>::get(collection_id);458 ensure!(sender == target_collection.owner, "You do not own this collection");459460 target_collection.unconfirmed_sponsor = new_sponsor;461 <Collection<T>>::insert(collection_id, target_collection);462463 Ok(())464 }465466 #[weight = 0]467 pub fn confirm_sponsorship(origin, collection_id: u64) -> DispatchResult {468469 let sender = ensure_signed(origin)?;470 ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");471472 let mut target_collection = <Collection<T>>::get(collection_id);473 ensure!(sender == target_collection.unconfirmed_sponsor, "This address is not set as sponsor, use setCollectionSponsor first");474475 target_collection.sponsor = target_collection.unconfirmed_sponsor;476 target_collection.unconfirmed_sponsor = T::AccountId::default();477 <Collection<T>>::insert(collection_id, target_collection);478479 Ok(())480 }481482 #[weight = 0]483 pub fn remove_collection_sponsor(origin, collection_id: u64) -> DispatchResult {484485 let sender = ensure_signed(origin)?;486 ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");487488 let mut target_collection = <Collection<T>>::get(collection_id);489 ensure!(sender == target_collection.owner, "You do not own this collection");490491 target_collection.sponsor = T::AccountId::default();492 <Collection<T>>::insert(collection_id, target_collection);493494 Ok(())495 }496497 #[weight = 0]498 pub fn create_item(origin, collection_id: u64, properties: Vec<u8>, owner: T::AccountId) -> DispatchResult {499500 let sender = ensure_signed(origin)?;501 Self::collection_exists(collection_id)?;502 let target_collection = <Collection<T>>::get(collection_id);503504 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {505 ensure!(target_collection.mint_mode == true, "Collection is not in mint mode");506 Self::check_white_list(collection_id, owner.clone())?;507 }508509 match target_collection.mode510 {511 CollectionMode::NFT(_) => {512513 // check size514 ensure!(target_collection.custom_data_size >= properties.len() as u32, "Size of item is too large");515516 // Create nft item517 let item = NftItemType {518 collection: collection_id,519 owner: owner,520 data: properties.clone(),521 };522523 Self::add_nft_item(item)?;524525 },526 CollectionMode::Fungible(_) => {527528 // check size529 ensure!(properties.len() as u32 == 0, "Size of item must be 0 with fungible type");530531 let item = FungibleItemType {532 collection: collection_id,533 owner: owner,534 value: (10 as u128).pow(target_collection.decimal_points)535 };536537 Self::add_fungible_item(item)?;538 },539 CollectionMode::ReFungible(_, _) => {540541 // check size542 ensure!(target_collection.custom_data_size >= properties.len() as u32, "Size of item is too large");543544 let mut owner_list = Vec::new();545 let value = (10 as u128).pow(target_collection.decimal_points);546 owner_list.push(Ownership {owner: owner.clone(), fraction: value});547548 let item = ReFungibleItemType {549 collection: collection_id,550 owner: owner_list,551 data: properties.clone()552 };553554 Self::add_refungible_item(item)?;555 },556 _ => { ensure!(1 == 0,"just error"); }557558 };559560 // call event561 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));562563 Ok(())564 }565566 #[weight = 0]567 pub fn burn_item(origin, collection_id: u64, item_id: u64) -> DispatchResult {568569 let sender = ensure_signed(origin)?;570 Self::collection_exists(collection_id)?;571572 // Transfer permissions check573 let target_collection = <Collection<T>>::get(collection_id);574 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) || 575 Self::is_owner_or_admin_permissions(collection_id, sender.clone()), 576 "Only item owner, collection owner and admins can modify item");577578 if target_collection.access == AccessMode::WhiteList {579 Self::check_white_list(collection_id, sender.clone())?;580 }581582 match target_collection.mode583 {584 CollectionMode::NFT(_) => Self::burn_nft_item(collection_id, item_id)?,585 CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?,586 CollectionMode::ReFungible(_, _) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,587 _ => ()588 };589590 // call event591 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));592593 Ok(())594 }595596 #[weight = 0]597 pub fn transfer(origin, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64) -> DispatchResult {598599 let sender = ensure_signed(origin)?;600601 // Transfer permissions check602 let target_collection = <Collection<T>>::get(collection_id);603 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) || 604 Self::is_owner_or_admin_permissions(collection_id, sender.clone()), 605 "Only item owner, collection owner and admins can modify item");606607 if target_collection.access == AccessMode::WhiteList {608 Self::check_white_list(collection_id, sender.clone())?;609 Self::check_white_list(collection_id, recipient.clone())?;610 }611612 match target_collection.mode613 {614 CollectionMode::NFT(_) => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,615 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,616 CollectionMode::ReFungible(_, _) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,617 _ => ()618 };619620 Ok(())621 }622623 #[weight = 0]624 pub fn approve(origin, approved: T::AccountId, collection_id: u64, item_id: u64) -> DispatchResult {625626 let sender = ensure_signed(origin)?;627628 // Transfer permissions check629 let target_collection = <Collection<T>>::get(collection_id);630 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) || 631 Self::is_owner_or_admin_permissions(collection_id, sender.clone()), 632 "Only item owner, collection owner and admins can approve");633634 if target_collection.access == AccessMode::WhiteList {635 Self::check_white_list(collection_id, sender.clone())?;636 Self::check_white_list(collection_id, approved.clone())?;637 }638639 // amount param stub640 let amount = 100000000;641642 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));643 if list_exists {644645 let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));646 let item_contains = list.iter().any(|i| i.approved == approved);647648 if !item_contains {649 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });650 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);651 }652 } else {653654 let mut list = Vec::new();655 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });656 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);657 }658659 Ok(())660 }661662 #[weight = 0]663 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64 ) -> DispatchResult {664665 let sender = ensure_signed(origin)?;666 let mut appoved_transfer = false;667668 // Check approve669 if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {670 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));671 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());672 appoved_transfer = opt_item.is_some();673 ensure!(opt_item.unwrap().amount >= value, "Requested value more than approved");674 }675676 // Transfer permissions check677 let target_collection = <Collection<T>>::get(collection_id);678 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()), 679 "Only item owner, collection owner and admins can modify items");680681 if target_collection.access == AccessMode::WhiteList {682 Self::check_white_list(collection_id, sender.clone())?;683 Self::check_white_list(collection_id, recipient.clone())?;684 }685686 // remove approve687 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))688 .into_iter().filter(|i| i.approved != sender.clone()).collect();689 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);690691692 match target_collection.mode693 {694 CollectionMode::NFT(_) => Self::transfer_nft(collection_id, item_id, from, recipient)?,695 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,696 CollectionMode::ReFungible(_, _) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,697 _ => ()698 };699700 Ok(())701 }702703 #[weight = 0]704 pub fn safe_transfer_from(origin, collection_id: u64, item_id: u64, new_owner: T::AccountId) -> DispatchResult {705706 // let no_perm_mes = "You do not have permissions to modify this collection";707 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);708 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));709 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);710711 // // on_nft_received call712713 // Self::transfer(origin, collection_id, item_id, new_owner)?;714715 Ok(())716 }717718 #[weight = 0]719 pub fn set_offchain_schema(720 origin,721 collection_id: u64,722 schema: Vec<u8>723 ) -> DispatchResult {724 let sender = ensure_signed(origin)?;725 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;726727 let mut target_collection = <Collection<T>>::get(collection_id);728 target_collection.offchain_schema = schema;729 <Collection<T>>::insert(collection_id, target_collection);730731 Ok(())732 }733 }734}735736impl<T: Trait> Module<T> {737 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {738 let current_index = <ItemListIndex>::get(item.collection)739 .checked_add(1)740 .expect("Item list index id error");741 let itemcopy = item.clone();742 let owner = item.owner.clone();743 let value = item.value as u64;744745 Self::add_token_index(item.collection, current_index, owner.clone())?;746747 <ItemListIndex>::insert(item.collection, current_index);748 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);749750 // Update balance751 let new_balance = <Balance<T>>::get(item.collection, owner.clone())752 .checked_add(value)753 .unwrap();754 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);755756 Ok(())757 }758759 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {760 let current_index = <ItemListIndex>::get(item.collection)761 .checked_add(1)762 .expect("Item list index id error");763 let itemcopy = item.clone();764765 let value = item.owner.first().unwrap().fraction as u64;766 let owner = item.owner.first().unwrap().owner.clone();767768 Self::add_token_index(item.collection, current_index, owner.clone())?;769770 <ItemListIndex>::insert(item.collection, current_index);771 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);772773 // Update balance774 let new_balance = <Balance<T>>::get(item.collection, owner.clone())775 .checked_add(value)776 .unwrap();777 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);778779 Ok(())780 }781782 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {783 let current_index = <ItemListIndex>::get(item.collection)784 .checked_add(1)785 .expect("Item list index id error");786787 let item_owner = item.owner.clone();788 let collection_id = item.collection.clone();789 Self::add_token_index(collection_id, current_index, item.owner.clone())?;790791 <ItemListIndex>::insert(collection_id, current_index);792 <NftItemList<T>>::insert(collection_id, current_index, item);793794 // Update balance795 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())796 .checked_add(1)797 .unwrap();798 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);799800 Ok(())801 }802803 fn burn_refungible_item(collection_id: u64, item_id: u64, owner: T::AccountId) -> DispatchResult {804 ensure!(805 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),806 "Item does not exists"807 );808 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);809 let item = collection810 .owner811 .iter()812 .filter(|&i| i.owner == owner)813 .next()814 .unwrap();815 Self::remove_token_index(collection_id, item_id, owner.clone())?;816817 // remove approve list818 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));819820 // update balance821 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())822 .checked_sub(item.fraction as u64)823 .unwrap();824 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);825826 <ReFungibleItemList<T>>::remove(collection_id, item_id);827828 Ok(())829 }830831 fn burn_nft_item(collection_id: u64, item_id: u64) -> DispatchResult {832 ensure!(833 <NftItemList<T>>::contains_key(collection_id, item_id),834 "Item does not exists"835 );836 let item = <NftItemList<T>>::get(collection_id, item_id);837 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;838839 // remove approve list840 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));841842 // update balance843 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())844 .checked_sub(1)845 .unwrap();846 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);847 <NftItemList<T>>::remove(collection_id, item_id);848849 Ok(())850 }851852 fn burn_fungible_item(collection_id: u64, item_id: u64) -> DispatchResult {853 ensure!(854 <FungibleItemList<T>>::contains_key(collection_id, item_id),855 "Item does not exists"856 );857 let item = <FungibleItemList<T>>::get(collection_id, item_id);858 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;859860 // remove approve list861 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));862863 // update balance864 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())865 .checked_sub(item.value as u64)866 .unwrap();867 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);868869 <FungibleItemList<T>>::remove(collection_id, item_id);870871 Ok(())872 }873874 fn collection_exists(collection_id: u64) -> DispatchResult {875 ensure!(876 <Collection<T>>::contains_key(collection_id),877 "This collection does not exist"878 );879 Ok(())880 }881882 fn check_owner_permissions(collection_id: u64, subject: T::AccountId) -> DispatchResult {883 Self::collection_exists(collection_id)?;884885 let target_collection = <Collection<T>>::get(collection_id);886 ensure!(887 subject == target_collection.owner,888 "You do not own this collection"889 );890891 Ok(())892 }893894 fn is_owner_or_admin_permissions(collection_id: u64, subject: T::AccountId) -> bool {895896 let target_collection = <Collection<T>>::get(collection_id);897 let mut result: bool = subject == target_collection.owner;898 let exists = <AdminList<T>>::contains_key(collection_id);899900 if !result & exists {901 if <AdminList<T>>::get(collection_id).contains(&subject) {902 result = true903 }904 }905906 result907 }908909 fn check_owner_or_admin_permissions(collection_id: u64, subject: T::AccountId) -> DispatchResult {910 911 Self::collection_exists(collection_id)?;912 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());913914 ensure!(result, "You do not have permissions to modify this collection");915 Ok(())916 }917918 fn is_item_owner(subject: T::AccountId, collection_id: u64, item_id: u64) -> bool {919 let target_collection = <Collection<T>>::get(collection_id);920921 match target_collection.mode {922 CollectionMode::NFT(_) => {923 <NftItemList<T>>::get(collection_id, item_id).owner == subject924 }925 CollectionMode::Fungible(_) => {926 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject927 }928 CollectionMode::ReFungible(_, _) => {929 <ReFungibleItemList<T>>::get(collection_id, item_id)930 .owner931 .iter()932 .any(|i| i.owner == subject)933 }934 CollectionMode::Invalid => false,935 }936 }937938 fn check_white_list(collection_id: u64, address: T::AccountId) -> DispatchResult {939940 let mes = "Address is not in white list";941 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);942 let wl = <WhiteList<T>>::get(collection_id);943 ensure!(wl.contains(&address.clone()), mes);944945 Ok(())946 }947948 fn transfer_fungible(949 collection_id: u64,950 item_id: u64,951 value: u64,952 owner: T::AccountId,953 new_owner: T::AccountId,954 ) -> DispatchResult {955956 ensure!(957 <FungibleItemList<T>>::contains_key(collection_id, item_id),958 "Item not exists"959 );960961 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);962 let amount = full_item.value;963964 ensure!(amount >= value.into(), "Item balance not enouth");965966 // update balance967 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())968 .checked_sub(value)969 .unwrap();970 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);971972 let mut new_owner_account_id = 0;973 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());974 if new_owner_items.len() > 0 {975 new_owner_account_id = new_owner_items[0];976 }977978 let val64 = value.into();979980 // transfer981 if amount == val64 && new_owner_account_id == 0 {982 // change owner983 // new owner do not have account984 let mut new_full_item = full_item.clone();985 new_full_item.owner = new_owner.clone();986 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);987988 // update balance989 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())990 .checked_add(value)991 .unwrap();992 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);993994 // update index collection995 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;996 } else {997 let mut new_full_item = full_item.clone();998 new_full_item.value -= val64;9991000 // separate amount1001 if new_owner_account_id > 0 {1002 // new owner has account1003 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1004 item.value += val64;10051006 // update balance1007 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1008 .checked_add(value)1009 .unwrap();1010 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);10111012 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1013 } else {1014 // new owner do not have account1015 let item = FungibleItemType {1016 collection: collection_id,1017 owner: new_owner.clone(),1018 value: val64,1019 };10201021 Self::add_fungible_item(item)?;1022 }10231024 if amount == val64 {1025 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;10261027 // remove approve list1028 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1029 <FungibleItemList<T>>::remove(collection_id, item_id);1030 }10311032 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1033 }10341035 Ok(())1036 }10371038 fn transfer_refungible(1039 collection_id: u64,1040 item_id: u64,1041 value: u64,1042 owner: T::AccountId,1043 new_owner: T::AccountId,1044 ) -> DispatchResult {10451046 ensure!(1047 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1048 "Item not exists"1049 );10501051 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1052 let item = full_item1053 .owner1054 .iter()1055 .filter(|i| i.owner == owner)1056 .next()1057 .unwrap();1058 let amount = item.fraction;10591060 ensure!(amount >= value.into(), "Item balance not enouth");10611062 // update balance1063 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1064 .checked_sub(value)1065 .unwrap();1066 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);10671068 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1069 .checked_add(value)1070 .unwrap();1071 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);10721073 let old_owner = item.owner.clone();1074 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);1075 let val64 = value.into();10761077 // transfer1078 if amount == val64 && !new_owner_has_account {1079 // change owner1080 // new owner do not have account1081 let mut new_full_item = full_item.clone();1082 new_full_item1083 .owner1084 .iter_mut()1085 .find(|i| i.owner == owner)1086 .unwrap()1087 .owner = new_owner.clone();1088 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);10891090 // update index collection1091 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1092 } else {1093 let mut new_full_item = full_item.clone();1094 new_full_item1095 .owner1096 .iter_mut()1097 .find(|i| i.owner == owner)1098 .unwrap()1099 .fraction -= val64;11001101 // separate amount1102 if new_owner_has_account {1103 // new owner has account1104 new_full_item1105 .owner1106 .iter_mut()1107 .find(|i| i.owner == new_owner)1108 .unwrap()1109 .fraction += val64;1110 } else {1111 // new owner do not have account1112 new_full_item.owner.push(Ownership {1113 owner: new_owner.clone(),1114 fraction: val64,1115 });1116 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1117 }11181119 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1120 }11211122 Ok(())1123 }11241125 fn transfer_nft(1126 collection_id: u64,1127 item_id: u64,1128 sender: T::AccountId,1129 new_owner: T::AccountId,1130 ) -> DispatchResult {1131 1132 ensure!(1133 <NftItemList<T>>::contains_key(collection_id, item_id),1134 "Item not exists"1135 );11361137 let mut item = <NftItemList<T>>::get(collection_id, item_id);11381139 ensure!(1140 sender == item.owner,1141 "sender parameter and item owner must be equal"1142 );11431144 // update balance1145 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1146 .checked_sub(1)1147 .unwrap();1148 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);11491150 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1151 .checked_add(1)1152 .unwrap();1153 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);11541155 // change owner1156 let old_owner = item.owner.clone();1157 item.owner = new_owner.clone();1158 <NftItemList<T>>::insert(collection_id, item_id, item);11591160 // update index collection1161 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;11621163 // reset approved list1164 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));1165 Ok(())1166 }11671168 fn init_collection(item: &CollectionType<T::AccountId>){11691170 // check params1171 assert!(item.decimal_points <= 4, "decimal_points parameter must be lower than 4");1172 assert!(item.name.len() <= 64, "Collection name can not be longer than 63 char");1173 assert!(item.name.len() <= 256, "Collection description can not be longer than 255 char");1174 assert!(item.token_prefix.len() <= 16, "Token prefix can not be longer than 15 char");1175 1176 // Generate next collection ID1177 let next_id = CreatedCollectionCount::get()1178 .checked_add(1)1179 .expect("collection id error");1180 1181 CreatedCollectionCount::put(next_id); 1182 }11831184 fn init_nft_token(item: &NftItemType<T::AccountId>){11851186 let current_index = <ItemListIndex>::get(item.collection)1187 .checked_add(1)1188 .expect("Item list index id error");11891190 let item_owner = item.owner.clone();1191 let collection_id = item.collection.clone();1192 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();11931194 <ItemListIndex>::insert(collection_id, current_index);11951196 // Update balance1197 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1198 .checked_add(1)1199 .unwrap();1200 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);1201 }12021203 fn init_fungible_token(item: &FungibleItemType<T::AccountId>){12041205 let current_index = <ItemListIndex>::get(item.collection)1206 .checked_add(1)1207 .expect("Item list index id error");1208 let owner = item.owner.clone();1209 let value = item.value as u64;12101211 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();12121213 <ItemListIndex>::insert(item.collection, current_index);12141215 // Update balance1216 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1217 .checked_add(value)1218 .unwrap();1219 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1220 }12211222 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>){12231224 let current_index = <ItemListIndex>::get(item.collection)1225 .checked_add(1)1226 .expect("Item list index id error");12271228 let value = item.owner.first().unwrap().fraction as u64;1229 let owner = item.owner.first().unwrap().owner.clone();12301231 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();12321233 <ItemListIndex>::insert(item.collection, current_index);12341235 // Update balance1236 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1237 .checked_add(value)1238 .unwrap();1239 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1240 }12411242 fn add_token_index(collection_id: u64, item_index: u64, owner: T::AccountId) -> DispatchResult {1243 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());1244 if list_exists {1245 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());1246 let item_contains = list.contains(&item_index.clone());12471248 if !item_contains {1249 list.push(item_index.clone());1250 }12511252 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);1253 } else {1254 let mut itm = Vec::new();1255 itm.push(item_index.clone());1256 <AddressTokens<T>>::insert(collection_id, owner, itm);1257 }12581259 Ok(())1260 }12611262 fn remove_token_index(1263 collection_id: u64,1264 item_index: u64,1265 owner: T::AccountId,1266 ) -> DispatchResult {1267 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());1268 if list_exists {1269 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());1270 let item_contains = list.contains(&item_index.clone());12711272 if item_contains {1273 list.retain(|&item| item != item_index);1274 <AddressTokens<T>>::insert(collection_id, owner, list);1275 }1276 }12771278 Ok(())1279 }12801281 fn move_token_index(1282 collection_id: u64,1283 item_index: u64,1284 old_owner: T::AccountId,1285 new_owner: T::AccountId,1286 ) -> DispatchResult {1287 Self::remove_token_index(collection_id, item_index, old_owner)?;1288 Self::add_token_index(collection_id, item_index, new_owner)?;12891290 Ok(())1291 }1292}12931294////////////////////////////////////////////////////////////////////////////////////////////////////1295// Economic models1296// #region12971298/// Fee multiplier.1299pub type Multiplier = FixedU128;13001301type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<1302 <T as system::Trait>::AccountId,1303>>::Balance;1304type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<1305 <T as system::Trait>::AccountId,1306>>::NegativeImbalance;13071308/// Require the transactor pay for themselves and maybe include a tip to gain additional priority1309/// in the queue.1310#[derive(Encode, Decode, Clone, Eq, PartialEq)]1311pub struct ChargeTransactionPayment<T: transaction_payment::Trait + Send + Sync>(1312 #[codec(compact)] BalanceOf<T>,1313);13141315impl<T: Trait + transaction_payment::Trait + Send + Sync> sp_std::fmt::Debug1316 for ChargeTransactionPayment<T>1317{1318 #[cfg(feature = "std")]1319 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {1320 write!(f, "ChargeTransactionPayment<{:?}>", self.0)1321 }1322 #[cfg(not(feature = "std"))]1323 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {1324 Ok(())1325 }1326}13271328impl<T: Trait + transaction_payment::Trait + Send + Sync> ChargeTransactionPayment<T>1329where1330 T::Call:1331 Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Module<T>, T>,1332 BalanceOf<T>: Send + Sync + FixedPointOperand,1333{1334 /// utility constructor. Used only in client/factory code.1335 pub fn from(fee: BalanceOf<T>) -> Self {1336 Self(fee)1337 }13381339 pub fn traditional_fee(1340 len: usize,1341 info: &DispatchInfoOf<T::Call>,1342 tip: BalanceOf<T>,1343 ) -> BalanceOf<T>1344 where1345 T::Call: Dispatchable<Info = DispatchInfo>,1346 {1347 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)1348 }13491350 fn withdraw_fee(1351 &self,1352 who: &T::AccountId,1353 call: &T::Call,1354 info: &DispatchInfoOf<T::Call>,1355 len: usize,1356 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {1357 let tip = self.0;13581359 // Set fee based on call type. Creating collection costs 1 Unique.1360 // All other transactions have traditional fees so far1361 let fee = match call.is_sub_type() {1362 Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),1363 _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes1364 // _ => <BalanceOf<T>>::from(100)1365 };13661367 // Determine who is paying transaction fee based on ecnomic model1368 // Parse call to extract collection ID and access collection sponsor1369 let sponsor: T::AccountId = match call.is_sub_type() {1370 Some(Call::create_item(collection_id, _properties, _owner)) => {1371 <Collection<T>>::get(collection_id).sponsor1372 }1373 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {1374 <Collection<T>>::get(collection_id).sponsor1375 }13761377 _ => T::AccountId::default(),1378 };13791380 let mut who_pays_fee: T::AccountId = sponsor.clone();1381 if sponsor == T::AccountId::default() {1382 who_pays_fee = who.clone();1383 }13841385 // Only mess with balances if fee is not zero.1386 if fee.is_zero() {1387 return Ok((fee, None));1388 }13891390 match <T as transaction_payment::Trait>::Currency::withdraw(1391 &who_pays_fee,1392 fee,1393 if tip.is_zero() {1394 WithdrawReason::TransactionPayment.into()1395 } else {1396 WithdrawReason::TransactionPayment | WithdrawReason::Tip1397 },1398 ExistenceRequirement::KeepAlive,1399 ) {1400 Ok(imbalance) => Ok((fee, Some(imbalance))),1401 Err(_) => Err(InvalidTransaction::Payment.into()),1402 }1403 }1404}14051406impl<T: Trait + transaction_payment::Trait + Send + Sync> SignedExtension1407 for ChargeTransactionPayment<T>1408where1409 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,1410 T::Call:1411 Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Module<T>, T>,1412{1413 const IDENTIFIER: &'static str = "ChargeTransactionPayment";1414 type AccountId = T::AccountId;1415 type Call = T::Call;1416 type AdditionalSigned = ();1417 type Pre = (1418 BalanceOf<T>,1419 Self::AccountId,1420 Option<NegativeImbalanceOf<T>>,1421 BalanceOf<T>,1422 );1423 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {1424 Ok(())1425 }14261427 fn validate(1428 &self,1429 who: &Self::AccountId,1430 call: &Self::Call,1431 info: &DispatchInfoOf<Self::Call>,1432 len: usize,1433 ) -> TransactionValidity {1434 let (fee, _) = self.withdraw_fee(who, call, info, len)?;14351436 let mut r = ValidTransaction::default();1437 // NOTE: we probably want to maximize the _fee (of any type) per weight unit_ here, which1438 // will be a bit more than setting the priority to tip. For now, this is enough.1439 r.priority = fee.saturated_into::<TransactionPriority>();1440 Ok(r)1441 }14421443 fn pre_dispatch(1444 self,1445 who: &Self::AccountId,1446 call: &Self::Call,1447 info: &DispatchInfoOf<Self::Call>,1448 len: usize,1449 ) -> Result<Self::Pre, TransactionValidityError> {1450 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;1451 Ok((self.0, who.clone(), imbalance, fee))1452 }14531454 fn post_dispatch(1455 pre: Self::Pre,1456 info: &DispatchInfoOf<Self::Call>,1457 post_info: &PostDispatchInfoOf<Self::Call>,1458 len: usize,1459 _result: &DispatchResult,1460 ) -> Result<(), TransactionValidityError> {1461 let (tip, who, imbalance, fee) = pre;1462 if let Some(payed) = imbalance {1463 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(1464 len as u32, info, post_info, tip,1465 );1466 let refund = fee.saturating_sub(actual_fee);1467 let actual_payment =1468 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(1469 &who, refund,1470 ) {1471 Ok(refund_imbalance) => {1472 // The refund cannot be larger than the up front payed max weight.1473 // `PostDispatchInfo::calc_unspent` guards against such a case.1474 match payed.offset(refund_imbalance) {1475 Ok(actual_payment) => actual_payment,1476 Err(_) => return Err(InvalidTransaction::Payment.into()),1477 }1478 }1479 // We do not recreate the account using the refund. The up front payment1480 // is gone in that case.1481 Err(_) => payed,1482 };1483 let imbalances = actual_payment.split(tip);1484 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(1485 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),1486 );1487 }1488 Ok(())1489 }1490}1491// #endregionpallets/nft/src/tests.rsdiffbeforeafterboth--- a/pallets/nft/src/tests.rs
+++ b/pallets/nft/src/tests.rs
@@ -3,6 +3,8 @@
use crate::{ApprovePermissions, CollectionMode, AccessMode, Ownership};
use frame_support::{assert_noop, assert_ok};
+// Use cases tests region
+// #region
#[test]
fn create_nft_item() {
new_test_ext().execute_with(|| {
@@ -330,7 +332,7 @@
// do approve
assert_ok!(TemplateModule::approve(origin1.clone(), 2, 1, 1));
assert_eq!(TemplateModule::approved(1, (1, 1)).len(), 1);
- assert_ok!(TemplateModule::approve(origin1.clone(), 10, 1, 1));
+ assert_ok!(TemplateModule::approve(origin1.clone(), 3, 1, 1));
assert_eq!(TemplateModule::approved(1, (1, 1)).len(), 2);
assert_eq!(
TemplateModule::approved(1, (1, 1))[0],
@@ -400,7 +402,7 @@
// do approve
assert_ok!(TemplateModule::approve(origin1.clone(), 2, 1, 1));
assert_eq!(TemplateModule::approved(1, (1, 1)).len(), 1);
- assert_ok!(TemplateModule::approve(origin1.clone(), 10, 1, 1));
+ assert_ok!(TemplateModule::approve(origin1.clone(), 3, 1, 1));
assert_eq!(TemplateModule::approved(1, (1, 1)).len(), 2);
assert_eq!(
TemplateModule::approved(1, (1, 1))[0],
@@ -427,7 +429,7 @@
assert_eq!(
TemplateModule::approved(1, (1, 1))[0],
ApprovePermissions {
- approved: 10,
+ approved: 3,
amount: 100000000
}
);
@@ -472,7 +474,7 @@
// do approve
assert_ok!(TemplateModule::approve(origin1.clone(), 2, 1, 1));
assert_eq!(TemplateModule::approved(1, (1, 1)).len(), 1);
- assert_ok!(TemplateModule::approve(origin1.clone(), 10, 1, 1));
+ assert_ok!(TemplateModule::approve(origin1.clone(), 3, 1, 1));
assert_eq!(TemplateModule::approved(1, (1, 1)).len(), 2);
assert_eq!(
TemplateModule::approved(1, (1, 1))[0],
@@ -499,7 +501,7 @@
assert_eq!(
TemplateModule::approved(1, (1, 1))[0],
ApprovePermissions {
- approved: 10,
+ approved: 3,
amount: 100000000
}
);
@@ -955,3 +957,833 @@
assert_eq!(TemplateModule::balance_count(1, 2), 1);
});
}
+
+// #endregion
+
+// Coverage tests region
+// #region
+
+#[test]
+fn owner_can_add_address_to_white_list() {
+ new_test_ext().execute_with(|| {
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+ let origin1 = Origin::signed(1);
+
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ));
+
+ assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2));
+ assert_eq!(TemplateModule::white_list(1)[0], 2);
+ });
+}
+
+#[test]
+fn admin_can_add_address_to_white_list() {
+ new_test_ext().execute_with(|| {
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+ let origin1 = Origin::signed(1);
+ let origin2 = Origin::signed(2);
+
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ));
+
+ assert_ok!(TemplateModule::add_collection_admin(origin1.clone(), 1, 2));
+ assert_ok!(TemplateModule::add_to_white_list(origin2.clone(), 1, 3));
+ assert_eq!(TemplateModule::white_list(1)[0], 3);
+ });
+}
+
+#[test]
+fn nonprivileged_user_cannot_add_address_to_white_list() {
+ new_test_ext().execute_with(|| {
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+ let origin1 = Origin::signed(1);
+ let origin2 = Origin::signed(2);
+
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ));
+
+ assert_noop!(TemplateModule::add_to_white_list(origin2.clone(), 1, 3), "You do not have permissions to modify this collection");
+ });
+}
+
+#[test]
+fn nobody_can_add_address_to_white_list_of_nonexisting_collection() {
+ new_test_ext().execute_with(|| {
+
+ let origin1 = Origin::signed(1);
+ assert_noop!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2), "This collection does not exist");
+ });
+}
+
+#[test]
+fn nobody_can_add_address_to_white_list_of_deleted_collection() {
+ new_test_ext().execute_with(|| {
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+ let origin1 = Origin::signed(1);
+
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ));
+
+ assert_ok!(TemplateModule::destroy_collection(origin1.clone(), 1));
+ assert_noop!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2), "This collection does not exist");
+ });
+}
+
+// If address is already added to white list, nothing happens
+#[test]
+fn address_is_already_added_to_white_list() {
+ new_test_ext().execute_with(|| {
+
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+ let origin1 = Origin::signed(1);
+
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ));
+
+ assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2));
+ assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2));
+ assert_eq!(TemplateModule::white_list(1)[0], 2);
+ assert_eq!(TemplateModule::white_list(1).len(), 1);
+ });
+}
+
+#[test]
+fn owner_can_remove_address_from_white_list() {
+ new_test_ext().execute_with(|| {
+
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+ let origin1 = Origin::signed(1);
+
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ));
+
+ assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2));
+ assert_ok!(TemplateModule::remove_from_white_list(origin1.clone(), 1, 2));
+ assert_eq!(TemplateModule::white_list(1).len(), 0);
+ });
+}
+
+#[test]
+fn admin_can_remove_address_from_white_list() {
+ new_test_ext().execute_with(|| {
+
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+ let origin1 = Origin::signed(1);
+ let origin2 = Origin::signed(2);
+
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ));
+
+ assert_ok!(TemplateModule::add_collection_admin(origin1.clone(), 1, 2));
+
+ assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 3));
+ assert_ok!(TemplateModule::remove_from_white_list(origin2.clone(), 1, 3));
+ assert_eq!(TemplateModule::white_list(1).len(), 0);
+ });
+}
+
+#[test]
+fn nonprivileged_user_cannot_remove_address_from_white_list() {
+ new_test_ext().execute_with(|| {
+
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+ let origin1 = Origin::signed(1);
+ let origin2 = Origin::signed(2);
+
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ));
+
+ assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2));
+ assert_noop!(TemplateModule::remove_from_white_list(origin2.clone(), 1, 2), "You do not have permissions to modify this collection");
+ assert_eq!(TemplateModule::white_list(1)[0], 2);
+ });
+}
+
+#[test]
+fn nobody_can_remove_address_from_white_list_of_nonexisting_collection() {
+ new_test_ext().execute_with(|| {
+
+ let origin1 = Origin::signed(1);
+ assert_noop!(TemplateModule::remove_from_white_list(origin1.clone(), 1, 2), "This collection does not exist");
+ });
+}
+
+#[test]
+fn nobody_can_remove_address_from_white_list_of_deleted_collection() {
+ new_test_ext().execute_with(|| {
+
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+ let origin1 = Origin::signed(1);
+ let origin2 = Origin::signed(2);
+
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ));
+
+ assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2));
+ assert_ok!(TemplateModule::destroy_collection(origin1.clone(), 1));
+ assert_noop!(TemplateModule::remove_from_white_list(origin2.clone(), 1, 2), "This collection does not exist");
+ assert_eq!(TemplateModule::white_list(1).len(), 0);
+ });
+}
+
+// If address is already removed from white list, nothing happens
+#[test]
+fn address_is_already_removed_from_white_list() {
+ new_test_ext().execute_with(|| {
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+ let origin1 = Origin::signed(1);
+
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ));
+
+ assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2));
+ assert_ok!(TemplateModule::remove_from_white_list(origin1.clone(), 1, 2));
+ assert_ok!(TemplateModule::remove_from_white_list(origin1.clone(), 1, 2));
+ assert_eq!(TemplateModule::white_list(1).len(), 0);
+ });
+}
+
+// If Public Access mode is set to WhiteList, tokens can’t be transferred from a non-whitelisted address with transfer or transferFrom (2 tests)
+#[test]
+fn white_list_test_1() {
+ new_test_ext().execute_with(|| {
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+
+ let origin1 = Origin::signed(1);
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ));
+ assert_eq!(TemplateModule::collection(1).owner, 1);
+
+ assert_ok!(TemplateModule::create_item(
+ origin1.clone(),
+ 1,
+ [1, 2, 3].to_vec(),
+ 1
+ ));
+
+ assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
+ assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2));
+
+ assert_noop!(TemplateModule::transfer(
+ origin1.clone(),
+ 3,
+ 1,
+ 1,
+ 1
+ ), "Address is not in white list");
+ });
+}
+
+#[test]
+fn white_list_test_2() {
+ new_test_ext().execute_with(|| {
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+
+ let origin1 = Origin::signed(1);
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ));
+ assert_eq!(TemplateModule::collection(1).owner, 1);
+
+ assert_ok!(TemplateModule::create_item(
+ origin1.clone(),
+ 1,
+ [1, 2, 3].to_vec(),
+ 1
+ ));
+
+ assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
+ assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 1));
+ assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2));
+
+ // do approve
+ assert_ok!(TemplateModule::approve(origin1.clone(), 1, 1, 1));
+ assert_eq!(TemplateModule::approved(1, (1, 1)).len(), 1);
+
+ assert_ok!(TemplateModule::remove_from_white_list(origin1.clone(), 1, 1));
+
+ assert_noop!(TemplateModule::transfer_from(
+ origin1.clone(),
+ 1,
+ 3,
+ 1,
+ 1,
+ 1
+ ), "Address is not in white list");
+ });
+}
+
+// If Public Access mode is set to WhiteList, tokens can’t be transferred to a non-whitelisted address with transfer or transferFrom (2 tests)
+#[test]
+fn white_list_test_3() {
+ new_test_ext().execute_with(|| {
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+
+ let origin1 = Origin::signed(1);
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ));
+ assert_eq!(TemplateModule::collection(1).owner, 1);
+
+ assert_ok!(TemplateModule::create_item(
+ origin1.clone(),
+ 1,
+ [1, 2, 3].to_vec(),
+ 1
+ ));
+
+ assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
+ assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 1));
+
+ assert_noop!(TemplateModule::transfer(
+ origin1.clone(),
+ 3,
+ 1,
+ 1,
+ 1
+ ), "Address is not in white list");
+ });
+}
+
+#[test]
+fn white_list_test_4() {
+ new_test_ext().execute_with(|| {
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+
+ let origin1 = Origin::signed(1);
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ));
+ assert_eq!(TemplateModule::collection(1).owner, 1);
+
+ assert_ok!(TemplateModule::create_item(
+ origin1.clone(),
+ 1,
+ [1, 2, 3].to_vec(),
+ 1
+ ));
+
+ assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
+ assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 1));
+ assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2));
+
+ // do approve
+ assert_ok!(TemplateModule::approve(origin1.clone(), 1, 1, 1));
+ assert_eq!(TemplateModule::approved(1, (1, 1)).len(), 1);
+
+ assert_ok!(TemplateModule::remove_from_white_list(origin1.clone(), 1, 2));
+
+ assert_noop!(TemplateModule::transfer_from(
+ origin1.clone(),
+ 1,
+ 3,
+ 1,
+ 1,
+ 1
+ ), "Address is not in white list");
+ });
+}
+
+// If Public Access mode is set to WhiteList, tokens can’t be destroyed by a non-whitelisted address (even if it owned them before enabling WhiteList mode)
+#[test]
+fn white_list_test_5() {
+ new_test_ext().execute_with(|| {
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+
+ let origin1 = Origin::signed(1);
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ));
+ assert_eq!(TemplateModule::collection(1).owner, 1);
+
+ assert_ok!(TemplateModule::create_item(
+ origin1.clone(),
+ 1,
+ [1, 2, 3].to_vec(),
+ 1
+ ));
+
+ assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
+ assert_noop!(TemplateModule::burn_item(origin1.clone(), 1, 1), "Address is not in white list");
+ });
+}
+
+// If Public Access mode is set to WhiteList, oken transfers can’t be Approved by a non-whitelisted address (see Approve method).
+#[test]
+fn white_list_test_6() {
+ new_test_ext().execute_with(|| {
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+
+ let origin1 = Origin::signed(1);
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ));
+ assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
+
+ // do approve
+ assert_noop!(TemplateModule::approve(origin1.clone(), 1, 1, 1), "Address is not in white list");
+ });
+}
+
+// If Public Access mode is set to WhiteList, tokens can be transferred from a whitelisted address with transfer or transferFrom (2 tests) and
+// tokens can be transferred from a whitelisted address with transfer or transferFrom (2 tests)
+#[test]
+fn white_list_test_7() {
+ new_test_ext().execute_with(|| {
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+
+ let origin1 = Origin::signed(1);
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ));
+ assert_eq!(TemplateModule::collection(1).owner, 1);
+
+ assert_ok!(TemplateModule::create_item(
+ origin1.clone(),
+ 1,
+ [1, 2, 3].to_vec(),
+ 1
+ ));
+
+ assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
+ assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 1));
+ assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2));
+
+ assert_ok!(TemplateModule::transfer(
+ origin1.clone(),
+ 2,
+ 1,
+ 1,
+ 1
+ ));
+ });
+}
+
+#[test]
+fn white_list_test_8() {
+ new_test_ext().execute_with(|| {
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+
+ let origin1 = Origin::signed(1);
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ));
+ assert_eq!(TemplateModule::collection(1).owner, 1);
+
+ assert_ok!(TemplateModule::create_item(
+ origin1.clone(),
+ 1,
+ [1, 2, 3].to_vec(),
+ 1
+ ));
+
+ assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
+ assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 1));
+ assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2));
+
+ // do approve
+ assert_ok!(TemplateModule::approve(origin1.clone(), 1, 1, 1));
+ assert_eq!(TemplateModule::approved(1, (1, 1)).len(), 1);
+
+ assert_ok!(TemplateModule::transfer_from(
+ origin1.clone(),
+ 1,
+ 2,
+ 1,
+ 1,
+ 1
+ ));
+ });
+}
+
+// If Public Access mode is set to WhiteList, and Mint Permission is set to false, tokens can be created by owner.
+#[test]
+fn white_list_test_9() {
+ new_test_ext().execute_with(|| {
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+
+ let origin1 = Origin::signed(1);
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ));
+
+ assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
+ assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, false));
+
+ assert_ok!(TemplateModule::create_item(
+ origin1.clone(),
+ 1,
+ [1, 2, 3].to_vec(),
+ 1
+ ));
+ });
+}
+
+// If Public Access mode is set to WhiteList, and Mint Permission is set to false, tokens can be created by admin.
+#[test]
+fn white_list_test_10() {
+ new_test_ext().execute_with(|| {
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+
+ let origin1 = Origin::signed(1);
+ let origin2 = Origin::signed(2);
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ));
+
+ assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
+ assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, false));
+
+ assert_ok!(TemplateModule::add_collection_admin(origin1.clone(), 1, 2));
+
+ assert_ok!(TemplateModule::create_item(
+ origin2.clone(),
+ 1,
+ [1, 2, 3].to_vec(),
+ 2
+ ));
+ });
+}
+
+// If Public Access mode is set to WhiteList, and Mint Permission is set to false, tokens cannot be created by non-privileged and white listed address.
+#[test]
+fn white_list_test_11() {
+ new_test_ext().execute_with(|| {
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+
+ let origin1 = Origin::signed(1);
+ let origin2 = Origin::signed(2);
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ));
+
+ assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
+ assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, false));
+ assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2));
+
+ assert_noop!(TemplateModule::create_item(
+ origin2.clone(),
+ 1,
+ [1, 2, 3].to_vec(),
+ 2
+ ), "Collection is not in mint mode");
+ });
+}
+
+// If Public Access mode is set to WhiteList, and Mint Permission is set to false, tokens cannot be created by non-privileged and non-white listed address.
+#[test]
+fn white_list_test_12() {
+ new_test_ext().execute_with(|| {
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+
+ let origin1 = Origin::signed(1);
+ let origin2 = Origin::signed(2);
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ));
+
+ assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
+ assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, false));
+
+ assert_noop!(TemplateModule::create_item(
+ origin2.clone(),
+ 1,
+ [1, 2, 3].to_vec(),
+ 2
+ ), "Collection is not in mint mode");
+ });
+}
+
+// If Public Access mode is set to WhiteList, and Mint Permission is set to true, tokens can be created by owner.
+#[test]
+fn white_list_test_13() {
+ new_test_ext().execute_with(|| {
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+
+ let origin1 = Origin::signed(1);
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ));
+
+ assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
+ assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, true));
+
+ assert_ok!(TemplateModule::create_item(
+ origin1.clone(),
+ 1,
+ [1, 2, 3].to_vec(),
+ 1
+ ));
+ });
+}
+
+// If Public Access mode is set to WhiteList, and Mint Permission is set to true, tokens can be created by admin.
+#[test]
+fn white_list_test_14() {
+ new_test_ext().execute_with(|| {
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+
+ let origin1 = Origin::signed(1);
+ let origin2 = Origin::signed(2);
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ));
+
+ assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
+ assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, true));
+
+ assert_ok!(TemplateModule::add_collection_admin(origin1.clone(), 1, 2));
+
+ assert_ok!(TemplateModule::create_item(
+ origin2.clone(),
+ 1,
+ [1, 2, 3].to_vec(),
+ 2
+ ));
+ });
+}
+
+// If Public Access mode is set to WhiteList, and Mint Permission is set to true, tokens cannot be created by non-privileged and non-white listed address.
+#[test]
+fn white_list_test_15() {
+ new_test_ext().execute_with(|| {
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+
+ let origin1 = Origin::signed(1);
+ let origin2 = Origin::signed(2);
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ));
+
+ assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
+ assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, true));
+
+ assert_noop!(TemplateModule::create_item(
+ origin2.clone(),
+ 1,
+ [1, 2, 3].to_vec(),
+ 2
+ ), "Address is not in white list");
+ });
+}
+
+// If Public Access mode is set to WhiteList, and Mint Permission is set to true, tokens can be created by non-privileged and white listed address.
+#[test]
+fn white_list_test_16() {
+ new_test_ext().execute_with(|| {
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+
+ let origin1 = Origin::signed(1);
+ let origin2 = Origin::signed(2);
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ));
+
+ assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
+ assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, true));
+ assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2));
+
+ assert_ok!(TemplateModule::create_item(
+ origin2.clone(),
+ 1,
+ [1, 2, 3].to_vec(),
+ 2
+ ));
+ });
+}
+
+// #endregion
\ No newline at end of file
runtime/src/lib.rsdiffbeforeafterboth--- a/runtime/src/lib.rs
+++ b/runtime/src/lib.rs
@@ -53,8 +53,10 @@
pub use timestamp::Call as TimestampCall;
-/// Importing a nft pallet
-pub use nft;
+/// Re-export a nft pallet
+/// TODO: Check this re-export. Is this safe and good style?
+extern crate nft;
+pub use nft::*;
/// An index to a block.
pub type BlockNumber = u32;
@@ -318,7 +320,7 @@
Balances: balances::{Module, Call, Storage, Config<T>, Event<T>},
TransactionPayment: transaction_payment::{Module, Storage},
Sudo: sudo::{Module, Call, Config<T>, Storage, Event<T>},
- Nft: nft::{Module, Call, Storage, Event<T>},
+ Nft: nft::{Module, Call, Config<T>, Storage, Event<T>},
}
);