From 54b2b64fced36815a4c90ebb92274539997aeb75 Mon Sep 17 00:00:00 2001 From: Muhammad-Jibril Date: Tue, 31 Aug 2021 04:02:04 +0800 Subject: [PATCH 1/4] update NFT --- lib-serml/tokens/nft/Cargo.toml | 5 +- lib-serml/tokens/nft/src/benchmarking.rs | 139 +++--- lib-serml/tokens/nft/src/lib.rs | 226 +++++++--- lib-serml/tokens/nft/src/mock.rs | 129 +++++- lib-serml/tokens/nft/src/tests.rs | 529 ++++++++++++++++------- lib-serml/tokens/nft/src/weights.rs | 38 +- 6 files changed, 760 insertions(+), 306 deletions(-) diff --git a/lib-serml/tokens/nft/Cargo.toml b/lib-serml/tokens/nft/Cargo.toml index 08adc1555..0b3353f8e 100644 --- a/lib-serml/tokens/nft/Cargo.toml +++ b/lib-serml/tokens/nft/Cargo.toml @@ -21,12 +21,11 @@ pallet-proxy = { default-features = false, git = "https://github.com/paritytech/ pallet-timestamp = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8" } # orml dependencies -orml-traits = { path = "../../../lib-openrml/traits", default-features = false } orml-nft = { path = "../../../lib-openrml/nft", default-features = false } +orml-traits = { path = "../../../lib-openrml/traits", default-features = false } # local dependencies primitives = { package = "setheum-primitives", path = "../../../primitives", default-features = false } -setheum-currencies = { path = "../../tokens/currencies", default-features = false } [dev-dependencies] sp-core = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8" } @@ -36,6 +35,7 @@ pallet-utility = { default-features = false, git = "https://github.com/paritytec setheum-currencies = { path = "../../../lib-serml/tokens/currencies", default-features = false } orml-tokens = { path = "../../../lib-openrml/tokens", default-features = false } support = { package = "setheum-support", path = "../../support" } +setheum-currencies = { path = "../../tokens/currencies", default-features = false } [features] default = ["std"] @@ -58,5 +58,6 @@ runtime-benchmarks = [ "frame-benchmarking", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", + "primitives/runtime-benchmarks" ] try-runtime = ["frame-support/try-runtime"] diff --git a/lib-serml/tokens/nft/src/benchmarking.rs b/lib-serml/tokens/nft/src/benchmarking.rs index dc0bb843f..f6b306e79 100644 --- a/lib-serml/tokens/nft/src/benchmarking.rs +++ b/lib-serml/tokens/nft/src/benchmarking.rs @@ -24,7 +24,7 @@ use sp_std::prelude::*; use sp_std::vec; use frame_benchmarking::{account, benchmarks}; -use frame_support::{traits::Get, weights::DispatchClass}; +use frame_support::{dispatch::DispatchErrorWithPostInfo, traits::Get, weights::DispatchClass}; use frame_system::RawOrigin; use sp_runtime::traits::{AccountIdConversion, StaticLookup, UniqueSaturatedInto}; @@ -40,14 +40,47 @@ fn dollar(d: u32) -> Balance { d.saturating_mul(1_000_000_000_000_000_000) } +fn test_attr() -> Attributes { + let mut attr: Attributes = BTreeMap::new(); + for i in 0..30 { + attr.insert(vec![i], vec![0; 64]); + } + attr +} + +fn create_token_class(caller: T::AccountId) -> Result { + let base_currency_amount = dollar(1000); + ::Currency::make_free_balance_be(&caller, base_currency_amount.unique_saturated_into()); + + let module_account: T::AccountId = T::PalletId::get().into_sub_account(orml_nft::Pallet::::next_class_id()); + crate::Pallet::::create_class( + RawOrigin::Signed(caller).into(), + vec![1], + Properties( + ClassProperty::Transferable + | ClassProperty::Burnable + | ClassProperty::Mintable + | ClassProperty::ClassPropertiesMutable, + ), + test_attr(), + )?; + + ::Currency::make_free_balance_be( + &module_account, + base_currency_amount.unique_saturated_into(), + ); + + Ok(module_account) +} + benchmarks! { // create NFT class create_class { let caller: T::AccountId = account("caller", 0, SEED); let base_currency_amount = dollar(1000); - T::Currency::make_free_balance_be(&caller, base_currency_amount.unique_saturated_into()); - }: _(RawOrigin::Signed(caller), vec![1], Properties(ClassProperty::Transferable | ClassProperty::Burnable)) + ::Currency::make_free_balance_be(&caller, base_currency_amount.unique_saturated_into()); + }: _(RawOrigin::Signed(caller), vec![1], Properties(ClassProperty::Transferable | ClassProperty::Burnable), test_attr()) // mint NFT token mint { @@ -57,13 +90,8 @@ benchmarks! { let to: T::AccountId = account("to", 0, SEED); let to_lookup = T::Lookup::unlookup(to); - let base_currency_amount = dollar(1000); - T::Currency::make_free_balance_be(&caller, base_currency_amount.unique_saturated_into()); - - let module_account: T::AccountId = T::PalletId::get().into_sub_account(orml_nft::Pallet::::next_class_id()); - crate::Pallet::::create_class(RawOrigin::Signed(caller).into(), vec![1], Properties(ClassProperty::Transferable | ClassProperty::Burnable))?; - T::Currency::make_free_balance_be(&module_account, base_currency_amount.unique_saturated_into()); - }: _(RawOrigin::Signed(module_account), to_lookup, 0u32.into(), vec![1], i) + let module_account = create_token_class::(caller)?; + }: _(RawOrigin::Signed(module_account), to_lookup, 0u32.into(), vec![1], test_attr(), i) // transfer NFT token to another account transfer { @@ -72,13 +100,9 @@ benchmarks! { let to: T::AccountId = account("to", 0, SEED); let to_lookup = T::Lookup::unlookup(to.clone()); - let base_currency_amount = dollar(1000); - T::Currency::make_free_balance_be(&caller, base_currency_amount.unique_saturated_into()); + let module_account = create_token_class::(caller)?; - let module_account: T::AccountId = T::PalletId::get().into_sub_account(orml_nft::Pallet::::next_class_id()); - crate::Pallet::::create_class(RawOrigin::Signed(caller).into(), vec![1], Properties(ClassProperty::Transferable | ClassProperty::Burnable))?; - T::Currency::make_free_balance_be(&module_account, base_currency_amount.unique_saturated_into()); - crate::Pallet::::mint(RawOrigin::Signed(module_account).into(), to_lookup, 0u32.into(), vec![1], 1)?; + crate::Pallet::::mint(RawOrigin::Signed(module_account).into(), to_lookup, 0u32.into(), vec![1], test_attr(), 1)?; }: _(RawOrigin::Signed(to), caller_lookup, (0u32.into(), 0u32.into())) // burn NFT token @@ -87,13 +111,9 @@ benchmarks! { let to: T::AccountId = account("to", 0, SEED); let to_lookup = T::Lookup::unlookup(to.clone()); - let base_currency_amount = dollar(1000); - T::Currency::make_free_balance_be(&caller, base_currency_amount.unique_saturated_into()); + let module_account = create_token_class::(caller)?; - let module_account: T::AccountId = T::PalletId::get().into_sub_account(orml_nft::Pallet::::next_class_id()); - crate::Pallet::::create_class(RawOrigin::Signed(caller).into(), vec![1], Properties(ClassProperty::Transferable | ClassProperty::Burnable))?; - T::Currency::make_free_balance_be(&module_account, base_currency_amount.unique_saturated_into()); - crate::Pallet::::mint(RawOrigin::Signed(module_account).into(), to_lookup, 0u32.into(), vec![1], 1)?; + crate::Pallet::::mint(RawOrigin::Signed(module_account).into(), to_lookup, 0u32.into(), vec![1], test_attr(), 1)?; }: _(RawOrigin::Signed(to), (0u32.into(), 0u32.into())) // burn NFT token with remark @@ -104,13 +124,9 @@ benchmarks! { let to: T::AccountId = account("to", 0, SEED); let to_lookup = T::Lookup::unlookup(to.clone()); - let base_currency_amount = dollar(1000); - T::Currency::make_free_balance_be(&caller, base_currency_amount.unique_saturated_into()); + let module_account = create_token_class::(caller)?; - let module_account: T::AccountId = T::PalletId::get().into_sub_account(orml_nft::Pallet::::next_class_id()); - crate::Pallet::::create_class(RawOrigin::Signed(caller).into(), vec![1], Properties(ClassProperty::Transferable | ClassProperty::Burnable))?; - T::Currency::make_free_balance_be(&module_account, base_currency_amount.unique_saturated_into()); - crate::Pallet::::mint(RawOrigin::Signed(module_account).into(), to_lookup, 0u32.into(), vec![1], 1)?; + crate::Pallet::::mint(RawOrigin::Signed(module_account).into(), to_lookup, 0u32.into(), vec![1], test_attr(), 1)?; }: _(RawOrigin::Signed(to), (0u32.into(), 0u32.into()), remark_message) // destroy NFT class @@ -120,11 +136,17 @@ benchmarks! { let base_currency_amount = dollar(1000); - T::Currency::make_free_balance_be(&caller, base_currency_amount.unique_saturated_into()); + let module_account = create_token_class::(caller)?; - let module_account: T::AccountId = T::PalletId::get().into_sub_account(orml_nft::Pallet::::next_class_id()); - crate::Pallet::::create_class(RawOrigin::Signed(caller).into(), vec![1], Properties(ClassProperty::Transferable | ClassProperty::Burnable))?; }: _(RawOrigin::Signed(module_account), 0u32.into(), caller_lookup) + + update_class_properties { + let caller: T::AccountId = account("caller", 0, SEED); + let to: T::AccountId = account("to", 0, SEED); + let to_lookup = T::Lookup::unlookup(to); + + let module_account = create_token_class::(caller)?; + }: _(RawOrigin::Signed(module_account), 0u32.into(), Properties(ClassProperty::Transferable.into())) } #[cfg(test)] @@ -182,6 +204,7 @@ mod mock { } parameter_types! { pub const ExistentialDeposit: u64 = 1; + pub const MaxReserves: u32 = 50; } impl pallet_balances::Config for Runtime { type Balance = Balance; @@ -190,6 +213,8 @@ mod mock { type ExistentialDeposit = ExistentialDeposit; type AccountStore = frame_system::Pallet; type MaxLocks = (); + type MaxReserves = MaxReserves; + type ReserveIdentifier = ReserveIdentifier; type WeightInfo = (); } impl pallet_utility::Config for Runtime { @@ -257,13 +282,19 @@ mod mock { parameter_types! { pub const CreateClassDeposit: Balance = 200; pub const CreateTokenDeposit: Balance = 100; + pub const DataDepositPerByte: Balance = 10; pub const NftPalletId: PalletId = PalletId(*b"set/sNFT"); + pub MaxAttributesBytes: u32 = 2048; } + impl crate::Config for Runtime { type Event = (); + type Currency = Balances; type CreateClassDeposit = CreateClassDeposit; type CreateTokenDeposit = CreateTokenDeposit; + type DataDepositPerByte = DataDepositPerByte; type PalletId = NftPalletId; + type MaxAttributesBytes = MaxAttributesBytes; type WeightInfo = (); } @@ -314,49 +345,9 @@ mod mock { #[cfg(test)] mod tests { + use super::mock::*; use super::*; - use frame_support::assert_ok; - use mock::{new_test_ext, Runtime}; - - #[test] - fn test_create_class() { - new_test_ext().execute_with(|| { - assert_ok!(test_benchmark_create_class::()); - }); - } - - #[test] - fn test_mint() { - new_test_ext().execute_with(|| { - assert_ok!(test_benchmark_mint::()); - }); - } + use frame_benchmarking::impl_benchmark_test_suite; - #[test] - fn test_transfer() { - new_test_ext().execute_with(|| { - assert_ok!(test_benchmark_transfer::()); - }); - } - - #[test] - fn test_burn() { - new_test_ext().execute_with(|| { - assert_ok!(test_benchmark_burn::()); - }); - } - - #[test] - fn test_burn_with_remark() { - new_test_ext().execute_with(|| { - assert_ok!(test_benchmark_burn_with_remark::()); - }); - } - - #[test] - fn test_destroy_class() { - new_test_ext().execute_with(|| { - assert_ok!(test_benchmark_destroy_class::()); - }); - } + impl_benchmark_test_suite!(Pallet, super::new_test_ext(), super::Runtime,); } diff --git a/lib-serml/tokens/nft/src/lib.rs b/lib-serml/tokens/nft/src/lib.rs index 7f2ea43c3..ed7f8afff 100644 --- a/lib-serml/tokens/nft/src/lib.rs +++ b/lib-serml/tokens/nft/src/lib.rs @@ -24,23 +24,24 @@ use enumflags2::BitFlags; use frame_support::{ pallet_prelude::*, + require_transactional, traits::{ Currency, ExistenceRequirement::{AllowDeath, KeepAlive}, - ReservableCurrency, + NamedReservableCurrency, }, transactional, PalletId, }; use frame_system::pallet_prelude::*; use orml_traits::NFT; -use primitives::NFTBalance; +use primitives::{NFTBalance, ReserveIdentifier}; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; use sp_runtime::{ traits::{AccountIdConversion, Hash, Saturating, StaticLookup, Zero}, DispatchResult, RuntimeDebug, }; -use sp_std::vec::Vec; +use sp_std::{collections::btree_map::BTreeMap, prelude::*}; pub mod benchmarking; mod mock; @@ -51,14 +52,19 @@ pub use module::*; pub use weights::WeightInfo; pub type CID = Vec; +pub type Attributes = BTreeMap, Vec>; #[repr(u8)] #[derive(Encode, Decode, Clone, Copy, BitFlags, RuntimeDebug, PartialEq, Eq)] pub enum ClassProperty { - /// Token can be transferred + /// Is token transferable Transferable = 0b00000001, - /// Token can be burned + /// Is token burnable Burnable = 0b00000010, + /// Is minting new tokens allowed + Mintable = 0b00000100, + /// Is class properties mutable + ClassPropertiesMutable = 0b00001000, } #[derive(Clone, Copy, PartialEq, Default, RuntimeDebug)] @@ -83,17 +89,21 @@ impl Decode for Properties { #[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct ClassData { - /// The minimum balance to create class + /// Deposit reserved to create token class pub deposit: Balance, - /// Property of token + /// Class properties pub properties: Properties, + /// Class attributes + pub attributes: Attributes, } #[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct TokenData { - /// The minimum balance to create token + /// Deposit reserved to create token pub deposit: Balance, + /// Token attributes + pub attributes: Attributes, } pub type TokenIdOf = ::TokenId; @@ -105,6 +115,8 @@ pub type BalanceOf = pub mod module { use super::*; + pub const RESERVE_ID: ReserveIdentifier = ReserveIdentifier::Nft; + #[pallet::config] pub trait Config: frame_system::Config @@ -113,6 +125,13 @@ pub mod module { { type Event: From> + IsType<::Event>; + /// Currency type for reserve balance. + type Currency: NamedReservableCurrency< + Self::AccountId, + Balance = BalanceOf, + ReserveIdentifier = ReserveIdentifier, + >; + /// The minimum balance to create class #[pallet::constant] type CreateClassDeposit: Get>; @@ -121,10 +140,18 @@ pub mod module { #[pallet::constant] type CreateTokenDeposit: Get>; + /// Deposit required for per byte. + #[pallet::constant] + type DataDepositPerByte: Get>; + /// The NFT's module id #[pallet::constant] type PalletId: Get; + /// Maximum number of bytes in attributes + #[pallet::constant] + type MaxAttributesBytes: Get; + /// Weight information for the extrinsics in this module. type WeightInfo: WeightInfo; } @@ -143,13 +170,20 @@ pub mod module { NonTransferable, /// Property of class don't support burn NonBurnable, + /// Property of class don't support mint + NonMintable, /// Can not destroy class /// Total issuance is not 0 CannotDestroyClass, + /// Cannot perform mutable action + Immutable, + /// Attributes too large + AttributesTooLarge, } #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] + #[pallet::metadata(T::AccountId = "AccountId", ClassIdOf = "ClassId", TokenIdOf = "TokenId", T::Hash = "Hash")] pub enum Event { /// Created NFT class. \[owner, class_id\] CreatedClass(T::AccountId, ClassIdOf), @@ -179,26 +213,34 @@ pub mod module { /// - `properties`: class property, include `Transferable` `Burnable` #[pallet::weight(::WeightInfo::create_class())] #[transactional] - pub fn create_class(origin: OriginFor, metadata: CID, properties: Properties) -> DispatchResultWithPostInfo { + pub fn create_class( + origin: OriginFor, + metadata: CID, + properties: Properties, + attributes: Attributes, + ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; let next_id = orml_nft::Pallet::::next_class_id(); let owner: T::AccountId = T::PalletId::get().into_sub_account(next_id); let class_deposit = T::CreateClassDeposit::get(); + let data_deposit = Self::data_deposit(&metadata, &attributes)?; let proxy_deposit = >::deposit(1u32); - let total_deposit = proxy_deposit.saturating_add(class_deposit); + let deposit = class_deposit.saturating_add(data_deposit); + let total_deposit = proxy_deposit.saturating_add(deposit); - // ensure enough token for proxy deposit + class deposit - T::Currency::transfer(&who, &owner, total_deposit, KeepAlive)?; + // ensure enough token for proxy deposit + class deposit + data deposit + ::Currency::transfer(&who, &owner, total_deposit, KeepAlive)?; - T::Currency::reserve(&owner, class_deposit)?; + ::Currency::reserve_named(&RESERVE_ID, &owner, deposit)?; // owner add proxy delegate to origin >::add_proxy_delegate(&owner, who, Default::default(), Zero::zero())?; let data = ClassData { - deposit: class_deposit, + deposit, properties, + attributes, }; orml_nft::Pallet::::create_class(&owner, metadata, data)?; @@ -219,28 +261,12 @@ pub mod module { to: ::Source, class_id: ClassIdOf, metadata: CID, + attributes: Attributes, quantity: u32, - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { let who = ensure_signed(origin)?; let to = T::Lookup::lookup(to)?; - ensure!(quantity >= 1, Error::::InvalidQuantity); - let class_info = orml_nft::Pallet::::classes(class_id).ok_or(Error::::ClassIdNotFound)?; - ensure!(who == class_info.owner, Error::::NoPermission); - let deposit = T::CreateTokenDeposit::get(); - let total_deposit = deposit.saturating_mul(quantity.into()); - - // `repatriate_reserved` will check `to` account exist and may return - // `DeadAccount`. - T::Currency::transfer(&who, &to, total_deposit, KeepAlive)?; - T::Currency::reserve(&to, total_deposit)?; - - let data = TokenData { deposit }; - for _ in 0..quantity { - orml_nft::Pallet::::mint(&to, class_id, metadata.clone(), data.clone())?; - } - - Self::deposit_event(Event::MintedToken(who, to, class_id, quantity)); - Ok(().into()) + Self::do_mint(who, to, class_id, metadata, attributes, quantity) } /// Transfer NFT token to another account @@ -253,11 +279,10 @@ pub mod module { origin: OriginFor, to: ::Source, token: (ClassIdOf, TokenIdOf), - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { let who = ensure_signed(origin)?; let to = T::Lookup::lookup(to)?; - Self::do_transfer(&who, &to, token)?; - Ok(().into()) + Self::do_transfer(&who, &to, token) } /// Burn NFT token @@ -265,11 +290,9 @@ pub mod module { /// - `token`: (class_id, token_id) #[pallet::weight(::WeightInfo::burn())] #[transactional] - pub fn burn(origin: OriginFor, token: (ClassIdOf, TokenIdOf)) -> DispatchResultWithPostInfo { + pub fn burn(origin: OriginFor, token: (ClassIdOf, TokenIdOf)) -> DispatchResult { let who = ensure_signed(origin)?; - Self::do_burn(&who, token)?; - Self::deposit_event(Event::BurnedToken(who, token.0, token.1)); - Ok(().into()) + Self::do_burn(who, token, None) } /// Burn NFT token @@ -282,12 +305,9 @@ pub mod module { origin: OriginFor, token: (ClassIdOf, TokenIdOf), remark: Vec, - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { let who = ensure_signed(origin)?; - Self::do_burn(&who, token)?; - let hash = T::Hashing::hash(&remark[..]); - Self::deposit_event(Event::BurnedTokenWithRemark(who, token.0, token.1, hash)); - Ok(().into()) + Self::do_burn(who, token, Some(remark)) } /// Destroy NFT class, remove dest from proxy, and send all the free @@ -313,24 +333,57 @@ pub mod module { let data = class_info.data; - T::Currency::unreserve(&who, data.deposit); + ::Currency::unreserve_named(&RESERVE_ID, &who, data.deposit); orml_nft::Pallet::::destroy_class(&who, class_id)?; // this should unresere proxy deposit pallet_proxy::Pallet::::remove_proxy_delegate(&who, dest.clone(), Default::default(), Zero::zero())?; - T::Currency::transfer(&who, &dest, T::Currency::free_balance(&who), AllowDeath)?; + ::Currency::transfer( + &who, + &dest, + ::Currency::free_balance(&who), + AllowDeath, + )?; Self::deposit_event(Event::DestroyedClass(who, class_id)); Ok(().into()) } + + /// Update NFT class properties. The current class properties must contains + /// ClassPropertiesMutable. + /// + /// - `class_id`: The class ID to update + /// - `properties`: The new properties + #[pallet::weight(::WeightInfo::update_class_properties())] + #[transactional] + pub fn update_class_properties( + origin: OriginFor, + class_id: ClassIdOf, + properties: Properties, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + orml_nft::Classes::::try_mutate(class_id, |class_info| { + let class_info = class_info.as_mut().ok_or(Error::::ClassIdNotFound)?; + ensure!(who == class_info.owner, Error::::NoPermission); + + let mut data = &mut class_info.data; + ensure!( + data.properties.0.contains(ClassProperty::ClassPropertiesMutable), + Error::::Immutable + ); + + data.properties = properties; + + Ok(()) + }) + } } } impl Pallet { - /// Ensured atomic. - #[transactional] + #[require_transactional] fn do_transfer(from: &T::AccountId, to: &T::AccountId, token: (ClassIdOf, TokenIdOf)) -> DispatchResult { let class_info = orml_nft::Pallet::::classes(token.0).ok_or(Error::::ClassIdNotFound)?; let data = class_info.data; @@ -343,17 +396,51 @@ impl Pallet { orml_nft::Pallet::::transfer(from, to, token)?; - T::Currency::unreserve(&from, token_info.data.deposit); - T::Currency::transfer(&from, &to, token_info.data.deposit, AllowDeath)?; - T::Currency::reserve(&to, token_info.data.deposit)?; + ::Currency::unreserve_named(&RESERVE_ID, &from, token_info.data.deposit); + ::Currency::transfer(&from, &to, token_info.data.deposit, AllowDeath)?; + ::Currency::reserve_named(&RESERVE_ID, &to, token_info.data.deposit)?; Self::deposit_event(Event::TransferredToken(from.clone(), to.clone(), token.0, token.1)); Ok(()) } - /// Ensured atomic. - #[transactional] - fn do_burn(who: &T::AccountId, token: (ClassIdOf, TokenIdOf)) -> DispatchResult { + #[require_transactional] + fn do_mint( + who: T::AccountId, + to: T::AccountId, + class_id: ClassIdOf, + metadata: CID, + attributes: Attributes, + quantity: u32, + ) -> DispatchResult { + ensure!(quantity >= 1, Error::::InvalidQuantity); + let class_info = orml_nft::Pallet::::classes(class_id).ok_or(Error::::ClassIdNotFound)?; + ensure!(who == class_info.owner, Error::::NoPermission); + + ensure!( + class_info.data.properties.0.contains(ClassProperty::Mintable), + Error::::NonMintable + ); + + let data_deposit = Self::data_deposit(&metadata, &attributes)?; + let deposit = T::CreateTokenDeposit::get().saturating_add(data_deposit); + let total_deposit = deposit.saturating_mul(quantity.into()); + + // `repatriate_reserved` will check `to` account exist and may return + // `DeadAccount`. + ::Currency::transfer(&who, &to, total_deposit, KeepAlive)?; + ::Currency::reserve_named(&RESERVE_ID, &to, total_deposit)?; + + let data = TokenData { deposit, attributes }; + for _ in 0..quantity { + orml_nft::Pallet::::mint(&to, class_id, metadata.clone(), data.clone())?; + } + + Self::deposit_event(Event::MintedToken(who, to, class_id, quantity)); + Ok(()) + } + + fn do_burn(who: T::AccountId, token: (ClassIdOf, TokenIdOf), remark: Option>) -> DispatchResult { let class_info = orml_nft::Pallet::::classes(token.0).ok_or(Error::::ClassIdNotFound)?; let data = class_info.data; ensure!( @@ -362,13 +449,36 @@ impl Pallet { ); let token_info = orml_nft::Pallet::::tokens(token.0, token.1).ok_or(Error::::TokenIdNotFound)?; - ensure!(*who == token_info.owner, Error::::NoPermission); + ensure!(who == token_info.owner, Error::::NoPermission); orml_nft::Pallet::::burn(&who, token)?; - T::Currency::unreserve(&who, token_info.data.deposit); + ::Currency::unreserve_named(&RESERVE_ID, &who, token_info.data.deposit); + + if let Some(remark) = remark { + let hash = T::Hashing::hash(&remark[..]); + Self::deposit_event(Event::BurnedTokenWithRemark(who, token.0, token.1, hash)); + } else { + Self::deposit_event(Event::BurnedToken(who, token.0, token.1)); + } + Ok(()) } + + fn data_deposit(metadata: &[u8], attributes: &Attributes) -> Result, DispatchError> { + // Addition can't overflow because we will be out of memory before that + let attributes_len = attributes.iter().fold(0, |acc, (k, v)| { + acc.saturating_add(v.len().saturating_add(k.len()) as u32) + }); + + ensure!( + attributes_len <= T::MaxAttributesBytes::get(), + Error::::AttributesTooLarge + ); + + let total_data_len = attributes_len.saturating_add(metadata.len() as u32); + Ok(T::DataDepositPerByte::get().saturating_mul(total_data_len.into())) + } } impl NFT for Pallet { @@ -377,7 +487,7 @@ impl NFT for Pallet { type Balance = NFTBalance; fn balance(who: &T::AccountId) -> Self::Balance { - orml_nft::TokensByOwner::::iter_prefix(who).count() as u128 + orml_nft::TokensByOwner::::iter_prefix((who,)).count() as u128 } fn owner(token: (Self::ClassId, Self::TokenId)) -> Option { diff --git a/lib-serml/tokens/nft/src/mock.rs b/lib-serml/tokens/nft/src/mock.rs index 1b1ab1655..a08de4086 100644 --- a/lib-serml/tokens/nft/src/mock.rs +++ b/lib-serml/tokens/nft/src/mock.rs @@ -28,13 +28,13 @@ use frame_support::{ RuntimeDebug, }; use orml_traits::parameter_type_with_key; -use primitives::{Amount, Balance, BlockNumber, CurrencyId, TokenSymbol}; +use primitives::{Amount, Balance, BlockNumber, CurrencyId, ReserveIdentifier, TokenSymbol}; use sp_core::{crypto::AccountId32, H256}; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, }; -use support::mocks::MockAddressMapping; +use support::{mocks::MockAddressMapping, SerpTreasury}; parameter_types! { pub const BlockHashCount: u64 = 250; @@ -69,6 +69,7 @@ impl frame_system::Config for Runtime { } parameter_types! { pub const ExistentialDeposit: u64 = 1; + pub const MaxReserves: u32 = 50; } impl pallet_balances::Config for Runtime { type Balance = Balance; @@ -77,6 +78,8 @@ impl pallet_balances::Config for Runtime { type ExistentialDeposit = ExistentialDeposit; type AccountStore = frame_system::Pallet; type MaxLocks = (); + type MaxReserves = MaxReserves; + type ReserveIdentifier = ReserveIdentifier; type WeightInfo = (); } impl pallet_utility::Config for Runtime { @@ -167,8 +170,126 @@ parameter_types! { pub const GetNativeCurrencyId: CurrencyId = NATIVE_CURRENCY_ID; } +pub struct MockSerpTreasury; +impl SerpTreasury for MockSerpTreasury { + type Balance = Balance; + type CurrencyId = CurrencyId; + + /// SerpUp ratio for BuyBack Swaps to burn Dinar + fn get_buyback_serpup( + _amount: Balance, + _currency_id: CurrencyId, + _min_target_amount: Balance + ) -> DispatchResult { + unimplemented!() + } + + /// SerpUp ratio for Setheum Foundation's Charity Fund + fn get_charity_fund_serpup( + _amount: Balance, + _currency_id: CurrencyId + ) -> DispatchResult { + unimplemented!() + } + + /// SerpUp ratio for SettPay Cashdrops + fn get_cashdrop_serpup( + _amount: Balance, + _currency_id: CurrencyId + ) -> DispatchResult { + unimplemented!() + } + + /// Reward SETR cashdrop to vault + fn setter_cashdrop_to_vault() -> DispatchResult { + unimplemented!() + } + + /// Reward SETUSD cashdrop to vault + fn usdj_cashdrop_to_vault() -> DispatchResult { + unimplemented!() + } + + /// issue serpup surplus(stable currencies) to their destinations according to the serpup_ratio. + fn on_serpup( + _currency_id: CurrencyId, + _amount: Balance, + _min_target_amount: Balance + ) -> DispatchResult { + unimplemented!() + } + + /// buy back and burn surplus(stable currencies) with swap by DEX. + fn on_serpdown( + _currency_id: CurrencyId, + _amount: Balance, + _max_supply_amount: Balance + ) -> DispatchResult { + unimplemented!() + } + + /// get the minimum supply of a setcurrency - by key + fn get_minimum_supply( + _currency_id: CurrencyId + ) -> Balance { + unimplemented!() + } + + /// issue standard to `who` + fn issue_standard( + _currency_id: CurrencyId, + _who: &AccountId, + _standard: Balance + ) -> DispatchResult { + unimplemented!() + } + + /// burn standard(stable currency) of `who` + fn burn_standard( + _currency_id: CurrencyId, + _who: &AccountId, + _standard: Balance + ) -> DispatchResult { + unimplemented!() + } + + /// issue setter of amount setter to `who` + fn issue_setter( + _who: &AccountId, + _setter: Balance + ) -> DispatchResult { + unimplemented!() + } + + /// burn setter of `who` + fn burn_setter( + _who: &AccountId, + _setter: Balance + ) -> DispatchResult { + unimplemented!() + } + + /// deposit reserve asset (Setter (SETR)) to serp treasury by `who` + fn deposit_setter( + _from: &AccountId, + _amount: Balance + ) -> DispatchResult { + unimplemented!() + } + + /// claim cashdrop of `currency_id` relative to `transfer_amount` for `who` + fn claim_cashdrop( + _currency_id: CurrencyId, + _who: &AccountId, + _transfer_amount: Balance + ) -> DispatchResult { + unimplemented!() + } +} + impl setheum_currencies::Config for Runtime { type Event = Event; + type SerpTreasury = MockSerpTreasury; type MultiCurrency = Tokens; type NativeCurrency = NativeCurrency; type GetNativeCurrencyId = GetNativeCurrencyId; @@ -180,13 +301,17 @@ impl setheum_currencies::Config for Runtime { parameter_types! { pub const CreateClassDeposit: Balance = 200; pub const CreateTokenDeposit: Balance = 100; + pub const DataDepositPerByte: Balance = 10; pub const NftPalletId: PalletId = PalletId(*b"set/sNFT"); + pub MaxAttributesBytes: u32 = 10; } impl Config for Runtime { type Event = Event; type CreateClassDeposit = CreateClassDeposit; type CreateTokenDeposit = CreateTokenDeposit; + type DataDepositPerByte = DataDepositPerByte; type PalletId = NftPalletId; + type MaxAttributesBytes = MaxAttributesBytes; type WeightInfo = (); } diff --git a/lib-serml/tokens/nft/src/tests.rs b/lib-serml/tokens/nft/src/tests.rs index 30dafff0f..0f0b29c85 100644 --- a/lib-serml/tokens/nft/src/tests.rs +++ b/lib-serml/tokens/nft/src/tests.rs @@ -41,58 +41,107 @@ fn class_id_account() -> AccountId { ::PalletId::get().into_sub_account(CLASS_ID) } +fn test_attr(x: u8) -> Attributes { + let mut attr: Attributes = BTreeMap::new(); + attr.insert(vec![x, x + 10], vec![x, x + 1, x + 2]); + attr.insert(vec![x + 1], vec![11]); + attr +} + +const TEST_ATTR_LEN: Balance = 7; + #[test] fn create_class_should_work() { ExtBuilder::default().build().execute_with(|| { - assert_ok!(SetheumNFT::create_class( + let metadata = vec![1]; + + assert_ok!(NFTModule::create_class( Origin::signed(ALICE), - vec![1], - Default::default() + metadata.clone(), + Default::default(), + test_attr(1), )); - System::assert_last_event(Event::nft(crate::Event::CreatedClass(class_id_account(), CLASS_ID))); + System::assert_last_event(Event::NFTModule(crate::Event::CreatedClass( + class_id_account(), + CLASS_ID, + ))); + + let cls_deposit = + CreateClassDeposit::get() + DataDepositPerByte::get() * ((metadata.len() as u128) + TEST_ATTR_LEN); + assert_eq!( reserved_balance(&class_id_account()), - ::CreateClassDeposit::get() + Proxy::deposit(1u32) + cls_deposit + Proxy::deposit(1u32), ); + + assert_eq!( + orml_nft::Pallet::::classes(0).unwrap().data, + ClassData { + deposit: cls_deposit, + properties: Default::default(), + attributes: test_attr(1), + } + ) }); } #[test] fn create_class_should_fail() { ExtBuilder::default().build().execute_with(|| { + let metadata = vec![1]; assert_noop!( - SetheumNFT::create_class( + NFTModule::create_class( Origin::signed(BOB), - vec![1], - Properties(ClassProperty::Transferable | ClassProperty::Burnable) + metadata.clone(), + Properties(ClassProperty::Transferable | ClassProperty::Burnable), + Default::default(), ), pallet_balances::Error::::InsufficientBalance ); + + let mut large_attr: Attributes = BTreeMap::new(); + large_attr.insert(vec![1, 2, 3, 4, 5], vec![6, 7, 8, 9, 10, 11]); + + assert_noop!( + NFTModule::create_class( + Origin::signed(ALICE), + metadata.clone(), + Properties(ClassProperty::Transferable | ClassProperty::Burnable), + large_attr, + ), + Error::::AttributesTooLarge + ); }); } #[test] fn mint_should_work() { ExtBuilder::default().build().execute_with(|| { - assert_ok!(SetheumNFT::create_class( + let metadata = vec![1]; + let metadata_2 = vec![2, 3]; + assert_ok!(NFTModule::create_class( Origin::signed(ALICE), - vec![1], - Properties(ClassProperty::Transferable | ClassProperty::Burnable) + metadata.clone(), + Properties(ClassProperty::Transferable | ClassProperty::Burnable | ClassProperty::Mintable), + test_attr(1), )); - System::assert_last_event(Event::nft(crate::Event::CreatedClass(class_id_account(), CLASS_ID))); - assert_eq!( - Balances::deposit_into_existing(&class_id_account(), 2 * ::CreateTokenDeposit::get()) - .is_ok(), - true - ); - assert_ok!(SetheumNFT::mint( + System::assert_last_event(Event::NFTModule(crate::Event::CreatedClass( + class_id_account(), + CLASS_ID, + ))); + assert_ok!(Balances::deposit_into_existing( + &class_id_account(), + 2 * (CreateTokenDeposit::get() + ((metadata_2.len() as u128 + TEST_ATTR_LEN) * DataDepositPerByte::get())) + )); + assert_ok!(NFTModule::mint( Origin::signed(class_id_account()), BOB, CLASS_ID, - vec![2], + metadata_2.clone(), + test_attr(2), 2 )); - System::assert_last_event(Event::nft(crate::Event::MintedToken( + System::assert_last_event(Event::NFTModule(crate::Event::MintedToken( class_id_account(), BOB, CLASS_ID, @@ -100,119 +149,185 @@ fn mint_should_work() { ))); assert_eq!( reserved_balance(&class_id_account()), - ::CreateClassDeposit::get() + Proxy::deposit(1u32) + CreateClassDeposit::get() + + Proxy::deposit(1u32) + + DataDepositPerByte::get() * (metadata.len() as u128 + TEST_ATTR_LEN) ); assert_eq!( reserved_balance(&BOB), - 2 * ::CreateTokenDeposit::get() + 2 * (CreateTokenDeposit::get() + DataDepositPerByte::get() * (metadata_2.len() as u128 + TEST_ATTR_LEN)) ); assert_eq!( orml_nft::Pallet::::tokens(0, 0).unwrap(), TokenInfo { - metadata: vec![2].try_into().unwrap(), + metadata: metadata_2.clone().try_into().unwrap(), owner: BOB, data: TokenData { - deposit: ::CreateTokenDeposit::get() + deposit: CreateTokenDeposit::get() + + DataDepositPerByte::get() * (metadata_2.len() as u128 + TEST_ATTR_LEN), + attributes: test_attr(2), } } ); assert_eq!( orml_nft::Pallet::::tokens(0, 1).unwrap(), TokenInfo { - metadata: vec![2].try_into().unwrap(), + metadata: metadata_2.clone().try_into().unwrap(), owner: BOB, data: TokenData { - deposit: ::CreateTokenDeposit::get() + deposit: CreateTokenDeposit::get() + + DataDepositPerByte::get() * (metadata_2.len() as u128 + TEST_ATTR_LEN), + attributes: test_attr(2), } } ); + assert_eq!( + orml_nft::TokensByOwner::::iter_prefix((BOB,)).collect::>(), + vec![((0, 1), ()), ((0, 0), ())] + ); }); } #[test] fn mint_should_fail() { ExtBuilder::default().build().execute_with(|| { - assert_ok!(SetheumNFT::create_class( + let metadata = vec![1]; + assert_ok!(NFTModule::create_class( Origin::signed(ALICE), - vec![1], - Properties(ClassProperty::Transferable | ClassProperty::Burnable) + metadata.clone(), + Properties(ClassProperty::Transferable | ClassProperty::Burnable | ClassProperty::Mintable), + Default::default(), )); assert_noop!( - SetheumNFT::mint(Origin::signed(ALICE), BOB, CLASS_ID_NOT_EXIST, vec![1], 2), + NFTModule::mint( + Origin::signed(ALICE), + BOB, + CLASS_ID_NOT_EXIST, + metadata.clone(), + Default::default(), + 2 + ), Error::::ClassIdNotFound ); assert_noop!( - SetheumNFT::mint(Origin::signed(BOB), BOB, CLASS_ID, vec![1], 0), + NFTModule::mint( + Origin::signed(BOB), + BOB, + CLASS_ID, + metadata.clone(), + Default::default(), + 0 + ), Error::::InvalidQuantity ); assert_noop!( - SetheumNFT::mint(Origin::signed(BOB), BOB, CLASS_ID, vec![1], 2), + NFTModule::mint( + Origin::signed(BOB), + BOB, + CLASS_ID, + metadata.clone(), + Default::default(), + 2 + ), Error::::NoPermission ); orml_nft::NextTokenId::::mutate(CLASS_ID, |id| { *id = ::TokenId::max_value() }); - assert_eq!( - Balances::deposit_into_existing(&class_id_account(), 2 * ::CreateTokenDeposit::get()) - .is_ok(), - true - ); + assert_ok!(Balances::deposit_into_existing( + &class_id_account(), + 2 * (CreateTokenDeposit::get() + DataDepositPerByte::get()) + )); assert_noop!( - SetheumNFT::mint(Origin::signed(class_id_account()), BOB, CLASS_ID, vec![1], 2), + NFTModule::mint( + Origin::signed(class_id_account()), + BOB, + CLASS_ID, + metadata.clone(), + Default::default(), + 2 + ), orml_nft::Error::::NoAvailableTokenId ); }); } #[test] -fn transfer_should_work() { +fn mint_should_fail_without_mintable() { ExtBuilder::default().build().execute_with(|| { - assert_ok!(SetheumNFT::create_class( + let metadata = vec![1]; + assert_ok!(NFTModule::create_class( Origin::signed(ALICE), - vec![1], - Properties(ClassProperty::Transferable | ClassProperty::Burnable) + metadata.clone(), + Default::default(), + Default::default(), )); - assert_eq!( - Balances::deposit_into_existing(&class_id_account(), 2 * ::CreateTokenDeposit::get()) - .is_ok(), - true + + assert_noop!( + NFTModule::mint( + Origin::signed(class_id_account()), + BOB, + CLASS_ID, + metadata.clone(), + Default::default(), + 2 + ), + Error::::NonMintable ); - assert_ok!(SetheumNFT::mint( + }); +} + +#[test] +fn transfer_should_work() { + ExtBuilder::default().build().execute_with(|| { + let metadata = vec![1]; + assert_ok!(NFTModule::create_class( + Origin::signed(ALICE), + metadata.clone(), + Properties(ClassProperty::Transferable | ClassProperty::Burnable | ClassProperty::Mintable), + Default::default(), + )); + assert_ok!(Balances::deposit_into_existing( + &class_id_account(), + 2 * (CreateTokenDeposit::get() + DataDepositPerByte::get()) + )); + assert_ok!(NFTModule::mint( Origin::signed(class_id_account()), BOB, CLASS_ID, - vec![1], + metadata.clone(), + Default::default(), 2 )); assert_eq!( reserved_balance(&BOB), - 2 * ::CreateTokenDeposit::get() + 2 * (CreateTokenDeposit::get() + DataDepositPerByte::get()) ); - assert_ok!(SetheumNFT::transfer(Origin::signed(BOB), ALICE, (CLASS_ID, TOKEN_ID))); - System::assert_last_event(Event::nft(crate::Event::TransferredToken( + assert_ok!(NFTModule::transfer(Origin::signed(BOB), ALICE, (CLASS_ID, TOKEN_ID))); + System::assert_last_event(Event::NFTModule(crate::Event::TransferredToken( BOB, ALICE, CLASS_ID, TOKEN_ID, ))); assert_eq!( reserved_balance(&BOB), - 1 * ::CreateTokenDeposit::get() + 1 * (CreateTokenDeposit::get() + DataDepositPerByte::get()) ); assert_eq!( reserved_balance(&ALICE), - 1 * ::CreateTokenDeposit::get() + 1 * (CreateTokenDeposit::get() + DataDepositPerByte::get()) ); - assert_ok!(SetheumNFT::transfer(Origin::signed(ALICE), BOB, (CLASS_ID, TOKEN_ID))); - System::assert_last_event(Event::nft(crate::Event::TransferredToken( + assert_ok!(NFTModule::transfer(Origin::signed(ALICE), BOB, (CLASS_ID, TOKEN_ID))); + System::assert_last_event(Event::NFTModule(crate::Event::TransferredToken( ALICE, BOB, CLASS_ID, TOKEN_ID, ))); assert_eq!( reserved_balance(&BOB), - 2 * ::CreateTokenDeposit::get() + 2 * (CreateTokenDeposit::get() + DataDepositPerByte::get()) ); assert_eq!(reserved_balance(&ALICE), 0); }); @@ -221,57 +336,61 @@ fn transfer_should_work() { #[test] fn transfer_should_fail() { ExtBuilder::default().build().execute_with(|| { - assert_ok!(SetheumNFT::create_class( + let metadata = vec![1]; + assert_ok!(NFTModule::create_class( Origin::signed(ALICE), - vec![1], - Properties(ClassProperty::Transferable | ClassProperty::Burnable) + metadata.clone(), + Properties(ClassProperty::Transferable | ClassProperty::Burnable | ClassProperty::Mintable), + Default::default(), )); - assert_eq!( - Balances::deposit_into_existing(&class_id_account(), 1 * ::CreateTokenDeposit::get()) - .is_ok(), - true - ); - assert_ok!(SetheumNFT::mint( + assert_ok!(Balances::deposit_into_existing( + &class_id_account(), + 1 * CreateTokenDeposit::get() + DataDepositPerByte::get() + )); + assert_ok!(NFTModule::mint( Origin::signed(class_id_account()), BOB, CLASS_ID, - vec![1], + metadata.clone(), + Default::default(), 1 )); assert_noop!( - SetheumNFT::transfer(Origin::signed(BOB), ALICE, (CLASS_ID_NOT_EXIST, TOKEN_ID)), + NFTModule::transfer(Origin::signed(BOB), ALICE, (CLASS_ID_NOT_EXIST, TOKEN_ID)), Error::::ClassIdNotFound ); assert_noop!( - SetheumNFT::transfer(Origin::signed(BOB), ALICE, (CLASS_ID, TOKEN_ID_NOT_EXIST)), + NFTModule::transfer(Origin::signed(BOB), ALICE, (CLASS_ID, TOKEN_ID_NOT_EXIST)), Error::::TokenIdNotFound ); assert_noop!( - SetheumNFT::transfer(Origin::signed(ALICE), BOB, (CLASS_ID, TOKEN_ID)), + NFTModule::transfer(Origin::signed(ALICE), BOB, (CLASS_ID, TOKEN_ID)), orml_nft::Error::::NoPermission ); }); ExtBuilder::default().build().execute_with(|| { - assert_ok!(SetheumNFT::create_class( + let metadata = vec![1]; + assert_ok!(NFTModule::create_class( Origin::signed(ALICE), - vec![1], - Default::default() + metadata.clone(), + Properties(ClassProperty::Mintable.into()), + Default::default(), )); - assert_eq!( - Balances::deposit_into_existing(&class_id_account(), 1 * ::CreateTokenDeposit::get()) - .is_ok(), - true - ); - assert_ok!(SetheumNFT::mint( + assert_ok!(Balances::deposit_into_existing( + &class_id_account(), + 1 * CreateTokenDeposit::get() + DataDepositPerByte::get() + )); + assert_ok!(NFTModule::mint( Origin::signed(class_id_account()), BOB, CLASS_ID, - vec![1], + metadata.clone(), + Default::default(), 1 )); assert_noop!( - SetheumNFT::transfer(Origin::signed(BOB), ALICE, (CLASS_ID, TOKEN_ID)), + NFTModule::transfer(Origin::signed(BOB), ALICE, (CLASS_ID, TOKEN_ID)), Error::::NonTransferable ); }); @@ -280,28 +399,30 @@ fn transfer_should_fail() { #[test] fn burn_should_work() { ExtBuilder::default().build().execute_with(|| { - assert_ok!(SetheumNFT::create_class( + let metadata = vec![1]; + assert_ok!(NFTModule::create_class( Origin::signed(ALICE), - vec![1], - Properties(ClassProperty::Transferable | ClassProperty::Burnable) + metadata.clone(), + Properties(ClassProperty::Transferable | ClassProperty::Burnable | ClassProperty::Mintable), + Default::default(), )); - assert_eq!( - Balances::deposit_into_existing(&class_id_account(), 1 * ::CreateTokenDeposit::get()) - .is_ok(), - true - ); - assert_ok!(SetheumNFT::mint( + assert_ok!(Balances::deposit_into_existing( + &class_id_account(), + 1 * CreateTokenDeposit::get() + DataDepositPerByte::get() + )); + assert_ok!(NFTModule::mint( Origin::signed(class_id_account()), BOB, CLASS_ID, - vec![1], + metadata.clone(), + Default::default(), 1 )); - assert_ok!(SetheumNFT::burn(Origin::signed(BOB), (CLASS_ID, TOKEN_ID))); - System::assert_last_event(Event::nft(crate::Event::BurnedToken(BOB, CLASS_ID, TOKEN_ID))); + assert_ok!(NFTModule::burn(Origin::signed(BOB), (CLASS_ID, TOKEN_ID))); + System::assert_last_event(Event::NFTModule(crate::Event::BurnedToken(BOB, CLASS_ID, TOKEN_ID))); assert_eq!( reserved_balance(&class_id_account()), - ::CreateClassDeposit::get() + Proxy::deposit(1u32) + CreateClassDeposit::get() + Proxy::deposit(1u32) + DataDepositPerByte::get() * (metadata.len() as u128) ); }); } @@ -309,30 +430,32 @@ fn burn_should_work() { #[test] fn burn_should_fail() { ExtBuilder::default().build().execute_with(|| { - assert_ok!(SetheumNFT::create_class( + let metadata = vec![1]; + assert_ok!(NFTModule::create_class( Origin::signed(ALICE), - vec![1], - Properties(ClassProperty::Transferable | ClassProperty::Burnable) + metadata.clone(), + Properties(ClassProperty::Transferable | ClassProperty::Burnable | ClassProperty::Mintable), + Default::default(), )); - assert_eq!( - Balances::deposit_into_existing(&class_id_account(), 1 * ::CreateTokenDeposit::get()) - .is_ok(), - true - ); - assert_ok!(SetheumNFT::mint( + assert_ok!(Balances::deposit_into_existing( + &class_id_account(), + 1 * CreateTokenDeposit::get() + DataDepositPerByte::get() + )); + assert_ok!(NFTModule::mint( Origin::signed(class_id_account()), BOB, CLASS_ID, - vec![1], + metadata.clone(), + Default::default(), 1 )); assert_noop!( - SetheumNFT::burn(Origin::signed(BOB), (CLASS_ID, TOKEN_ID_NOT_EXIST)), + NFTModule::burn(Origin::signed(BOB), (CLASS_ID, TOKEN_ID_NOT_EXIST)), Error::::TokenIdNotFound ); assert_noop!( - SetheumNFT::burn(Origin::signed(ALICE), (CLASS_ID, TOKEN_ID)), + NFTModule::burn(Origin::signed(ALICE), (CLASS_ID, TOKEN_ID)), Error::::NoPermission ); @@ -340,31 +463,33 @@ fn burn_should_fail() { class_info.as_mut().unwrap().total_issuance = 0; }); assert_noop!( - SetheumNFT::burn(Origin::signed(BOB), (CLASS_ID, TOKEN_ID)), + NFTModule::burn(Origin::signed(BOB), (CLASS_ID, TOKEN_ID)), ArithmeticError::Overflow, ); }); ExtBuilder::default().build().execute_with(|| { - assert_ok!(SetheumNFT::create_class( + let metadata = vec![1]; + assert_ok!(NFTModule::create_class( Origin::signed(ALICE), - vec![1], - Default::default() + metadata.clone(), + Properties(ClassProperty::Mintable.into()), + Default::default(), )); - assert_eq!( - Balances::deposit_into_existing(&class_id_account(), 1 * ::CreateTokenDeposit::get()) - .is_ok(), - true - ); - assert_ok!(SetheumNFT::mint( + assert_ok!(Balances::deposit_into_existing( + &class_id_account(), + 1 * CreateTokenDeposit::get() + DataDepositPerByte::get() + )); + assert_ok!(NFTModule::mint( Origin::signed(class_id_account()), BOB, CLASS_ID, - vec![1], + metadata.clone(), + Default::default(), 1 )); assert_noop!( - SetheumNFT::burn(Origin::signed(BOB), (CLASS_ID, TOKEN_ID)), + NFTModule::burn(Origin::signed(BOB), (CLASS_ID, TOKEN_ID)), Error::::NonBurnable ); }); @@ -373,32 +498,34 @@ fn burn_should_fail() { #[test] fn burn_with_remark_should_work() { ExtBuilder::default().build().execute_with(|| { - assert_ok!(SetheumNFT::create_class( + let metadata = vec![1]; + assert_ok!(NFTModule::create_class( Origin::signed(ALICE), - vec![1], - Properties(ClassProperty::Transferable | ClassProperty::Burnable) + metadata.clone(), + Properties(ClassProperty::Transferable | ClassProperty::Burnable | ClassProperty::Mintable), + Default::default(), )); - assert_eq!( - Balances::deposit_into_existing(&class_id_account(), 1 * ::CreateTokenDeposit::get()) - .is_ok(), - true - ); - assert_ok!(SetheumNFT::mint( + assert_ok!(Balances::deposit_into_existing( + &class_id_account(), + 1 * CreateTokenDeposit::get() + DataDepositPerByte::get() + )); + assert_ok!(NFTModule::mint( Origin::signed(class_id_account()), BOB, CLASS_ID, - vec![1], + metadata.clone(), + Default::default(), 1 )); let remark = "remark info".as_bytes().to_vec(); let remark_hash = BlakeTwo256::hash(&remark[..]); - assert_ok!(SetheumNFT::burn_with_remark( + assert_ok!(NFTModule::burn_with_remark( Origin::signed(BOB), (CLASS_ID, TOKEN_ID), remark )); - System::assert_last_event(Event::nft(crate::Event::BurnedTokenWithRemark( + System::assert_last_event(Event::NFTModule(crate::Event::BurnedTokenWithRemark( BOB, CLASS_ID, TOKEN_ID, @@ -407,7 +534,7 @@ fn burn_with_remark_should_work() { assert_eq!( reserved_balance(&class_id_account()), - ::CreateClassDeposit::get() + Proxy::deposit(1u32) + CreateClassDeposit::get() + Proxy::deposit(1u32) + DataDepositPerByte::get() * (metadata.len() as u128) ); }); } @@ -415,82 +542,170 @@ fn burn_with_remark_should_work() { #[test] fn destroy_class_should_work() { ExtBuilder::default().build().execute_with(|| { - assert_ok!(SetheumNFT::create_class( + let metadata = vec![1]; + assert_ok!(NFTModule::create_class( Origin::signed(ALICE), - vec![1], - Properties(ClassProperty::Transferable | ClassProperty::Burnable) + metadata.clone(), + Properties(ClassProperty::Transferable | ClassProperty::Burnable | ClassProperty::Mintable), + Default::default(), )); + + let deposit = + Proxy::deposit(1u32) + CreateClassDeposit::get() + DataDepositPerByte::get() * (metadata.len() as u128); + assert_eq!(free_balance(&ALICE), 100000 - deposit); + assert_eq!(reserved_balance(&ALICE), 0); + assert_eq!(free_balance(&class_id_account()), 0); + assert_eq!(reserved_balance(&class_id_account()), deposit); + assert_eq!(free_balance(&BOB), 0); + assert_eq!(reserved_balance(&BOB), 0); assert_ok!(Balances::deposit_into_existing( &class_id_account(), - 1 * ::CreateTokenDeposit::get() - )); // + 100 - assert_ok!(SetheumNFT::mint( + 1 * CreateTokenDeposit::get() + DataDepositPerByte::get() + )); + assert_ok!(NFTModule::mint( Origin::signed(class_id_account()), BOB, CLASS_ID, - vec![1], + metadata.clone(), + Default::default(), 1 )); - assert_ok!(SetheumNFT::burn(Origin::signed(BOB), (CLASS_ID, TOKEN_ID))); - assert_ok!(SetheumNFT::destroy_class( + assert_ok!(NFTModule::burn(Origin::signed(BOB), (CLASS_ID, TOKEN_ID))); + assert_ok!(NFTModule::destroy_class( Origin::signed(class_id_account()), CLASS_ID, ALICE )); - System::assert_last_event(Event::nft(crate::Event::DestroyedClass(class_id_account(), CLASS_ID))); + System::assert_last_event(Event::NFTModule(crate::Event::DestroyedClass( + class_id_account(), + CLASS_ID, + ))); assert_eq!(free_balance(&class_id_account()), 0); assert_eq!(reserved_balance(&class_id_account()), 0); assert_eq!(free_balance(&ALICE), 100000); - assert_eq!(free_balance(&BOB), ::CreateTokenDeposit::get()); + assert_eq!(reserved_balance(&ALICE), 0); + assert_eq!( + free_balance(&BOB), + CreateTokenDeposit::get() + DataDepositPerByte::get() + ); + assert_eq!(reserved_balance(&BOB), 0); }); } #[test] fn destroy_class_should_fail() { ExtBuilder::default().build().execute_with(|| { - assert_ok!(SetheumNFT::create_class( + let metadata = vec![1]; + assert_ok!(NFTModule::create_class( Origin::signed(ALICE), - vec![1], - Properties(ClassProperty::Transferable | ClassProperty::Burnable) + metadata.clone(), + Properties(ClassProperty::Transferable | ClassProperty::Burnable | ClassProperty::Mintable), + Default::default(), )); - assert_eq!( - Balances::deposit_into_existing(&class_id_account(), 1 * ::CreateTokenDeposit::get()) - .is_ok(), - true - ); - assert_ok!(SetheumNFT::mint( + assert_ok!(Balances::deposit_into_existing( + &class_id_account(), + 1 * CreateTokenDeposit::get() + DataDepositPerByte::get() + )); + assert_ok!(NFTModule::mint( Origin::signed(class_id_account()), BOB, CLASS_ID, - vec![1], + metadata.clone(), + Default::default(), 1 )); assert_noop!( - SetheumNFT::destroy_class(Origin::signed(class_id_account()), CLASS_ID_NOT_EXIST, BOB), + NFTModule::destroy_class(Origin::signed(class_id_account()), CLASS_ID_NOT_EXIST, BOB), Error::::ClassIdNotFound ); assert_noop!( - SetheumNFT::destroy_class(Origin::signed(BOB), CLASS_ID, BOB), + NFTModule::destroy_class(Origin::signed(BOB), CLASS_ID, BOB), Error::::NoPermission ); assert_noop!( - SetheumNFT::destroy_class(Origin::signed(class_id_account()), CLASS_ID, BOB), + NFTModule::destroy_class(Origin::signed(class_id_account()), CLASS_ID, BOB), Error::::CannotDestroyClass ); - assert_ok!(SetheumNFT::burn(Origin::signed(BOB), (CLASS_ID, TOKEN_ID))); + assert_ok!(NFTModule::burn(Origin::signed(BOB), (CLASS_ID, TOKEN_ID))); assert_noop!( - SetheumNFT::destroy_class(Origin::signed(class_id_account()), CLASS_ID, BOB), + NFTModule::destroy_class(Origin::signed(class_id_account()), CLASS_ID, BOB), pallet_proxy::Error::::NotFound ); - assert_ok!(SetheumNFT::destroy_class( + assert_ok!(NFTModule::destroy_class( Origin::signed(class_id_account()), CLASS_ID, ALICE )); }); } + +#[test] +fn update_class_properties_should_work() { + ExtBuilder::default().build().execute_with(|| { + let metadata = vec![1]; + + assert_ok!(NFTModule::create_class( + Origin::signed(ALICE), + metadata.clone(), + Properties(ClassProperty::Transferable | ClassProperty::ClassPropertiesMutable | ClassProperty::Mintable), + Default::default(), + )); + + assert_ok!(Balances::deposit_into_existing( + &class_id_account(), + CreateTokenDeposit::get() + ((metadata.len() as u128 + TEST_ATTR_LEN) * DataDepositPerByte::get()) + )); + + assert_ok!(NFTModule::mint( + Origin::signed(class_id_account()), + BOB, + CLASS_ID, + metadata.clone(), + Default::default(), + 1 + )); + + assert_ok!(NFTModule::transfer(Origin::signed(BOB), ALICE, (CLASS_ID, TOKEN_ID))); + + assert_ok!(NFTModule::update_class_properties( + Origin::signed(class_id_account()), + CLASS_ID, + Properties(ClassProperty::ClassPropertiesMutable.into()) + )); + + assert_noop!( + NFTModule::transfer(Origin::signed(ALICE), BOB, (CLASS_ID, TOKEN_ID)), + Error::::NonTransferable + ); + + assert_ok!(NFTModule::update_class_properties( + Origin::signed(class_id_account()), + CLASS_ID, + Properties(ClassProperty::Transferable.into()) + )); + + assert_ok!(NFTModule::transfer(Origin::signed(ALICE), BOB, (CLASS_ID, TOKEN_ID))); + + assert_noop!( + NFTModule::update_class_properties(Origin::signed(class_id_account()), CLASS_ID, Default::default()), + Error::::Immutable + ); + + assert_noop!( + NFTModule::mint( + Origin::signed(class_id_account()), + BOB, + CLASS_ID, + metadata.clone(), + Default::default(), + 1 + ), + Error::::NonMintable + ); + }); +} diff --git a/lib-serml/tokens/nft/src/weights.rs b/lib-serml/tokens/nft/src/weights.rs index 742322f80..90d4d817f 100644 --- a/lib-serml/tokens/nft/src/weights.rs +++ b/lib-serml/tokens/nft/src/weights.rs @@ -52,56 +52,63 @@ pub trait WeightInfo { fn burn() -> Weight; fn burn_with_remark(b: u32, ) -> Weight; fn destroy_class() -> Weight; + fn update_class_properties() -> Weight; } /// Weights for setheum_nft using the Setheum node and recommended hardware. pub struct SetheumWeight(PhantomData); impl WeightInfo for SetheumWeight { fn create_class() -> Weight { - (200_357_000 as Weight) + (177_661_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } fn mint(i: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 5_000 + (44_387_000 as Weight) + // Standard Error: 46_000 .saturating_add((17_893_000 as Weight).saturating_mul(i as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) .saturating_add(T::DbWeight::get().writes((2 as Weight).saturating_mul(i as Weight))) } fn transfer() -> Weight { - (54_749_000 as Weight) + (266_936_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } fn burn() -> Weight { - (154_177_000 as Weight) + (189_094_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } fn burn_with_remark(b: u32, ) -> Weight { - (154_177_000 as Weight) + (196_036_000 as Weight) + // Standard Error: 0 .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) .saturating_add((1_000 as Weight).saturating_mul(b as Weight)) } fn destroy_class() -> Weight { - (137_255_000 as Weight) + (217_091_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } + fn update_class_properties() -> Weight { + (52_914_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } } // For backwards compatibility and tests impl WeightInfo for () { fn create_class() -> Weight { - (200_357_000 as Weight) + (177_661_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } fn mint(i: u32, ) -> Weight { - (0 as Weight) + (44_387_000 as Weight) // Standard Error: 5_000 .saturating_add((17_893_000 as Weight).saturating_mul(i as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) @@ -109,24 +116,29 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().writes((2 as Weight).saturating_mul(i as Weight))) } fn transfer() -> Weight { - (54_749_000 as Weight) + (266_936_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } fn burn() -> Weight { - (154_177_000 as Weight) + (189_094_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } fn burn_with_remark(b: u32, ) -> Weight { - (154_177_000 as Weight) + (196_036_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) .saturating_add((1_000 as Weight).saturating_mul(b as Weight)) } fn destroy_class() -> Weight { - (137_255_000 as Weight) + (217_091_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } + fn update_class_properties() -> Weight { + (52_914_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } } From 7313fcf8c0c85290b98e3e2db89aacee661c71e3 Mon Sep 17 00:00:00 2001 From: Muhammad-Jibril Date: Tue, 31 Aug 2021 04:05:25 +0800 Subject: [PATCH 2/4] Update Cargo.toml --- lib-serml/tokens/nft/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/lib-serml/tokens/nft/Cargo.toml b/lib-serml/tokens/nft/Cargo.toml index 0b3353f8e..9a8ab011f 100644 --- a/lib-serml/tokens/nft/Cargo.toml +++ b/lib-serml/tokens/nft/Cargo.toml @@ -32,7 +32,6 @@ sp-core = { default-features = false, git = "https://github.com/paritytech/subst sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8" } pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8" } pallet-utility = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8" } -setheum-currencies = { path = "../../../lib-serml/tokens/currencies", default-features = false } orml-tokens = { path = "../../../lib-openrml/tokens", default-features = false } support = { package = "setheum-support", path = "../../support" } setheum-currencies = { path = "../../tokens/currencies", default-features = false } From 2ead64149f2f5cf32d61f49555503bc16af2e717 Mon Sep 17 00:00:00 2001 From: Muhammad-Jibril Date: Tue, 31 Aug 2021 04:15:51 +0800 Subject: [PATCH 3/4] NFT fixes --- lib-serml/tokens/nft/Cargo.toml | 15 ++-- lib-serml/tokens/nft/src/mock.rs | 1 + lib-serml/tokens/nft/src/tests.rs | 134 +++++++++++++++--------------- 3 files changed, 75 insertions(+), 75 deletions(-) diff --git a/lib-serml/tokens/nft/Cargo.toml b/lib-serml/tokens/nft/Cargo.toml index 9a8ab011f..fe99551a1 100644 --- a/lib-serml/tokens/nft/Cargo.toml +++ b/lib-serml/tokens/nft/Cargo.toml @@ -11,14 +11,13 @@ serde = { version = "1.0.124", optional = true } codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false } enumflags2 = { version = "0.6.3" } -# Substrate dependencies -sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8" } -sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8" } -frame-support = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8" } -frame-system = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8" } -frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8", default-features = false, optional = true } -pallet-proxy = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8" } -pallet-timestamp = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8" } +max-encoded-len = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8", default-features = false } +frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8", default-features = false, optional = true} +frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8", default-features = false } +pallet-proxy = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8", default-features = false } # orml dependencies orml-nft = { path = "../../../lib-openrml/nft", default-features = false } diff --git a/lib-serml/tokens/nft/src/mock.rs b/lib-serml/tokens/nft/src/mock.rs index a08de4086..e7d0af080 100644 --- a/lib-serml/tokens/nft/src/mock.rs +++ b/lib-serml/tokens/nft/src/mock.rs @@ -307,6 +307,7 @@ parameter_types! { } impl Config for Runtime { type Event = Event; + type Currency = Balances; type CreateClassDeposit = CreateClassDeposit; type CreateTokenDeposit = CreateTokenDeposit; type DataDepositPerByte = DataDepositPerByte; diff --git a/lib-serml/tokens/nft/src/tests.rs b/lib-serml/tokens/nft/src/tests.rs index 0f0b29c85..d6b75dc84 100644 --- a/lib-serml/tokens/nft/src/tests.rs +++ b/lib-serml/tokens/nft/src/tests.rs @@ -55,13 +55,13 @@ fn create_class_should_work() { ExtBuilder::default().build().execute_with(|| { let metadata = vec![1]; - assert_ok!(NFTModule::create_class( + assert_ok!(SetheumNFT::create_class( Origin::signed(ALICE), metadata.clone(), Default::default(), test_attr(1), )); - System::assert_last_event(Event::NFTModule(crate::Event::CreatedClass( + System::assert_last_event(Event::SetheumNFT(crate::Event::CreatedClass( class_id_account(), CLASS_ID, ))); @@ -90,7 +90,7 @@ fn create_class_should_fail() { ExtBuilder::default().build().execute_with(|| { let metadata = vec![1]; assert_noop!( - NFTModule::create_class( + SetheumNFT::create_class( Origin::signed(BOB), metadata.clone(), Properties(ClassProperty::Transferable | ClassProperty::Burnable), @@ -103,7 +103,7 @@ fn create_class_should_fail() { large_attr.insert(vec![1, 2, 3, 4, 5], vec![6, 7, 8, 9, 10, 11]); assert_noop!( - NFTModule::create_class( + SetheumNFT::create_class( Origin::signed(ALICE), metadata.clone(), Properties(ClassProperty::Transferable | ClassProperty::Burnable), @@ -119,13 +119,13 @@ fn mint_should_work() { ExtBuilder::default().build().execute_with(|| { let metadata = vec![1]; let metadata_2 = vec![2, 3]; - assert_ok!(NFTModule::create_class( + assert_ok!(SetheumNFT::create_class( Origin::signed(ALICE), metadata.clone(), Properties(ClassProperty::Transferable | ClassProperty::Burnable | ClassProperty::Mintable), test_attr(1), )); - System::assert_last_event(Event::NFTModule(crate::Event::CreatedClass( + System::assert_last_event(Event::SetheumNFT(crate::Event::CreatedClass( class_id_account(), CLASS_ID, ))); @@ -133,7 +133,7 @@ fn mint_should_work() { &class_id_account(), 2 * (CreateTokenDeposit::get() + ((metadata_2.len() as u128 + TEST_ATTR_LEN) * DataDepositPerByte::get())) )); - assert_ok!(NFTModule::mint( + assert_ok!(SetheumNFT::mint( Origin::signed(class_id_account()), BOB, CLASS_ID, @@ -141,7 +141,7 @@ fn mint_should_work() { test_attr(2), 2 )); - System::assert_last_event(Event::NFTModule(crate::Event::MintedToken( + System::assert_last_event(Event::SetheumNFT(crate::Event::MintedToken( class_id_account(), BOB, CLASS_ID, @@ -192,14 +192,14 @@ fn mint_should_work() { fn mint_should_fail() { ExtBuilder::default().build().execute_with(|| { let metadata = vec![1]; - assert_ok!(NFTModule::create_class( + assert_ok!(SetheumNFT::create_class( Origin::signed(ALICE), metadata.clone(), Properties(ClassProperty::Transferable | ClassProperty::Burnable | ClassProperty::Mintable), Default::default(), )); assert_noop!( - NFTModule::mint( + SetheumNFT::mint( Origin::signed(ALICE), BOB, CLASS_ID_NOT_EXIST, @@ -211,7 +211,7 @@ fn mint_should_fail() { ); assert_noop!( - NFTModule::mint( + SetheumNFT::mint( Origin::signed(BOB), BOB, CLASS_ID, @@ -223,7 +223,7 @@ fn mint_should_fail() { ); assert_noop!( - NFTModule::mint( + SetheumNFT::mint( Origin::signed(BOB), BOB, CLASS_ID, @@ -242,7 +242,7 @@ fn mint_should_fail() { 2 * (CreateTokenDeposit::get() + DataDepositPerByte::get()) )); assert_noop!( - NFTModule::mint( + SetheumNFT::mint( Origin::signed(class_id_account()), BOB, CLASS_ID, @@ -259,7 +259,7 @@ fn mint_should_fail() { fn mint_should_fail_without_mintable() { ExtBuilder::default().build().execute_with(|| { let metadata = vec![1]; - assert_ok!(NFTModule::create_class( + assert_ok!(SetheumNFT::create_class( Origin::signed(ALICE), metadata.clone(), Default::default(), @@ -267,7 +267,7 @@ fn mint_should_fail_without_mintable() { )); assert_noop!( - NFTModule::mint( + SetheumNFT::mint( Origin::signed(class_id_account()), BOB, CLASS_ID, @@ -284,7 +284,7 @@ fn mint_should_fail_without_mintable() { fn transfer_should_work() { ExtBuilder::default().build().execute_with(|| { let metadata = vec![1]; - assert_ok!(NFTModule::create_class( + assert_ok!(SetheumNFT::create_class( Origin::signed(ALICE), metadata.clone(), Properties(ClassProperty::Transferable | ClassProperty::Burnable | ClassProperty::Mintable), @@ -294,7 +294,7 @@ fn transfer_should_work() { &class_id_account(), 2 * (CreateTokenDeposit::get() + DataDepositPerByte::get()) )); - assert_ok!(NFTModule::mint( + assert_ok!(SetheumNFT::mint( Origin::signed(class_id_account()), BOB, CLASS_ID, @@ -308,8 +308,8 @@ fn transfer_should_work() { 2 * (CreateTokenDeposit::get() + DataDepositPerByte::get()) ); - assert_ok!(NFTModule::transfer(Origin::signed(BOB), ALICE, (CLASS_ID, TOKEN_ID))); - System::assert_last_event(Event::NFTModule(crate::Event::TransferredToken( + assert_ok!(SetheumNFT::transfer(Origin::signed(BOB), ALICE, (CLASS_ID, TOKEN_ID))); + System::assert_last_event(Event::SetheumNFT(crate::Event::TransferredToken( BOB, ALICE, CLASS_ID, TOKEN_ID, ))); assert_eq!( @@ -321,8 +321,8 @@ fn transfer_should_work() { 1 * (CreateTokenDeposit::get() + DataDepositPerByte::get()) ); - assert_ok!(NFTModule::transfer(Origin::signed(ALICE), BOB, (CLASS_ID, TOKEN_ID))); - System::assert_last_event(Event::NFTModule(crate::Event::TransferredToken( + assert_ok!(SetheumNFT::transfer(Origin::signed(ALICE), BOB, (CLASS_ID, TOKEN_ID))); + System::assert_last_event(Event::SetheumNFT(crate::Event::TransferredToken( ALICE, BOB, CLASS_ID, TOKEN_ID, ))); assert_eq!( @@ -337,7 +337,7 @@ fn transfer_should_work() { fn transfer_should_fail() { ExtBuilder::default().build().execute_with(|| { let metadata = vec![1]; - assert_ok!(NFTModule::create_class( + assert_ok!(SetheumNFT::create_class( Origin::signed(ALICE), metadata.clone(), Properties(ClassProperty::Transferable | ClassProperty::Burnable | ClassProperty::Mintable), @@ -347,7 +347,7 @@ fn transfer_should_fail() { &class_id_account(), 1 * CreateTokenDeposit::get() + DataDepositPerByte::get() )); - assert_ok!(NFTModule::mint( + assert_ok!(SetheumNFT::mint( Origin::signed(class_id_account()), BOB, CLASS_ID, @@ -356,22 +356,22 @@ fn transfer_should_fail() { 1 )); assert_noop!( - NFTModule::transfer(Origin::signed(BOB), ALICE, (CLASS_ID_NOT_EXIST, TOKEN_ID)), + SetheumNFT::transfer(Origin::signed(BOB), ALICE, (CLASS_ID_NOT_EXIST, TOKEN_ID)), Error::::ClassIdNotFound ); assert_noop!( - NFTModule::transfer(Origin::signed(BOB), ALICE, (CLASS_ID, TOKEN_ID_NOT_EXIST)), + SetheumNFT::transfer(Origin::signed(BOB), ALICE, (CLASS_ID, TOKEN_ID_NOT_EXIST)), Error::::TokenIdNotFound ); assert_noop!( - NFTModule::transfer(Origin::signed(ALICE), BOB, (CLASS_ID, TOKEN_ID)), + SetheumNFT::transfer(Origin::signed(ALICE), BOB, (CLASS_ID, TOKEN_ID)), orml_nft::Error::::NoPermission ); }); ExtBuilder::default().build().execute_with(|| { let metadata = vec![1]; - assert_ok!(NFTModule::create_class( + assert_ok!(SetheumNFT::create_class( Origin::signed(ALICE), metadata.clone(), Properties(ClassProperty::Mintable.into()), @@ -381,7 +381,7 @@ fn transfer_should_fail() { &class_id_account(), 1 * CreateTokenDeposit::get() + DataDepositPerByte::get() )); - assert_ok!(NFTModule::mint( + assert_ok!(SetheumNFT::mint( Origin::signed(class_id_account()), BOB, CLASS_ID, @@ -390,7 +390,7 @@ fn transfer_should_fail() { 1 )); assert_noop!( - NFTModule::transfer(Origin::signed(BOB), ALICE, (CLASS_ID, TOKEN_ID)), + SetheumNFT::transfer(Origin::signed(BOB), ALICE, (CLASS_ID, TOKEN_ID)), Error::::NonTransferable ); }); @@ -400,7 +400,7 @@ fn transfer_should_fail() { fn burn_should_work() { ExtBuilder::default().build().execute_with(|| { let metadata = vec![1]; - assert_ok!(NFTModule::create_class( + assert_ok!(SetheumNFT::create_class( Origin::signed(ALICE), metadata.clone(), Properties(ClassProperty::Transferable | ClassProperty::Burnable | ClassProperty::Mintable), @@ -410,7 +410,7 @@ fn burn_should_work() { &class_id_account(), 1 * CreateTokenDeposit::get() + DataDepositPerByte::get() )); - assert_ok!(NFTModule::mint( + assert_ok!(SetheumNFT::mint( Origin::signed(class_id_account()), BOB, CLASS_ID, @@ -418,8 +418,8 @@ fn burn_should_work() { Default::default(), 1 )); - assert_ok!(NFTModule::burn(Origin::signed(BOB), (CLASS_ID, TOKEN_ID))); - System::assert_last_event(Event::NFTModule(crate::Event::BurnedToken(BOB, CLASS_ID, TOKEN_ID))); + assert_ok!(SetheumNFT::burn(Origin::signed(BOB), (CLASS_ID, TOKEN_ID))); + System::assert_last_event(Event::SetheumNFT(crate::Event::BurnedToken(BOB, CLASS_ID, TOKEN_ID))); assert_eq!( reserved_balance(&class_id_account()), CreateClassDeposit::get() + Proxy::deposit(1u32) + DataDepositPerByte::get() * (metadata.len() as u128) @@ -431,7 +431,7 @@ fn burn_should_work() { fn burn_should_fail() { ExtBuilder::default().build().execute_with(|| { let metadata = vec![1]; - assert_ok!(NFTModule::create_class( + assert_ok!(SetheumNFT::create_class( Origin::signed(ALICE), metadata.clone(), Properties(ClassProperty::Transferable | ClassProperty::Burnable | ClassProperty::Mintable), @@ -441,7 +441,7 @@ fn burn_should_fail() { &class_id_account(), 1 * CreateTokenDeposit::get() + DataDepositPerByte::get() )); - assert_ok!(NFTModule::mint( + assert_ok!(SetheumNFT::mint( Origin::signed(class_id_account()), BOB, CLASS_ID, @@ -450,12 +450,12 @@ fn burn_should_fail() { 1 )); assert_noop!( - NFTModule::burn(Origin::signed(BOB), (CLASS_ID, TOKEN_ID_NOT_EXIST)), + SetheumNFT::burn(Origin::signed(BOB), (CLASS_ID, TOKEN_ID_NOT_EXIST)), Error::::TokenIdNotFound ); assert_noop!( - NFTModule::burn(Origin::signed(ALICE), (CLASS_ID, TOKEN_ID)), + SetheumNFT::burn(Origin::signed(ALICE), (CLASS_ID, TOKEN_ID)), Error::::NoPermission ); @@ -463,14 +463,14 @@ fn burn_should_fail() { class_info.as_mut().unwrap().total_issuance = 0; }); assert_noop!( - NFTModule::burn(Origin::signed(BOB), (CLASS_ID, TOKEN_ID)), + SetheumNFT::burn(Origin::signed(BOB), (CLASS_ID, TOKEN_ID)), ArithmeticError::Overflow, ); }); ExtBuilder::default().build().execute_with(|| { let metadata = vec![1]; - assert_ok!(NFTModule::create_class( + assert_ok!(SetheumNFT::create_class( Origin::signed(ALICE), metadata.clone(), Properties(ClassProperty::Mintable.into()), @@ -480,7 +480,7 @@ fn burn_should_fail() { &class_id_account(), 1 * CreateTokenDeposit::get() + DataDepositPerByte::get() )); - assert_ok!(NFTModule::mint( + assert_ok!(SetheumNFT::mint( Origin::signed(class_id_account()), BOB, CLASS_ID, @@ -489,7 +489,7 @@ fn burn_should_fail() { 1 )); assert_noop!( - NFTModule::burn(Origin::signed(BOB), (CLASS_ID, TOKEN_ID)), + SetheumNFT::burn(Origin::signed(BOB), (CLASS_ID, TOKEN_ID)), Error::::NonBurnable ); }); @@ -499,7 +499,7 @@ fn burn_should_fail() { fn burn_with_remark_should_work() { ExtBuilder::default().build().execute_with(|| { let metadata = vec![1]; - assert_ok!(NFTModule::create_class( + assert_ok!(SetheumNFT::create_class( Origin::signed(ALICE), metadata.clone(), Properties(ClassProperty::Transferable | ClassProperty::Burnable | ClassProperty::Mintable), @@ -509,7 +509,7 @@ fn burn_with_remark_should_work() { &class_id_account(), 1 * CreateTokenDeposit::get() + DataDepositPerByte::get() )); - assert_ok!(NFTModule::mint( + assert_ok!(SetheumNFT::mint( Origin::signed(class_id_account()), BOB, CLASS_ID, @@ -520,12 +520,12 @@ fn burn_with_remark_should_work() { let remark = "remark info".as_bytes().to_vec(); let remark_hash = BlakeTwo256::hash(&remark[..]); - assert_ok!(NFTModule::burn_with_remark( + assert_ok!(SetheumNFT::burn_with_remark( Origin::signed(BOB), (CLASS_ID, TOKEN_ID), remark )); - System::assert_last_event(Event::NFTModule(crate::Event::BurnedTokenWithRemark( + System::assert_last_event(Event::SetheumNFT(crate::Event::BurnedTokenWithRemark( BOB, CLASS_ID, TOKEN_ID, @@ -543,7 +543,7 @@ fn burn_with_remark_should_work() { fn destroy_class_should_work() { ExtBuilder::default().build().execute_with(|| { let metadata = vec![1]; - assert_ok!(NFTModule::create_class( + assert_ok!(SetheumNFT::create_class( Origin::signed(ALICE), metadata.clone(), Properties(ClassProperty::Transferable | ClassProperty::Burnable | ClassProperty::Mintable), @@ -562,7 +562,7 @@ fn destroy_class_should_work() { &class_id_account(), 1 * CreateTokenDeposit::get() + DataDepositPerByte::get() )); - assert_ok!(NFTModule::mint( + assert_ok!(SetheumNFT::mint( Origin::signed(class_id_account()), BOB, CLASS_ID, @@ -570,13 +570,13 @@ fn destroy_class_should_work() { Default::default(), 1 )); - assert_ok!(NFTModule::burn(Origin::signed(BOB), (CLASS_ID, TOKEN_ID))); - assert_ok!(NFTModule::destroy_class( + assert_ok!(SetheumNFT::burn(Origin::signed(BOB), (CLASS_ID, TOKEN_ID))); + assert_ok!(SetheumNFT::destroy_class( Origin::signed(class_id_account()), CLASS_ID, ALICE )); - System::assert_last_event(Event::NFTModule(crate::Event::DestroyedClass( + System::assert_last_event(Event::SetheumNFT(crate::Event::DestroyedClass( class_id_account(), CLASS_ID, ))); @@ -596,7 +596,7 @@ fn destroy_class_should_work() { fn destroy_class_should_fail() { ExtBuilder::default().build().execute_with(|| { let metadata = vec![1]; - assert_ok!(NFTModule::create_class( + assert_ok!(SetheumNFT::create_class( Origin::signed(ALICE), metadata.clone(), Properties(ClassProperty::Transferable | ClassProperty::Burnable | ClassProperty::Mintable), @@ -606,7 +606,7 @@ fn destroy_class_should_fail() { &class_id_account(), 1 * CreateTokenDeposit::get() + DataDepositPerByte::get() )); - assert_ok!(NFTModule::mint( + assert_ok!(SetheumNFT::mint( Origin::signed(class_id_account()), BOB, CLASS_ID, @@ -615,28 +615,28 @@ fn destroy_class_should_fail() { 1 )); assert_noop!( - NFTModule::destroy_class(Origin::signed(class_id_account()), CLASS_ID_NOT_EXIST, BOB), + SetheumNFT::destroy_class(Origin::signed(class_id_account()), CLASS_ID_NOT_EXIST, BOB), Error::::ClassIdNotFound ); assert_noop!( - NFTModule::destroy_class(Origin::signed(BOB), CLASS_ID, BOB), + SetheumNFT::destroy_class(Origin::signed(BOB), CLASS_ID, BOB), Error::::NoPermission ); assert_noop!( - NFTModule::destroy_class(Origin::signed(class_id_account()), CLASS_ID, BOB), + SetheumNFT::destroy_class(Origin::signed(class_id_account()), CLASS_ID, BOB), Error::::CannotDestroyClass ); - assert_ok!(NFTModule::burn(Origin::signed(BOB), (CLASS_ID, TOKEN_ID))); + assert_ok!(SetheumNFT::burn(Origin::signed(BOB), (CLASS_ID, TOKEN_ID))); assert_noop!( - NFTModule::destroy_class(Origin::signed(class_id_account()), CLASS_ID, BOB), + SetheumNFT::destroy_class(Origin::signed(class_id_account()), CLASS_ID, BOB), pallet_proxy::Error::::NotFound ); - assert_ok!(NFTModule::destroy_class( + assert_ok!(SetheumNFT::destroy_class( Origin::signed(class_id_account()), CLASS_ID, ALICE @@ -649,7 +649,7 @@ fn update_class_properties_should_work() { ExtBuilder::default().build().execute_with(|| { let metadata = vec![1]; - assert_ok!(NFTModule::create_class( + assert_ok!(SetheumNFT::create_class( Origin::signed(ALICE), metadata.clone(), Properties(ClassProperty::Transferable | ClassProperty::ClassPropertiesMutable | ClassProperty::Mintable), @@ -661,7 +661,7 @@ fn update_class_properties_should_work() { CreateTokenDeposit::get() + ((metadata.len() as u128 + TEST_ATTR_LEN) * DataDepositPerByte::get()) )); - assert_ok!(NFTModule::mint( + assert_ok!(SetheumNFT::mint( Origin::signed(class_id_account()), BOB, CLASS_ID, @@ -670,34 +670,34 @@ fn update_class_properties_should_work() { 1 )); - assert_ok!(NFTModule::transfer(Origin::signed(BOB), ALICE, (CLASS_ID, TOKEN_ID))); + assert_ok!(SetheumNFT::transfer(Origin::signed(BOB), ALICE, (CLASS_ID, TOKEN_ID))); - assert_ok!(NFTModule::update_class_properties( + assert_ok!(SetheumNFT::update_class_properties( Origin::signed(class_id_account()), CLASS_ID, Properties(ClassProperty::ClassPropertiesMutable.into()) )); assert_noop!( - NFTModule::transfer(Origin::signed(ALICE), BOB, (CLASS_ID, TOKEN_ID)), + SetheumNFT::transfer(Origin::signed(ALICE), BOB, (CLASS_ID, TOKEN_ID)), Error::::NonTransferable ); - assert_ok!(NFTModule::update_class_properties( + assert_ok!(SetheumNFT::update_class_properties( Origin::signed(class_id_account()), CLASS_ID, Properties(ClassProperty::Transferable.into()) )); - assert_ok!(NFTModule::transfer(Origin::signed(ALICE), BOB, (CLASS_ID, TOKEN_ID))); + assert_ok!(SetheumNFT::transfer(Origin::signed(ALICE), BOB, (CLASS_ID, TOKEN_ID))); assert_noop!( - NFTModule::update_class_properties(Origin::signed(class_id_account()), CLASS_ID, Default::default()), + SetheumNFT::update_class_properties(Origin::signed(class_id_account()), CLASS_ID, Default::default()), Error::::Immutable ); assert_noop!( - NFTModule::mint( + SetheumNFT::mint( Origin::signed(class_id_account()), BOB, CLASS_ID, From 7c856b71c20079324ff896bc6ee93d63c0a9bbe3 Mon Sep 17 00:00:00 2001 From: Muhammad-Jibril Date: Tue, 31 Aug 2021 05:24:21 +0800 Subject: [PATCH 4/4] fix NFTs --- Cargo.lock | 2 +- lib-serml/tokens/nft/Cargo.toml | 3 +-- lib-serml/tokens/nft/src/mock.rs | 5 ++++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f63dcc7d0..8c67c7854 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8203,12 +8203,12 @@ dependencies = [ "frame-benchmarking 3.1.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.8)", "frame-support 3.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.8)", "frame-system 3.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.8)", + "max-encoded-len 3.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.8)", "orml-nft", "orml-tokens", "orml-traits", "pallet-balances 3.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.8)", "pallet-proxy", - "pallet-timestamp", "pallet-utility", "parity-scale-codec", "serde", diff --git a/lib-serml/tokens/nft/Cargo.toml b/lib-serml/tokens/nft/Cargo.toml index fe99551a1..833450de6 100644 --- a/lib-serml/tokens/nft/Cargo.toml +++ b/lib-serml/tokens/nft/Cargo.toml @@ -31,7 +31,7 @@ sp-core = { default-features = false, git = "https://github.com/paritytech/subst sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8" } pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8" } pallet-utility = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.8" } -orml-tokens = { path = "../../../lib-openrml/tokens", default-features = false } +orml-tokens = { path = "../../../lib-openrml/tokens" } support = { package = "setheum-support", path = "../../support" } setheum-currencies = { path = "../../tokens/currencies", default-features = false } @@ -49,7 +49,6 @@ std = [ "primitives/std", "orml-traits/std", "orml-nft/std", - "pallet-timestamp/std", "setheum-currencies/std", ] runtime-benchmarks = [ diff --git a/lib-serml/tokens/nft/src/mock.rs b/lib-serml/tokens/nft/src/mock.rs index e7d0af080..dda09cec7 100644 --- a/lib-serml/tokens/nft/src/mock.rs +++ b/lib-serml/tokens/nft/src/mock.rs @@ -34,7 +34,10 @@ use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, }; -use support::{mocks::MockAddressMapping, SerpTreasury}; +use support::{ + mocks::MockAddressMapping, + SerpTreasury +}; parameter_types! { pub const BlockHashCount: u64 = 250;