--- a/Cargo.lock +++ b/Cargo.lock @@ -2512,25 +2512,12 @@ ] [[package]] -name = "nix" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" -dependencies = [ - "bitflags", - "cc", - "cfg-if", - "libc", - "void", -] - -[[package]] -name = "node-template" +name = "nft" version = "2.0.0-alpha.6" dependencies = [ "futures 0.3.4", "log", - "node-template-runtime", + "nft-runtime", "sc-basic-authorship", "sc-cli", "sc-client", @@ -2553,7 +2540,7 @@ ] [[package]] -name = "node-template-runtime" +name = "nft-runtime" version = "2.0.0-alpha.6" dependencies = [ "frame-executive", @@ -2562,9 +2549,9 @@ "pallet-aura", "pallet-balances", "pallet-grandpa", + "pallet-nft", "pallet-randomness-collective-flip", "pallet-sudo", - "pallet-template", "pallet-timestamp", "pallet-transaction-payment", "parity-scale-codec", @@ -2585,6 +2572,19 @@ ] [[package]] +name = "nix" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", + "void", +] + +[[package]] name = "nodrop" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2802,6 +2802,19 @@ ] [[package]] +name = "pallet-nft" +version = "2.0.0-alpha.6" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] name = "pallet-randomness-collective-flip" version = "2.0.0-alpha.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2844,19 +2857,6 @@ "frame-system", "parity-scale-codec", "serde", - "sp-io", - "sp-runtime", - "sp-std", -] - -[[package]] -name = "pallet-template" -version = "2.0.0-alpha.6" -dependencies = [ - "frame-support", - "frame-system", - "parity-scale-codec", - "sp-core", "sp-io", "sp-runtime", "sp-std", --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [workspace] members = [ 'node', - 'pallets/template', + 'pallets/nft', 'runtime', ] [profile.release] --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -1,11 +1,11 @@ [package] authors = ['Anonymous'] build = 'build.rs' -description = 'Substrate Node template' +description = 'Substrate Node nft' edition = '2018' homepage = 'https://substrate.dev' license = 'Unlicense' -name = 'node-template' +name = 'nft' repository = 'https://github.com/paritytech/substrate/' version = '2.0.0-alpha.6' @@ -17,7 +17,7 @@ log = '0.4.8' structopt = '0.3.8' -[dependencies.node-template-runtime] +[dependencies.nft-runtime] path = '../runtime' version = '2.0.0-alpha.6' @@ -75,4 +75,4 @@ version = '2.0.0-alpha.6' [[bin]] -name = 'node-template' +name = 'nft' --- a/node/src/chain_spec.rs +++ b/node/src/chain_spec.rs @@ -1,5 +1,5 @@ use sp_core::{Pair, Public, sr25519}; -use node_template_runtime::{ +use nft_runtime::{ AccountId, AuraConfig, BalancesConfig, GenesisConfig, GrandpaConfig, SudoConfig, SystemConfig, WASM_BINARY, Signature }; --- a/node/src/command.rs +++ b/node/src/command.rs @@ -74,7 +74,7 @@ runner.run_node( service::new_light, service::new_full, - node_template_runtime::VERSION + nft_runtime::VERSION ) } } --- a/node/src/service.rs +++ b/node/src/service.rs @@ -4,7 +4,7 @@ use std::time::Duration; use sc_client::LongestChain; use sc_client_api::ExecutorProvider; -use node_template_runtime::{self, opaque::Block, RuntimeApi}; +use nft_runtime::{self, opaque::Block, RuntimeApi}; use sc_service::{error::{Error as ServiceError}, AbstractService, Configuration, ServiceBuilder}; use sp_inherents::InherentDataProviders; use sc_executor::native_executor_instance; @@ -15,8 +15,8 @@ // Our native executor instance. native_executor_instance!( pub Executor, - node_template_runtime::api::dispatch, - node_template_runtime::native_version, + nft_runtime::api::dispatch, + nft_runtime::native_version, ); /// Starts a `ServiceBuilder` for a full service. @@ -30,7 +30,7 @@ let inherent_data_providers = sp_inherents::InherentDataProviders::new(); let builder = sc_service::ServiceBuilder::new_full::< - node_template_runtime::opaque::Block, node_template_runtime::RuntimeApi, crate::service::Executor + nft_runtime::opaque::Block, nft_runtime::RuntimeApi, crate::service::Executor >($config)? .with_select_chain(|_config, backend| { Ok(sc_client::LongestChain::new(backend.clone())) --- /dev/null +++ b/pallets/nft/Cargo.toml @@ -0,0 +1,54 @@ +[package] +authors = ['Anonymous'] +description = 'FRAME pallet nft' +edition = '2018' +homepage = 'https://substrate.dev' +license = 'Unlicense' +name = 'pallet-nft' +repository = 'https://github.com/paritytech/substrate/' +version = '2.0.0-alpha.6' + +[package.metadata.docs.rs] +targets = ['x86_64-unknown-linux-gnu'] + +[dependencies.codec] +default-features = false +features = ['derive'] +package = 'parity-scale-codec' +version = '1.3.0' + +[dependencies.sp-std] +default-features = false +version = '2.0.0-alpha.6' + +[dependencies.sp-runtime] +default-features = false +version = '2.0.0-alpha.6' + +[dependencies.frame-support] +default-features = false +version = '2.0.0-alpha.6' + +[dependencies.frame-system] +default-features = false +version = '2.0.0-alpha.6' +[dev-dependencies.sp-core] +default-features = false +version = '2.0.0-alpha.6' + +[dev-dependencies.sp-io] +default-features = false +version = '2.0.0-alpha.6' + + + +[features] +default = ['std'] +std = [ + 'codec/std', + 'frame-support/std', + 'frame-system/std', + "sp-io/std", + "sp-runtime/std", + 'sp-std/std', +] --- /dev/null +++ b/pallets/nft/src/lib.rs @@ -0,0 +1,315 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +/// A FRAME pallet template with necessary imports + +/// Feel free to remove or edit this file as needed. +/// If you change the name of this file, make sure to update its references in runtime/src/lib.rs +/// If you remove this file, you can remove those references + +/// For more guidance on Substrate FRAME, see the example pallet +/// https://github.com/paritytech/substrate/blob/master/frame/example/src/lib.rs + +use frame_support::{ + dispatch::DispatchResult, decl_module, decl_storage, decl_event, + ensure, +}; +use frame_system::{self as system, ensure_signed }; +use codec::{Encode, Decode}; +use sp_runtime::sp_std::prelude::Vec; + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +#[derive(Encode, Decode, Default, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct CollectionType { + pub owner: AccountId, + pub next_item_id: u64, + pub custom_data_size: u32, +} + +#[derive(Encode, Decode, Default, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct CollectionAdminsType { + pub admin: AccountId, + pub collection_id: u64, +} + +#[derive(Encode, Decode, Default, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct NftItemType { + pub collection: u64, + pub owner: AccountId, + pub data: Vec, +} + +/// The pallet's configuration trait. +pub trait Trait: system::Trait { + // Add other types and constants required to configure this pallet. + + /// The overarching event type. + type Event: From> + Into<::Event>; +} + +// This pallet's storage items. +decl_storage! { + // It is important to update your storage name so that your pallet's + // storage items are isolated from other pallets. + trait Store for Module as TemplateModule { + + /// Next available collection ID + pub NextCollectionID get(next_collection_id): u64 = 1; + + /// Collection map + pub Collection get(collection): map hasher(blake2_128_concat) u64 => CollectionType; + + /// Admins map (collection) + pub AdminList get(admin_list_collection): map hasher(blake2_128_concat) u64 => Vec; + + /// Balance owner per collection map + pub Balance get(balance_count): map hasher(blake2_128_concat) (u64, T::AccountId) => u64; + + /// Item double map (collection) + pub ItemList get(item_id): map hasher(blake2_128_concat) (u64, u64) => NftItemType; + pub ItemListIndex get(item_index): map hasher(blake2_128_concat) u64 => u64; + } +} + +// The pallet's events +decl_event!( + pub enum Event where AccountId = ::AccountId { + Created(u32, AccountId), + } +); + +// The pallet's dispatchable functions. +decl_module! { + /// The module declaration. + pub struct Module for enum Call where origin: T::Origin { + + // Initializing events + // this is needed only if you are using events in your pallet + fn deposit_event() = default; + + // Initializing events + // this is needed only if you are using events in your module + // fn deposit_event() = default; + + // Create collection of NFT with given parameters + // + // @param customDataSz size of custom data in each collection item + // returns collection ID + + // Create collection of NFT with given parameters + // + // @param customDataSz size of custom data in each collection item + // returns collection ID + #[weight = frame_support::weights::SimpleDispatchInfo::default()] + pub fn create_collection(origin, custom_data_sz: u32) -> DispatchResult { + // Anyone can create a collection + let who = ensure_signed(origin)?; + + // Generate next collection ID + let next_id = Self::next_collection_id(); + ::put(next_id+1); + + // Create new collection + let new_collection = CollectionType { + owner: who, + next_item_id: next_id, + custom_data_size: custom_data_sz, + }; + + // Add new collection to map + >::insert(next_id, new_collection); + + Ok(()) + } + + #[weight = frame_support::weights::SimpleDispatchInfo::default()] + pub fn destroy_collection(origin, collection_id: u64) -> DispatchResult { + + let sender = ensure_signed(origin)?; + ensure!(>::contains_key(collection_id), "This collection does not exist"); + + let owner = >::get(collection_id).owner; + ensure!(sender == owner, "You do not own this collection"); + >::remove(collection_id); + + Ok(()) + } + + #[weight = frame_support::weights::SimpleDispatchInfo::default()] + pub fn change_collection_owner(origin, collection_id: u64, new_owner: T::AccountId) -> DispatchResult { + + let sender = ensure_signed(origin)?; + ensure!(>::contains_key(collection_id), "This collection does not exist"); + + let mut target_collection = >::get(collection_id); + ensure!(sender == target_collection.owner, "You do not own this collection"); + + target_collection.owner = new_owner; + >::insert(collection_id, target_collection); + + Ok(()) + } + + #[weight = frame_support::weights::SimpleDispatchInfo::default()] + pub fn add_collection_admin(origin, collection_id: u64, new_admin_id: T::AccountId) -> DispatchResult { + + let sender = ensure_signed(origin)?; + ensure!(>::contains_key(collection_id), "This collection does not exist"); + + let target_collection = >::get(collection_id); + let is_owner = sender == target_collection.owner; + + let no_perm_mes = "You do not have permissions to modify this collection"; + let exists = >::contains_key(collection_id); + + if !is_owner + { + ensure!(exists, no_perm_mes); + ensure!(>::get(collection_id).contains(&sender), no_perm_mes); + } + + let mut admin_arr: Vec = Vec::new(); + if exists + { + admin_arr = >::get(collection_id); + ensure!(!admin_arr.contains(&new_admin_id), "Account already has admin role"); + } + + admin_arr.push(new_admin_id); + >::insert(collection_id, admin_arr); + + Ok(()) + } + + #[weight = frame_support::weights::SimpleDispatchInfo::default()] + pub fn remove_collection_admin(origin, collection_id: u64, account_id: T::AccountId) -> DispatchResult { + + let sender = ensure_signed(origin)?; + ensure!(>::contains_key(collection_id), "This collection does not exist"); + + let target_collection = >::get(collection_id); + let is_owner = sender == target_collection.owner; + + let no_perm_mes = "You do not have permissions to modify this collection"; + let exists = >::contains_key(collection_id); + + if !is_owner + { + ensure!(exists, no_perm_mes); + ensure!(>::get(collection_id).contains(&sender), no_perm_mes); + } + + if exists + { + let mut admin_arr = >::get(collection_id); + admin_arr.retain(|i| *i != account_id); + >::insert(collection_id, admin_arr); + } + + Ok(()) + } + + #[weight = frame_support::weights::SimpleDispatchInfo::default()] + pub fn create_item(origin, collection_id: u64, properties: Vec) -> DispatchResult { + + let sender = ensure_signed(origin)?; + ensure!(>::contains_key(collection_id), "This collection does not exist"); + + let target_collection = >::get(collection_id); + ensure!(target_collection.custom_data_size >= properties.len() as u32, "Size of item is too large"); + let is_owner = sender == target_collection.owner; + + let no_perm_mes = "You do not have permissions to modify this collection"; + let exists = >::contains_key(collection_id); + + if !is_owner + { + ensure!(exists, no_perm_mes); + ensure!(>::get(collection_id).contains(&sender), no_perm_mes); + } + + let new_balance = >::get((collection_id, sender.clone())) + 1; + >::insert((collection_id, sender.clone()), new_balance); + + // Create new item + let new_item = NftItemType { + collection: collection_id, + owner: sender, + data: properties, + }; + + let current_index = ::get(collection_id); + ::insert(collection_id, current_index+1); + >::insert((collection_id, current_index), new_item); + + Ok(()) + } + + #[weight = frame_support::weights::SimpleDispatchInfo::default()] + pub fn burn_item(origin, collection_id: u64, item_id: u64) -> DispatchResult { + + let sender = ensure_signed(origin)?; + ensure!(>::contains_key(collection_id), "This collection does not exist"); + + let target_collection = >::get(collection_id); + let is_owner = sender == target_collection.owner; + + ensure!(>::contains_key((collection_id, item_id)), "Item does not exists"); + let item = >::get((collection_id, item_id)); + + if !is_owner + { + // check if item owner + if item.owner != sender + { + let no_perm_mes = "You do not have permissions to modify this collection"; + + ensure!(>::contains_key(collection_id), no_perm_mes); + ensure!(>::get(collection_id).contains(&sender), no_perm_mes); + } + } + >::remove((collection_id, item_id)); + + Ok(()) + } + + #[weight = frame_support::weights::SimpleDispatchInfo::default()] + pub fn transfer(origin, collection_id: u64, item_id: u64, new_owner: T::AccountId) -> DispatchResult { + + let sender = ensure_signed(origin)?; + ensure!(>::contains_key(collection_id), "This collection does not exist"); + + let target_collection = >::get(collection_id); + let is_owner = sender == target_collection.owner; + + ensure!(>::contains_key((collection_id, item_id)), "Item does not exists"); + let mut item = >::get((collection_id, item_id)); + + if !is_owner + { + // check if item owner + if item.owner != sender + { + let no_perm_mes = "You do not have permissions to modify this collection"; + + ensure!(>::contains_key(collection_id), no_perm_mes); + ensure!(>::get(collection_id).contains(&sender), no_perm_mes); + } + } + >::remove((collection_id, item_id)); + + // change owner + item.owner = new_owner; + >::insert((collection_id, item_id), item); + + Ok(()) + } + } +} \ No newline at end of file --- /dev/null +++ b/pallets/nft/src/mock.rs @@ -0,0 +1,56 @@ +// Creating mock runtime here + +use crate::{Module, Trait}; +use sp_core::H256; +use frame_support::{impl_outer_origin, parameter_types, weights::Weight}; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, testing::Header, Perbill, +}; +use frame_system as system; + +impl_outer_origin! { + pub enum Origin for Test {} +} + +// For testing the pallet, we construct most of a mock runtime. This means +// first constructing a configuration type (`Test`) which `impl`s each of the +// configuration traits of pallets we want to use. +#[derive(Clone, Eq, PartialEq)] +pub struct Test; +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const MaximumBlockWeight: Weight = 1024; + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75); +} +impl system::Trait for Test { + type Origin = Origin; + type Call = (); + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = (); + type BlockHashCount = BlockHashCount; + type MaximumBlockWeight = MaximumBlockWeight; + type MaximumBlockLength = MaximumBlockLength; + type AvailableBlockRatio = AvailableBlockRatio; + type Version = (); + type ModuleToIndex = (); + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); +} +impl Trait for Test { + type Event = (); +} +pub type TemplateModule = Module; + +// This function basically just builds a genesis storage key/value store according to +// our desired mockup. +pub fn new_test_ext() -> sp_io::TestExternalities { + system::GenesisConfig::default().build_storage::().unwrap().into() +} --- /dev/null +++ b/pallets/nft/src/tests.rs @@ -0,0 +1,84 @@ +// Tests to be written here +use crate::{ mock::*}; +use frame_support::{assert_ok, assert_noop}; + +#[test] +fn create_collection_test() { + new_test_ext().execute_with(|| { + let size = 1024; + let origin1 = Origin::signed(1); + assert_ok!(TemplateModule::create_collection(origin1.clone(), size)); + assert_eq!(TemplateModule::collection(1).owner, 1); + }); +} + +#[test] +fn change_collection_owner() { + new_test_ext().execute_with(|| { + let size = 1024; + let origin1 = Origin::signed(1); + let origin2 = Origin::signed(2); + + assert_ok!(TemplateModule::create_collection(origin1.clone(), size)); + assert_ok!(TemplateModule::change_collection_owner(origin1.clone(), 1, 2)); + assert_eq!(TemplateModule::collection(1).owner, 2); + }); +} + +#[test] +fn destroy_collection() { + new_test_ext().execute_with(|| { + let size = 1024; + let origin1 = Origin::signed(1); + + assert_ok!(TemplateModule::create_collection(origin1.clone(), size)); + assert_ok!(TemplateModule::destroy_collection(origin1.clone(), 1)); + }); +} + +#[test] +fn create_item() { + new_test_ext().execute_with(|| { + let size = 1024; + let origin1 = Origin::signed(1); + let origin2 = Origin::signed(2); + + assert_ok!(TemplateModule::create_collection(origin1.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())); + + // check balance (collection with id = 1, user id = 2) + assert_eq!(TemplateModule::balance_count((1, 2)), 1); + }); +} + +// #[test] +// fn burn_item() { +// new_test_ext().execute_with(|| { +// let size = 1024; +// let origin1 = Origin::signed(1); +// let origin2 = Origin::signed(2); + +// assert_ok!(TemplateModule::create_collection(origin1.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())); + +// // check balance (collection with id = 1, user id = 2) +// assert_eq!(TemplateModule::balance_count((1, 2)), 1); + +// // burn item +// assert_ok!(TemplateModule::burn_item(origin1.clone(), 1, 1)); +// assert_noop!(TemplateModule::burn_item(origin1.clone(), 1, 1), "Item does not exists"); +// }); +// } + +// #[test] +// fn correct_error_for_none_value() { +// new_test_ext().execute_with(|| { +// // Ensure the correct error is thrown on None value +// assert_noop!( +// TemplateModule::cause_error(Origin::signed(1)), +// Error::::NoneValue +// ); +// }); +// } --- a/pallets/template/Cargo.toml +++ /dev/null @@ -1,54 +0,0 @@ -[package] -authors = ['Anonymous'] -description = 'FRAME pallet template' -edition = '2018' -homepage = 'https://substrate.dev' -license = 'Unlicense' -name = 'pallet-template' -repository = 'https://github.com/paritytech/substrate/' -version = '2.0.0-alpha.6' - -[package.metadata.docs.rs] -targets = ['x86_64-unknown-linux-gnu'] - -[dependencies.codec] -default-features = false -features = ['derive'] -package = 'parity-scale-codec' -version = '1.3.0' - -[dependencies.sp-std] -default-features = false -version = '2.0.0-alpha.6' - -[dependencies.sp-runtime] -default-features = false -version = '2.0.0-alpha.6' - -[dependencies.frame-support] -default-features = false -version = '2.0.0-alpha.6' - -[dependencies.frame-system] -default-features = false -version = '2.0.0-alpha.6' -[dev-dependencies.sp-core] -default-features = false -version = '2.0.0-alpha.6' - -[dev-dependencies.sp-io] -default-features = false -version = '2.0.0-alpha.6' - - - -[features] -default = ['std'] -std = [ - 'codec/std', - 'frame-support/std', - 'frame-system/std', - "sp-io/std", - "sp-runtime/std", - 'sp-std/std', -] --- a/pallets/template/src/lib.rs +++ /dev/null @@ -1,316 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std)] - -/// A FRAME pallet template with necessary imports - -/// Feel free to remove or edit this file as needed. -/// If you change the name of this file, make sure to update its references in runtime/src/lib.rs -/// If you remove this file, you can remove those references - -/// For more guidance on Substrate FRAME, see the example pallet -/// https://github.com/paritytech/substrate/blob/master/frame/example/src/lib.rs - -use frame_support::{ - dispatch::DispatchResult, decl_module, decl_storage, decl_event, - weights::{DispatchClass, ClassifyDispatch, WeighData, Weight}, - ensure, -}; -use frame_system::{self as system, ensure_signed }; -use codec::{Encode, Decode}; -use sp_runtime::sp_std::prelude::Vec; - -#[cfg(test)] -mod mock; - -#[cfg(test)] -mod tests; - -#[derive(Encode, Decode, Default, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Debug))] -pub struct CollectionType { - pub owner: AccountId, - pub next_item_id: u64, - pub custom_data_size: u32, -} - -#[derive(Encode, Decode, Default, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Debug))] -pub struct CollectionAdminsType { - pub admin: AccountId, - pub collection_id: u64, -} - -#[derive(Encode, Decode, Default, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Debug))] -pub struct NftItemType { - pub collection: u64, - pub owner: AccountId, - pub data: Vec, -} - -/// The pallet's configuration trait. -pub trait Trait: system::Trait { - // Add other types and constants required to configure this pallet. - - /// The overarching event type. - type Event: From> + Into<::Event>; -} - -// This pallet's storage items. -decl_storage! { - // It is important to update your storage name so that your pallet's - // storage items are isolated from other pallets. - trait Store for Module as TemplateModule { - - /// Next available collection ID - pub NextCollectionID get(next_collection_id): u64; - - /// Collection map - pub Collection get(collection): map hasher(blake2_128_concat) u64 => CollectionType; - - /// Admins map (collection) - pub AdminList get(admin_list_collection): map hasher(blake2_128_concat) u64 => Vec; - - /// Balance owner per collection map - pub Balance get(balance_count): map hasher(blake2_128_concat) (u64, T::AccountId) => u64; - - /// Item double map (collection) - pub ItemList get(item_id): map hasher(blake2_128_concat) (u64, u64) => NftItemType; - pub ItemListIndex get(item_index): map hasher(blake2_128_concat) u64 => u64; - } -} - -// The pallet's events -decl_event!( - pub enum Event where AccountId = ::AccountId { - Created(u32, AccountId), - } -); - -// The pallet's dispatchable functions. -decl_module! { - /// The module declaration. - pub struct Module for enum Call where origin: T::Origin { - - // Initializing events - // this is needed only if you are using events in your pallet - fn deposit_event() = default; - - // Initializing events - // this is needed only if you are using events in your module - // fn deposit_event() = default; - - // Create collection of NFT with given parameters - // - // @param customDataSz size of custom data in each collection item - // returns collection ID - - // Create collection of NFT with given parameters - // - // @param customDataSz size of custom data in each collection item - // returns collection ID - #[weight = frame_support::weights::SimpleDispatchInfo::default()] - pub fn create_collection(origin, custom_data_sz: u32) -> DispatchResult { - // Anyone can create a collection - let who = ensure_signed(origin)?; - - // Generate next collection ID - let next_id = Self::next_collection_id(); - ::put(next_id+1); - - // Create new collection - let new_collection = CollectionType { - owner: who, - next_item_id: next_id, - custom_data_size: custom_data_sz, - }; - - // Add new collection to map - >::insert(next_id, new_collection); - - Ok(()) - } - - #[weight = frame_support::weights::SimpleDispatchInfo::default()] - pub fn destroy_collection(origin, collection_id: u64) -> DispatchResult { - - let sender = ensure_signed(origin)?; - ensure!(>::contains_key(collection_id), "This collection does not exist"); - - let owner = >::get(collection_id).owner; - ensure!(sender == owner, "You do not own this collection"); - >::remove(collection_id); - - Ok(()) - } - - #[weight = frame_support::weights::SimpleDispatchInfo::default()] - pub fn change_collection_owner(origin, collection_id: u64, new_owner: T::AccountId) -> DispatchResult { - - let sender = ensure_signed(origin)?; - ensure!(>::contains_key(collection_id), "This collection does not exist"); - - let mut target_collection = >::get(collection_id); - ensure!(sender == target_collection.owner, "You do not own this collection"); - - target_collection.owner = new_owner; - >::insert(collection_id, target_collection); - - Ok(()) - } - - #[weight = frame_support::weights::SimpleDispatchInfo::default()] - pub fn add_collection_admin(origin, collection_id: u64, new_admin_id: T::AccountId) -> DispatchResult { - - let sender = ensure_signed(origin)?; - ensure!(>::contains_key(collection_id), "This collection does not exist"); - - let target_collection = >::get(collection_id); - let is_owner = sender == target_collection.owner; - - let no_perm_mes = "You do not have permissions to modify this collection"; - let exists = >::contains_key(collection_id); - - if !is_owner - { - ensure!(exists, no_perm_mes); - ensure!(>::get(collection_id).contains(&sender), no_perm_mes); - } - - let mut admin_arr: Vec = Vec::new(); - if exists - { - admin_arr = >::get(collection_id); - ensure!(!admin_arr.contains(&new_admin_id), "Account already has admin role"); - } - - admin_arr.push(new_admin_id); - >::insert(collection_id, admin_arr); - - Ok(()) - } - - #[weight = frame_support::weights::SimpleDispatchInfo::default()] - pub fn remove_collection_admin(origin, collection_id: u64, account_id: T::AccountId) -> DispatchResult { - - let sender = ensure_signed(origin)?; - ensure!(>::contains_key(collection_id), "This collection does not exist"); - - let target_collection = >::get(collection_id); - let is_owner = sender == target_collection.owner; - - let no_perm_mes = "You do not have permissions to modify this collection"; - let exists = >::contains_key(collection_id); - - if !is_owner - { - ensure!(exists, no_perm_mes); - ensure!(>::get(collection_id).contains(&sender), no_perm_mes); - } - - if exists - { - let mut admin_arr = >::get(collection_id); - admin_arr.retain(|i| *i != account_id); - >::insert(collection_id, admin_arr); - } - - Ok(()) - } - - #[weight = frame_support::weights::SimpleDispatchInfo::default()] - pub fn create_item(origin, collection_id: u64, properties: Vec) -> DispatchResult { - - let sender = ensure_signed(origin)?; - ensure!(>::contains_key(collection_id), "This collection does not exist"); - - let target_collection = >::get(collection_id); - ensure!(target_collection.custom_data_size >= properties.len() as u32, "Size of item is too large"); - let is_owner = sender == target_collection.owner; - - let no_perm_mes = "You do not have permissions to modify this collection"; - let exists = >::contains_key(collection_id); - - if !is_owner - { - ensure!(exists, no_perm_mes); - ensure!(>::get(collection_id).contains(&sender), no_perm_mes); - } - - let new_balance = >::get((collection_id, sender.clone())) + 1; - >::insert((collection_id, sender.clone()), new_balance); - - // Create new item - let new_item = NftItemType { - collection: collection_id, - owner: sender, - data: properties, - }; - - let current_index = ::get(collection_id); - ::insert(collection_id, current_index+1); - >::insert((collection_id, current_index), new_item); - - Ok(()) - } - - #[weight = frame_support::weights::SimpleDispatchInfo::default()] - pub fn burn_item(origin, collection_id: u64, item_id: u64) -> DispatchResult { - - let sender = ensure_signed(origin)?; - ensure!(>::contains_key(collection_id), "This collection does not exist"); - - let target_collection = >::get(collection_id); - let is_owner = sender == target_collection.owner; - - ensure!(>::contains_key((collection_id, item_id)), "Item does not exists"); - let item = >::get((collection_id, item_id)); - - if !is_owner - { - // check if item owner - if item.owner != sender - { - let no_perm_mes = "You do not have permissions to modify this collection"; - - ensure!(>::contains_key(collection_id), no_perm_mes); - ensure!(>::get(collection_id).contains(&sender), no_perm_mes); - } - } - >::remove((collection_id, item_id)); - - Ok(()) - } - - #[weight = frame_support::weights::SimpleDispatchInfo::default()] - pub fn transfer(origin, collection_id: u64, item_id: u64, new_owner: T::AccountId) -> DispatchResult { - - let sender = ensure_signed(origin)?; - ensure!(>::contains_key(collection_id), "This collection does not exist"); - - let target_collection = >::get(collection_id); - let is_owner = sender == target_collection.owner; - - ensure!(>::contains_key((collection_id, item_id)), "Item does not exists"); - let mut item = >::get((collection_id, item_id)); - - if !is_owner - { - // check if item owner - if item.owner != sender - { - let no_perm_mes = "You do not have permissions to modify this collection"; - - ensure!(>::contains_key(collection_id), no_perm_mes); - ensure!(>::get(collection_id).contains(&sender), no_perm_mes); - } - } - >::remove((collection_id, item_id)); - - // change owner - item.owner = new_owner; - >::insert((collection_id, item_id), item); - - Ok(()) - } - } -} \ No newline at end of file --- a/pallets/template/src/mock.rs +++ /dev/null @@ -1,56 +0,0 @@ -// Creating mock runtime here - -use crate::{Module, Trait}; -use sp_core::H256; -use frame_support::{impl_outer_origin, parameter_types, weights::Weight}; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, testing::Header, Perbill, -}; -use frame_system as system; - -impl_outer_origin! { - pub enum Origin for Test {} -} - -// For testing the pallet, we construct most of a mock runtime. This means -// first constructing a configuration type (`Test`) which `impl`s each of the -// configuration traits of pallets we want to use. -#[derive(Clone, Eq, PartialEq)] -pub struct Test; -parameter_types! { - pub const BlockHashCount: u64 = 250; - pub const MaximumBlockWeight: Weight = 1024; - pub const MaximumBlockLength: u32 = 2 * 1024; - pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75); -} -impl system::Trait for Test { - type Origin = Origin; - type Call = (); - type Index = u64; - type BlockNumber = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Header = Header; - type Event = (); - type BlockHashCount = BlockHashCount; - type MaximumBlockWeight = MaximumBlockWeight; - type MaximumBlockLength = MaximumBlockLength; - type AvailableBlockRatio = AvailableBlockRatio; - type Version = (); - type ModuleToIndex = (); - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); -} -impl Trait for Test { - type Event = (); -} -pub type TemplateModule = Module; - -// This function basically just builds a genesis storage key/value store according to -// our desired mockup. -pub fn new_test_ext() -> sp_io::TestExternalities { - system::GenesisConfig::default().build_storage::().unwrap().into() -} --- a/pallets/template/src/tests.rs +++ /dev/null @@ -1,24 +0,0 @@ -// Tests to be written here -use frame_support::{assert_ok, assert_noop}; - -// #[test] -// fn it_works_for_default_value() { -// new_test_ext().execute_with(|| { -// // Just a dummy test for the dummy function `do_something` -// // calling the `do_something` function with a value 42 -// assert_ok!(TemplateModule::do_something(Origin::signed(1), 42)); -// // asserting that the stored value is equal to what we stored -// assert_eq!(TemplateModule::something(), Some(42)); -// }); -// } - -// #[test] -// fn correct_error_for_none_value() { -// new_test_ext().execute_with(|| { -// // Ensure the correct error is thrown on None value -// assert_noop!( -// TemplateModule::cause_error(Origin::signed(1)), -// Error::::NoneValue -// ); -// }); -// } --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -95,10 +95,10 @@ package = 'frame-system' version = '2.0.0-alpha.6' -[dependencies.template] +[dependencies.nft] default-features = false -package = 'pallet-template' -path = '../pallets/template' +package = 'pallet-nft' +path = '../pallets/nft' version = '2.0.0-alpha.6' [dependencies.timestamp] @@ -120,7 +120,7 @@ edition = '2018' homepage = 'https://substrate.dev' license = 'Unlicense' -name = 'node-template-runtime' +name = 'nft-runtime' repository = 'https://github.com/paritytech/substrate/' version = '2.0.0-alpha.6' @@ -154,5 +154,5 @@ 'system/std', 'timestamp/std', 'transaction-payment/std', - 'template/std', + 'nft/std', ] --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -38,7 +38,7 @@ }; /// Importing a template pallet -pub use template; +pub use nft; /// An index to a block. pub type BlockNumber = u32; @@ -92,8 +92,8 @@ /// This runtime version. pub const VERSION: RuntimeVersion = RuntimeVersion { - spec_name: create_runtime_str!("node-template"), - impl_name: create_runtime_str!("node-template"), + spec_name: create_runtime_str!("nft"), + impl_name: create_runtime_str!("nft"), authoring_version: 1, spec_version: 1, impl_version: 1, @@ -222,7 +222,7 @@ } /// Used for the module template in `./template.rs` -impl template::Trait for Runtime { +impl nft::Trait for Runtime { type Event = Event; } @@ -241,7 +241,7 @@ TransactionPayment: transaction_payment::{Module, Storage}, Sudo: sudo::{Module, Call, Config, Storage, Event}, // Used for the module template in `./template.rs` - TemplateModule: template::{Module, Call, Storage, Event}, + Nft: nft::{Module, Call, Storage, Event}, } );