--- a/node/src/chain_spec.rs +++ b/node/src/chain_spec.rs @@ -1,8 +1,9 @@ -use nft_runtime::{ - AccountId, AuraConfig, BalancesConfig, GenesisConfig, GrandpaConfig, Signature, SudoConfig, - SystemConfig, WASM_BINARY, -}; -use nft_runtime::{ContractsConfig, ContractsSchedule}; +// use nft_runtime::{ +// AccountId, AuraConfig, BalancesConfig, GenesisConfig, GrandpaConfig, Signature, SudoConfig, +// SystemConfig, WASM_BINARY, +// }; +// use nft_runtime::{ContractsConfig, ContractsSchedule, NftConfig, CollectionType}; +use nft_runtime::*; use sc_service::ChainType; use sp_consensus_aura::sr25519::AuthorityId as AuraId; use sp_core::{sr25519, Pair, Public}; @@ -128,6 +129,25 @@ .collect(), }), sudo: Some(SudoConfig { key: root_key }), + nft: Some(NftConfig { + collection: vec![(1, CollectionType { + owner: get_account_id_from_seed::("Alice"), + mode: CollectionMode::NFT(50), + access: AccessMode::Normal, + decimal_points: 0, + name: vec!(), + description: vec!(), + token_prefix: vec!(), + custom_data_size: 50, + mint_mode: false, + offchain_schema: vec!(), + sponsor: get_account_id_from_seed::("Alice"), + unconfirmed_sponsor: get_account_id_from_seed::("Alice"), + })], + nft_item_id: vec!(), + fungible_item_id: vec!(), + refungible_item_id: vec!(), + }), contracts: Some(ContractsConfig { current_schedule: ContractsSchedule { enable_println, --- a/pallets/nft/src/lib.rs +++ b/pallets/nft/src/lib.rs @@ -1,7 +1,8 @@ #![cfg_attr(not(feature = "std"), no_std)] -/// For more guidance on Substrate FRAME, see the example pallet -/// https://github.com/paritytech/substrate/blob/master/frame/example/src/lib.rs +#[cfg(feature = "std")] +pub use serde::*; + use codec::{Decode, Encode}; pub use frame_support::{ construct_runtime, decl_event, decl_module, decl_storage, @@ -32,7 +33,6 @@ }, FixedPointOperand, FixedU128, }; -use sp_std::prelude::*; #[cfg(test)] mod mock; @@ -40,7 +40,8 @@ #[cfg(test)] mod tests; -#[derive(Encode, Decode, Debug, Eq, Clone, PartialEq)] +#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub enum CollectionMode { Invalid, // custom data size @@ -62,7 +63,8 @@ } } -#[derive(Encode, Decode, Debug, Clone, PartialEq)] +#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub enum AccessMode { Normal, WhiteList, @@ -79,15 +81,15 @@ } } -#[derive(Encode, Decode, Default, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Debug))] +#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct Ownership { pub owner: AccountId, pub fraction: u128, } -#[derive(Encode, Decode, Default, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Debug))] +#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct CollectionType { pub owner: AccountId, pub mode: CollectionMode, @@ -103,46 +105,46 @@ pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship } -#[derive(Encode, Decode, Default, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Debug))] +#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct CollectionAdminsType { pub admin: AccountId, pub collection_id: u64, } -#[derive(Encode, Decode, Default, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Debug))] +#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct NftItemType { pub collection: u64, pub owner: AccountId, pub data: Vec, } -#[derive(Encode, Decode, Default, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Debug))] +#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct FungibleItemType { pub collection: u64, pub owner: AccountId, pub value: u128, } -#[derive(Encode, Decode, Default, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Debug))] +#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct ReFungibleItemType { pub collection: u64, pub owner: Vec>, pub data: Vec, } -#[derive(Encode, Decode, Default, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Debug))] +#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct ApprovePermissions { pub approved: AccountId, pub amount: u64, } -#[derive(Encode, Decode, Default, Clone, PartialEq)] -#[cfg_attr(feature = "std", derive(Debug))] +#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct VestingItem { pub sender: AccountId, pub recipient: AccountId, @@ -165,7 +167,7 @@ ChainVersion: u64; ItemListIndex: map hasher(blake2_128_concat) u64 => u64; - pub Collection get(fn collection): map hasher(identity) u64 => CollectionType; + pub Collection get(fn collection) config(): 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; @@ -176,9 +178,9 @@ 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; + pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => NftItemType; + pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => FungibleItemType; + pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => ReFungibleItemType; /// Index list pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => Vec; @@ -187,6 +189,26 @@ pub ContractSponsor get(fn contract_sponsor): map hasher(identity) T::AccountId => T::AccountId; pub UnconfirmedContractSponsor get(fn unconfirmed_contract_sponsor): map hasher(identity) T::AccountId => T::AccountId; } + add_extra_genesis { + build(|config: &GenesisConfig| { + // Modification of storage + for (_num, _c) in &config.collection { + >::init_collection(_c); + } + + for (_num, _q, _i) in &config.nft_item_id { + >::init_nft_token(_i); + } + + for (_num, _q, _i) in &config.fungible_item_id { + >::init_fungible_token(_i); + } + + for (_num, _q, _i) in &config.refungible_item_id { + >::init_refungible_token(_i); + } + }) + } } decl_event!( @@ -567,9 +589,16 @@ pub fn transfer(origin, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64) -> DispatchResult { let sender = ensure_signed(origin)?; - Self::check_white_list(collection_id, sender.clone())?; - Self::check_white_list(collection_id, recipient.clone())?; + + // Check access and mint mode let target_collection = >::get(collection_id); + if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) { + + Self::check_white_list(collection_id, sender.clone())?; + Self::check_white_list(collection_id, recipient.clone())?; + ensure!(target_collection.access == AccessMode::WhiteList, "Collection must have WhiteList access"); + ensure!(target_collection.mint_mode == true, "Collection must be in mint mode"); + } match target_collection.mode { @@ -620,23 +649,28 @@ let sender = ensure_signed(origin)?; let approved_list_exists = >::contains_key(collection_id, (item_id, from.clone())); - ensure!(approved_list_exists, "Only approved addresses can call this method"); - - Self::check_white_list(collection_id, from.clone())?; - Self::check_white_list(collection_id, 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"); + // Check access and mint mode + let target_collection = >::get(collection_id); + if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) { + + Self::check_white_list(collection_id, sender.clone())?; + Self::check_white_list(collection_id, recipient.clone())?; + ensure!(target_collection.access == AccessMode::WhiteList, "Collection must have WhiteList access"); + ensure!(target_collection.mint_mode == true, "Collection must be in mint mode"); + } + // 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); - let target_collection = >::get(collection_id); match target_collection.mode { @@ -1114,6 +1148,80 @@ Ok(()) } + fn init_collection(item: &CollectionType){ + + // check params + assert!(item.decimal_points <= 4, "decimal_points parameter must be lower than 4"); + assert!(item.name.len() <= 64, "Collection name can not be longer than 63 char"); + assert!(item.name.len() <= 256, "Collection description can not be longer than 255 char"); + assert!(item.token_prefix.len() <= 16, "Token prefix can not be longer than 15 char"); + + // Generate next collection ID + let next_id = CreatedCollectionCount::get() + .checked_add(1) + .expect("collection id error"); + + CreatedCollectionCount::put(next_id); + } + + fn init_nft_token(item: &NftItemType){ + + 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()).unwrap(); + + ::insert(collection_id, current_index); + + // Update balance + let new_balance = >::get(collection_id, item_owner.clone()) + .checked_add(1) + .unwrap(); + >::insert(collection_id, item_owner.clone(), new_balance); + } + + fn init_fungible_token(item: &FungibleItemType){ + + let current_index = ::get(item.collection) + .checked_add(1) + .expect("Item list index id error"); + let owner = item.owner.clone(); + let value = item.value as u64; + + Self::add_token_index(item.collection, current_index, owner.clone()).unwrap(); + + ::insert(item.collection, current_index); + + // Update balance + let new_balance = >::get(item.collection, owner.clone()) + .checked_add(value) + .unwrap(); + >::insert(item.collection, owner.clone(), new_balance); + } + + fn init_refungible_token(item: &ReFungibleItemType){ + + let current_index = ::get(item.collection) + .checked_add(1) + .expect("Item list index id error"); + + 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()).unwrap(); + + ::insert(item.collection, current_index); + + // Update balance + let new_balance = >::get(item.collection, owner.clone()) + .checked_add(value) + .unwrap(); + >::insert(item.collection, owner.clone(), new_balance); + } + fn add_token_index(collection_id: u64, item_index: u64, owner: T::AccountId) -> DispatchResult { let list_exists = >::contains_key(collection_id, owner.clone()); if list_exists { --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -53,8 +53,10 @@ pub use timestamp::Call as TimestampCall; -/// Importing a nft pallet -pub use nft; +/// Re-export a nft pallet +/// TODO: Check this re-export. Is this safe and good style? +extern crate nft; +pub use nft::*; /// An index to a block. pub type BlockNumber = u32; @@ -318,7 +320,7 @@ Balances: balances::{Module, Call, Storage, Config, Event}, TransactionPayment: transaction_payment::{Module, Storage}, Sudo: sudo::{Module, Call, Config, Storage, Event}, - Nft: nft::{Module, Call, Storage, Event}, + Nft: nft::{Module, Call, Config, Storage, Event}, } );