From f94b085b239704c835d39e387860a62d8f957711 Mon Sep 17 00:00:00 2001 From: str-mv <51784859+str-mv@users.noreply.github.com> Date: Tue, 22 Sep 2020 08:35:03 +0000 Subject: [PATCH] Merge pull request #4 from usetech-llc/release/v1.0.1 Release/v1.0.1 --- --- a/README.md +++ b/README.md @@ -171,6 +171,20 @@ "ReFungible": "(u32, u32)" } }, + "Ownership": { + "Owner": "AccountId", + "Fraction": "u128" + }, + "FungibleItemType": { + "Collection": "u64", + "Owner": "AccountId", + "Value": "u128" + }, + "ReFungibleItemType": { + "Collection": "u64", + "Owner": "Vec", + "Data": "Vec" + }, "NftItemType": { "Collection": "u64", "Owner": "AccountId", --- a/node/src/cli.rs +++ b/node/src/cli.rs @@ -3,9 +3,9 @@ #[derive(Debug, StructOpt)] pub struct Cli { - #[structopt(subcommand)] - pub subcommand: Option, + #[structopt(subcommand)] + pub subcommand: Option, - #[structopt(flatten)] - pub run: RunCmd, + #[structopt(flatten)] + pub run: RunCmd, } --- a/node/src/command.rs +++ b/node/src/command.rs @@ -21,61 +21,57 @@ use sc_cli::SubstrateCli; impl SubstrateCli for Cli { - fn impl_name() -> &'static str { - "Substrate Node" - } + fn impl_name() -> &'static str { + "Substrate Node" + } - fn impl_version() -> &'static str { - env!("SUBSTRATE_CLI_IMPL_VERSION") - } + fn impl_version() -> &'static str { + env!("SUBSTRATE_CLI_IMPL_VERSION") + } - fn description() -> &'static str { - env!("CARGO_PKG_DESCRIPTION") - } + fn description() -> &'static str { + env!("CARGO_PKG_DESCRIPTION") + } - fn author() -> &'static str { - env!("CARGO_PKG_AUTHORS") - } + fn author() -> &'static str { + env!("CARGO_PKG_AUTHORS") + } - fn support_url() -> &'static str { - "support.anonymous.an" - } + fn support_url() -> &'static str { + "support.anonymous.an" + } - fn copyright_start_year() -> i32 { - 2017 - } + fn copyright_start_year() -> i32 { + 2017 + } - fn executable_name() -> &'static str { - env!("CARGO_PKG_NAME") - } + fn executable_name() -> &'static str { + env!("CARGO_PKG_NAME") + } - fn load_spec(&self, id: &str) -> Result, String> { - Ok(match id { - "dev" => Box::new(chain_spec::development_config()), - "" | "local" => Box::new(chain_spec::local_testnet_config()), - path => Box::new(chain_spec::ChainSpec::from_json_file( - std::path::PathBuf::from(path), - )?), - }) - } + fn load_spec(&self, id: &str) -> Result, String> { + Ok(match id { + "dev" => Box::new(chain_spec::development_config()), + "" | "local" => Box::new(chain_spec::local_testnet_config()), + path => Box::new(chain_spec::ChainSpec::from_json_file( + std::path::PathBuf::from(path), + )?), + }) + } } /// Parse and run command line arguments pub fn run() -> sc_cli::Result<()> { - let cli = Cli::from_args(); + let cli = Cli::from_args(); - match &cli.subcommand { - Some(subcommand) => { - let runner = cli.create_runner(subcommand)?; - runner.run_subcommand(subcommand, |config| Ok(new_full_start!(config).0)) - } - None => { - let runner = cli.create_runner(&cli.run)?; - runner.run_node( - service::new_light, - service::new_full, - nft_runtime::VERSION - ) - } - } -} \ No newline at end of file + match &cli.subcommand { + Some(subcommand) => { + let runner = cli.create_runner(subcommand)?; + runner.run_subcommand(subcommand, |config| Ok(new_full_start!(config).0)) + } + None => { + let runner = cli.create_runner(&cli.run)?; + runner.run_node(service::new_light, service::new_full, nft_runtime::VERSION) + } + } +} --- a/node/src/main.rs +++ b/node/src/main.rs @@ -8,5 +8,5 @@ mod command; fn main() -> sc_cli::Result<()> { - command::run() + command::run() } --- a/node/src/service.rs +++ b/node/src/service.rs @@ -188,7 +188,7 @@ prometheus_registry: service.prometheus_registry(), shared_voter_state: SharedVoterState::empty(), }; - + // the GRANDPA voter task is considered infallible, i.e. // if it fails we take down the service with it. service.spawn_essential_task_handle().spawn_blocking( --- a/pallets/nft/src/lib.rs +++ b/pallets/nft/src/lib.rs @@ -2,34 +2,37 @@ /// For more guidance on Substrate FRAME, see the example pallet /// https://github.com/paritytech/substrate/blob/master/frame/example/src/lib.rs - use codec::{Decode, Encode}; pub use frame_support::{ - decl_event, decl_module, decl_storage, - construct_runtime, parameter_types, - traits::{Currency, Get, ExistenceRequirement, KeyOwnerProofSystem, OnUnbalanced, Randomness, WithdrawReason, Imbalance}, + construct_runtime, decl_event, decl_module, decl_storage, + dispatch::DispatchResult, + ensure, parameter_types, + traits::{ + Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced, + Randomness, WithdrawReason, + }, weights::{ - DispatchInfo, PostDispatchInfo, constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND}, - IdentityFee, Weight, WeightToFeePolynomial, GetDispatchInfo, Pays, + constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND}, + DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight, + WeightToFeePolynomial, }, - StorageValue, - dispatch::DispatchResult, - IsSubType, - ensure + IsSubType, StorageValue, }; use frame_system::{self as system, ensure_signed}; use sp_runtime::sp_std::prelude::Vec; -use sp_std::prelude::*; use sp_runtime::{ - FixedU128, FixedPointOperand, - transaction_validity::{ - TransactionPriority, ValidTransaction, InvalidTransaction, TransactionValidityError, TransactionValidity - }, - traits::{ - Saturating, Dispatchable, DispatchInfoOf, PostDispatchInfoOf, SignedExtension, Zero, SaturatedConversion, - }, + traits::{ + DispatchInfoOf, Dispatchable, PostDispatchInfoOf, SaturatedConversion, Saturating, + SignedExtension, Zero, + }, + transaction_validity::{ + InvalidTransaction, TransactionPriority, TransactionValidity, TransactionValidityError, + ValidTransaction, + }, + FixedPointOperand, FixedU128, }; +use sp_std::prelude::*; #[cfg(test)] mod mock; @@ -45,11 +48,11 @@ // decimal points Fungible(u32), // custom data size and decimal points - ReFungible(u32, u32), + ReFungible(u32, u32), } impl Into for CollectionMode { - fn into(self) -> u8{ + fn into(self) -> u8 { match self { CollectionMode::Invalid => 0, CollectionMode::NFT(_) => 1, @@ -62,17 +65,25 @@ #[derive(Encode, Decode, Debug, Clone, PartialEq)] pub enum AccessMode { Normal, - WhiteList, + WhiteList, } -impl Default for AccessMode { fn default() -> Self { Self::Normal } } +impl Default for AccessMode { + fn default() -> Self { + Self::Normal + } +} -impl Default for CollectionMode { fn default() -> Self { Self::Invalid } } +impl Default for CollectionMode { + fn default() -> Self { + Self::Invalid + } +} #[derive(Encode, Decode, Default, Clone, PartialEq)] #[cfg_attr(feature = "std", derive(Debug))] pub struct Ownership { pub owner: AccountId, - pub fraction: u128 + pub fraction: u128, } #[derive(Encode, Decode, Default, Clone, PartialEq)] @@ -87,7 +98,7 @@ pub token_prefix: Vec, // 16 include null escape char pub custom_data_size: u32, pub offchain_schema: Vec, - pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender + pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship } @@ -122,31 +133,56 @@ pub data: Vec, } +#[derive(Encode, Decode, Default, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct ApprovePermissions { + pub approved: AccountId, + pub amount: u64, +} + +#[derive(Encode, Decode, Default, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct VestingItem { + pub sender: AccountId, + pub recipient: AccountId, + pub collection_id: u64, + pub item_id: u64, + pub amount: u64, + pub vesting_date: Moment, +} + pub trait Trait: system::Trait { type Event: From> + Into<::Event>; - } decl_storage! { trait Store for Module as Nft { // Private members + NextCollectionID: u64; CreatedCollectionCount: u64; + ChainVersion: u64; ItemListIndex: map hasher(blake2_128_concat) u64 => u64; pub Collection get(fn collection): map hasher(identity) u64 => CollectionType; pub AdminList get(fn admin_list_collection): map hasher(identity) u64 => Vec; pub WhiteList get(fn white_list): map hasher(identity) u64 => Vec; - // Balance owner per collection map + /// Balance owner per collection map pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => u64; - pub ApprovedList get(fn approved): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => Vec; - // Item collections + /// second parameter: item id + owner account id + pub ApprovedList get(fn approved): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) (u64, T::AccountId) => Vec>; + + /// Item collections pub NftItemList get(fn nft_item_id): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => NftItemType; pub FungibleItemList get(fn fungible_item_id): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => FungibleItemType; pub ReFungibleItemList get(fn refungible_item_id): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => ReFungibleItemType; + // Active vesting list + // pub VestingList get(fn vesting): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => VestingItem; + + /// Index list pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => Vec; // Sponsorship @@ -171,6 +207,18 @@ fn deposit_event() = default; + fn on_initialize(now: T::BlockNumber) -> Weight { + + if ChainVersion::get() == 0 + { + let value = NextCollectionID::get(); + CreatedCollectionCount::put(value); + ChainVersion::put(2); + } + + 0 + } + // Create collection of NFT with given parameters // // @param customDataSz size of custom data in each collection item @@ -197,7 +245,7 @@ }; // check params - ensure!(decimal_points <= 4, "decimal_points parameter must be lower than 4"); + ensure!(decimal_points <= 4, "decimal_points parameter must be lower than 4"); let mut name = collection_name.to_vec(); name.push(0); @@ -248,6 +296,7 @@ let sender = ensure_signed(origin)?; Self::check_owner_permissions(collection_id, sender)?; + // TODO Items remove >::remove_prefix(collection_id); >::remove_prefix(collection_id); >::remove_prefix(collection_id); @@ -351,33 +400,49 @@ Ok(()) } - + #[weight = 0] pub fn create_item(origin, collection_id: u64, properties: Vec, owner: T::AccountId) -> DispatchResult { let sender = ensure_signed(origin)?; - - // check size let target_collection = >::get(collection_id); - ensure!(target_collection.custom_data_size >= properties.len() as u32, "Size of item is too large"); - Self::check_owner_or_admin_permissions(collection_id, sender.clone())?; - // TODO: implement other modes - match target_collection.mode + match target_collection.mode { CollectionMode::NFT(_) => { - // Create nft item + + // check size + ensure!(target_collection.custom_data_size >= properties.len() as u32, "Size of item is too large"); + + // Create nft item let item = NftItemType { collection: collection_id, owner: owner, data: properties.clone(), }; - + Self::add_nft_item(item)?; - + }, + CollectionMode::Fungible(_) => { + + // check size + ensure!(properties.len() as u32 == 0, "Size of item must be 0 with fungible type"); + + let item = FungibleItemType { + collection: collection_id, + owner: owner, + value: (10 as u128).pow(target_collection.decimal_points) + }; + + Self::add_fungible_item(item)?; + }, CollectionMode::ReFungible(_, _) => { + + // check size + ensure!(target_collection.custom_data_size >= properties.len() as u32, "Size of item is too large"); + let mut owner_list = Vec::new(); let value = (10 as u128).pow(target_collection.decimal_points); owner_list.push(Ownership {owner: owner.clone(), fraction: value}); @@ -387,7 +452,7 @@ owner: owner_list, data: properties.clone() }; - + Self::add_refungible_item(item)?; }, _ => { ensure!(1 == 0,"just error"); } @@ -411,9 +476,10 @@ } let target_collection = >::get(collection_id); - match target_collection.mode + match target_collection.mode { CollectionMode::NFT(_) => Self::burn_nft_item(collection_id, item_id)?, + CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?, CollectionMode::ReFungible(_, _) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?, _ => () }; @@ -433,9 +499,10 @@ let target_collection = >::get(collection_id); // TODO: implement other modes - match target_collection.mode + match target_collection.mode { CollectionMode::NFT(_) => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?, + CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?, CollectionMode::ReFungible(_, _) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?, _ => () }; @@ -448,26 +515,26 @@ let sender = ensure_signed(origin)?; - let item_owner = Self::is_item_owner(sender.clone(), collection_id, item_id); - if !item_owner - { - Self::check_owner_or_admin_permissions(collection_id, sender.clone())?; - } + // amount param stub + let amount = 100000000; + + ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id), "Only item owner can call transfer method"); - let list_exists = >::contains_key(collection_id, item_id); + let list_exists = >::contains_key(collection_id, (item_id, sender.clone())); if list_exists { - let mut list = >::get(collection_id, item_id); - let item_contains = list.contains(&approved.clone()); + let mut list = >::get(collection_id, (item_id, sender.clone())); + let item_contains = list.iter().any(|i| i.approved == approved); if !item_contains { - list.push(approved.clone()); + list.push(ApprovePermissions { approved: approved.clone(), amount: amount }); + >::insert(collection_id, (item_id, sender.clone()), list); } } else { - let mut itm = Vec::new(); - itm.push(approved.clone()); - >::insert(collection_id, item_id, itm); + let mut list = Vec::new(); + list.push(ApprovePermissions { approved: approved.clone(), amount: amount }); + >::insert(collection_id, (item_id, sender.clone()), list); } Ok(()) @@ -476,27 +543,32 @@ #[weight = 0] pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64 ) -> DispatchResult { - let mut approved: bool = false; let sender = ensure_signed(origin)?; - let approved_list_exists = >::contains_key(collection_id, item_id); + let approved_list_exists = >::contains_key(collection_id, (item_id, from.clone())); if approved_list_exists { - let list_itm = >::get(collection_id, item_id); - approved = list_itm.contains(&recipient.clone()); + let list_itm = >::get(collection_id, (item_id, from.clone())); + let opt_item = list_itm.iter().find(|i| i.approved == sender.clone()); + ensure!(opt_item.is_some(), "No approve found"); + ensure!(opt_item.unwrap().amount >= value, "Requested value more than approved"); + + // remove approve + let approve_list: Vec> = >::get(collection_id, (item_id, from.clone())) + .into_iter().filter(|i| i.approved != sender.clone()).collect(); + >::insert(collection_id, (item_id, from.clone()), approve_list); } - - if !approved + else { Self::check_owner_or_admin_permissions(collection_id, sender)?; } - + let target_collection = >::get(collection_id); match target_collection.mode { CollectionMode::NFT(_) => Self::transfer_nft(collection_id, item_id, from, recipient)?, - CollectionMode::ReFungible(_, _) => Self::transfer_refungible(collection_id, item_id, value, from, recipient)?, - // TODO: implement other modes + CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?, + CollectionMode::ReFungible(_, _) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?, _ => () }; @@ -526,35 +598,182 @@ ) -> DispatchResult { let sender = ensure_signed(origin)?; Self::check_owner_or_admin_permissions(collection_id, sender.clone())?; - + let mut target_collection = >::get(collection_id); target_collection.offchain_schema = schema; >::insert(collection_id, target_collection); - Ok(()) + Ok(()) } } } impl Module { + fn add_fungible_item(item: FungibleItemType) -> DispatchResult { + let current_index = ::get(item.collection) + .checked_add(1) + .expect("Item list index id error"); + let itemcopy = item.clone(); + let owner = item.owner.clone(); + let value = item.value as u64; - fn collection_exists(collection_id: u64) -> DispatchResult{ - ensure!(>::contains_key(collection_id), "This collection does not exist"); + Self::add_token_index(item.collection, current_index, owner.clone())?; + + ::insert(item.collection, current_index); + >::insert(item.collection, current_index, itemcopy); + + // Update balance + let new_balance = >::get(item.collection, owner.clone()) + .checked_add(value) + .unwrap(); + >::insert(item.collection, owner.clone(), new_balance); + Ok(()) } - fn check_owner_permissions(collection_id: u64, subject: T::AccountId) -> DispatchResult { + fn add_refungible_item(item: ReFungibleItemType) -> DispatchResult { + let current_index = ::get(item.collection) + .checked_add(1) + .expect("Item list index id error"); + let itemcopy = item.clone(); + + let value = item.owner.first().unwrap().fraction as u64; + let owner = item.owner.first().unwrap().owner.clone(); + + Self::add_token_index(item.collection, current_index, owner.clone())?; + + ::insert(item.collection, current_index); + >::insert(item.collection, current_index, itemcopy); + + // Update balance + let new_balance = >::get(item.collection, owner.clone()) + .checked_add(value) + .unwrap(); + >::insert(item.collection, owner.clone(), new_balance); + Ok(()) + } + + fn add_nft_item(item: NftItemType) -> DispatchResult { + let current_index = ::get(item.collection) + .checked_add(1) + .expect("Item list index id error"); + + let item_owner = item.owner.clone(); + let collection_id = item.collection.clone(); + Self::add_token_index(collection_id, current_index, item.owner.clone())?; + + ::insert(collection_id, current_index); + >::insert(collection_id, current_index, item); + + // Update balance + let new_balance = >::get(collection_id, item_owner.clone()) + .checked_add(1) + .unwrap(); + >::insert(collection_id, item_owner.clone(), new_balance); + + Ok(()) + } + + fn burn_refungible_item( + collection_id: u64, + item_id: u64, + owner: T::AccountId, + ) -> DispatchResult { + ensure!( + >::contains_key(collection_id, item_id), + "Item does not exists" + ); + let collection = >::get(collection_id, item_id); + let item = collection + .owner + .iter() + .filter(|&i| i.owner == owner) + .next() + .unwrap(); + Self::remove_token_index(collection_id, item_id, owner.clone())?; + + // remove approve list + >::remove(collection_id, (item_id, owner.clone())); + + // update balance + let new_balance = >::get(collection_id, item.owner.clone()) + .checked_sub(item.fraction as u64) + .unwrap(); + >::insert(collection_id, item.owner.clone(), new_balance); + + >::remove(collection_id, item_id); + + Ok(()) + } + + fn burn_nft_item(collection_id: u64, item_id: u64) -> DispatchResult { + ensure!( + >::contains_key(collection_id, item_id), + "Item does not exists" + ); + let item = >::get(collection_id, item_id); + Self::remove_token_index(collection_id, item_id, item.owner.clone())?; + + // remove approve list + >::remove(collection_id, (item_id, item.owner.clone())); + + // update balance + let new_balance = >::get(collection_id, item.owner.clone()) + .checked_sub(1) + .unwrap(); + >::insert(collection_id, item.owner.clone(), new_balance); + >::remove(collection_id, item_id); + + Ok(()) + } + + fn burn_fungible_item(collection_id: u64, item_id: u64) -> DispatchResult { + ensure!( + >::contains_key(collection_id, item_id), + "Item does not exists" + ); + let item = >::get(collection_id, item_id); + Self::remove_token_index(collection_id, item_id, item.owner.clone())?; + + // remove approve list + >::remove(collection_id, (item_id, item.owner.clone())); + + // update balance + let new_balance = >::get(collection_id, item.owner.clone()) + .checked_sub(item.value as u64) + .unwrap(); + >::insert(collection_id, item.owner.clone(), new_balance); + + >::remove(collection_id, item_id); + + Ok(()) + } + + fn collection_exists(collection_id: u64) -> DispatchResult { + ensure!( + >::contains_key(collection_id), + "This collection does not exist" + ); + Ok(()) + } + + fn check_owner_permissions(collection_id: u64, subject: T::AccountId) -> DispatchResult { Self::collection_exists(collection_id)?; let target_collection = >::get(collection_id); - ensure!(subject == target_collection.owner, "You do not own this collection"); + ensure!( + subject == target_collection.owner, + "You do not own this collection" + ); Ok(()) } - fn check_owner_or_admin_permissions(collection_id: u64, subject: T::AccountId) -> DispatchResult { - + fn check_owner_or_admin_permissions( + collection_id: u64, + subject: T::AccountId, + ) -> DispatchResult { Self::collection_exists(collection_id)?; let target_collection = >::get(collection_id); @@ -563,77 +782,147 @@ let no_perm_mes = "You do not have permissions to modify this collection"; let exists = >::contains_key(collection_id); - if !is_owner - { + if !is_owner { ensure!(exists, no_perm_mes); - ensure!(>::get(collection_id).contains(&subject), no_perm_mes); + ensure!( + >::get(collection_id).contains(&subject), + no_perm_mes + ); } Ok(()) } - fn is_item_owner(subject: T::AccountId, collection_id: u64, item_id: u64) -> bool{ - + fn is_item_owner(subject: T::AccountId, collection_id: u64, item_id: u64) -> bool { let target_collection = >::get(collection_id); match target_collection.mode { - CollectionMode::NFT(_) => >::get(collection_id, item_id).owner == subject, - CollectionMode::Fungible(_) => >::get(collection_id, item_id).owner == subject, - CollectionMode::ReFungible(_, _) => >::get(collection_id, item_id).owner.iter().any(|i| i.owner == subject), - CollectionMode::Invalid => false + CollectionMode::NFT(_) => { + >::get(collection_id, item_id).owner == subject + } + CollectionMode::Fungible(_) => { + >::get(collection_id, item_id).owner == subject + } + CollectionMode::ReFungible(_, _) => { + >::get(collection_id, item_id) + .owner + .iter() + .any(|i| i.owner == subject) + } + CollectionMode::Invalid => false, } } - fn add_refungible_item(item: ReFungibleItemType) -> DispatchResult { + fn transfer_fungible( + collection_id: u64, + item_id: u64, + value: u64, + owner: T::AccountId, + new_owner: T::AccountId, + ) -> DispatchResult { + let full_item = >::get(collection_id, item_id); + let amount = full_item.value; + + ensure!(amount >= value.into(), "Item balance not enouth"); + + // update balance + let balance_old_owner = >::get(collection_id, owner.clone()) + .checked_sub(value) + .unwrap(); + >::insert(collection_id, owner.clone(), balance_old_owner); + + let mut new_owner_account_id = 0; + let new_owner_items = >::get(collection_id, new_owner.clone()); + if new_owner_items.len() > 0 { + new_owner_account_id = new_owner_items[0]; + } + + let val64 = value.into(); - let current_index = ::get(item.collection) - .checked_add(1) - .expect("Item list index id error"); - let itemcopy = item.clone(); + // transfer + if amount == val64 && new_owner_account_id == 0 { + // change owner + // new owner do not have account + let mut new_full_item = full_item.clone(); + new_full_item.owner = new_owner.clone(); + >::insert(collection_id, item_id, new_full_item); + + // update balance + let balance_new_owner = >::get(collection_id, new_owner.clone()) + .checked_add(value) + .unwrap(); + >::insert(collection_id, new_owner.clone(), balance_new_owner); + + // update index collection + Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?; + } else { + let mut new_full_item = full_item.clone(); + new_full_item.value -= val64; - let value = item.owner.first().unwrap().fraction as u64; - let owner = item.owner.first().unwrap().owner.clone(); + // separate amount + if new_owner_account_id > 0 { + // new owner has account + let mut item = >::get(collection_id, new_owner_account_id); + item.value += val64; - Self::add_token_index(item.collection, current_index, owner.clone())?; + // update balance + let balance_new_owner = >::get(collection_id, new_owner.clone()) + .checked_add(value) + .unwrap(); + >::insert(collection_id, new_owner.clone(), balance_new_owner); - ::insert(item.collection, current_index); - >::insert(item.collection, current_index, itemcopy); - - // Update balance - let new_balance = >::get(item.collection, owner.clone()).checked_add(value).unwrap(); - >::insert(item.collection, owner.clone(), new_balance); + >::insert(collection_id, new_owner_account_id, item); + } else { + // new owner do not have account + let item = FungibleItemType { + collection: collection_id, + owner: new_owner.clone(), + value: val64, + }; - Ok(()) - } + Self::add_fungible_item(item)?; + } - fn burn_refungible_item(collection_id: u64, item_id: u64, owner: T::AccountId) -> DispatchResult { - - let collection = >::get(collection_id, item_id); - let item = collection.owner.iter().filter(|&i| i.owner == owner).next().unwrap(); - Self::remove_token_index(collection_id, item_id, owner)?; + if amount == val64 { + Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?; - // update balance - let new_balance = >::get(collection_id, item.owner.clone()).checked_sub(item.fraction as u64).unwrap(); - >::insert(collection_id, item.owner.clone(), new_balance); + // remove approve list + >::remove(collection_id, (item_id, full_item.owner.clone())); + >::remove(collection_id, item_id); + } - // TODO - >::remove(collection_id, item_id); + >::insert(collection_id, item_id, new_full_item); + } Ok(()) } - fn transfer_refungible(collection_id: u64, item_id: u64, value: u64, owner: T::AccountId, new_owner: T::AccountId) -> DispatchResult { - + fn transfer_refungible( + collection_id: u64, + item_id: u64, + value: u64, + owner: T::AccountId, + new_owner: T::AccountId, + ) -> DispatchResult { let full_item = >::get(collection_id, item_id); - let item = full_item.owner.iter().filter(|i| i.owner == owner).next().unwrap(); + let item = full_item + .owner + .iter() + .filter(|i| i.owner == owner) + .next() + .unwrap(); let amount = item.fraction; - ensure!(amount >= value.into(),"Item balance not enouth"); + ensure!(amount >= value.into(), "Item balance not enouth"); // update balance - let balance_old_owner = >::get(collection_id, item.owner.clone()).checked_sub(value).unwrap(); + let balance_old_owner = >::get(collection_id, item.owner.clone()) + .checked_sub(value) + .unwrap(); >::insert(collection_id, item.owner.clone(), balance_old_owner); - let balance_new_owner = >::get(collection_id, new_owner.clone()).checked_add(value).unwrap(); + let balance_new_owner = >::get(collection_id, new_owner.clone()) + .checked_add(value) + .unwrap(); >::insert(collection_id, new_owner.clone(), balance_new_owner); let old_owner = item.owner.clone(); @@ -641,83 +930,75 @@ let val64 = value.into(); // transfer - if amount == val64 && !new_owner_has_account - { + if amount == val64 && !new_owner_has_account { // change owner // new owner do not have account let mut new_full_item = full_item.clone(); - new_full_item.owner.iter_mut().find(|i| i.owner == owner).unwrap().owner = new_owner.clone(); + new_full_item + .owner + .iter_mut() + .find(|i| i.owner == owner) + .unwrap() + .owner = new_owner.clone(); >::insert(collection_id, item_id, new_full_item); // update index collection Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?; - } - else - { + } else { let mut new_full_item = full_item.clone(); - new_full_item.owner.iter_mut().find(|i| i.owner == owner).unwrap().fraction -= val64; + new_full_item + .owner + .iter_mut() + .find(|i| i.owner == owner) + .unwrap() + .fraction -= val64; // separate amount if new_owner_has_account { // new owner has account - new_full_item.owner.iter_mut().find(|i| i.owner == new_owner).unwrap().fraction += val64; - } - else - { + new_full_item + .owner + .iter_mut() + .find(|i| i.owner == new_owner) + .unwrap() + .fraction += val64; + } else { // new owner do not have account - new_full_item.owner.push(Ownership { owner: new_owner.clone(), fraction: val64}); + new_full_item.owner.push(Ownership { + owner: new_owner.clone(), + fraction: val64, + }); Self::add_token_index(collection_id, item_id, new_owner.clone())?; } >::insert(collection_id, item_id, new_full_item); } - - Ok(()) - } - - fn add_nft_item(item: NftItemType) -> DispatchResult { - - let current_index = ::get(item.collection) - .checked_add(1) - .expect("Item list index id error"); - let itemcopy = item.clone(); - - Self::add_token_index(item.collection, current_index, item.owner.clone())?; - - ::insert(item.collection, current_index); - >::insert(item.collection, current_index, item); - // Update balance - let new_balance = >::get(itemcopy.collection, itemcopy.owner.clone()).checked_add(1).unwrap(); - >::insert(itemcopy.collection, itemcopy.owner.clone(), new_balance); - Ok(()) } - fn burn_nft_item(collection_id: u64, item_id: u64) -> DispatchResult { - - let item = >::get(collection_id, item_id); - Self::remove_token_index(collection_id, item_id, item.owner.clone())?; - - // update balance - let new_balance = >::get(collection_id, item.owner.clone()).checked_sub(1).unwrap(); - >::insert(collection_id, item.owner.clone(), new_balance); - >::remove(collection_id, item_id); - - Ok(()) - } - - fn transfer_nft(collection_id: u64, item_id: u64, sender: T::AccountId, new_owner: T::AccountId) -> DispatchResult { - + fn transfer_nft( + collection_id: u64, + item_id: u64, + sender: T::AccountId, + new_owner: T::AccountId, + ) -> DispatchResult { let mut item = >::get(collection_id, item_id); - ensure!(sender == item.owner,"sender parameter and item owner must be equal"); + ensure!( + sender == item.owner, + "sender parameter and item owner must be equal" + ); // update balance - let balance_old_owner = >::get(collection_id, item.owner.clone()).checked_sub(1).unwrap(); + let balance_old_owner = >::get(collection_id, item.owner.clone()) + .checked_sub(1) + .unwrap(); >::insert(collection_id, item.owner.clone(), balance_old_owner); - let balance_new_owner = >::get(collection_id, new_owner.clone()).checked_add(1).unwrap(); + let balance_new_owner = >::get(collection_id, new_owner.clone()) + .checked_add(1) + .unwrap(); >::insert(collection_id, new_owner.clone(), balance_new_owner); // change owner @@ -726,12 +1007,10 @@ >::insert(collection_id, item_id, item); // update index collection - Self::move_token_index(collection_id, item_id, old_owner, new_owner.clone())?; + Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?; // reset approved list - let itm: Vec = Vec::new(); - >::insert(collection_id, item_id, itm); - + >::remove(collection_id, (item_id, old_owner)); Ok(()) } @@ -786,7 +1065,6 @@ Ok(()) } } - //////////////////////////////////////////////////////////////////////////////////////////////////// // Economic models @@ -794,65 +1072,70 @@ /// Fee multiplier. pub type Multiplier = FixedU128; -type BalanceOf = - <::Currency as Currency<::AccountId>>::Balance; +type BalanceOf = <::Currency as Currency< + ::AccountId, +>>::Balance; type NegativeImbalanceOf = <::Currency as Currency< - ::AccountId,>>::NegativeImbalance; - - + ::AccountId, +>>::NegativeImbalance; /// Require the transactor pay for themselves and maybe include a tip to gain additional priority /// in the queue. #[derive(Encode, Decode, Clone, Eq, PartialEq)] -pub struct ChargeTransactionPayment(#[codec(compact)] BalanceOf); +pub struct ChargeTransactionPayment( + #[codec(compact)] BalanceOf, +); -impl sp_std::fmt::Debug for ChargeTransactionPayment { - #[cfg(feature = "std")] - fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { - write!(f, "ChargeTransactionPayment<{:?}>", self.0) - } - #[cfg(not(feature = "std"))] - fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { - Ok(()) - } +impl sp_std::fmt::Debug + for ChargeTransactionPayment +{ + #[cfg(feature = "std")] + fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + write!(f, "ChargeTransactionPayment<{:?}>", self.0) + } + #[cfg(not(feature = "std"))] + fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + Ok(()) + } } -impl ChargeTransactionPayment where - T::Call: Dispatchable + IsSubType, T>, - BalanceOf: Send + Sync + FixedPointOperand, +impl ChargeTransactionPayment +where + T::Call: + Dispatchable + IsSubType, T>, + BalanceOf: Send + Sync + FixedPointOperand, { - /// utility constructor. Used only in client/factory code. - pub fn from(fee: BalanceOf) -> Self { - Self(fee) - } + /// utility constructor. Used only in client/factory code. + pub fn from(fee: BalanceOf) -> Self { + Self(fee) + } pub fn traditional_fee( len: usize, info: &DispatchInfoOf, tip: BalanceOf, - ) -> BalanceOf where - T::Call: Dispatchable, + ) -> BalanceOf + where + T::Call: Dispatchable, { >::compute_fee(len as u32, info, tip) } - fn withdraw_fee( - &self, + fn withdraw_fee( + &self, who: &T::AccountId, call: &T::Call, - info: &DispatchInfoOf, - len: usize, - ) -> Result<(BalanceOf, Option>), TransactionValidityError> { + info: &DispatchInfoOf, + len: usize, + ) -> Result<(BalanceOf, Option>), TransactionValidityError> { let tip = self.0; // Set fee based on call type. Creating collection costs 1 Unique. // All other transactions have traditional fees so far let fee = match call.is_sub_type() { Some(Call::create_collection(..)) => >::from(1_000_000_000), - _ => Self::traditional_fee(len, info, tip) - - // Flat fee model, use only for testing purposes - // _ => >::from(100) + _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes + // _ => >::from(100) }; // Determine who is paying transaction fee based on ecnomic model @@ -860,12 +1143,12 @@ let sponsor: T::AccountId = match call.is_sub_type() { Some(Call::create_item(collection_id, _properties, _owner)) => { >::get(collection_id).sponsor - }, + } Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => { >::get(collection_id).sponsor - }, + } - _ => T::AccountId::default() + _ => T::AccountId::default(), }; let mut who_pays_fee: T::AccountId = sponsor.clone(); @@ -873,98 +1156,109 @@ who_pays_fee = who.clone(); } - // Only mess with balances if fee is not zero. - if fee.is_zero() { - return Ok((fee, None)); - } + // Only mess with balances if fee is not zero. + if fee.is_zero() { + return Ok((fee, None)); + } - match ::Currency::withdraw( - &who_pays_fee, - fee, - if tip.is_zero() { - WithdrawReason::TransactionPayment.into() - } else { - WithdrawReason::TransactionPayment | WithdrawReason::Tip - }, - ExistenceRequirement::KeepAlive, - ) { - Ok(imbalance) => Ok((fee, Some(imbalance))), - Err(_) => Err(InvalidTransaction::Payment.into()), - } - } + match ::Currency::withdraw( + &who_pays_fee, + fee, + if tip.is_zero() { + WithdrawReason::TransactionPayment.into() + } else { + WithdrawReason::TransactionPayment | WithdrawReason::Tip + }, + ExistenceRequirement::KeepAlive, + ) { + Ok(imbalance) => Ok((fee, Some(imbalance))), + Err(_) => Err(InvalidTransaction::Payment.into()), + } + } } -impl SignedExtension for ChargeTransactionPayment where +impl SignedExtension + for ChargeTransactionPayment +where BalanceOf: Send + Sync + From + FixedPointOperand, - T::Call: Dispatchable + IsSubType, T>, + T::Call: + Dispatchable + IsSubType, T>, { - const IDENTIFIER: &'static str = "ChargeTransactionPayment"; - type AccountId = T::AccountId; - type Call = T::Call; - type AdditionalSigned = (); - type Pre = (BalanceOf, Self::AccountId, Option>, BalanceOf); - fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> { Ok(()) } + const IDENTIFIER: &'static str = "ChargeTransactionPayment"; + type AccountId = T::AccountId; + type Call = T::Call; + type AdditionalSigned = (); + type Pre = ( + BalanceOf, + Self::AccountId, + Option>, + BalanceOf, + ); + fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> { + Ok(()) + } - fn validate( - &self, - who: &Self::AccountId, - call: &Self::Call, - info: &DispatchInfoOf, - len: usize, - ) -> TransactionValidity { - let (fee, _) = self.withdraw_fee(who, call, info, len)?; + fn validate( + &self, + who: &Self::AccountId, + call: &Self::Call, + info: &DispatchInfoOf, + len: usize, + ) -> TransactionValidity { + let (fee, _) = self.withdraw_fee(who, call, info, len)?; - let mut r = ValidTransaction::default(); - // NOTE: we probably want to maximize the _fee (of any type) per weight unit_ here, which - // will be a bit more than setting the priority to tip. For now, this is enough. - r.priority = fee.saturated_into::(); - Ok(r) - } + let mut r = ValidTransaction::default(); + // NOTE: we probably want to maximize the _fee (of any type) per weight unit_ here, which + // will be a bit more than setting the priority to tip. For now, this is enough. + r.priority = fee.saturated_into::(); + Ok(r) + } - fn pre_dispatch( - self, - who: &Self::AccountId, - call: &Self::Call, - info: &DispatchInfoOf, - len: usize - ) -> Result { - let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?; - Ok((self.0, who.clone(), imbalance, fee)) - } + fn pre_dispatch( + self, + who: &Self::AccountId, + call: &Self::Call, + info: &DispatchInfoOf, + len: usize, + ) -> Result { + let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?; + Ok((self.0, who.clone(), imbalance, fee)) + } - fn post_dispatch( - pre: Self::Pre, - info: &DispatchInfoOf, - post_info: &PostDispatchInfoOf, - len: usize, - _result: &DispatchResult, - ) -> Result<(), TransactionValidityError> { - let (tip, who, imbalance, fee) = pre; - if let Some(payed) = imbalance { - let actual_fee = >::compute_actual_fee( - len as u32, - info, - post_info, - tip, - ); - let refund = fee.saturating_sub(actual_fee); - let actual_payment = match ::Currency::deposit_into_existing(&who, refund) { - Ok(refund_imbalance) => { - // The refund cannot be larger than the up front payed max weight. - // `PostDispatchInfo::calc_unspent` guards against such a case. - match payed.offset(refund_imbalance) { - Ok(actual_payment) => actual_payment, - Err(_) => return Err(InvalidTransaction::Payment.into()), - } - } - // We do not recreate the account using the refund. The up front payment - // is gone in that case. - Err(_) => payed, - }; - let imbalances = actual_payment.split(tip); - ::OnTransactionPayment::on_unbalanceds(Some(imbalances.0).into_iter() - .chain(Some(imbalances.1))); - } - Ok(()) - } + fn post_dispatch( + pre: Self::Pre, + info: &DispatchInfoOf, + post_info: &PostDispatchInfoOf, + len: usize, + _result: &DispatchResult, + ) -> Result<(), TransactionValidityError> { + let (tip, who, imbalance, fee) = pre; + if let Some(payed) = imbalance { + let actual_fee = >::compute_actual_fee( + len as u32, info, post_info, tip, + ); + let refund = fee.saturating_sub(actual_fee); + let actual_payment = + match ::Currency::deposit_into_existing( + &who, refund, + ) { + Ok(refund_imbalance) => { + // The refund cannot be larger than the up front payed max weight. + // `PostDispatchInfo::calc_unspent` guards against such a case. + match payed.offset(refund_imbalance) { + Ok(actual_payment) => actual_payment, + Err(_) => return Err(InvalidTransaction::Payment.into()), + } + } + // We do not recreate the account using the refund. The up front payment + // is gone in that case. + Err(_) => payed, + }; + let imbalances = actual_payment.split(tip); + ::OnTransactionPayment::on_unbalanceds( + Some(imbalances.0).into_iter().chain(Some(imbalances.1)), + ); + } + Ok(()) + } } --- a/pallets/nft/src/mock.rs +++ b/pallets/nft/src/mock.rs @@ -1,19 +1,19 @@ // Creating mock runtime here use crate::{Module, Trait}; +use frame_support::{ + impl_outer_origin, parameter_types, + weights::{ + constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}, + Weight, + }, +}; use frame_system as system; use sp_core::H256; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup, Saturating}, Perbill, -}; -use frame_support::{ - parameter_types, impl_outer_origin, - weights::{ - constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}, - Weight, - }, }; impl_outer_origin! { @@ -49,9 +49,9 @@ type MaximumBlockWeight = MaximumBlockWeight; type MaximumBlockLength = MaximumBlockLength; type AvailableBlockRatio = AvailableBlockRatio; - type BaseCallFilter = (); - type DbWeight = RocksDbWeight; - type BlockExecutionWeight = BlockExecutionWeight; + type BaseCallFilter = (); + type DbWeight = RocksDbWeight; + type BlockExecutionWeight = BlockExecutionWeight; type ExtrinsicBaseWeight = ExtrinsicBaseWeight; type MaximumExtrinsicWeight = MaximumExtrinsicWeight; type Version = (); --- a/pallets/nft/src/tests.rs +++ b/pallets/nft/src/tests.rs @@ -1,6 +1,6 @@ // Tests to be written here use crate::mock::*; -use crate::{CollectionMode, Ownership}; +use crate::{ApprovePermissions, CollectionMode, Ownership}; use frame_support::{assert_noop, assert_ok}; #[test] @@ -21,9 +21,13 @@ )); assert_eq!(TemplateModule::collection(1).owner, 1); - - assert_ok!(TemplateModule::create_item(origin1.clone(), 1, [1,2,3].to_vec(), 1)); - assert_eq!(TemplateModule::nft_item_id(1,1).data, [1,2,3].to_vec()); + assert_ok!(TemplateModule::create_item( + origin1.clone(), + 1, + [1, 2, 3].to_vec(), + 1 + )); + assert_eq!(TemplateModule::nft_item_id(1, 1).data, [1, 2, 3].to_vec()); }); } @@ -45,10 +49,111 @@ )); assert_eq!(TemplateModule::collection(1).owner, 1); + assert_ok!(TemplateModule::create_item( + origin1.clone(), + 1, + [1, 2, 3].to_vec(), + 1 + )); + assert_eq!( + TemplateModule::refungible_item_id(1, 1).data, + [1, 2, 3].to_vec() + ); + assert_eq!( + TemplateModule::refungible_item_id(1, 1).owner[0], + Ownership { + owner: 1, + fraction: 1000 + } + ); + }); +} + +#[test] +fn create_fungible_item() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + let mode: CollectionMode = CollectionMode::Fungible(3); + + 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, + [].to_vec(), + 1 + )); + assert_eq!(TemplateModule::fungible_item_id(1, 1).owner, 1); + assert_eq!(TemplateModule::balance_count(1, 1), 1000); + assert_eq!(TemplateModule::address_tokens(1, 1), [1]); + }); +} + +#[test] +fn transfer_fungible_item() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + let mode: CollectionMode = CollectionMode::Fungible(3); + + 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_eq!(TemplateModule::collection(1).owner, 1); + + assert_ok!(TemplateModule::create_item( + origin1.clone(), + 1, + [].to_vec(), + 1 + )); + assert_eq!(TemplateModule::fungible_item_id(1, 1).owner, 1); + assert_eq!(TemplateModule::balance_count(1, 1), 1000); + assert_eq!(TemplateModule::address_tokens(1, 1), [1]); + + // change owner scenario + assert_ok!(TemplateModule::transfer(origin1.clone(), 2, 1, 1, 1000)); + assert_eq!(TemplateModule::fungible_item_id(1, 1).owner, 2); + assert_eq!(TemplateModule::fungible_item_id(1, 1).value, 1000); + assert_eq!(TemplateModule::balance_count(1, 1), 0); + assert_eq!(TemplateModule::balance_count(1, 2), 1000); + assert_eq!(TemplateModule::address_tokens(1, 1), []); + assert_eq!(TemplateModule::address_tokens(1, 2), [1]); - assert_ok!(TemplateModule::create_item(origin1.clone(), 1, [1,2,3].to_vec(), 1)); - assert_eq!(TemplateModule::refungible_item_id(1,1).data, [1,2,3].to_vec()); - assert_eq!(TemplateModule::refungible_item_id(1,1).owner[0], Ownership { owner: 1, fraction: 1000 }); + // split item scenario + assert_ok!(TemplateModule::transfer(origin2.clone(), 3, 1, 1, 500)); + assert_eq!(TemplateModule::fungible_item_id(1, 1).owner, 2); + assert_eq!(TemplateModule::fungible_item_id(1, 2).owner, 3); + assert_eq!(TemplateModule::balance_count(1, 2), 500); + assert_eq!(TemplateModule::balance_count(1, 3), 500); + assert_eq!(TemplateModule::address_tokens(1, 2), [1]); + assert_eq!(TemplateModule::address_tokens(1, 3), [2]); + + // split item and new owner has account scenario + assert_ok!(TemplateModule::transfer(origin2.clone(), 3, 1, 1, 200)); + assert_eq!(TemplateModule::fungible_item_id(1, 1).value, 300); + assert_eq!(TemplateModule::fungible_item_id(1, 2).value, 700); + assert_eq!(TemplateModule::balance_count(1, 2), 300); + assert_eq!(TemplateModule::balance_count(1, 3), 700); + assert_eq!(TemplateModule::address_tokens(1, 2), [1]); + assert_eq!(TemplateModule::address_tokens(1, 3), [2]); }); } @@ -71,40 +176,83 @@ )); assert_eq!(TemplateModule::collection(1).owner, 1); - assert_ok!(TemplateModule::create_item(origin1.clone(), 1, [1,2,3].to_vec(), 1)); - assert_eq!(TemplateModule::refungible_item_id(1,1).data, [1,2,3].to_vec()); - assert_eq!(TemplateModule::refungible_item_id(1,1).owner[0], Ownership { owner: 1, fraction: 1000 }); - assert_eq!(TemplateModule::balance_count(1,1), 1000); - assert_eq!(TemplateModule::address_tokens(1,1), [1]); + assert_ok!(TemplateModule::create_item( + origin1.clone(), + 1, + [1, 2, 3].to_vec(), + 1 + )); + assert_eq!( + TemplateModule::refungible_item_id(1, 1).data, + [1, 2, 3].to_vec() + ); + assert_eq!( + TemplateModule::refungible_item_id(1, 1).owner[0], + Ownership { + owner: 1, + fraction: 1000 + } + ); + assert_eq!(TemplateModule::balance_count(1, 1), 1000); + assert_eq!(TemplateModule::address_tokens(1, 1), [1]); // change owner scenario assert_ok!(TemplateModule::transfer(origin1.clone(), 2, 1, 1, 1000)); - assert_eq!(TemplateModule::refungible_item_id(1,1).owner[0], Ownership { owner: 2, fraction: 1000 }); - assert_eq!(TemplateModule::balance_count(1,1), 0); - assert_eq!(TemplateModule::balance_count(1,2), 1000); - assert_eq!(TemplateModule::address_tokens(1,1), []); - assert_eq!(TemplateModule::address_tokens(1,2), [1]); + assert_eq!( + TemplateModule::refungible_item_id(1, 1).owner[0], + Ownership { + owner: 2, + fraction: 1000 + } + ); + assert_eq!(TemplateModule::balance_count(1, 1), 0); + assert_eq!(TemplateModule::balance_count(1, 2), 1000); + assert_eq!(TemplateModule::address_tokens(1, 1), []); + assert_eq!(TemplateModule::address_tokens(1, 2), [1]); // split item scenario assert_ok!(TemplateModule::transfer(origin2.clone(), 3, 1, 1, 500)); - assert_eq!(TemplateModule::refungible_item_id(1,1).owner[0], Ownership { owner: 2, fraction: 500 }); - assert_eq!(TemplateModule::refungible_item_id(1,1).owner[1], Ownership { owner: 3, fraction: 500 }); - assert_eq!(TemplateModule::balance_count(1,2), 500); - assert_eq!(TemplateModule::balance_count(1,3), 500); - assert_eq!(TemplateModule::address_tokens(1,2), [1]); - assert_eq!(TemplateModule::address_tokens(1,3), [1]); + assert_eq!( + TemplateModule::refungible_item_id(1, 1).owner[0], + Ownership { + owner: 2, + fraction: 500 + } + ); + assert_eq!( + TemplateModule::refungible_item_id(1, 1).owner[1], + Ownership { + owner: 3, + fraction: 500 + } + ); + assert_eq!(TemplateModule::balance_count(1, 2), 500); + assert_eq!(TemplateModule::balance_count(1, 3), 500); + assert_eq!(TemplateModule::address_tokens(1, 2), [1]); + assert_eq!(TemplateModule::address_tokens(1, 3), [1]); // split item and new owner has account scenario assert_ok!(TemplateModule::transfer(origin2.clone(), 3, 1, 1, 200)); - assert_eq!(TemplateModule::refungible_item_id(1,1).owner[0], Ownership { owner: 2, fraction: 300 }); - assert_eq!(TemplateModule::refungible_item_id(1,1).owner[1], Ownership { owner: 3, fraction: 700 }); - assert_eq!(TemplateModule::balance_count(1,2), 300); - assert_eq!(TemplateModule::balance_count(1,3), 700); - assert_eq!(TemplateModule::address_tokens(1,2), [1]); - assert_eq!(TemplateModule::address_tokens(1,3), [1]); + assert_eq!( + TemplateModule::refungible_item_id(1, 1).owner[0], + Ownership { + owner: 2, + fraction: 300 + } + ); + assert_eq!( + TemplateModule::refungible_item_id(1, 1).owner[1], + Ownership { + owner: 3, + fraction: 700 + } + ); + assert_eq!(TemplateModule::balance_count(1, 2), 300); + assert_eq!(TemplateModule::balance_count(1, 3), 700); + assert_eq!(TemplateModule::address_tokens(1, 2), [1]); + assert_eq!(TemplateModule::address_tokens(1, 3), [1]); }); } - #[test] fn transfer_nft_item() { @@ -124,582 +272,673 @@ )); assert_eq!(TemplateModule::collection(1).owner, 1); - - assert_ok!(TemplateModule::create_item(origin1.clone(), 1, [1,2,3].to_vec(), 1)); - assert_eq!(TemplateModule::nft_item_id(1,1).data, [1,2,3].to_vec()); - assert_eq!(TemplateModule::balance_count(1,1), 1); - assert_eq!(TemplateModule::address_tokens(1,1), [1]); + assert_ok!(TemplateModule::create_item( + origin1.clone(), + 1, + [1, 2, 3].to_vec(), + 1 + )); + assert_eq!(TemplateModule::nft_item_id(1, 1).data, [1, 2, 3].to_vec()); + assert_eq!(TemplateModule::balance_count(1, 1), 1); + assert_eq!(TemplateModule::address_tokens(1, 1), [1]); // default scenario assert_ok!(TemplateModule::transfer(origin1.clone(), 2, 1, 1, 1000)); - assert_eq!(TemplateModule::nft_item_id(1,1).owner, 2); - assert_eq!(TemplateModule::balance_count(1,1), 0); - assert_eq!(TemplateModule::balance_count(1,2), 1); - assert_eq!(TemplateModule::address_tokens(1,1), []); - assert_eq!(TemplateModule::address_tokens(1,2), [1]); + assert_eq!(TemplateModule::nft_item_id(1, 1).owner, 2); + assert_eq!(TemplateModule::balance_count(1, 1), 0); + assert_eq!(TemplateModule::balance_count(1, 2), 1); + assert_eq!(TemplateModule::address_tokens(1, 1), []); + assert_eq!(TemplateModule::address_tokens(1, 2), [1]); }); } +#[test] +fn nft_approve_and_transfer_from() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + let mode: CollectionMode = CollectionMode::NFT(2000); -// #[test] -// fn create_collection_test() { -// new_test_ext().execute_with(|| { -// let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); -// let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); -// let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + 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_eq!(TemplateModule::collection(1).owner, 1); -// let size = 1024; -// let origin1 = Origin::signed(1); -// assert_ok!(TemplateModule::create_collection( -// origin1.clone(), -// col_name1.clone(), -// col_desc1.clone(), -// token_prefix1.clone(), -// size -// )); -// assert_eq!(TemplateModule::collection(1).owner, 1); -// }); -// } + assert_ok!(TemplateModule::create_item( + origin1.clone(), + 1, + [1, 2, 3].to_vec(), + 1 + )); + assert_eq!(TemplateModule::nft_item_id(1, 1).data, [1, 2, 3].to_vec()); + assert_eq!(TemplateModule::balance_count(1, 1), 1); + assert_eq!(TemplateModule::address_tokens(1, 1), [1]); -// #[test] -// fn change_collection_owner() { -// new_test_ext().execute_with(|| { -// let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); -// let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); -// let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + assert_noop!( + TemplateModule::transfer_from(origin2.clone(), 1, 3, 1, 1, 1), + "You do not have permissions to modify this collection" + ); -// let size = 1024; -// let origin1 = Origin::signed(1); + // 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_eq!(TemplateModule::approved(1, (1, 1)).len(), 2); + assert_eq!( + TemplateModule::approved(1, (1, 1))[0], + ApprovePermissions { + approved: 2, + amount: 100000000 + } + ); -// assert_ok!(TemplateModule::create_collection( -// origin1.clone(), -// col_name1.clone(), -// col_desc1.clone(), -// token_prefix1.clone(), -// size -// )); -// assert_ok!(TemplateModule::change_collection_owner( -// origin1.clone(), -// 1, -// 2 -// )); -// assert_eq!(TemplateModule::collection(1).owner, 2); -// }); -// } + assert_ok!(TemplateModule::transfer_from( + origin2.clone(), + 1, + 3, + 1, + 1, + 1 + )); + assert_eq!(TemplateModule::approved(1, (1, 1)).len(), 0); + }); +} -// #[test] -// fn destroy_collection() { -// new_test_ext().execute_with(|| { -// let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); -// let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); -// let token_prefix1: Vec = b"token_prefix1\0".to_vec(); +#[test] +fn refungible_approve_and_transfer_from() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + let mode: CollectionMode = CollectionMode::ReFungible(2000, 3); -// let size = 1024; -// let origin1 = Origin::signed(1); + 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_eq!(TemplateModule::collection(1).owner, 1); -// assert_ok!(TemplateModule::create_collection( -// origin1.clone(), -// col_name1.clone(), -// col_desc1.clone(), -// token_prefix1.clone(), -// size -// )); -// assert_ok!(TemplateModule::destroy_collection(origin1.clone(), 1)); -// }); -// } + assert_ok!(TemplateModule::create_item( + origin1.clone(), + 1, + [1, 2, 3].to_vec(), + 1 + )); + assert_eq!( + TemplateModule::refungible_item_id(1, 1).data, + [1, 2, 3].to_vec() + ); + assert_eq!( + TemplateModule::refungible_item_id(1, 1).owner[0], + Ownership { + owner: 1, + fraction: 1000 + } + ); + assert_eq!(TemplateModule::balance_count(1, 1), 1000); + assert_eq!(TemplateModule::address_tokens(1, 1), [1]); -// #[test] -// fn create_item() { -// new_test_ext().execute_with(|| { -// let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); -// let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); -// let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + assert_noop!( + TemplateModule::transfer_from(origin2.clone(), 1, 3, 1, 1, 1), + "You do not have permissions to modify this collection" + ); -// let size = 1024; -// let origin1 = Origin::signed(1); -// let origin2 = Origin::signed(2); + // 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_eq!(TemplateModule::approved(1, (1, 1)).len(), 2); + assert_eq!( + TemplateModule::approved(1, (1, 1))[0], + ApprovePermissions { + approved: 2, + amount: 100000000 + } + ); -// assert_ok!(TemplateModule::create_collection( -// origin1.clone(), -// col_name1.clone(), -// col_desc1.clone(), -// token_prefix1.clone(), -// size -// )); -// assert_ok!(TemplateModule::add_collection_admin(origin1.clone(), 1, 2)); -// assert_ok!(TemplateModule::create_item( -// origin2.clone(), -// 1, -// [1, 1, 1].to_vec() -// )); + assert_ok!(TemplateModule::transfer_from( + origin2.clone(), + 1, + 3, + 1, + 1, + 100 + )); + assert_eq!(TemplateModule::balance_count(1, 1), 900); + assert_eq!(TemplateModule::balance_count(1, 3), 100); + assert_eq!(TemplateModule::address_tokens(1, 1), [1]); + assert_eq!(TemplateModule::address_tokens(1, 3), [1]); -// // check balance (collection with id = 1, user id = 2) -// assert_eq!(TemplateModule::balance_count((1, 2)), 1); -// }); -// } + assert_eq!(TemplateModule::approved(1, (1, 1)).len(), 1); + assert_eq!( + TemplateModule::approved(1, (1, 1))[0], + ApprovePermissions { + approved: 10, + amount: 100000000 + } + ); + }); +} -// #[test] -// fn burn_item() { -// new_test_ext().execute_with(|| { -// let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); -// let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); -// let token_prefix1: Vec = b"token_prefix1\0".to_vec(); +#[test] +fn fungible_approve_and_transfer_from() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + let mode: CollectionMode = CollectionMode::Fungible(3); -// let size = 1024; -// let origin1 = Origin::signed(1); -// let origin2 = Origin::signed(2); + 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_eq!(TemplateModule::collection(1).owner, 1); -// assert_ok!(TemplateModule::create_collection( -// origin1.clone(), -// col_name1.clone(), -// col_desc1.clone(), -// token_prefix1.clone(), -// size -// )); -// assert_ok!(TemplateModule::add_collection_admin(origin1.clone(), 1, 2)); -// assert_ok!(TemplateModule::create_item( -// origin2.clone(), -// 1, -// [1, 1, 1].to_vec() -// )); + assert_ok!(TemplateModule::create_item( + origin1.clone(), + 1, + [].to_vec(), + 1 + )); + assert_eq!(TemplateModule::fungible_item_id(1, 1).owner, 1); + assert_eq!(TemplateModule::balance_count(1, 1), 1000); + assert_eq!(TemplateModule::address_tokens(1, 1), [1]); -// // check balance (collection with id = 1, user id = 2) -// assert_eq!(TemplateModule::balance_count((1, 2)), 1); + assert_noop!( + TemplateModule::transfer_from(origin2.clone(), 1, 3, 1, 1, 1), + "You do not have permissions to modify this collection" + ); -// // burn item -// assert_ok!(TemplateModule::burn_item(origin1.clone(), 1, 1)); -// assert_noop!( -// TemplateModule::burn_item(origin1.clone(), 1, 1), -// "Item does not exists" -// ); + // 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_eq!(TemplateModule::approved(1, (1, 1)).len(), 2); + assert_eq!( + TemplateModule::approved(1, (1, 1))[0], + ApprovePermissions { + approved: 2, + amount: 100000000 + } + ); -// assert_eq!(TemplateModule::balance_count((1, 1)), 0); -// }); -// } + assert_ok!(TemplateModule::transfer_from( + origin2.clone(), + 1, + 3, + 1, + 1, + 100 + )); + assert_eq!(TemplateModule::balance_count(1, 1), 900); + assert_eq!(TemplateModule::balance_count(1, 3), 100); + assert_eq!(TemplateModule::address_tokens(1, 1), [1]); + assert_eq!(TemplateModule::address_tokens(1, 3), [2]); -// #[test] -// fn add_collection_admin() { -// new_test_ext().execute_with(|| { -// let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); -// let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); -// let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + assert_eq!(TemplateModule::approved(1, (1, 1)).len(), 1); + assert_eq!( + TemplateModule::approved(1, (1, 1))[0], + ApprovePermissions { + approved: 10, + amount: 100000000 + } + ); -// let size = 1024; -// let origin1 = Origin::signed(1); -// let origin2 = Origin::signed(2); -// let origin3 = Origin::signed(3); + assert_ok!(TemplateModule::approve(origin1.clone(), 2, 1, 1)); + assert_ok!(TemplateModule::transfer_from( + origin2.clone(), + 1, + 3, + 1, + 1, + 900 + )); + assert_eq!(TemplateModule::balance_count(1, 1), 0); + assert_eq!(TemplateModule::balance_count(1, 3), 1000); + assert_eq!(TemplateModule::address_tokens(1, 1), []); + assert_eq!(TemplateModule::address_tokens(1, 3), [2]); -// assert_ok!(TemplateModule::create_collection( -// origin1.clone(), -// col_name1.clone(), -// col_desc1.clone(), -// token_prefix1.clone(), -// size -// )); -// assert_ok!(TemplateModule::create_collection( -// origin2.clone(), -// col_name1.clone(), -// col_desc1.clone(), -// token_prefix1.clone(), -// size -// )); -// assert_ok!(TemplateModule::create_collection( -// origin3.clone(), -// col_name1.clone(), -// col_desc1.clone(), -// token_prefix1.clone(), -// size -// )); + assert_eq!(TemplateModule::approved(1, (1, 1)).len(), 0); + }); +} -// assert_eq!(TemplateModule::collection(1).owner, 1); -// assert_eq!(TemplateModule::collection(2).owner, 2); -// assert_eq!(TemplateModule::collection(3).owner, 3); +#[test] +fn change_collection_owner() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + let mode: CollectionMode = CollectionMode::NFT(2000); -// // collection admin -// assert_ok!(TemplateModule::add_collection_admin(origin1.clone(), 1, 2)); -// assert_ok!(TemplateModule::add_collection_admin(origin1.clone(), 1, 3)); + 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::change_collection_owner( + origin1.clone(), + 1, + 2 + )); + assert_eq!(TemplateModule::collection(1).owner, 2); + }); +} -// assert_eq!(TemplateModule::admin_list_collection(1).contains(&2), true); -// assert_eq!(TemplateModule::admin_list_collection(1).contains(&3), true); -// }); -// } +#[test] +fn destroy_collection() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + let mode: CollectionMode = CollectionMode::NFT(2000); -// #[test] -// fn remove_collection_admin() { -// new_test_ext().execute_with(|| { -// let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); -// let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); -// let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + 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)); + }); +} -// let size = 1024; -// let origin1 = Origin::signed(1); -// let origin2 = Origin::signed(2); -// let origin3 = Origin::signed(3); +#[test] +fn burn_nft_item() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + let mode: CollectionMode = CollectionMode::NFT(2000); -// assert_ok!(TemplateModule::create_collection( -// origin1.clone(), -// col_name1.clone(), -// col_desc1.clone(), -// token_prefix1.clone(), -// size -// )); -// assert_ok!(TemplateModule::create_collection( -// origin2.clone(), -// col_name1.clone(), -// col_desc1.clone(), -// token_prefix1.clone(), -// size -// )); -// assert_ok!(TemplateModule::create_collection( -// origin3.clone(), -// col_name1.clone(), -// col_desc1.clone(), -// token_prefix1.clone(), -// size -// )); + 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::create_item( + origin2.clone(), + 1, + [1, 2, 3].to_vec(), + 1 + )); -// assert_eq!(TemplateModule::collection(1).owner, 1); -// assert_eq!(TemplateModule::collection(2).owner, 2); -// assert_eq!(TemplateModule::collection(3).owner, 3); + assert_eq!(TemplateModule::nft_item_id(1, 1).data, [1, 2, 3].to_vec()); -// // collection admin -// assert_ok!(TemplateModule::add_collection_admin(origin1.clone(), 1, 2)); -// assert_ok!(TemplateModule::add_collection_admin(origin1.clone(), 1, 3)); + // check balance (collection with id = 1, user id = 1) + assert_eq!(TemplateModule::balance_count(1, 1), 1); -// assert_eq!(TemplateModule::admin_list_collection(1).contains(&2), true); -// assert_eq!(TemplateModule::admin_list_collection(1).contains(&3), true); + // burn item + assert_ok!(TemplateModule::burn_item(origin1.clone(), 1, 1)); + assert_noop!( + TemplateModule::burn_item(origin1.clone(), 1, 1), + "Item does not exists" + ); -// // remove admin -// assert_ok!(TemplateModule::remove_collection_admin( -// origin2.clone(), -// 1, -// 3 -// )); -// assert_eq!(TemplateModule::admin_list_collection(1).contains(&3), false); -// }); -// } + assert_eq!(TemplateModule::balance_count(1, 1), 0); + }); +} -// #[test] -// fn balance_of() { -// new_test_ext().execute_with(|| { -// let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); -// let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); -// let token_prefix1: Vec = b"token_prefix1\0".to_vec(); +#[test] +fn burn_fungible_item() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + let mode: CollectionMode = CollectionMode::Fungible(3); -// let size = 1024; -// let origin1 = Origin::signed(1); -// let origin2 = Origin::signed(2); -// let origin3 = Origin::signed(3); + 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::create_item( + origin2.clone(), + 1, + [].to_vec(), + 1 + )); -// assert_ok!(TemplateModule::create_collection( -// origin1.clone(), -// col_name1.clone(), -// col_desc1.clone(), -// token_prefix1.clone(), -// size -// )); -// assert_ok!(TemplateModule::create_collection( -// origin2.clone(), -// col_name1.clone(), -// col_desc1.clone(), -// token_prefix1.clone(), -// size -// )); -// assert_ok!(TemplateModule::create_collection( -// origin3.clone(), -// col_name1.clone(), -// col_desc1.clone(), -// token_prefix1.clone(), -// size -// )); + // check balance (collection with id = 1, user id = 1) + assert_eq!(TemplateModule::balance_count(1, 1), 1000); -// assert_eq!(TemplateModule::collection(1).owner, 1); -// assert_eq!(TemplateModule::collection(2).owner, 2); -// assert_eq!(TemplateModule::collection(3).owner, 3); + // burn item + assert_ok!(TemplateModule::burn_item(origin1.clone(), 1, 1)); + assert_noop!( + TemplateModule::burn_item(origin1.clone(), 1, 1), + "Item does not exists" + ); -// // check balance before -// assert_eq!(TemplateModule::balance_count((1, 1)), 0); + assert_eq!(TemplateModule::balance_count(1, 1), 0); + }); +} -// // create item -// assert_ok!(TemplateModule::create_item( -// origin1.clone(), -// 1, -// [1, 1, 1].to_vec() -// )); +#[test] +fn burn_refungible_item() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + let mode: CollectionMode = CollectionMode::ReFungible(200, 3); -// // check balance (collection with id = 1, user id = 2) -// assert_eq!(TemplateModule::balance_count((1, 1)), 1); -// assert_eq!(TemplateModule::item_id((1, 1)).owner, 1); -// }); -// } + 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::create_item( + origin2.clone(), + 1, + [1, 2, 3].to_vec(), + 1 + )); -// #[test] -// fn transfer() { -// new_test_ext().execute_with(|| { -// let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); -// let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); -// let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + assert_eq!( + TemplateModule::refungible_item_id(1, 1).data, + [1, 2, 3].to_vec() + ); -// let size = 1024; -// let origin1 = Origin::signed(1); -// let origin2 = Origin::signed(2); -// let origin3 = Origin::signed(3); + // check balance (collection with id = 1, user id = 2) + assert_eq!(TemplateModule::balance_count(1, 1), 1000); -// assert_ok!(TemplateModule::create_collection( -// origin1.clone(), -// col_name1.clone(), -// col_desc1.clone(), -// token_prefix1.clone(), -// size -// )); -// assert_ok!(TemplateModule::create_collection( -// origin2.clone(), -// col_name1.clone(), -// col_desc1.clone(), -// token_prefix1.clone(), -// size -// )); -// assert_ok!(TemplateModule::create_collection( -// origin3.clone(), -// col_name1.clone(), -// col_desc1.clone(), -// token_prefix1.clone(), -// size -// )); + // burn item + assert_ok!(TemplateModule::burn_item(origin1.clone(), 1, 1)); + assert_noop!( + TemplateModule::burn_item(origin1.clone(), 1, 1), + "Item does not exists" + ); -// assert_eq!(TemplateModule::collection(1).owner, 1); -// assert_eq!(TemplateModule::collection(2).owner, 2); -// assert_eq!(TemplateModule::collection(3).owner, 3); + assert_eq!(TemplateModule::balance_count(1, 1), 0); + }); +} -// // create item -// assert_ok!(TemplateModule::create_item( -// origin1.clone(), -// 1, -// [1, 1, 1].to_vec() -// )); +#[test] +fn add_collection_admin() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + let mode: CollectionMode = CollectionMode::NFT(2000); -// // transfer -// assert_ok!(TemplateModule::transfer(origin1.clone(), 1, 1, 2)); -// assert_eq!(TemplateModule::item_id((1, 1)).owner, 2); + let origin1 = Origin::signed(1); + let origin2 = Origin::signed(2); + let origin3 = Origin::signed(3); + assert_ok!(TemplateModule::create_collection( + origin1.clone(), + col_name1.clone(), + col_desc1.clone(), + token_prefix1.clone(), + mode.clone() + )); + assert_ok!(TemplateModule::create_collection( + origin2.clone(), + col_name1.clone(), + col_desc1.clone(), + token_prefix1.clone(), + mode.clone() + )); + assert_ok!(TemplateModule::create_collection( + origin3.clone(), + col_name1.clone(), + col_desc1.clone(), + token_prefix1.clone(), + mode.clone() + )); -// // balance_of check -// assert_eq!(TemplateModule::balance_count((1, 1)), 0); -// assert_eq!(TemplateModule::balance_count((1, 2)), 1); -// }); -// } + assert_eq!(TemplateModule::collection(1).owner, 1); + assert_eq!(TemplateModule::collection(2).owner, 2); + assert_eq!(TemplateModule::collection(3).owner, 3); -// #[test] -// fn approve() { -// new_test_ext().execute_with(|| { -// let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); -// let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); -// let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + // collection admin + assert_ok!(TemplateModule::add_collection_admin(origin1.clone(), 1, 2)); + assert_ok!(TemplateModule::add_collection_admin(origin1.clone(), 1, 3)); -// let size = 1024; -// let origin1 = Origin::signed(1); -// let origin2 = Origin::signed(2); -// let origin3 = Origin::signed(3); + assert_eq!(TemplateModule::admin_list_collection(1).contains(&2), true); + assert_eq!(TemplateModule::admin_list_collection(1).contains(&3), true); + }); +} -// assert_ok!(TemplateModule::create_collection( -// origin1.clone(), -// col_name1.clone(), -// col_desc1.clone(), -// token_prefix1.clone(), -// size -// )); -// assert_ok!(TemplateModule::create_collection( -// origin2.clone(), -// col_name1.clone(), -// col_desc1.clone(), -// token_prefix1.clone(), -// size -// )); -// assert_ok!(TemplateModule::create_collection( -// origin3.clone(), -// col_name1.clone(), -// col_desc1.clone(), -// token_prefix1.clone(), -// size -// )); +#[test] +fn remove_collection_admin() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + let mode: CollectionMode = CollectionMode::NFT(2000); -// assert_eq!(TemplateModule::collection(1).owner, 1); -// assert_eq!(TemplateModule::collection(2).owner, 2); -// assert_eq!(TemplateModule::collection(3).owner, 3); + let origin1 = Origin::signed(1); + let origin2 = Origin::signed(2); + let origin3 = Origin::signed(3); -// // create item -// assert_ok!(TemplateModule::create_item( -// origin1.clone(), -// 1, -// [1, 1, 1].to_vec() -// )); + assert_ok!(TemplateModule::create_collection( + origin1.clone(), + col_name1.clone(), + col_desc1.clone(), + token_prefix1.clone(), + mode.clone() + )); + assert_ok!(TemplateModule::create_collection( + origin2.clone(), + col_name1.clone(), + col_desc1.clone(), + token_prefix1.clone(), + mode.clone() + )); + assert_ok!(TemplateModule::create_collection( + origin3.clone(), + col_name1.clone(), + col_desc1.clone(), + token_prefix1.clone(), + mode.clone() + )); -// // approve -// assert_ok!(TemplateModule::approve(origin1.clone(), 2, 1, 1)); -// assert_eq!(TemplateModule::approved((1, 1)).contains(&2), true); -// }); -// } + assert_eq!(TemplateModule::collection(1).owner, 1); + assert_eq!(TemplateModule::collection(2).owner, 2); + assert_eq!(TemplateModule::collection(3).owner, 3); -// #[test] -// fn get_approved() { -// new_test_ext().execute_with(|| { -// let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); -// let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); -// let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + // collection admin + assert_ok!(TemplateModule::add_collection_admin(origin1.clone(), 1, 2)); + assert_ok!(TemplateModule::add_collection_admin(origin1.clone(), 1, 3)); -// let size = 1024; -// let origin1 = Origin::signed(1); -// let origin2 = Origin::signed(2); -// let origin3 = Origin::signed(3); + assert_eq!(TemplateModule::admin_list_collection(1).contains(&2), true); + assert_eq!(TemplateModule::admin_list_collection(1).contains(&3), true); -// assert_ok!(TemplateModule::create_collection( -// origin1.clone(), -// col_name1.clone(), -// col_desc1.clone(), -// token_prefix1.clone(), -// size -// )); -// assert_ok!(TemplateModule::create_collection( -// origin2.clone(), -// col_name1.clone(), -// col_desc1.clone(), -// token_prefix1.clone(), -// size -// )); -// assert_ok!(TemplateModule::create_collection( -// origin3.clone(), -// col_name1.clone(), -// col_desc1.clone(), -// token_prefix1.clone(), -// size -// )); + // remove admin + assert_ok!(TemplateModule::remove_collection_admin( + origin2.clone(), + 1, + 3 + )); + assert_eq!(TemplateModule::admin_list_collection(1).contains(&3), false); + }); +} -// assert_eq!(TemplateModule::collection(1).owner, 1); -// assert_eq!(TemplateModule::collection(2).owner, 2); -// assert_eq!(TemplateModule::collection(3).owner, 3); +#[test] +fn balance_of() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + let nft_mode: CollectionMode = CollectionMode::NFT(2000); + let furg_mode: CollectionMode = CollectionMode::Fungible(3); + let refung_mode: CollectionMode = CollectionMode::ReFungible(2000, 3); -// // create item -// assert_ok!(TemplateModule::create_item( -// origin1.clone(), -// 1, -// [1, 1, 1].to_vec() -// )); + let origin1 = Origin::signed(1); -// // approve -// assert_ok!(TemplateModule::approve(origin1.clone(), 2, 1, 1)); -// assert_eq!(TemplateModule::approved((1, 1)).contains(&2), true); -// }); -// } + assert_ok!(TemplateModule::create_collection( + origin1.clone(), + col_name1.clone(), + col_desc1.clone(), + token_prefix1.clone(), + nft_mode.clone() + )); + assert_ok!(TemplateModule::create_collection( + origin1.clone(), + col_name1.clone(), + col_desc1.clone(), + token_prefix1.clone(), + furg_mode.clone() + )); + assert_ok!(TemplateModule::create_collection( + origin1.clone(), + col_name1.clone(), + col_desc1.clone(), + token_prefix1.clone(), + refung_mode.clone() + )); -// #[test] -// fn transfer_from() { -// new_test_ext().execute_with(|| { -// let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); -// let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); -// let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + assert_eq!(TemplateModule::collection(1).owner, 1); + assert_eq!(TemplateModule::collection(2).owner, 1); + assert_eq!(TemplateModule::collection(3).owner, 1); -// let size = 1024; -// let origin1 = Origin::signed(1); -// let origin2 = Origin::signed(2); -// let origin3 = Origin::signed(3); + // check balance before + assert_eq!(TemplateModule::balance_count(1, 1), 0); + assert_eq!(TemplateModule::balance_count(2, 1), 0); + assert_eq!(TemplateModule::balance_count(3, 1), 0); -// assert_ok!(TemplateModule::create_collection( -// origin1.clone(), -// col_name1.clone(), -// col_desc1.clone(), -// token_prefix1.clone(), -// size -// )); -// assert_ok!(TemplateModule::create_collection( -// origin2.clone(), -// col_name1.clone(), -// col_desc1.clone(), -// token_prefix1.clone(), -// size -// )); -// assert_ok!(TemplateModule::create_collection( -// origin3.clone(), -// col_name1.clone(), -// col_desc1.clone(), -// token_prefix1.clone(), -// size -// )); + // create item + assert_ok!(TemplateModule::create_item( + origin1.clone(), + 1, + [1, 1, 1].to_vec(), + 1 + )); -// assert_eq!(TemplateModule::collection(1).owner, 1); -// assert_eq!(TemplateModule::collection(2).owner, 2); -// assert_eq!(TemplateModule::collection(3).owner, 3); + assert_ok!(TemplateModule::create_item( + origin1.clone(), + 2, + [].to_vec(), + 1 + )); -// // create item -// assert_ok!(TemplateModule::create_item( -// origin1.clone(), -// 1, -// [1, 1, 1].to_vec() -// )); + assert_ok!(TemplateModule::create_item( + origin1.clone(), + 3, + [1, 1, 1].to_vec(), + 1 + )); -// // approve -// assert_ok!(TemplateModule::approve(origin1.clone(), 2, 1, 1)); -// assert_ok!(TemplateModule::transfer_from(origin1.clone(), 1, 1, 2)); -// }); -// } + // check balance (collection with id = 1, user id = 1) + assert_eq!(TemplateModule::balance_count(1, 1), 1); + assert_eq!(TemplateModule::balance_count(2, 1), 1000); + assert_eq!(TemplateModule::balance_count(3, 1), 1000); + assert_eq!(TemplateModule::nft_item_id(1, 1).owner, 1); + assert_eq!(TemplateModule::fungible_item_id(2, 1).owner, 1); + assert_eq!(TemplateModule::refungible_item_id(3, 1).owner[0].owner, 1); + }); +} -// #[test] -// fn index_list() { -// new_test_ext().execute_with(|| { -// let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); -// let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); -// let token_prefix1: Vec = b"token_prefix1\0".to_vec(); +#[test] +fn approve() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = b"token_prefix1\0".to_vec(); + let nft_mode: CollectionMode = CollectionMode::NFT(2000); + let origin1 = Origin::signed(1); -// let size = 1024; -// let origin1 = Origin::signed(1); -// let origin2 = Origin::signed(2); -// let origin3 = Origin::signed(3); + assert_ok!(TemplateModule::create_collection( + origin1.clone(), + col_name1.clone(), + col_desc1.clone(), + token_prefix1.clone(), + nft_mode.clone() + )); -// assert_ok!(TemplateModule::create_collection( -// origin1.clone(), -// col_name1.clone(), -// col_desc1.clone(), -// token_prefix1.clone(), -// size -// )); -// assert_ok!(TemplateModule::create_collection( -// origin2.clone(), -// col_name1.clone(), -// col_desc1.clone(), -// token_prefix1.clone(), -// size -// )); -// assert_ok!(TemplateModule::create_collection( -// origin3.clone(), -// col_name1.clone(), -// col_desc1.clone(), -// token_prefix1.clone(), -// size -// )); + assert_eq!(TemplateModule::collection(1).owner, 1); -// assert_eq!(TemplateModule::collection(1).owner, 1); -// assert_eq!(TemplateModule::collection(2).owner, 2); -// assert_eq!(TemplateModule::collection(3).owner, 3); -// // create items -// assert_ok!(TemplateModule::create_item( -// origin1.clone(), -// 1, -// [1, 1, 1].to_vec() -// )); -// assert_ok!(TemplateModule::create_item( -// origin1.clone(), -// 1, -// [1, 1, 2].to_vec() -// )); -// assert_ok!(TemplateModule::create_item( -// origin1.clone(), -// 1, -// [1, 2, 3].to_vec() -// )); -// assert_eq!(TemplateModule::address_tokens((1, 1)).len(), 3); -// // burn one -// assert_ok!(TemplateModule::burn_item(origin1.clone(), 1, 2)); -// assert_eq!(TemplateModule::address_tokens((1, 1)).len(), 2); -// // burn another one -// assert_ok!(TemplateModule::burn_item(origin1.clone(), 1, 3)); -// assert_eq!(TemplateModule::address_tokens((1, 1))[0], 1); -// }); -// } + // create item + assert_ok!(TemplateModule::create_item( + origin1.clone(), + 1, + [1, 1, 1].to_vec(), + 1 + )); + + // approve + assert_ok!(TemplateModule::approve(origin1.clone(), 2, 1, 1)); + assert_eq!(TemplateModule::approved(1, (1, 1))[0].approved, 2); + }); +} + +#[test] +fn transfer_from() { + new_test_ext().execute_with(|| { + let col_name1: Vec = "Test1\0".encode_utf16().collect::>(); + let col_desc1: Vec = "TestDescription1\0".encode_utf16().collect::>(); + let token_prefix1: Vec = 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_eq!(TemplateModule::collection(1).owner, 1); + + // create item + assert_ok!(TemplateModule::create_item( + origin1.clone(), + 1, + [1, 1, 1].to_vec(), + 1 + )); + + // approve + assert_ok!(TemplateModule::approve(origin1.clone(), 2, 1, 1)); + assert_eq!(TemplateModule::approved(1, (1, 1))[0].approved, 2); + assert_ok!(TemplateModule::transfer_from( + origin2.clone(), + 1, + 2, + 1, + 1, + 1 + )); + + // after transfer + assert_eq!(TemplateModule::balance_count(1, 1), 0); + assert_eq!(TemplateModule::balance_count(1, 2), 1); + }); +} --- a/runtime/build.rs +++ b/runtime/build.rs @@ -1,10 +1,10 @@ use wasm_builder_runner::WasmBuilder; fn main() { - WasmBuilder::new() - .with_current_project() - .with_wasm_builder_from_crates("1.0.11") - .export_heap_base() - .import_memory() - .build() + WasmBuilder::new() + .with_current_project() + .with_wasm_builder_from_crates("1.0.11") + .export_heap_base() + .import_memory() + .build() } --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -16,11 +16,12 @@ use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, + traits::{ + BlakeTwo256, Block as BlockT, IdentifyAccount, IdentityLookup, NumberFor, Saturating, + Verify, + }, transaction_validity::{TransactionSource, TransactionValidity}, ApplyExtrinsicResult, MultiSignature, - traits::{ - BlakeTwo256, Block as BlockT, IdentifyAccount, IdentityLookup, NumberFor, Saturating, Verify, - }, }; use sp_std::prelude::*; #[cfg(feature = "std")] @@ -31,22 +32,24 @@ pub use balances::Call as BalancesCall; pub use contracts::Schedule as ContractsSchedule; pub use frame_support::{ - construct_runtime, parameter_types, - traits::{Currency, Get, ExistenceRequirement, KeyOwnerProofSystem, OnUnbalanced, Randomness, WithdrawReason}, + construct_runtime, + dispatch::DispatchResult, + parameter_types, + traits::{ + Currency, ExistenceRequirement, Get, KeyOwnerProofSystem, OnUnbalanced, Randomness, + WithdrawReason, + }, weights::{ - DispatchInfo, PostDispatchInfo, constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND}, - IdentityFee, Weight, WeightToFeePolynomial, GetDispatchInfo, Pays, + constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND}, + DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight, + WeightToFeePolynomial, }, StorageValue, - dispatch::DispatchResult, }; -use system::{self as system}; #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; -use sp_runtime::{ - Perbill, -}; - +use sp_runtime::Perbill; +use system::{self as system}; pub use timestamp::Call as TimestampCall; @@ -190,7 +193,6 @@ /// Version of the runtime. type Version = Version; /// Converts a module to the index of the module in `construct_runtime!`. - /// /// This type is being generated by `construct_runtime!`. type ModuleToIndex = ModuleToIndex; /// What to do if a new account is created. @@ -493,4 +495,3 @@ } } } - -- gitstuff