difftreelog
fix set prop for not existed token (#933)
in: master
* fix: set prop for not existed token * optimize token checking * remove comments * test(token properties): on token non-existence * fix PR comments * rename value * refactor(modify token properties): readability + grammar * revert: unused import used for try-runtime * fix prop permission check * Add self_mint flag * Add LazyValue * fix tests * fix unit tests * fix docker * fix mintCross sponsoring * Generalize next_token_id * fix: set sponsored properties ---------
20 files changed
.docker/Dockerfile-chain-dev-unitdiffbeforeafterboth--- a/.docker/Dockerfile-chain-dev-unit
+++ b/.docker/Dockerfile-chain-dev-unit
@@ -17,4 +17,4 @@
WORKDIR /dev_chain
-CMD cargo test --features=limit-testing --workspace
+CMD cargo test --features=limit-testing,tests --workspace
pallets/common/Cargo.tomldiffbeforeafterboth--- a/pallets/common/Cargo.toml
+++ b/pallets/common/Cargo.toml
@@ -36,4 +36,5 @@
"up-pov-estimate-rpc/std",
]
stubgen = ["evm-coder/stubgen"]
+tests = []
try-runtime = ["frame-support/try-runtime"]
pallets/common/src/eth.rsdiffbeforeafterboth--- a/pallets/common/src/eth.rs
+++ b/pallets/common/src/eth.rs
@@ -131,6 +131,18 @@
value: evm_coder::types::Bytes,
}
+impl Property {
+ /// Property key.
+ pub fn key(&self) -> &str {
+ self.key.as_str()
+ }
+
+ /// Property value.
+ pub fn value(&self) -> &[u8] {
+ self.value.0.as_slice()
+ }
+}
+
impl TryFrom<up_data_structs::Property> for Property {
type Error = pallet_evm_coder_substrate::execution::Error;
@@ -227,11 +239,9 @@
Some(value) => match value {
0 => Ok(Some(false)),
1 => Ok(Some(true)),
- _ => {
- return Err(Self::Error::Revert(format!(
- "can't convert value to boolean \"{value}\""
- )))
- }
+ _ => Err(Self::Error::Revert(format!(
+ "can't convert value to boolean \"{value}\""
+ ))),
},
None => Ok(None),
};
pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -216,7 +216,6 @@
///
/// # Arguments
///
- /// * `sender`: Caller's account.
/// * `sponsor`: ID of the account of the sponsor-to-be.
pub fn force_set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {
self.check_is_internal()?;
@@ -867,6 +866,74 @@
>;
}
+/// Represents the change mode for the token property.
+pub enum SetPropertyMode {
+ /// The token already exists.
+ ExistingToken,
+
+ /// New token.
+ NewToken {
+ /// The creator of the token is the recipient.
+ mint_target_is_sender: bool,
+ },
+}
+
+/// Value representation with delayed initialization time.
+pub struct LazyValue<T, F: FnOnce() -> T> {
+ value: Option<T>,
+ f: Option<F>,
+}
+
+impl<T, F: FnOnce() -> T> LazyValue<T, F> {
+ /// Create a new LazyValue.
+ pub fn new(f: F) -> Self {
+ Self {
+ value: None,
+ f: Some(f),
+ }
+ }
+
+ /// Get the value. If it call furst time the value will be initialized.
+ pub fn value(&mut self) -> &T {
+ if self.value.is_none() {
+ self.value = Some(self.f.take().unwrap()())
+ }
+
+ self.value.as_ref().unwrap()
+ }
+
+ /// Is value initialized.
+ pub fn has_value(&self) -> bool {
+ self.value.is_some()
+ }
+}
+
+fn check_token_permissions<T, FCA, FTO, FTE>(
+ collection_admin_permitted: bool,
+ token_owner_permitted: bool,
+ is_collection_admin: &mut LazyValue<bool, FCA>,
+ is_token_owner: &mut LazyValue<Result<bool, DispatchError>, FTO>,
+ is_token_exist: &mut LazyValue<bool, FTE>,
+) -> DispatchResult
+where
+ T: Config,
+ FCA: FnOnce() -> bool,
+ FTO: FnOnce() -> Result<bool, DispatchError>,
+ FTE: FnOnce() -> bool,
+{
+ if !(collection_admin_permitted && *is_collection_admin.value()
+ || token_owner_permitted && (*is_token_owner.value())?)
+ {
+ fail!(<Error<T>>::NoPermission);
+ }
+
+ let token_certainly_exist = is_token_owner.has_value() && (*is_token_owner.value())?;
+ if !token_certainly_exist && !is_token_exist.value() {
+ fail!(<Error<T>>::TokenNotFound);
+ }
+ Ok(())
+}
+
impl<T: Config> Pallet<T> {
/// Enshure that receiver address is correct.
///
@@ -1218,10 +1285,6 @@
/// * sets a property under the <key> with the value provided `(<key>, Some(<value>))`
/// * removes a property under the <key> if the value is `None` `(<key>, None)`.
///
- /// - `nesting_budget`: Limit for searching parents in-depth to check ownership.
- /// - `is_token_create`: Indicates that method is called during token initialization.
- /// Allows to bypass ownership check.
- ///
/// All affected properties should have `mutable` permission
/// to be **deleted** or to be **set more than once**,
/// and the sender should have permission to edit those properties.
@@ -1229,35 +1292,36 @@
/// This function fires an event for each property change.
/// In case of an error, all the changes (including the events) will be reverted
/// since the function is transactional.
- pub fn modify_token_properties(
+ #[allow(clippy::too_many_arguments)]
+ pub fn modify_token_properties<FTO, FTE>(
collection: &CollectionHandle<T>,
sender: &T::CrossAccountId,
token_id: TokenId,
+ is_token_exist: &mut LazyValue<bool, FTE>,
properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,
- is_token_create: bool,
mut stored_properties: TokenProperties,
- is_token_owner: impl Fn() -> Result<bool, DispatchError>,
+ is_token_owner: &mut LazyValue<Result<bool, DispatchError>, FTO>,
set_token_properties: impl FnOnce(TokenProperties),
log: evm_coder::ethereum::Log,
- ) -> DispatchResult {
- let is_collection_admin = collection.is_owner_or_admin(sender);
+ ) -> DispatchResult
+ where
+ FTO: FnOnce() -> Result<bool, DispatchError>,
+ FTE: FnOnce() -> bool,
+ {
+ let mut is_collection_admin = LazyValue::new(|| collection.is_owner_or_admin(sender));
let permissions = Self::property_permissions(collection.id);
- let mut token_owner_result = None;
- let mut is_token_owner = || -> Result<bool, DispatchError> {
- *token_owner_result.get_or_insert_with(&is_token_owner)
- };
-
+ let mut changed = false;
for (key, value) in properties_updates {
let permission = permissions
.get(&key)
.cloned()
.unwrap_or_else(PropertyPermission::none);
- let is_property_exists = stored_properties.get(&key).is_some();
+ let property_exists = stored_properties.get(&key).is_some();
match permission {
- PropertyPermission { mutable: false, .. } if is_property_exists => {
+ PropertyPermission { mutable: false, .. } if property_exists => {
return Err(<Error<T>>::NoPermission.into());
}
@@ -1265,17 +1329,13 @@
collection_admin,
token_owner,
..
- } => {
- //TODO: investigate threats during public minting.
- let is_token_create =
- is_token_create && (collection_admin || token_owner) && value.is_some();
- if !(is_token_create
- || (collection_admin && is_collection_admin)
- || (token_owner && is_token_owner()?))
- {
- fail!(<Error<T>>::NoPermission);
- }
- }
+ } => check_token_permissions::<T, _, FTO, FTE>(
+ collection_admin,
+ token_owner,
+ &mut is_collection_admin,
+ is_token_owner,
+ is_token_exist,
+ )?,
}
match value {
@@ -1293,9 +1353,13 @@
}
}
- <PalletEvm<T>>::deposit_log(log.clone());
+ changed = true;
}
+ if changed {
+ <PalletEvm<T>>::deposit_log(log);
+ }
+
set_token_properties(stored_properties);
Ok(())
@@ -2322,3 +2386,86 @@
}
}
}
+
+#[cfg(feature = "tests")]
+pub mod tests {
+ use crate::{DispatchResult, DispatchError, LazyValue, Config};
+
+ const fn to_bool(u: u8) -> bool {
+ u != 0
+ }
+
+ #[derive(Debug)]
+ pub struct TestCase {
+ pub collection_admin: bool,
+ pub is_collection_admin: bool,
+ pub token_owner: bool,
+ pub is_token_owner: bool,
+ pub no_permission: bool,
+ }
+
+ impl TestCase {
+ const fn new(
+ collection_admin: u8,
+ is_collection_admin: u8,
+ token_owner: u8,
+ is_token_owner: u8,
+ no_permission: u8,
+ ) -> Self {
+ Self {
+ collection_admin: to_bool(collection_admin),
+ is_collection_admin: to_bool(is_collection_admin),
+ token_owner: to_bool(token_owner),
+ is_token_owner: to_bool(is_token_owner),
+ no_permission: to_bool(no_permission),
+ }
+ }
+ }
+
+ #[rustfmt::skip]
+ pub const table: [TestCase; 16] = [
+ // ┌╴collection_admin
+ // │ ┌╴is_collection_admin
+ // │ │ ┌╴token_owner
+ // │ │ │ ┌╴is_token_ownership
+ // │ │ │ │ ┌╴no_permission
+ /* 0*/ TestCase::new(0, 0, 0, 0, 1),
+ /* 1*/ TestCase::new(0, 0, 0, 1, 1),
+ /* 2*/ TestCase::new(0, 0, 1, 0, 1),
+ /* 3*/ TestCase::new(0, 0, 1, 1, 0),
+ /* 4*/ TestCase::new(0, 1, 0, 0, 1),
+ /* 5*/ TestCase::new(0, 1, 0, 1, 1),
+ /* 6*/ TestCase::new(0, 1, 1, 0, 1),
+ /* 7*/ TestCase::new(0, 1, 1, 1, 0),
+ /* 8*/ TestCase::new(1, 0, 0, 0, 1),
+ /* 9*/ TestCase::new(1, 0, 0, 1, 1),
+ /* 10*/ TestCase::new(1, 0, 1, 0, 1),
+ /* 11*/ TestCase::new(1, 0, 1, 1, 0),
+ /* 12*/ TestCase::new(1, 1, 0, 0, 0),
+ /* 13*/ TestCase::new(1, 1, 0, 1, 0),
+ /* 14*/ TestCase::new(1, 1, 1, 0, 0),
+ /* 15*/ TestCase::new(1, 1, 1, 1, 0),
+ ];
+
+ pub fn check_token_permissions<T, FCA, FTO, FTE>(
+ collection_admin_permitted: bool,
+ token_owner_permitted: bool,
+ is_collection_admin: &mut LazyValue<bool, FCA>,
+ check_token_ownership: &mut LazyValue<Result<bool, DispatchError>, FTO>,
+ check_token_existence: &mut LazyValue<bool, FTE>,
+ ) -> DispatchResult
+ where
+ T: Config,
+ FCA: FnOnce() -> bool,
+ FTO: FnOnce() -> Result<bool, DispatchError>,
+ FTE: FnOnce() -> bool,
+ {
+ crate::check_token_permissions::<T, FCA, FTO, FTE>(
+ collection_admin_permitted,
+ token_owner_permitted,
+ is_collection_admin,
+ check_token_ownership,
+ check_token_existence,
+ )
+ }
+}
pallets/nonfungible/src/common.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -245,7 +245,7 @@
&sender,
token_id,
properties.into_iter(),
- false,
+ pallet_common::SetPropertyMode::ExistingToken,
nesting_budget,
),
weight,
pallets/nonfungible/src/erc.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -194,7 +194,7 @@
&caller,
TokenId(token_id),
properties.into_iter(),
- false,
+ pallet_common::SetPropertyMode::ExistingToken,
&nesting_budget,
)
.map_err(dispatch_to_evm::<T>)
@@ -939,9 +939,8 @@
/// @notice Returns next free NFT ID.
fn next_token_id(&self) -> Result<U256> {
self.consume_store_reads(1)?;
- Ok(<TokensMinted<T>>::get(self.id)
- .checked_add(1)
- .ok_or("item id overflow")?
+ Ok(<Pallet<T>>::next_token_id(self)
+ .map_err(dispatch_to_evm::<T>)?
.into())
}
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -109,7 +109,7 @@
use pallet_common::{
Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, CollectionHandle,
eth::collection_id_to_address, SelfWeightOf as PalletCommonWeightOf,
- weights::WeightInfo as CommonWeightInfo, helpers::add_weight_to_post_info,
+ weights::WeightInfo as CommonWeightInfo, helpers::add_weight_to_post_info, SetPropertyMode,
};
use pallet_structure::{Pallet as PalletStructure, Error as StructureError};
use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};
@@ -585,8 +585,6 @@
/// A batch operation to add, edit or remove properties for a token.
///
/// - `nesting_budget`: Limit for searching parents in-depth to check ownership.
- /// - `is_token_create`: Indicates that method is called during token initialization.
- /// Allows to bypass ownership check.
///
/// All affected properties should have `mutable` permission
/// to be **deleted** or to be **set more than once**,
@@ -601,10 +599,17 @@
sender: &T::CrossAccountId,
token_id: TokenId,
properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,
- is_token_create: bool,
+ mode: SetPropertyMode,
nesting_budget: &dyn Budget,
) -> DispatchResult {
- let is_token_owner = || {
+ let mut is_token_owner = pallet_common::LazyValue::new(|| {
+ if let SetPropertyMode::NewToken {
+ mint_target_is_sender,
+ } = mode
+ {
+ return Ok(mint_target_is_sender);
+ }
+
let is_owned = <PalletStructure<T>>::check_indirectly_owned(
sender.clone(),
collection.id,
@@ -614,18 +619,21 @@
)?;
Ok(is_owned)
- };
+ });
+ let mut is_token_exist =
+ pallet_common::LazyValue::new(|| Self::token_exists(collection, token_id));
+
let stored_properties = <TokenProperties<T>>::get((collection.id, token_id));
<PalletCommon<T>>::modify_token_properties(
collection,
sender,
token_id,
+ &mut is_token_exist,
properties_updates,
- is_token_create,
stored_properties,
- is_token_owner,
+ &mut is_token_owner,
|properties| <TokenProperties<T>>::set((collection.id, token_id), properties),
erc::ERC721TokenEvent::TokenChanged {
token_id: token_id.into(),
@@ -634,6 +642,19 @@
)
}
+ pub fn next_token_id(collection: &NonfungibleHandle<T>) -> Result<TokenId, DispatchError> {
+ let next_token_id = <TokensMinted<T>>::get(collection.id)
+ .checked_add(1)
+ .ok_or(<CommonError<T>>::CollectionTokenLimitExceeded)?;
+
+ ensure!(
+ collection.limits.token_limit() >= next_token_id,
+ <CommonError<T>>::CollectionTokenLimitExceeded
+ );
+
+ Ok(TokenId(next_token_id))
+ }
+
/// Batch operation to add or edit properties for the token
///
/// Same as [`modify_token_properties`] but doesn't allow to remove properties
@@ -644,7 +665,7 @@
sender: &T::CrossAccountId,
token_id: TokenId,
properties: impl Iterator<Item = Property>,
- is_token_create: bool,
+ mode: SetPropertyMode,
nesting_budget: &dyn Budget,
) -> DispatchResult {
Self::modify_token_properties(
@@ -652,7 +673,7 @@
sender,
token_id,
properties.map(|p| (p.key, Some(p.value))),
- is_token_create,
+ mode,
nesting_budget,
)
}
@@ -669,14 +690,12 @@
property: Property,
nesting_budget: &dyn Budget,
) -> DispatchResult {
- let is_token_create = false;
-
Self::set_token_properties(
collection,
sender,
token_id,
[property].into_iter(),
- is_token_create,
+ SetPropertyMode::ExistingToken,
nesting_budget,
)
}
@@ -693,14 +712,12 @@
property_keys: impl Iterator<Item = PropertyKey>,
nesting_budget: &dyn Budget,
) -> DispatchResult {
- let is_token_create = false;
-
Self::modify_token_properties(
collection,
sender,
token_id,
property_keys.into_iter().map(|key| (key, None)),
- is_token_create,
+ SetPropertyMode::ExistingToken,
nesting_budget,
)
}
@@ -985,7 +1002,9 @@
sender,
TokenId(token),
data.properties.clone().into_iter(),
- true,
+ SetPropertyMode::NewToken {
+ mint_target_is_sender: sender.conv_eq(&data.owner),
+ },
nesting_budget,
) {
return TransactionOutcome::Rollback(Err(e));
pallets/refungible/src/common.rsdiffbeforeafterboth--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -399,7 +399,7 @@
&sender,
token_id,
properties.into_iter(),
- false,
+ pallet_common::SetPropertyMode::ExistingToken,
nesting_budget,
),
weight,
pallets/refungible/src/erc.rsdiffbeforeafterboth--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -196,7 +196,7 @@
&caller,
TokenId(token_id),
properties.into_iter(),
- false,
+ pallet_common::SetPropertyMode::ExistingToken,
&nesting_budget,
)
.map_err(dispatch_to_evm::<T>)
@@ -973,9 +973,8 @@
/// @notice Returns next free RFT ID.
fn next_token_id(&self) -> Result<U256> {
self.consume_store_reads(1)?;
- Ok(<TokensMinted<T>>::get(self.id)
- .checked_add(1)
- .ok_or("item id overflow")?
+ Ok(<Pallet<T>>::next_token_id(self)
+ .map_err(dispatch_to_evm::<T>)?
.into())
}
pallets/refungible/src/lib.rsdiffbeforeafterboth--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -97,7 +97,7 @@
use pallet_evm_coder_substrate::WithRecorder;
use pallet_common::{
CommonCollectionOperations, Error as CommonError, eth::collection_id_to_address,
- Event as CommonEvent, Pallet as PalletCommon,
+ Event as CommonEvent, Pallet as PalletCommon, SetPropertyMode,
};
use pallet_structure::Pallet as PalletStructure;
use sp_core::{Get, H160};
@@ -521,8 +521,6 @@
/// * removes a property under the <key> if the value is `None` `(<key>, None)`.
///
/// - `nesting_budget`: Limit for searching parents in-depth to check ownership.
- /// - `is_token_create`: Indicates that method is called during token initialization.
- /// Allows to bypass ownership check.
///
/// All affected properties should have `mutable` permission
/// to be **deleted** or to be **set more than once**,
@@ -537,27 +535,38 @@
sender: &T::CrossAccountId,
token_id: TokenId,
properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,
- is_token_create: bool,
+ mode: SetPropertyMode,
nesting_budget: &dyn Budget,
) -> DispatchResult {
- let is_token_owner = || -> Result<bool, DispatchError> {
- let balance = collection.balance(sender.clone(), token_id);
- let total_pieces: u128 =
- Self::total_pieces(collection.id, token_id).unwrap_or(u128::MAX);
- if balance != total_pieces {
- return Ok(false);
- }
+ let mut is_token_owner =
+ pallet_common::LazyValue::new(|| -> Result<bool, DispatchError> {
+ if let SetPropertyMode::NewToken {
+ mint_target_is_sender,
+ } = mode
+ {
+ return Ok(mint_target_is_sender);
+ }
- let is_bundle_owner = <PalletStructure<T>>::check_indirectly_owned(
- sender.clone(),
- collection.id,
- token_id,
- None,
- nesting_budget,
- )?;
+ let balance = collection.balance(sender.clone(), token_id);
+ let total_pieces: u128 =
+ Self::total_pieces(collection.id, token_id).unwrap_or(u128::MAX);
+ if balance != total_pieces {
+ return Ok(false);
+ }
+
+ let is_bundle_owner = <PalletStructure<T>>::check_indirectly_owned(
+ sender.clone(),
+ collection.id,
+ token_id,
+ None,
+ nesting_budget,
+ )?;
+
+ Ok(is_bundle_owner)
+ });
- Ok(is_bundle_owner)
- };
+ let mut is_token_exist =
+ pallet_common::LazyValue::new(|| Self::token_exists(collection, token_id));
let stored_properties = <TokenProperties<T>>::get((collection.id, token_id));
@@ -565,10 +574,10 @@
collection,
sender,
token_id,
+ &mut is_token_exist,
properties_updates,
- is_token_create,
stored_properties,
- is_token_owner,
+ &mut is_token_owner,
|properties| <TokenProperties<T>>::set((collection.id, token_id), properties),
erc::ERC721TokenEvent::TokenChanged {
token_id: token_id.into(),
@@ -577,12 +586,25 @@
)
}
+ pub fn next_token_id(collection: &RefungibleHandle<T>) -> Result<TokenId, DispatchError> {
+ let next_token_id = <TokensMinted<T>>::get(collection.id)
+ .checked_add(1)
+ .ok_or(<CommonError<T>>::CollectionTokenLimitExceeded)?;
+
+ ensure!(
+ collection.limits.token_limit() >= next_token_id,
+ <CommonError<T>>::CollectionTokenLimitExceeded
+ );
+
+ Ok(TokenId(next_token_id))
+ }
+
pub fn set_token_properties(
collection: &RefungibleHandle<T>,
sender: &T::CrossAccountId,
token_id: TokenId,
properties: impl Iterator<Item = Property>,
- is_token_create: bool,
+ mode: SetPropertyMode,
nesting_budget: &dyn Budget,
) -> DispatchResult {
Self::modify_token_properties(
@@ -590,7 +612,7 @@
sender,
token_id,
properties.map(|p| (p.key, Some(p.value))),
- is_token_create,
+ mode,
nesting_budget,
)
}
@@ -602,14 +624,12 @@
property: Property,
nesting_budget: &dyn Budget,
) -> DispatchResult {
- let is_token_create = false;
-
Self::set_token_properties(
collection,
sender,
token_id,
[property].into_iter(),
- is_token_create,
+ SetPropertyMode::ExistingToken,
nesting_budget,
)
}
@@ -621,14 +641,12 @@
property_keys: impl Iterator<Item = PropertyKey>,
nesting_budget: &dyn Budget,
) -> DispatchResult {
- let is_token_create = false;
-
Self::modify_token_properties(
collection,
sender,
token_id,
property_keys.into_iter().map(|key| (key, None)),
- is_token_create,
+ SetPropertyMode::ExistingToken,
nesting_budget,
)
}
@@ -914,10 +932,14 @@
let token_id = first_token_id + i as u32 + 1;
<TotalSupply<T>>::insert((collection.id, token_id), totals[i]);
+ let mut mint_target_is_sender = true;
for (user, amount) in data.users.iter() {
if *amount == 0 {
continue;
}
+
+ mint_target_is_sender = mint_target_is_sender && sender.conv_eq(user);
+
<Balance<T>>::insert((collection.id, token_id, &user), amount);
<Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);
<PalletStructure<T>>::nest_if_sent_to_token_unchecked(
@@ -932,7 +954,9 @@
sender,
TokenId(token_id),
data.properties.clone().into_iter(),
- true,
+ SetPropertyMode::NewToken {
+ mint_target_is_sender,
+ },
nesting_budget,
) {
return TransactionOutcome::Rollback(Err(e));
runtime/common/ethereum/sponsoring.rsdiffbeforeafterboth--- a/runtime/common/ethereum/sponsoring.rs
+++ b/runtime/common/ethereum/sponsoring.rs
@@ -22,7 +22,7 @@
use pallet_evm::account::CrossAccountId;
use pallet_evm_transaction_payment::CallContext;
use pallet_nonfungible::{
- Config as NonfungibleConfig,
+ Config as NonfungibleConfig, Pallet as NonfungiblePallet, NonfungibleHandle,
erc::{
UniqueNFTCall, ERC721UniqueExtensionsCall, ERC721UniqueMintableCall, ERC721Call,
TokenPropertiesCall,
@@ -56,6 +56,8 @@
pub struct UniqueEthSponsorshipHandler<T: UniqueConfig>(PhantomData<*const T>);
impl<T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig>
SponsorshipHandler<T::CrossAccountId, CallContext> for UniqueEthSponsorshipHandler<T>
+where
+ T::AccountId: From<[u8; 32]>,
{
fn get_sponsor(
who: &T::CrossAccountId,
@@ -67,29 +69,71 @@
let (method_id, mut reader) = AbiReader::new_call(&call_context.input).ok()?;
Some(T::CrossAccountId::from_sub(match &collection.mode {
CollectionMode::NFT => {
+ let collection = NonfungibleHandle::cast(collection);
let call = <UniqueNFTCall<T>>::parse(method_id, &mut reader).ok()??;
match call {
- UniqueNFTCall::TokenProperties(TokenPropertiesCall::SetProperty {
- token_id,
- key,
- value,
- ..
- }) => {
- let token_id: TokenId = token_id.try_into().ok()?;
- withdraw_set_token_property::<T>(
- &collection,
- who,
- &token_id,
- key.len() + value.len(),
- )
- .map(|()| sponsor)
- }
- UniqueNFTCall::ERC721UniqueExtensions(
- ERC721UniqueExtensionsCall::Transfer { token_id, .. },
- ) => {
- let token_id: TokenId = token_id.try_into().ok()?;
- withdraw_transfer::<T>(&collection, who, &token_id).map(|()| sponsor)
- }
+ UniqueNFTCall::TokenProperties(call) => match call {
+ TokenPropertiesCall::SetProperty {
+ token_id,
+ key,
+ value,
+ ..
+ } => {
+ let token_id: TokenId = token_id.try_into().ok()?;
+ withdraw_set_existing_token_property::<T>(
+ &collection,
+ who,
+ &token_id,
+ key.len() + value.len(),
+ )
+ .map(|()| sponsor)
+ }
+ TokenPropertiesCall::SetProperties {
+ token_id,
+ properties,
+ ..
+ } => {
+ let token_id: TokenId = token_id.try_into().ok()?;
+ let data_size = properties
+ .into_iter()
+ .map(|p| p.key().len() + p.value().len())
+ .sum();
+
+ withdraw_set_existing_token_property::<T>(
+ &collection,
+ who,
+ &token_id,
+ data_size,
+ )
+ .map(|()| sponsor)
+ }
+ _ => None,
+ },
+ UniqueNFTCall::ERC721UniqueExtensions(call) => match call {
+ ERC721UniqueExtensionsCall::Transfer { token_id, .. } => {
+ let token_id: TokenId = token_id.try_into().ok()?;
+ withdraw_transfer::<T>(&collection, who, &token_id)
+ .map(|()| sponsor)
+ }
+ ERC721UniqueExtensionsCall::MintCross { properties, .. } => {
+ withdraw_create_item::<T>(
+ &collection,
+ who,
+ &CreateItemData::NFT(CreateNftData::default()),
+ )?;
+
+ let token_id =
+ <NonfungiblePallet<T>>::next_token_id(&collection).ok()?;
+ let data_size: usize = properties
+ .into_iter()
+ .map(|p| p.key().len() + p.value().len())
+ .sum();
+
+ withdraw_set_token_property::<T>(&collection, &token_id, data_size)
+ .map(|()| sponsor)
+ }
+ _ => None,
+ },
UniqueNFTCall::ERC721UniqueMintable(
ERC721UniqueMintableCall::Mint { .. }
| ERC721UniqueMintableCall::MintCheckId { .. }
runtime/common/ethereum/sponsoring/refungible.rsdiffbeforeafterboth--- a/runtime/common/ethereum/sponsoring/refungible.rs
+++ b/runtime/common/ethereum/sponsoring/refungible.rs
@@ -94,7 +94,12 @@
..
} => {
let token_id = TokenId::try_from(token_id).ok()?;
- withdraw_set_token_property::<T>(&collection, who, &token_id, key.len() + value.len())
+ withdraw_set_existing_token_property::<T>(
+ &collection,
+ who,
+ &token_id,
+ key.len() + value.len(),
+ )
}
}
}
runtime/common/sponsoring.rsdiffbeforeafterboth--- a/runtime/common/sponsoring.rs
+++ b/runtime/common/sponsoring.rs
@@ -39,7 +39,7 @@
impl<T> Config for T where T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig {}
// TODO: permission check?
-pub fn withdraw_set_token_property<T: Config>(
+pub fn withdraw_set_existing_token_property<T: Config>(
collection: &CollectionHandle<T>,
who: &T::CrossAccountId,
item_id: &TokenId,
@@ -64,6 +64,17 @@
}
}
+ withdraw_set_token_property(collection, item_id, data_size)
+}
+
+pub fn withdraw_set_token_property<T: Config>(
+ collection: &CollectionHandle<T>,
+ item_id: &TokenId,
+ data_size: usize,
+) -> Option<()> {
+ if data_size == 0 {
+ return Some(());
+ }
if data_size > collection.limits.sponsored_data_size() as usize {
return None;
}
@@ -173,7 +184,6 @@
return None;
}
}
-
CreateItemBasket::<T>::insert((collection.id, who.as_sub()), block_number);
Some(())
@@ -237,7 +247,7 @@
..
} => {
let (sponsor, collection) = load::<T>(*collection_id)?;
- withdraw_set_token_property(
+ withdraw_set_existing_token_property(
&collection,
&T::CrossAccountId::from_sub(who.clone()),
token_id,
runtime/tests/Cargo.tomldiffbeforeafterboth--- a/runtime/tests/Cargo.toml
+++ b/runtime/tests/Cargo.toml
@@ -5,6 +5,7 @@
[features]
default = ['refungible']
+tests = ['pallet-common/tests']
refungible = []
runtime/tests/src/tests.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617// Tests to be written here18use crate::{Test, TestCrossAccountId, CollectionCreationPrice, RuntimeOrigin, Unique, new_test_ext};19use up_data_structs::{20 COLLECTION_NUMBER_LIMIT, CollectionId, CreateItemData, CreateFungibleData, CreateNftData,21 CreateReFungibleData, MAX_DECIMAL_POINTS, COLLECTION_ADMINS_LIMIT, TokenId,22 MAX_TOKEN_OWNERSHIP, CreateCollectionData, CollectionMode, AccessMode, CollectionPermissions,23 PropertyKeyPermission, PropertyPermission, Property, CollectionPropertiesVec,24 CollectionPropertiesPermissionsVec,25};26use frame_support::{assert_noop, assert_ok, assert_err};27use sp_std::convert::TryInto;28use pallet_evm::account::CrossAccountId;29use pallet_common::Error as CommonError;30use pallet_unique::Error as UniqueError;3132fn add_balance(user: u64, value: u64) {33 const DONOR_USER: u64 = 999;34 assert_ok!(<pallet_balances::Pallet<Test>>::force_set_balance(35 RuntimeOrigin::root(),36 DONOR_USER,37 value,38 ));39 assert_ok!(<pallet_balances::Pallet<Test>>::force_transfer(40 RuntimeOrigin::root(),41 DONOR_USER,42 user,43 value44 ));45}4647fn default_nft_data() -> CreateNftData {48 CreateNftData {49 properties: vec![Property {50 key: b"test-prop".to_vec().try_into().unwrap(),51 value: b"test-nft-prop".to_vec().try_into().unwrap(),52 }]53 .try_into()54 .unwrap(),55 }56}5758fn default_fungible_data() -> CreateFungibleData {59 CreateFungibleData { value: 5 }60}6162fn default_re_fungible_data() -> CreateReFungibleData {63 CreateReFungibleData {64 pieces: 1023,65 properties: vec![Property {66 key: b"test-prop".to_vec().try_into().unwrap(),67 value: b"test-nft-prop".to_vec().try_into().unwrap(),68 }]69 .try_into()70 .unwrap(),71 }72}7374fn create_test_collection_for_owner(75 mode: &CollectionMode,76 owner: u64,77 id: CollectionId,78) -> CollectionId {79 add_balance(owner, CollectionCreationPrice::get() as u64 + 1);8081 let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();82 let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();83 let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();84 let token_property_permissions: CollectionPropertiesPermissionsVec =85 vec![PropertyKeyPermission {86 key: b"test-prop".to_vec().try_into().unwrap(),87 permission: PropertyPermission {88 mutable: true,89 collection_admin: false,90 token_owner: true,91 },92 }]93 .try_into()94 .unwrap();95 let properties: CollectionPropertiesVec = vec![Property {96 key: b"test-collection-prop".to_vec().try_into().unwrap(),97 value: b"test-collection-value".to_vec().try_into().unwrap(),98 }]99 .try_into()100 .unwrap();101102 let data: CreateCollectionData<u64> = CreateCollectionData {103 name: col_name1.try_into().unwrap(),104 description: col_desc1.try_into().unwrap(),105 token_prefix: token_prefix1.try_into().unwrap(),106 mode: mode.clone(),107 token_property_permissions: token_property_permissions.clone(),108 properties: properties.clone(),109 ..Default::default()110 };111112 let origin1 = RuntimeOrigin::signed(owner);113 assert_ok!(Unique::create_collection_ex(origin1, data));114115 let saved_col_name: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();116 let saved_description: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();117 let saved_prefix: Vec<u8> = b"token_prefix1\0".to_vec();118 assert_eq!(119 <pallet_common::CollectionById<Test>>::get(id)120 .unwrap()121 .owner,122 owner123 );124 assert_eq!(125 <pallet_common::CollectionById<Test>>::get(id).unwrap().name,126 saved_col_name127 );128 assert_eq!(129 <pallet_common::CollectionById<Test>>::get(id).unwrap().mode,130 *mode131 );132 assert_eq!(133 <pallet_common::CollectionById<Test>>::get(id)134 .unwrap()135 .description,136 saved_description137 );138 assert_eq!(139 <pallet_common::CollectionById<Test>>::get(id)140 .unwrap()141 .token_prefix,142 saved_prefix143 );144 assert_eq!(145 get_collection_property_permissions(id).as_slice(),146 token_property_permissions.as_slice()147 );148 assert_eq!(149 get_collection_properties(id).as_slice(),150 properties.as_slice()151 );152 id153}154155fn get_collection_property_permissions(collection_id: CollectionId) -> Vec<PropertyKeyPermission> {156 <pallet_common::Pallet<Test>>::property_permissions(collection_id)157 .into_iter()158 .map(|(key, permission)| PropertyKeyPermission { key, permission })159 .collect()160}161162fn get_collection_properties(collection_id: CollectionId) -> Vec<Property> {163 <pallet_common::Pallet<Test>>::collection_properties(collection_id)164 .into_iter()165 .map(|(key, value)| Property { key, value })166 .collect()167}168169fn get_token_properties(collection_id: CollectionId, token_id: TokenId) -> Vec<Property> {170 <pallet_nonfungible::Pallet<Test>>::token_properties((collection_id, token_id))171 .into_iter()172 .map(|(key, value)| Property { key, value })173 .collect()174}175176fn create_test_collection(mode: &CollectionMode, id: CollectionId) -> CollectionId {177 create_test_collection_for_owner(&mode, 1, id)178}179180fn create_test_item(collection_id: CollectionId, data: &CreateItemData) {181 let origin1 = RuntimeOrigin::signed(1);182 assert_ok!(Unique::create_item(183 origin1,184 collection_id,185 account(1),186 data.clone()187 ));188}189190fn account(sub: u64) -> TestCrossAccountId {191 TestCrossAccountId::from_sub(sub)192}193194// Use cases tests region195// #region196197#[test]198fn check_not_sufficient_founds() {199 new_test_ext().execute_with(|| {200 let acc: u64 = 1;201 <pallet_balances::Pallet<Test>>::force_set_balance(RuntimeOrigin::root(), acc, 0).unwrap();202203 let name: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();204 let description: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();205 let token_prefix: Vec<u8> = b"token_prefix1\0".to_vec();206207 let data: CreateCollectionData<<Test as frame_system::Config>::AccountId> =208 CreateCollectionData {209 name: name.try_into().unwrap(),210 description: description.try_into().unwrap(),211 token_prefix: token_prefix.try_into().unwrap(),212 mode: CollectionMode::NFT,213 ..Default::default()214 };215216 let result = Unique::create_collection_ex(RuntimeOrigin::signed(acc), data);217 assert_err!(result, <CommonError<Test>>::NotSufficientFounds);218 });219}220221#[test]222fn create_fungible_collection_fails_with_large_decimal_numbers() {223 new_test_ext().execute_with(|| {224 let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();225 let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();226 let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();227228 let data: CreateCollectionData<u64> = CreateCollectionData {229 name: col_name1.try_into().unwrap(),230 description: col_desc1.try_into().unwrap(),231 token_prefix: token_prefix1.try_into().unwrap(),232 mode: CollectionMode::Fungible(MAX_DECIMAL_POINTS + 1),233 ..Default::default()234 };235236 let origin1 = RuntimeOrigin::signed(1);237 assert_noop!(238 Unique::create_collection_ex(origin1, data),239 UniqueError::<Test>::CollectionDecimalPointLimitExceeded240 );241 });242}243244#[test]245fn create_nft_item() {246 new_test_ext().execute_with(|| {247 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));248249 let data = default_nft_data();250 create_test_item(collection_id, &data.clone().into());251252 assert_eq!(253 get_token_properties(collection_id, TokenId(1)).as_slice(),254 data.properties.as_slice(),255 );256 });257}258259// Use cases tests region260// #region261#[test]262fn create_nft_multiple_items() {263 new_test_ext().execute_with(|| {264 create_test_collection(&CollectionMode::NFT, CollectionId(1));265266 let origin1 = RuntimeOrigin::signed(1);267268 let items_data = vec![default_nft_data(), default_nft_data(), default_nft_data()];269270 assert_ok!(Unique::create_multiple_items(271 origin1,272 CollectionId(1),273 account(1),274 items_data275 .clone()276 .into_iter()277 .map(|d| { d.into() })278 .collect()279 ));280 for (index, data) in items_data.into_iter().enumerate() {281 assert_eq!(282 get_token_properties(CollectionId(1), TokenId(index as u32 + 1)).as_slice(),283 data.properties.as_slice()284 );285 }286 });287}288289#[test]290fn create_refungible_item() {291 new_test_ext().execute_with(|| {292 let collection_id = create_test_collection(&CollectionMode::ReFungible, CollectionId(1));293294 let data = default_re_fungible_data();295 create_test_item(collection_id, &data.clone().into());296 let balance =297 <pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(1)));298 assert_eq!(balance, 1023);299 });300}301302#[test]303fn create_multiple_refungible_items() {304 new_test_ext().execute_with(|| {305 create_test_collection(&CollectionMode::ReFungible, CollectionId(1));306307 let origin1 = RuntimeOrigin::signed(1);308309 let items_data = vec![310 default_re_fungible_data(),311 default_re_fungible_data(),312 default_re_fungible_data(),313 ];314315 assert_ok!(Unique::create_multiple_items(316 origin1,317 CollectionId(1),318 account(1),319 items_data320 .clone()321 .into_iter()322 .map(|d| { d.into() })323 .collect()324 ));325 for (index, _data) in items_data.into_iter().enumerate() {326 let balance = <pallet_refungible::Balance<Test>>::get((327 CollectionId(1),328 TokenId((index + 1) as u32),329 account(1),330 ));331 assert_eq!(balance, 1023);332 }333 });334}335336#[test]337fn create_fungible_item() {338 new_test_ext().execute_with(|| {339 let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1));340341 let data = default_fungible_data();342 create_test_item(collection_id, &data.into());343344 assert_eq!(345 <pallet_fungible::Balance<Test>>::get((collection_id, account(1))),346 5347 );348 });349}350351//#[test]352// fn create_multiple_fungible_items() {353// new_test_ext().execute_with(|| {354// default_limits();355356// create_test_collection(&CollectionMode::Fungible(3), CollectionId(1));357358// let origin1 = RuntimeOrigin::signed(1);359360// let items_data = vec![default_fungible_data(), default_fungible_data(), default_fungible_data()];361362// assert_ok!(Unique::create_multiple_items(363// origin1.clone(),364// 1,365// 1,366// items_data.clone().into_iter().map(|d| { d.into() }).collect()367// ));368369// for (index, _) in items_data.iter().enumerate() {370// assert_eq!(Unique::fungible_item_id(1, (index + 1) as TokenId).value, 5);371// }372// assert_eq!(Unique::balance_count(1, 1), 3000);373// assert_eq!(Unique::address_tokens(1, 1), [1, 2, 3]);374// });375// }376377#[test]378fn transfer_fungible_item() {379 new_test_ext().execute_with(|| {380 let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1));381382 let origin1 = RuntimeOrigin::signed(1);383 let origin2 = RuntimeOrigin::signed(2);384385 let data = default_fungible_data();386 create_test_item(collection_id, &data.into());387388 assert_eq!(389 <pallet_fungible::Balance<Test>>::get((CollectionId(1), account(1))),390 5391 );392393 // change owner scenario394 assert_ok!(Unique::transfer(395 origin1,396 account(2),397 CollectionId(1),398 TokenId(0),399 5400 ));401 assert_eq!(402 <pallet_fungible::Balance<Test>>::get((CollectionId(1), account(1))),403 0404 );405406 // split item scenario407 assert_ok!(Unique::transfer(408 origin2.clone(),409 account(3),410 CollectionId(1),411 TokenId(0),412 3413 ));414415 // split item and new owner has account scenario416 assert_ok!(Unique::transfer(417 origin2,418 account(3),419 CollectionId(1),420 TokenId(0),421 1422 ));423 assert_eq!(424 <pallet_fungible::Balance<Test>>::get((CollectionId(1), account(2))),425 1426 );427 assert_eq!(428 <pallet_fungible::Balance<Test>>::get((CollectionId(1), account(3))),429 4430 );431 });432}433434#[test]435fn transfer_refungible_item() {436 new_test_ext().execute_with(|| {437 let collection_id = create_test_collection(&CollectionMode::ReFungible, CollectionId(1));438439 // Create RFT 1 in 1023 pieces for account 1440 let data = default_re_fungible_data();441 create_test_item(collection_id, &data.clone().into());442 assert_eq!(443 <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(1))),444 1445 );446 assert_eq!(447 <pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(1))),448 1023449 );450 assert_eq!(451 <pallet_refungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),452 true453 );454455 // Account 1 transfers all 1023 pieces of RFT 1 to account 2456 let origin1 = RuntimeOrigin::signed(1);457 let origin2 = RuntimeOrigin::signed(2);458 assert_ok!(Unique::transfer(459 origin1,460 account(2),461 CollectionId(1),462 TokenId(1),463 1023464 ));465 assert_eq!(466 <pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(2))),467 1023468 );469 assert_eq!(470 <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(1))),471 0472 );473 assert_eq!(474 <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(2))),475 1476 );477 assert_eq!(478 <pallet_refungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),479 false480 );481 assert_eq!(482 <pallet_refungible::Owned<Test>>::get((collection_id, account(2), TokenId(1))),483 true484 );485486 // Account 2 transfers 500 pieces of RFT 1 to account 3487 assert_ok!(Unique::transfer(488 origin2.clone(),489 account(3),490 CollectionId(1),491 TokenId(1),492 500493 ));494 assert_eq!(495 <pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(2))),496 523497 );498 assert_eq!(499 <pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(3))),500 500501 );502 assert_eq!(503 <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(2))),504 1505 );506 assert_eq!(507 <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(3))),508 1509 );510 assert_eq!(511 <pallet_refungible::Owned<Test>>::get((collection_id, account(2), TokenId(1))),512 true513 );514 assert_eq!(515 <pallet_refungible::Owned<Test>>::get((collection_id, account(3), TokenId(1))),516 true517 );518519 // Account 2 transfers 200 more pieces of RFT 1 to account 3 with pre-existing balance520 assert_ok!(Unique::transfer(521 origin2,522 account(3),523 CollectionId(1),524 TokenId(1),525 200526 ));527 assert_eq!(528 <pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(2))),529 323530 );531 assert_eq!(532 <pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(3))),533 700534 );535 assert_eq!(536 <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(2))),537 1538 );539 assert_eq!(540 <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(3))),541 1542 );543 assert_eq!(544 <pallet_refungible::Owned<Test>>::get((collection_id, account(2), TokenId(1))),545 true546 );547 assert_eq!(548 <pallet_refungible::Owned<Test>>::get((collection_id, account(3), TokenId(1))),549 true550 );551 });552}553554#[test]555fn transfer_nft_item() {556 new_test_ext().execute_with(|| {557 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));558559 let data = default_nft_data();560 create_test_item(collection_id, &data.into());561 assert_eq!(562 <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),563 1564 );565 assert_eq!(566 <pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),567 true568 );569570 let origin1 = RuntimeOrigin::signed(1);571 // default scenario572 assert_ok!(Unique::transfer(573 origin1,574 account(2),575 CollectionId(1),576 TokenId(1),577 1578 ));579 assert_eq!(580 <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),581 0582 );583 assert_eq!(584 <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(2))),585 1586 );587 assert_eq!(588 <pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),589 false590 );591 assert_eq!(592 <pallet_nonfungible::Owned<Test>>::get((collection_id, account(2), TokenId(1))),593 true594 );595 });596}597598#[test]599fn transfer_nft_item_wrong_value() {600 new_test_ext().execute_with(|| {601 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));602603 let data = default_nft_data();604 create_test_item(collection_id, &data.into());605 assert_eq!(606 <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),607 1608 );609 assert_eq!(610 <pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),611 true612 );613614 let origin1 = RuntimeOrigin::signed(1);615616 assert_noop!(617 Unique::transfer(origin1, account(2), CollectionId(1), TokenId(1), 2)618 .map_err(|e| e.error),619 <pallet_nonfungible::Error::<Test>>::NonfungibleItemsHaveNoAmount620 );621 });622}623624#[test]625fn transfer_nft_item_zero_value() {626 new_test_ext().execute_with(|| {627 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));628629 let data = default_nft_data();630 create_test_item(collection_id, &data.into());631 assert_eq!(632 <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),633 1634 );635 assert_eq!(636 <pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),637 true638 );639640 let origin1 = RuntimeOrigin::signed(1);641642 // Transferring 0 amount works on NFT...643 assert_ok!(Unique::transfer(644 origin1,645 account(2),646 CollectionId(1),647 TokenId(1),648 0649 ));650 // ... and results in no transfer651 assert_eq!(652 <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),653 1654 );655 assert_eq!(656 <pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),657 true658 );659 });660}661662#[test]663fn nft_approve_and_transfer_from() {664 new_test_ext().execute_with(|| {665 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));666667 let data = default_nft_data();668 create_test_item(collection_id, &data.into());669670 let origin1 = RuntimeOrigin::signed(1);671 let origin2 = RuntimeOrigin::signed(2);672673 assert_eq!(674 <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),675 1676 );677 assert_eq!(678 <pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),679 true680 );681682 // neg transfer_from683 assert_noop!(684 Unique::transfer_from(685 origin2.clone(),686 account(1),687 account(2),688 CollectionId(1),689 TokenId(1),690 1691 )692 .map_err(|e| e.error),693 CommonError::<Test>::ApprovedValueTooLow694 );695696 // do approve697 assert_ok!(Unique::approve(698 origin1,699 account(2),700 CollectionId(1),701 TokenId(1),702 1703 ));704 assert_eq!(705 <pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).unwrap(),706 account(2)707 );708709 assert_ok!(Unique::transfer_from(710 origin2,711 account(1),712 account(3),713 CollectionId(1),714 TokenId(1),715 1716 ));717 assert!(718 <pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).is_none()719 );720 });721}722723#[test]724fn nft_approve_and_transfer_from_allow_list() {725 new_test_ext().execute_with(|| {726 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));727728 let origin1 = RuntimeOrigin::signed(1);729 let origin2 = RuntimeOrigin::signed(2);730731 // Create NFT 1 for account 1732 let data = default_nft_data();733 create_test_item(collection_id, &data.clone().into());734 assert_eq!(735 <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),736 1737 );738 assert_eq!(739 <pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),740 true741 );742743 // Allow allow-list users to mint and add accounts 1, 2, and 3 to allow-list744 assert_ok!(Unique::set_collection_permissions(745 origin1.clone(),746 CollectionId(1),747 CollectionPermissions {748 mint_mode: Some(true),749 access: Some(AccessMode::AllowList),750 nesting: None,751 }752 ));753 assert_ok!(Unique::add_to_allow_list(754 origin1.clone(),755 CollectionId(1),756 account(1)757 ));758 assert_ok!(Unique::add_to_allow_list(759 origin1.clone(),760 CollectionId(1),761 account(2)762 ));763 assert_ok!(Unique::add_to_allow_list(764 origin1.clone(),765 CollectionId(1),766 account(3)767 ));768769 // Account 1 approves account 2 for NFT 1770 assert_ok!(Unique::approve(771 origin1.clone(),772 account(2),773 CollectionId(1),774 TokenId(1),775 1776 ));777 assert_eq!(778 <pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).unwrap(),779 account(2)780 );781782 // Account 2 transfers NFT 1 from account 1 to account 3783 assert_ok!(Unique::transfer_from(784 origin2,785 account(1),786 account(3),787 CollectionId(1),788 TokenId(1),789 1790 ));791 assert!(792 <pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).is_none()793 );794 });795}796797#[test]798fn refungible_approve_and_transfer_from() {799 new_test_ext().execute_with(|| {800 let collection_id = create_test_collection(&CollectionMode::ReFungible, CollectionId(1));801802 let origin1 = RuntimeOrigin::signed(1);803 let origin2 = RuntimeOrigin::signed(2);804805 // Create RFT 1 in 1023 pieces for account 1806 let data = default_re_fungible_data();807 create_test_item(collection_id, &data.into());808809 assert_eq!(810 <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(1))),811 1812 );813 assert_eq!(814 <pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(1))),815 1023816 );817 assert_eq!(818 <pallet_refungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),819 true820 );821822 // Allow public minting, enable allow-list and add accounts 1, 2, 3 to allow-list823 assert_ok!(Unique::set_collection_permissions(824 origin1.clone(),825 CollectionId(1),826 CollectionPermissions {827 mint_mode: Some(true),828 access: Some(AccessMode::AllowList),829 nesting: None,830 }831 ));832 assert_ok!(Unique::add_to_allow_list(833 origin1.clone(),834 CollectionId(1),835 account(1)836 ));837 assert_ok!(Unique::add_to_allow_list(838 origin1.clone(),839 CollectionId(1),840 account(2)841 ));842 assert_ok!(Unique::add_to_allow_list(843 origin1.clone(),844 CollectionId(1),845 account(3)846 ));847848 // Account 1 approves account 2 for 1023 pieces of RFT 1849 assert_ok!(Unique::approve(850 origin1,851 account(2),852 CollectionId(1),853 TokenId(1),854 1023855 ));856 assert_eq!(857 <pallet_refungible::Allowance<Test>>::get((858 CollectionId(1),859 TokenId(1),860 account(1),861 account(2)862 )),863 1023864 );865866 // Account 2 transfers 100 pieces of RFT 1 from account 1 to account 3867 assert_ok!(Unique::transfer_from(868 origin2,869 account(1),870 account(3),871 CollectionId(1),872 TokenId(1),873 100874 ));875 assert_eq!(876 <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(1))),877 1878 );879 assert_eq!(880 <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(3))),881 1882 );883 assert_eq!(884 <pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(1))),885 923886 );887 assert_eq!(888 <pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(3))),889 100890 );891 assert_eq!(892 <pallet_refungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),893 true894 );895 assert_eq!(896 <pallet_refungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),897 true898 );899 assert_eq!(900 <pallet_refungible::Allowance<Test>>::get((901 CollectionId(1),902 TokenId(1),903 account(1),904 account(2)905 )),906 923907 );908 });909}910911#[test]912fn fungible_approve_and_transfer_from() {913 new_test_ext().execute_with(|| {914 let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1));915916 let data = default_fungible_data();917 create_test_item(collection_id, &data.into());918919 let origin1 = RuntimeOrigin::signed(1);920 let origin2 = RuntimeOrigin::signed(2);921922 assert_ok!(Unique::set_collection_permissions(923 origin1.clone(),924 CollectionId(1),925 CollectionPermissions {926 mint_mode: Some(true),927 access: Some(AccessMode::AllowList),928 nesting: None,929 }930 ));931 assert_ok!(Unique::add_to_allow_list(932 origin1.clone(),933 CollectionId(1),934 account(1)935 ));936 assert_ok!(Unique::add_to_allow_list(937 origin1.clone(),938 CollectionId(1),939 account(2)940 ));941 assert_ok!(Unique::add_to_allow_list(942 origin1.clone(),943 CollectionId(1),944 account(3)945 ));946947 // do approve948 assert_ok!(Unique::approve(949 origin1.clone(),950 account(2),951 CollectionId(1),952 TokenId(0),953 5954 ));955 assert_eq!(956 <pallet_fungible::Allowance<Test>>::get((CollectionId(1), account(1), account(2))),957 5958 );959 assert_ok!(Unique::approve(960 origin1,961 account(3),962 CollectionId(1),963 TokenId(0),964 5965 ));966 assert_eq!(967 <pallet_fungible::Allowance<Test>>::get((CollectionId(1), account(1), account(2))),968 5969 );970 assert_eq!(971 <pallet_fungible::Allowance<Test>>::get((CollectionId(1), account(1), account(3))),972 5973 );974975 assert_ok!(Unique::transfer_from(976 origin2.clone(),977 account(1),978 account(3),979 CollectionId(1),980 TokenId(0),981 4982 ));983984 assert_eq!(985 <pallet_fungible::Allowance<Test>>::get((CollectionId(1), account(1), account(2))),986 1987 );988989 assert_noop!(990 Unique::transfer_from(991 origin2,992 account(1),993 account(3),994 CollectionId(1),995 TokenId(0),996 4997 )998 .map_err(|e| e.error),999 CommonError::<Test>::ApprovedValueTooLow1000 );1001 });1002}10031004#[test]1005fn change_collection_owner() {1006 new_test_ext().execute_with(|| {1007 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));10081009 let origin1 = RuntimeOrigin::signed(1);1010 assert_ok!(Unique::change_collection_owner(origin1, collection_id, 2));1011 assert_eq!(1012 <pallet_common::CollectionById<Test>>::get(collection_id)1013 .unwrap()1014 .owner,1015 21016 );1017 });1018}10191020#[test]1021fn destroy_collection() {1022 new_test_ext().execute_with(|| {1023 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));10241025 let origin1 = RuntimeOrigin::signed(1);1026 assert_ok!(Unique::destroy_collection(origin1, collection_id));1027 });1028}10291030#[test]1031fn burn_nft_item() {1032 new_test_ext().execute_with(|| {1033 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));10341035 let origin1 = RuntimeOrigin::signed(1);10361037 let data = default_nft_data();1038 create_test_item(collection_id, &data.into());10391040 // check balance (collection with id = 1, user id = 1)1041 assert_eq!(1042 <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),1043 11044 );10451046 // burn item1047 assert_ok!(Unique::burn_item(1048 origin1.clone(),1049 collection_id,1050 TokenId(1),1051 11052 ));1053 assert_eq!(1054 <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),1055 01056 );1057 });1058}10591060#[test]1061fn burn_same_nft_item_twice() {1062 new_test_ext().execute_with(|| {1063 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));10641065 let origin1 = RuntimeOrigin::signed(1);10661067 let data = default_nft_data();1068 create_test_item(collection_id, &data.into());10691070 // check balance (collection with id = 1, user id = 1)1071 assert_eq!(1072 <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),1073 11074 );10751076 // burn item1077 assert_ok!(Unique::burn_item(1078 origin1.clone(),1079 collection_id,1080 TokenId(1),1081 11082 ));10831084 // burn item again1085 assert_noop!(1086 Unique::burn_item(origin1, collection_id, TokenId(1), 1).map_err(|e| e.error),1087 CommonError::<Test>::TokenNotFound1088 );10891090 assert_eq!(1091 <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),1092 01093 );1094 });1095}10961097#[test]1098fn burn_fungible_item() {1099 new_test_ext().execute_with(|| {1100 let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1));11011102 let origin1 = RuntimeOrigin::signed(1);1103 assert_ok!(Unique::add_collection_admin(1104 origin1.clone(),1105 collection_id,1106 account(2)1107 ));11081109 let data = default_fungible_data();1110 create_test_item(collection_id, &data.into());11111112 // check balance (collection with id = 1, user id = 1)1113 assert_eq!(1114 <pallet_fungible::Balance<Test>>::get((collection_id, account(1))),1115 51116 );11171118 // burn item1119 assert_ok!(Unique::burn_item(1120 origin1.clone(),1121 CollectionId(1),1122 TokenId(0),1123 51124 ));1125 assert_noop!(1126 Unique::burn_item(origin1, CollectionId(1), TokenId(0), 5).map_err(|e| e.error),1127 CommonError::<Test>::TokenValueTooLow1128 );11291130 assert_eq!(1131 <pallet_fungible::Balance<Test>>::get((collection_id, account(1))),1132 01133 );1134 });1135}11361137#[test]1138fn burn_fungible_item_with_token_id() {1139 new_test_ext().execute_with(|| {1140 let collection_id = create_test_collection(&CollectionMode::Fungible(3), CollectionId(1));11411142 let origin1 = RuntimeOrigin::signed(1);1143 assert_ok!(Unique::add_collection_admin(1144 origin1.clone(),1145 collection_id,1146 account(2)1147 ));11481149 let data = default_fungible_data();1150 create_test_item(collection_id, &data.into());11511152 // check balance (collection with id = 1, user id = 1)1153 assert_eq!(1154 <pallet_fungible::Balance<Test>>::get((collection_id, account(1))),1155 51156 );11571158 // Try to burn item using Token ID1159 assert_noop!(1160 Unique::burn_item(origin1, CollectionId(1), TokenId(1), 5).map_err(|e| e.error),1161 <pallet_fungible::Error::<Test>>::FungibleItemsHaveNoId1162 );1163 });1164}1165#[test]1166fn burn_refungible_item() {1167 new_test_ext().execute_with(|| {1168 let collection_id = create_test_collection(&CollectionMode::ReFungible, CollectionId(1));1169 let origin1 = RuntimeOrigin::signed(1);11701171 assert_ok!(Unique::set_collection_permissions(1172 origin1.clone(),1173 collection_id,1174 CollectionPermissions {1175 mint_mode: Some(true),1176 access: Some(AccessMode::AllowList),1177 nesting: None,1178 }1179 ));1180 assert_ok!(Unique::add_to_allow_list(1181 origin1.clone(),1182 collection_id,1183 account(1)1184 ));11851186 assert_ok!(Unique::add_collection_admin(1187 origin1.clone(),1188 collection_id,1189 account(2)1190 ));11911192 let data = default_re_fungible_data();1193 create_test_item(collection_id, &data.into());11941195 // check balance (collection with id = 1, user id = 2)1196 assert_eq!(1197 <pallet_refungible::AccountBalance<Test>>::get((collection_id, account(1))),1198 11199 );1200 assert_eq!(1201 <pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(1))),1202 10231203 );12041205 // burn item1206 assert_ok!(Unique::burn_item(1207 origin1.clone(),1208 collection_id,1209 TokenId(1),1210 10231211 ));1212 assert_noop!(1213 Unique::burn_item(origin1, collection_id, TokenId(1), 1023).map_err(|e| e.error),1214 CommonError::<Test>::TokenValueTooLow1215 );12161217 assert_eq!(1218 <pallet_refungible::Balance<Test>>::get((collection_id, TokenId(1), account(1))),1219 01220 );1221 });1222}12231224#[test]1225fn add_collection_admin() {1226 new_test_ext().execute_with(|| {1227 let collection1_id =1228 create_test_collection_for_owner(&CollectionMode::NFT, 1, CollectionId(1));1229 let origin1 = RuntimeOrigin::signed(1);12301231 // Add collection admins1232 assert_ok!(Unique::add_collection_admin(1233 origin1.clone(),1234 collection1_id,1235 account(2)1236 ));1237 assert_ok!(Unique::add_collection_admin(1238 origin1,1239 collection1_id,1240 account(3)1241 ));12421243 // Owner is not an admin by default1244 assert_eq!(1245 <pallet_common::IsAdmin<Test>>::get((CollectionId(1), account(1))),1246 false1247 );1248 assert!(<pallet_common::IsAdmin<Test>>::get((1249 CollectionId(1),1250 account(2)1251 )));1252 assert!(<pallet_common::IsAdmin<Test>>::get((1253 CollectionId(1),1254 account(3)1255 )));1256 });1257}12581259#[test]1260fn remove_collection_admin() {1261 new_test_ext().execute_with(|| {1262 let collection1_id =1263 create_test_collection_for_owner(&CollectionMode::NFT, 1, CollectionId(1));1264 let origin1 = RuntimeOrigin::signed(1);12651266 // Add collection admins 2 and 31267 assert_ok!(Unique::add_collection_admin(1268 origin1.clone(),1269 collection1_id,1270 account(2)1271 ));1272 assert_ok!(Unique::add_collection_admin(1273 origin1.clone(),1274 collection1_id,1275 account(3)1276 ));12771278 assert!(<pallet_common::IsAdmin<Test>>::get((1279 CollectionId(1),1280 account(2)1281 )));1282 assert!(<pallet_common::IsAdmin<Test>>::get((1283 CollectionId(1),1284 account(3)1285 )));12861287 // remove admin 31288 assert_ok!(Unique::remove_collection_admin(1289 origin1,1290 CollectionId(1),1291 account(3)1292 ));12931294 // 2 is still admin, 3 is not an admin anymore1295 assert!(<pallet_common::IsAdmin<Test>>::get((1296 CollectionId(1),1297 account(2)1298 )));1299 assert_eq!(1300 <pallet_common::IsAdmin<Test>>::get((CollectionId(1), account(3))),1301 false1302 );1303 });1304}13051306#[test]1307fn balance_of() {1308 new_test_ext().execute_with(|| {1309 let nft_collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1310 let fungible_collection_id =1311 create_test_collection(&CollectionMode::Fungible(3), CollectionId(2));1312 let re_fungible_collection_id =1313 create_test_collection(&CollectionMode::ReFungible, CollectionId(3));13141315 // check balance before1316 assert_eq!(1317 <pallet_nonfungible::AccountBalance<Test>>::get((nft_collection_id, account(1))),1318 01319 );1320 assert_eq!(1321 <pallet_fungible::Balance<Test>>::get((fungible_collection_id, account(1))),1322 01323 );1324 assert_eq!(1325 <pallet_refungible::AccountBalance<Test>>::get((re_fungible_collection_id, account(1))),1326 01327 );13281329 let nft_data = default_nft_data();1330 create_test_item(nft_collection_id, &nft_data.into());13311332 let fungible_data = default_fungible_data();1333 create_test_item(fungible_collection_id, &fungible_data.into());13341335 let re_fungible_data = default_re_fungible_data();1336 create_test_item(re_fungible_collection_id, &re_fungible_data.into());13371338 // check balance (collection with id = 1, user id = 1)1339 assert_eq!(1340 <pallet_nonfungible::AccountBalance<Test>>::get((nft_collection_id, account(1))),1341 11342 );1343 assert_eq!(1344 <pallet_fungible::Balance<Test>>::get((fungible_collection_id, account(1))),1345 51346 );1347 assert_eq!(1348 <pallet_refungible::AccountBalance<Test>>::get((re_fungible_collection_id, account(1))),1349 11350 );13511352 assert_eq!(1353 <pallet_nonfungible::Owned<Test>>::get((nft_collection_id, account(1), TokenId(1))),1354 true1355 );1356 assert_eq!(1357 <pallet_refungible::Owned<Test>>::get((1358 re_fungible_collection_id,1359 account(1),1360 TokenId(1)1361 )),1362 true1363 );1364 });1365}13661367#[test]1368fn approve() {1369 new_test_ext().execute_with(|| {1370 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));13711372 let data = default_nft_data();1373 create_test_item(collection_id, &data.into());13741375 let origin1 = RuntimeOrigin::signed(1);13761377 // approve1378 assert_ok!(Unique::approve(1379 origin1,1380 account(2),1381 CollectionId(1),1382 TokenId(1),1383 11384 ));1385 assert_eq!(1386 <pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).unwrap(),1387 account(2)1388 );1389 });1390}13911392#[test]1393fn transfer_from() {1394 new_test_ext().execute_with(|| {1395 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1396 let origin1 = RuntimeOrigin::signed(1);1397 let origin2 = RuntimeOrigin::signed(2);13981399 let data = default_nft_data();1400 create_test_item(collection_id, &data.into());14011402 // approve1403 assert_ok!(Unique::approve(1404 origin1.clone(),1405 account(2),1406 CollectionId(1),1407 TokenId(1),1408 11409 ));1410 assert_eq!(1411 <pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).unwrap(),1412 account(2)1413 );14141415 assert_ok!(Unique::set_collection_permissions(1416 origin1.clone(),1417 CollectionId(1),1418 CollectionPermissions {1419 mint_mode: Some(true),1420 access: Some(AccessMode::AllowList),1421 nesting: None,1422 }1423 ));1424 assert_ok!(Unique::add_to_allow_list(1425 origin1.clone(),1426 CollectionId(1),1427 account(1)1428 ));1429 assert_ok!(Unique::add_to_allow_list(1430 origin1.clone(),1431 CollectionId(1),1432 account(2)1433 ));1434 assert_ok!(Unique::add_to_allow_list(1435 origin1,1436 CollectionId(1),1437 account(3)1438 ));14391440 assert_ok!(Unique::transfer_from(1441 origin2,1442 account(1),1443 account(2),1444 CollectionId(1),1445 TokenId(1),1446 11447 ));14481449 // after transfer1450 assert_eq!(1451 <pallet_nonfungible::AccountBalance<Test>>::get((CollectionId(1), account(1))),1452 01453 );1454 assert_eq!(1455 <pallet_nonfungible::AccountBalance<Test>>::get((CollectionId(1), account(2))),1456 11457 );1458 });1459}14601461// #endregion14621463// Coverage tests region1464// #region14651466#[test]1467fn owner_can_add_address_to_allow_list() {1468 new_test_ext().execute_with(|| {1469 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));14701471 let origin1 = RuntimeOrigin::signed(1);1472 assert_ok!(Unique::add_to_allow_list(1473 origin1,1474 collection_id,1475 account(2)1476 ));1477 assert!(<pallet_common::Allowlist<Test>>::get((1478 collection_id,1479 account(2)1480 )));1481 });1482}14831484#[test]1485fn admin_can_add_address_to_allow_list() {1486 new_test_ext().execute_with(|| {1487 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1488 let origin1 = RuntimeOrigin::signed(1);1489 let origin2 = RuntimeOrigin::signed(2);14901491 assert_ok!(Unique::add_collection_admin(1492 origin1,1493 collection_id,1494 account(2)1495 ));1496 assert_ok!(Unique::add_to_allow_list(1497 origin2,1498 collection_id,1499 account(3)1500 ));1501 assert!(<pallet_common::Allowlist<Test>>::get((1502 collection_id,1503 account(3)1504 )));1505 });1506}15071508#[test]1509fn nonprivileged_user_cannot_add_address_to_allow_list() {1510 new_test_ext().execute_with(|| {1511 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));15121513 let origin2 = RuntimeOrigin::signed(2);1514 assert_noop!(1515 Unique::add_to_allow_list(origin2, collection_id, account(3)),1516 CommonError::<Test>::NoPermission1517 );1518 });1519}15201521#[test]1522fn nobody_can_add_address_to_allow_list_of_nonexisting_collection() {1523 new_test_ext().execute_with(|| {1524 let origin1 = RuntimeOrigin::signed(1);15251526 assert_noop!(1527 Unique::add_to_allow_list(origin1, CollectionId(1), account(2)),1528 CommonError::<Test>::CollectionNotFound1529 );1530 });1531}15321533#[test]1534fn nobody_can_add_address_to_allow_list_of_deleted_collection() {1535 new_test_ext().execute_with(|| {1536 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));15371538 let origin1 = RuntimeOrigin::signed(1);1539 assert_ok!(Unique::destroy_collection(origin1.clone(), collection_id));1540 assert_noop!(1541 Unique::add_to_allow_list(origin1, collection_id, account(2)),1542 CommonError::<Test>::CollectionNotFound1543 );1544 });1545}15461547// If address is already added to allow list, nothing happens1548#[test]1549fn address_is_already_added_to_allow_list() {1550 new_test_ext().execute_with(|| {1551 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1552 let origin1 = RuntimeOrigin::signed(1);15531554 assert_ok!(Unique::add_to_allow_list(1555 origin1.clone(),1556 collection_id,1557 account(2)1558 ));1559 assert_ok!(Unique::add_to_allow_list(1560 origin1,1561 collection_id,1562 account(2)1563 ));1564 assert!(<pallet_common::Allowlist<Test>>::get((1565 collection_id,1566 account(2)1567 )));1568 });1569}15701571#[test]1572fn owner_can_remove_address_from_allow_list() {1573 new_test_ext().execute_with(|| {1574 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));15751576 let origin1 = RuntimeOrigin::signed(1);1577 assert_ok!(Unique::add_to_allow_list(1578 origin1.clone(),1579 collection_id,1580 account(2)1581 ));1582 assert_ok!(Unique::remove_from_allow_list(1583 origin1,1584 collection_id,1585 account(2)1586 ));1587 assert_eq!(1588 <pallet_common::Allowlist<Test>>::get((collection_id, account(2))),1589 false1590 );1591 });1592}15931594#[test]1595fn admin_can_remove_address_from_allow_list() {1596 new_test_ext().execute_with(|| {1597 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1598 let origin1 = RuntimeOrigin::signed(1);1599 let origin2 = RuntimeOrigin::signed(2);16001601 // Owner adds admin1602 assert_ok!(Unique::add_collection_admin(1603 origin1.clone(),1604 collection_id,1605 account(2)1606 ));16071608 // Owner adds address 3 to allow list1609 assert_ok!(Unique::add_to_allow_list(1610 origin1,1611 collection_id,1612 account(3)1613 ));16141615 // Admin removes address 3 from allow list1616 assert_ok!(Unique::remove_from_allow_list(1617 origin2,1618 collection_id,1619 account(3)1620 ));1621 assert_eq!(1622 <pallet_common::Allowlist<Test>>::get((collection_id, account(3))),1623 false1624 );1625 });1626}16271628#[test]1629fn nonprivileged_user_cannot_remove_address_from_allow_list() {1630 new_test_ext().execute_with(|| {1631 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1632 let origin1 = RuntimeOrigin::signed(1);1633 let origin2 = RuntimeOrigin::signed(2);16341635 assert_ok!(Unique::add_to_allow_list(1636 origin1,1637 collection_id,1638 account(2)1639 ));1640 assert_noop!(1641 Unique::remove_from_allow_list(origin2, collection_id, account(2)),1642 CommonError::<Test>::NoPermission1643 );1644 assert!(<pallet_common::Allowlist<Test>>::get((1645 collection_id,1646 account(2)1647 )));1648 });1649}16501651#[test]1652fn nobody_can_remove_address_from_allow_list_of_nonexisting_collection() {1653 new_test_ext().execute_with(|| {1654 let origin1 = RuntimeOrigin::signed(1);16551656 assert_noop!(1657 Unique::remove_from_allow_list(origin1, CollectionId(1), account(2)),1658 CommonError::<Test>::CollectionNotFound1659 );1660 });1661}16621663#[test]1664fn nobody_can_remove_address_from_allow_list_of_deleted_collection() {1665 new_test_ext().execute_with(|| {1666 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1667 let origin1 = RuntimeOrigin::signed(1);1668 let origin2 = RuntimeOrigin::signed(2);16691670 // Add account 2 to allow list1671 assert_ok!(Unique::add_to_allow_list(1672 origin1.clone(),1673 collection_id,1674 account(2)1675 ));16761677 // Account 2 is in collection allow-list1678 assert!(<pallet_common::Allowlist<Test>>::get((1679 collection_id,1680 account(2)1681 )));16821683 // Destroy collection1684 assert_ok!(Unique::destroy_collection(origin1, collection_id));16851686 // Attempt to remove account 2 from collection allow-list => error1687 assert_noop!(1688 Unique::remove_from_allow_list(origin2, collection_id, account(2)),1689 CommonError::<Test>::CollectionNotFound1690 );16911692 // Account 2 is not found in collection allow-list anyway1693 assert_eq!(1694 <pallet_common::Allowlist<Test>>::get((collection_id, account(2))),1695 false1696 );1697 });1698}16991700// If address is already removed from allow list, nothing happens1701#[test]1702fn address_is_already_removed_from_allow_list() {1703 new_test_ext().execute_with(|| {1704 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1705 let origin1 = RuntimeOrigin::signed(1);17061707 assert_ok!(Unique::add_to_allow_list(1708 origin1.clone(),1709 collection_id,1710 account(2)1711 ));1712 assert_ok!(Unique::remove_from_allow_list(1713 origin1.clone(),1714 collection_id,1715 account(2)1716 ));1717 assert_eq!(1718 <pallet_common::Allowlist<Test>>::get((collection_id, account(2))),1719 false1720 );1721 assert_ok!(Unique::remove_from_allow_list(1722 origin1,1723 collection_id,1724 account(2)1725 ));1726 assert_eq!(1727 <pallet_common::Allowlist<Test>>::get((collection_id, account(2))),1728 false1729 );1730 });1731}17321733// If Public Access mode is set to AllowList, tokens can’t be transferred from a non-allowlisted address with transfer or transferFrom (2 tests)1734#[test]1735fn allow_list_test_1() {1736 new_test_ext().execute_with(|| {1737 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));17381739 let origin1 = RuntimeOrigin::signed(1);17401741 let data = default_nft_data();1742 create_test_item(collection_id, &data.into());17431744 assert_ok!(Unique::set_collection_permissions(1745 origin1.clone(),1746 collection_id,1747 CollectionPermissions {1748 mint_mode: None,1749 access: Some(AccessMode::AllowList),1750 nesting: None,1751 }1752 ));1753 assert_ok!(Unique::add_to_allow_list(1754 origin1.clone(),1755 collection_id,1756 account(2)1757 ));17581759 assert_noop!(1760 Unique::transfer(origin1, account(3), CollectionId(1), TokenId(1), 1)1761 .map_err(|e| e.error),1762 CommonError::<Test>::AddressNotInAllowlist1763 );1764 });1765}17661767#[test]1768fn allow_list_test_2() {1769 new_test_ext().execute_with(|| {1770 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));1771 let origin1 = RuntimeOrigin::signed(1);17721773 let data = default_nft_data();1774 create_test_item(collection_id, &data.into());17751776 assert_ok!(Unique::set_collection_permissions(1777 origin1.clone(),1778 collection_id,1779 CollectionPermissions {1780 mint_mode: None,1781 access: Some(AccessMode::AllowList),1782 nesting: None,1783 }1784 ));1785 assert_ok!(Unique::add_to_allow_list(1786 origin1.clone(),1787 collection_id,1788 account(1)1789 ));1790 assert_ok!(Unique::add_to_allow_list(1791 origin1.clone(),1792 collection_id,1793 account(2)1794 ));17951796 // do approve1797 assert_ok!(Unique::approve(1798 origin1.clone(),1799 account(1),1800 collection_id,1801 TokenId(1),1802 11803 ));1804 assert_eq!(1805 <pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).unwrap(),1806 account(1)1807 );18081809 assert_ok!(Unique::remove_from_allow_list(1810 origin1.clone(),1811 collection_id,1812 account(1)1813 ));18141815 assert_noop!(1816 Unique::transfer_from(1817 origin1,1818 account(1),1819 account(3),1820 CollectionId(1),1821 TokenId(1),1822 11823 )1824 .map_err(|e| e.error),1825 CommonError::<Test>::AddressNotInAllowlist1826 );1827 });1828}18291830// If Public Access mode is set to AllowList, tokens can’t be transferred to a non-allowlisted address with transfer or transferFrom (2 tests)1831#[test]1832fn allow_list_test_3() {1833 new_test_ext().execute_with(|| {1834 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));18351836 let origin1 = RuntimeOrigin::signed(1);18371838 let data = default_nft_data();1839 create_test_item(collection_id, &data.into());18401841 assert_ok!(Unique::set_collection_permissions(1842 origin1.clone(),1843 collection_id,1844 CollectionPermissions {1845 mint_mode: None,1846 access: Some(AccessMode::AllowList),1847 nesting: None,1848 }1849 ));1850 assert_ok!(Unique::add_to_allow_list(1851 origin1.clone(),1852 collection_id,1853 account(1)1854 ));18551856 assert_noop!(1857 Unique::transfer(origin1, account(3), collection_id, TokenId(1), 1)1858 .map_err(|e| e.error),1859 CommonError::<Test>::AddressNotInAllowlist1860 );1861 });1862}18631864#[test]1865fn allow_list_test_4() {1866 new_test_ext().execute_with(|| {1867 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));18681869 let origin1 = RuntimeOrigin::signed(1);18701871 let data = default_nft_data();1872 create_test_item(collection_id, &data.into());18731874 assert_ok!(Unique::set_collection_permissions(1875 origin1.clone(),1876 collection_id,1877 CollectionPermissions {1878 mint_mode: None,1879 access: Some(AccessMode::AllowList),1880 nesting: None,1881 }1882 ));1883 assert_ok!(Unique::add_to_allow_list(1884 origin1.clone(),1885 collection_id,1886 account(1)1887 ));1888 assert_ok!(Unique::add_to_allow_list(1889 origin1.clone(),1890 collection_id,1891 account(2)1892 ));18931894 // do approve1895 assert_ok!(Unique::approve(1896 origin1.clone(),1897 account(1),1898 collection_id,1899 TokenId(1),1900 11901 ));1902 assert_eq!(1903 <pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).unwrap(),1904 account(1)1905 );19061907 assert_ok!(Unique::remove_from_allow_list(1908 origin1.clone(),1909 collection_id,1910 account(2)1911 ));19121913 assert_noop!(1914 Unique::transfer_from(1915 origin1,1916 account(1),1917 account(3),1918 collection_id,1919 TokenId(1),1920 11921 )1922 .map_err(|e| e.error),1923 CommonError::<Test>::AddressNotInAllowlist1924 );1925 });1926}19271928// If Public Access mode is set to AllowList, tokens can’t be destroyed by a non-allowlisted address (even if it owned them before enabling AllowList mode)1929#[test]1930fn allow_list_test_5() {1931 new_test_ext().execute_with(|| {1932 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));19331934 let origin1 = RuntimeOrigin::signed(1);19351936 let data = default_nft_data();1937 create_test_item(collection_id, &data.into());19381939 assert_ok!(Unique::set_collection_permissions(1940 origin1.clone(),1941 collection_id,1942 CollectionPermissions {1943 mint_mode: None,1944 access: Some(AccessMode::AllowList),1945 nesting: None,1946 }1947 ));1948 assert_noop!(1949 Unique::burn_item(origin1.clone(), CollectionId(1), TokenId(1), 1).map_err(|e| e.error),1950 CommonError::<Test>::AddressNotInAllowlist1951 );1952 });1953}19541955// If Public Access mode is set to AllowList, token transfers can’t be Approved by a non-allowlisted address (see Approve method).1956#[test]1957fn allow_list_test_6() {1958 new_test_ext().execute_with(|| {1959 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));19601961 let origin1 = RuntimeOrigin::signed(1);19621963 let data = default_nft_data();1964 create_test_item(collection_id, &data.into());19651966 assert_ok!(Unique::set_collection_permissions(1967 origin1.clone(),1968 collection_id,1969 CollectionPermissions {1970 mint_mode: None,1971 access: Some(AccessMode::AllowList),1972 nesting: None,1973 }1974 ));19751976 // do approve1977 assert_noop!(1978 Unique::approve(origin1, account(1), CollectionId(1), TokenId(1), 1)1979 .map_err(|e| e.error),1980 CommonError::<Test>::AddressNotInAllowlist1981 );1982 });1983}19841985// If Public Access mode is set to AllowList, tokens can be transferred from a allowlisted address with transfer or transferFrom (2 tests) and1986// tokens can be transferred from a allowlisted address with transfer or transferFrom (2 tests)1987#[test]1988fn allow_list_test_7() {1989 new_test_ext().execute_with(|| {1990 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));19911992 let data = default_nft_data();1993 create_test_item(collection_id, &data.into());19941995 let origin1 = RuntimeOrigin::signed(1);19961997 assert_ok!(Unique::set_collection_permissions(1998 origin1.clone(),1999 collection_id,2000 CollectionPermissions {2001 mint_mode: None,2002 access: Some(AccessMode::AllowList),2003 nesting: None,2004 }2005 ));2006 assert_ok!(Unique::add_to_allow_list(2007 origin1.clone(),2008 collection_id,2009 account(1)2010 ));2011 assert_ok!(Unique::add_to_allow_list(2012 origin1.clone(),2013 collection_id,2014 account(2)2015 ));20162017 assert_ok!(Unique::transfer(2018 origin1,2019 account(2),2020 CollectionId(1),2021 TokenId(1),2022 12023 ));2024 });2025}20262027#[test]2028fn allow_list_test_8() {2029 new_test_ext().execute_with(|| {2030 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));20312032 // Create NFT for account 12033 let data = default_nft_data();2034 create_test_item(collection_id, &data.into());20352036 let origin1 = RuntimeOrigin::signed(1);20372038 // Toggle Allow List mode and add accounts 1 and 22039 assert_ok!(Unique::set_collection_permissions(2040 origin1.clone(),2041 collection_id,2042 CollectionPermissions {2043 mint_mode: None,2044 access: Some(AccessMode::AllowList),2045 nesting: None,2046 }2047 ));2048 assert_ok!(Unique::add_to_allow_list(2049 origin1.clone(),2050 collection_id,2051 account(1)2052 ));2053 assert_ok!(Unique::add_to_allow_list(2054 origin1.clone(),2055 collection_id,2056 account(2)2057 ));20582059 // Sself-approve account 1 for NFT 12060 assert_ok!(Unique::approve(2061 origin1.clone(),2062 account(1),2063 CollectionId(1),2064 TokenId(1),2065 12066 ));2067 assert_eq!(2068 <pallet_nonfungible::Allowance<Test>>::get((CollectionId(1), TokenId(1))).unwrap(),2069 account(1)2070 );20712072 // Transfer from 1 to 22073 assert_ok!(Unique::transfer_from(2074 origin1,2075 account(1),2076 account(2),2077 CollectionId(1),2078 TokenId(1),2079 12080 ));2081 });2082}20832084// If Public Access mode is set to AllowList, and Mint Permission is set to false, tokens can be created by owner.2085#[test]2086fn allow_list_test_9() {2087 new_test_ext().execute_with(|| {2088 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));2089 let origin1 = RuntimeOrigin::signed(1);20902091 assert_ok!(Unique::set_collection_permissions(2092 origin1.clone(),2093 collection_id,2094 CollectionPermissions {2095 mint_mode: Some(false),2096 access: Some(AccessMode::AllowList),2097 nesting: None,2098 }2099 ));21002101 let data = default_nft_data();2102 create_test_item(collection_id, &data.into());2103 });2104}21052106// If Public Access mode is set to AllowList, and Mint Permission is set to false, tokens can be created by admin.2107#[test]2108fn allow_list_test_10() {2109 new_test_ext().execute_with(|| {2110 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));21112112 let origin1 = RuntimeOrigin::signed(1);2113 let origin2 = RuntimeOrigin::signed(2);21142115 assert_ok!(Unique::set_collection_permissions(2116 origin1.clone(),2117 collection_id,2118 CollectionPermissions {2119 mint_mode: Some(false),2120 access: Some(AccessMode::AllowList),2121 nesting: None,2122 }2123 ));21242125 assert_ok!(Unique::add_collection_admin(2126 origin1,2127 collection_id,2128 account(2)2129 ));21302131 assert_ok!(Unique::create_item(2132 origin2,2133 collection_id,2134 account(2),2135 default_nft_data().into()2136 ));2137 });2138}21392140// If Public Access mode is set to AllowList, and Mint Permission is set to false, tokens cannot be created by non-privileged and allow listed address.2141#[test]2142fn allow_list_test_11() {2143 new_test_ext().execute_with(|| {2144 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));21452146 let origin1 = RuntimeOrigin::signed(1);2147 let origin2 = RuntimeOrigin::signed(2);21482149 assert_ok!(Unique::set_collection_permissions(2150 origin1.clone(),2151 collection_id,2152 CollectionPermissions {2153 mint_mode: Some(false),2154 access: Some(AccessMode::AllowList),2155 nesting: None,2156 }2157 ));2158 assert_ok!(Unique::add_to_allow_list(2159 origin1,2160 collection_id,2161 account(2)2162 ));21632164 assert_noop!(2165 Unique::create_item(2166 origin2,2167 CollectionId(1),2168 account(2),2169 default_nft_data().into()2170 )2171 .map_err(|e| e.error),2172 CommonError::<Test>::PublicMintingNotAllowed2173 );2174 });2175}21762177// If Public Access mode is set to AllowList, and Mint Permission is set to false, tokens cannot be created by non-privileged and non-allow listed address.2178#[test]2179fn allow_list_test_12() {2180 new_test_ext().execute_with(|| {2181 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));21822183 let origin1 = RuntimeOrigin::signed(1);2184 let origin2 = RuntimeOrigin::signed(2);21852186 assert_ok!(Unique::set_collection_permissions(2187 origin1.clone(),2188 collection_id,2189 CollectionPermissions {2190 mint_mode: Some(false),2191 access: Some(AccessMode::AllowList),2192 nesting: None,2193 }2194 ));21952196 assert_noop!(2197 Unique::create_item(2198 origin2,2199 CollectionId(1),2200 account(2),2201 default_nft_data().into()2202 )2203 .map_err(|e| e.error),2204 CommonError::<Test>::PublicMintingNotAllowed2205 );2206 });2207}22082209// If Public Access mode is set to AllowList, and Mint Permission is set to true, tokens can be created by owner.2210#[test]2211fn allow_list_test_13() {2212 new_test_ext().execute_with(|| {2213 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));22142215 let origin1 = RuntimeOrigin::signed(1);22162217 assert_ok!(Unique::set_collection_permissions(2218 origin1.clone(),2219 collection_id,2220 CollectionPermissions {2221 mint_mode: Some(true),2222 access: Some(AccessMode::AllowList),2223 nesting: None,2224 }2225 ));22262227 let data = default_nft_data();2228 create_test_item(collection_id, &data.into());2229 });2230}22312232// If Public Access mode is set to AllowList, and Mint Permission is set to true, tokens can be created by admin.2233#[test]2234fn allow_list_test_14() {2235 new_test_ext().execute_with(|| {2236 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));22372238 let origin1 = RuntimeOrigin::signed(1);2239 let origin2 = RuntimeOrigin::signed(2);22402241 assert_ok!(Unique::set_collection_permissions(2242 origin1.clone(),2243 collection_id,2244 CollectionPermissions {2245 mint_mode: Some(true),2246 access: Some(AccessMode::AllowList),2247 nesting: None,2248 }2249 ));22502251 assert_ok!(Unique::add_collection_admin(2252 origin1,2253 collection_id,2254 account(2)2255 ));22562257 assert_ok!(Unique::create_item(2258 origin2,2259 collection_id,2260 account(2),2261 default_nft_data().into()2262 ));2263 });2264}22652266// If Public Access mode is set to AllowList, and Mint Permission is set to true, tokens cannot be created by non-privileged and non-allow listed address.2267#[test]2268fn allow_list_test_15() {2269 new_test_ext().execute_with(|| {2270 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));22712272 let origin1 = RuntimeOrigin::signed(1);2273 let origin2 = RuntimeOrigin::signed(2);22742275 assert_ok!(Unique::set_collection_permissions(2276 origin1.clone(),2277 collection_id,2278 CollectionPermissions {2279 mint_mode: Some(true),2280 access: Some(AccessMode::AllowList),2281 nesting: None,2282 }2283 ));22842285 assert_noop!(2286 Unique::create_item(2287 origin2,2288 collection_id,2289 account(2),2290 default_nft_data().into()2291 )2292 .map_err(|e| e.error),2293 CommonError::<Test>::AddressNotInAllowlist2294 );2295 });2296}22972298// If Public Access mode is set to AllowList, and Mint Permission is set to true, tokens can be created by non-privileged and allow listed address.2299#[test]2300fn allow_list_test_16() {2301 new_test_ext().execute_with(|| {2302 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));23032304 let origin1 = RuntimeOrigin::signed(1);2305 let origin2 = RuntimeOrigin::signed(2);23062307 assert_ok!(Unique::set_collection_permissions(2308 origin1.clone(),2309 collection_id,2310 CollectionPermissions {2311 mint_mode: Some(true),2312 access: Some(AccessMode::AllowList),2313 nesting: None,2314 }2315 ));2316 assert_ok!(Unique::add_to_allow_list(2317 origin1,2318 collection_id,2319 account(2)2320 ));23212322 assert_ok!(Unique::create_item(2323 origin2,2324 collection_id,2325 account(2),2326 default_nft_data().into()2327 ));2328 });2329}23302331// Total number of collections. Positive test2332#[test]2333fn total_number_collections_bound() {2334 new_test_ext().execute_with(|| {2335 create_test_collection(&CollectionMode::NFT, CollectionId(1));2336 });2337}23382339#[test]2340fn create_max_collections() {2341 new_test_ext().execute_with(|| {2342 for i in 1..COLLECTION_NUMBER_LIMIT {2343 create_test_collection(&CollectionMode::NFT, CollectionId(i));2344 }2345 });2346}23472348// Total number of collections. Negative test2349#[test]2350fn total_number_collections_bound_neg() {2351 new_test_ext().execute_with(|| {2352 let origin1 = RuntimeOrigin::signed(1);23532354 for i in 1..=COLLECTION_NUMBER_LIMIT {2355 create_test_collection(&CollectionMode::NFT, CollectionId(i));2356 }23572358 let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();2359 let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();2360 let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();23612362 let data: CreateCollectionData<u64> = CreateCollectionData {2363 name: col_name1.try_into().unwrap(),2364 description: col_desc1.try_into().unwrap(),2365 token_prefix: token_prefix1.try_into().unwrap(),2366 mode: CollectionMode::NFT,2367 ..Default::default()2368 };23692370 // 11-th collection in chain. Expects error2371 assert_noop!(2372 Unique::create_collection_ex(origin1, data),2373 CommonError::<Test>::TotalCollectionsLimitExceeded2374 );2375 });2376}23772378// Owned tokens by a single address. Positive test2379#[test]2380fn owned_tokens_bound() {2381 new_test_ext().execute_with(|| {2382 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));23832384 let data = default_nft_data();2385 create_test_item(collection_id, &data.clone().into());2386 create_test_item(collection_id, &data.into());2387 });2388}23892390// Owned tokens by a single address. Negotive test2391#[test]2392fn owned_tokens_bound_neg() {2393 new_test_ext().execute_with(|| {2394 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));23952396 let origin1 = RuntimeOrigin::signed(1);23972398 for _ in 1..=MAX_TOKEN_OWNERSHIP {2399 let data = default_nft_data();2400 create_test_item(collection_id, &data.clone().into());2401 }24022403 let data = default_nft_data();2404 assert_noop!(2405 Unique::create_item(origin1, CollectionId(1), account(1), data.into())2406 .map_err(|e| e.error),2407 CommonError::<Test>::AccountTokenLimitExceeded2408 );2409 });2410}24112412// Number of collection admins. Positive test2413#[test]2414fn collection_admins_bound() {2415 new_test_ext().execute_with(|| {2416 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));24172418 let origin1 = RuntimeOrigin::signed(1);24192420 assert_ok!(Unique::add_collection_admin(2421 origin1.clone(),2422 collection_id,2423 account(2)2424 ));2425 assert_ok!(Unique::add_collection_admin(2426 origin1,2427 collection_id,2428 account(3)2429 ));2430 });2431}24322433// Number of collection admins. Negotive test2434#[test]2435fn collection_admins_bound_neg() {2436 new_test_ext().execute_with(|| {2437 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));24382439 let origin1 = RuntimeOrigin::signed(1);24402441 for i in 0..COLLECTION_ADMINS_LIMIT {2442 assert_ok!(Unique::add_collection_admin(2443 origin1.clone(),2444 collection_id,2445 account((2 + i).into())2446 ));2447 }2448 assert_noop!(2449 Unique::add_collection_admin(2450 origin1,2451 collection_id,2452 account((3 + COLLECTION_ADMINS_LIMIT).into())2453 ),2454 CommonError::<Test>::CollectionAdminCountExceeded2455 );2456 });2457}2458// #endregion24592460#[test]2461fn collection_transfer_flag_works() {2462 new_test_ext().execute_with(|| {2463 let origin1 = RuntimeOrigin::signed(1);24642465 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));2466 assert_ok!(Unique::set_transfers_enabled_flag(2467 origin1,2468 collection_id,2469 true2470 ));24712472 let data = default_nft_data();2473 create_test_item(collection_id, &data.into());2474 assert_eq!(2475 <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),2476 12477 );2478 assert_eq!(2479 <pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),2480 true2481 );24822483 let origin1 = RuntimeOrigin::signed(1);24842485 // default scenario2486 assert_ok!(Unique::transfer(2487 origin1,2488 account(2),2489 collection_id,2490 TokenId(1),2491 12492 ));2493 assert_eq!(2494 <pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),2495 false2496 );2497 assert_eq!(2498 <pallet_nonfungible::Owned<Test>>::get((collection_id, account(2), TokenId(1))),2499 true2500 );2501 assert_eq!(2502 <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),2503 02504 );2505 assert_eq!(2506 <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(2))),2507 12508 );2509 });2510}25112512#[test]2513fn collection_transfer_flag_works_neg() {2514 new_test_ext().execute_with(|| {2515 let origin1 = RuntimeOrigin::signed(1);25162517 let collection_id = create_test_collection(&CollectionMode::NFT, CollectionId(1));2518 assert_ok!(Unique::set_transfers_enabled_flag(2519 origin1,2520 collection_id,2521 false2522 ));25232524 let data = default_nft_data();2525 create_test_item(collection_id, &data.into());2526 assert_eq!(2527 <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),2528 12529 );2530 assert_eq!(2531 <pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),2532 true2533 );25342535 let origin1 = RuntimeOrigin::signed(1);25362537 // default scenario2538 assert_noop!(2539 Unique::transfer(origin1, account(2), CollectionId(1), TokenId(1), 1)2540 .map_err(|e| e.error),2541 CommonError::<Test>::TransferNotAllowed2542 );2543 assert_eq!(2544 <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(1))),2545 12546 );2547 assert_eq!(2548 <pallet_nonfungible::AccountBalance<Test>>::get((collection_id, account(2))),2549 02550 );2551 assert_eq!(2552 <pallet_nonfungible::Owned<Test>>::get((collection_id, account(1), TokenId(1))),2553 true2554 );2555 assert_eq!(2556 <pallet_nonfungible::Owned<Test>>::get((collection_id, account(2), TokenId(1))),2557 false2558 );2559 });2560}25612562#[test]2563fn collection_sponsoring() {2564 new_test_ext().execute_with(|| {2565 // default_limits();2566 let user1 = 1_u64;2567 let user2 = 777_u64;2568 let origin1 = RuntimeOrigin::signed(user1);2569 let origin2 = RuntimeOrigin::signed(user2);2570 let account2 = account(user2);25712572 let collection_id =2573 create_test_collection_for_owner(&CollectionMode::NFT, user1, CollectionId(1));2574 assert_ok!(Unique::set_collection_sponsor(2575 origin1.clone(),2576 collection_id,2577 user12578 ));2579 assert_ok!(Unique::confirm_sponsorship(origin1.clone(), collection_id));25802581 // Expect error while have no permissions2582 assert!(Unique::create_item(2583 origin2.clone(),2584 collection_id,2585 account2.clone(),2586 default_nft_data().into()2587 )2588 .is_err());25892590 assert_ok!(Unique::set_collection_permissions(2591 origin1.clone(),2592 collection_id,2593 CollectionPermissions {2594 mint_mode: Some(true),2595 access: Some(AccessMode::AllowList),2596 nesting: None,2597 }2598 ));2599 assert_ok!(Unique::add_to_allow_list(2600 origin1.clone(),2601 collection_id,2602 account2.clone()2603 ));26042605 assert_ok!(Unique::create_item(2606 origin2,2607 collection_id,2608 account2,2609 default_nft_data().into()2610 ));2611 });2612}tests/src/createMultipleItemsEx.test.tsdiffbeforeafterboth--- a/tests/src/createMultipleItemsEx.test.ts
+++ b/tests/src/createMultipleItemsEx.test.ts
@@ -195,7 +195,7 @@
description: 'descr',
tokenPrefix: 'COL',
tokenPropertyPermissions: [
- {key: 'k', permission: {tokenOwner: true, mutable: false, collectionAdmin: false}},
+ {key: 'k', permission: {tokenOwner: false, mutable: false, collectionAdmin: true}},
],
});
tests/src/eth/collectionSponsoring.test.tsdiffbeforeafterboth--- a/tests/src/eth/collectionSponsoring.test.ts
+++ b/tests/src/eth/collectionSponsoring.test.ts
@@ -17,6 +17,7 @@
import {IKeyringPair} from '@polkadot/types/types';
import {Pallets, requirePalletsOrSkip, usingPlaygrounds} from '../util/index';
import {itEth, expect} from './util';
+import {CollectionLimitField, TokenPermissionField} from './util/playgrounds/types';
describe('evm nft collection sponsoring', () => {
let donor: IKeyringPair;
@@ -138,8 +139,7 @@
expect(sponsorship.Confirmed).to.be.eq(helper.address.ethToSubstrate(sponsorEth, true));
// Create user with no balance:
- const user = helper.eth.createAccount();
- const userCross = helper.ethCrossAccount.fromAddress(user);
+ const user = helper.ethCrossAccount.createAccount();
const nextTokenId = await collectionEvm.methods.nextTokenId().call();
expect(nextTokenId).to.be.equal('1');
@@ -149,20 +149,29 @@
expect(oldPermissions.access).to.be.equal('Normal');
await collectionEvm.methods.setCollectionAccess(1 /*'AllowList'*/).send({from: owner});
- await collectionEvm.methods.addToCollectionAllowListCross(userCross).send({from: owner});
+ await collectionEvm.methods.addToCollectionAllowListCross(user).send({from: owner});
await collectionEvm.methods.setCollectionMintMode(true).send({from: owner});
+ await collectionEvm.methods.setCollectionLimit({field: CollectionLimitField.SponsoredDataRateLimit, value: {status: true, value: 30}}).send();
const newPermissions = (await collectionSub.getData())!.raw.permissions;
expect(newPermissions.mintMode).to.be.true;
expect(newPermissions.access).to.be.equal('AllowList');
+ // Set token permissions
+ await collectionEvm.methods.setTokenPropertyPermissions([
+ ['key', [
+ [TokenPermissionField.TokenOwner, true],
+ ],
+ ],
+ ]).send({from: owner});
+
const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));
const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsorEth));
- const userBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user));
+ const userBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user.eth));
// User can mint token without balance:
{
- const result = await collectionEvm.methods.mintWithTokenURI(user, 'Test URI').send({from: user});
+ const result = await collectionEvm.methods.mintCross(user, [{key: 'key', value: Buffer.from('Value')}]).send({from: user.eth});
const event = helper.eth.normalizeEvents(result.events)
.find(event => event.event === 'Transfer');
@@ -171,22 +180,102 @@
event: 'Transfer',
args: {
from: '0x0000000000000000000000000000000000000000',
- to: user,
+ to: user.eth,
tokenId: '1',
},
});
+ // await collectionEvm.methods.setProperties(1, [{key: 'key', value: Buffer.from('Value1')}]).send({from: user.eth});
+
const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));
const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsorEth));
- const userBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user));
+ const userBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user.eth));
- expect(await collectionEvm.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');
+ expect(await collectionEvm.methods.properties(nextTokenId, []).call())
+ .to.be.like([
+ [
+ 'key',
+ '0x' + Buffer.from('Value').toString('hex'),
+ ],
+ ]);
expect(ownerBalanceBefore).to.be.eq(ownerBalanceAfter);
expect(userBalanceAfter).to.be.eq(userBalanceBefore);
expect(sponsorBalanceBefore > sponsorBalanceAfter).to.be.true;
}
}));
+ itEth('Can sponsor [set token properties] via access list', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const sponsorEth = await helper.eth.createAccountWithBalance(donor);
+ const sponsorCrossEth = helper.ethCrossAccount.fromAddress(sponsorEth);
+
+ const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Sponsor collection', '1', '1', '');
+ const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner, false);
+
+ // Set collection sponsor:
+ await collectionEvm.methods.setCollectionSponsorCross(sponsorCrossEth).send({from: owner});
+
+ // Sponsor can confirm sponsorship:
+ await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsorEth});
+
+ // Create user with no balance:
+ const user = helper.ethCrossAccount.createAccount();
+ const nextTokenId = await collectionEvm.methods.nextTokenId().call();
+ expect(nextTokenId).to.be.equal('1');
+
+ // Set collection permissions:
+ await collectionEvm.methods.setCollectionAccess(1 /*'AllowList'*/).send({from: owner});
+ await collectionEvm.methods.addToCollectionAllowListCross(user).send({from: owner});
+ await collectionEvm.methods.setCollectionMintMode(true).send({from: owner});
+ await collectionEvm.methods.setCollectionLimit({field: CollectionLimitField.SponsoredDataRateLimit, value: {status: true, value: 30}}).send();
+
+ // Set token permissions
+ await collectionEvm.methods.setTokenPropertyPermissions([
+ ['key', [
+ [TokenPermissionField.TokenOwner, true],
+ ],
+ ],
+ ]).send({from: owner});
+
+ const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));
+ const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsorEth));
+ const userBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user.eth));
+
+ // User can mint token without balance:
+ {
+ const result = await collectionEvm.methods.mintCross(user, []).send({from: user.eth});
+ const event = helper.eth.normalizeEvents(result.events)
+ .find(event => event.event === 'Transfer');
+
+ expect(event).to.be.deep.equal({
+ address: collectionAddress,
+ event: 'Transfer',
+ args: {
+ from: '0x0000000000000000000000000000000000000000',
+ to: user.eth,
+ tokenId: '1',
+ },
+ });
+
+ await collectionEvm.methods.setProperties(1, [{key: 'key', value: Buffer.from('Value')}]).send({from: user.eth});
+
+ const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));
+ const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsorEth));
+ const userBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user.eth));
+
+ expect(await collectionEvm.methods.properties(nextTokenId, []).call())
+ .to.be.like([
+ [
+ 'key',
+ '0x' + Buffer.from('Value').toString('hex'),
+ ],
+ ]);
+ expect(ownerBalanceBefore).to.be.eq(ownerBalanceAfter);
+ expect(userBalanceAfter).to.be.eq(userBalanceBefore);
+ expect(sponsorBalanceBefore > sponsorBalanceAfter).to.be.true;
+ }
+ });
+
// TODO: Temprorary off. Need refactor
// itWeb3('Sponsoring collection from substrate address via access list', async ({api, web3, privateKeyWrapper}) => {
// const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
@@ -456,6 +545,15 @@
expect(newPermissions.mintMode).to.be.true;
expect(newPermissions.access).to.be.equal('AllowList');
+ // Set token permissions
+ await collectionEvm.methods.setTokenPropertyPermissions([
+ ['URI', [
+ [TokenPermissionField.TokenOwner, true],
+ [TokenPermissionField.CollectionAdmin, true],
+ ],
+ ],
+ ]).send({from: owner});
+
const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));
const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsorEth));
const userBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user));
@@ -623,6 +721,15 @@
await collectionEvm.methods.addToCollectionAllowListCross(userCross).send({from: owner});
await collectionEvm.methods.setCollectionMintMode(true).send({from: owner});
+ // Set token permissions
+ await collectionEvm.methods.setTokenPropertyPermissions([
+ ['URI', [
+ [TokenPermissionField.TokenOwner, true],
+ [TokenPermissionField.CollectionAdmin, true],
+ ],
+ ],
+ ]).send({from: owner});
+
const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner));
const sponsorBalanceBefore = await helper.balance.getSubstrate(sponsor.address);
const userBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user));
tests/src/eth/tokenProperties.test.tsdiffbeforeafterboth--- a/tests/src/eth/tokenProperties.test.ts
+++ b/tests/src/eth/tokenProperties.test.ts
@@ -553,6 +553,63 @@
]).call({from: owner})).to.be.rejectedWith('NoPermission');
}
}));
+
+ [
+ {mode: 'nft' as const, requiredPallets: []},
+ {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
+ ].map(testCase =>
+ itEth.ifWithPallets(`[${testCase.mode}] Can't be multiple set/read for non-existent token`, testCase.requiredPallets, async({helper}) => {
+ const caller = await helper.eth.createAccountWithBalance(donor);
+
+ const properties = Array(5).fill(0).map((_, i) => { return {key: `key_${i}`, value: Buffer.from(`value_${i}`)}; });
+ const permissions: ITokenPropertyPermission[] = properties.map(p => { return {key: p.key, permission: {tokenOwner: true,
+ collectionAdmin: true,
+ mutable: true}}; });
+
+ const collection = await helper[testCase.mode].mintCollection(alice, {
+ tokenPrefix: 'ethp',
+ tokenPropertyPermissions: permissions,
+ }) as UniqueNFTCollection | UniqueRFTCollection;
+
+ await collection.addAdmin(alice, {Ethereum: caller});
+
+ const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+ const contract = await helper.ethNativeContract.collection(address, testCase.mode, caller);
+
+ await expect(contract.methods.setProperties(1, properties).call({from: caller})).to.be.rejectedWith('TokenNotFound');
+ }));
+
+ [
+ {mode: 'nft' as const, requiredPallets: []},
+ {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
+ ].map(testCase =>
+ itEth.ifWithPallets(`[${testCase.mode}] Can't be deleted for non-existent token`, testCase.requiredPallets, async({helper}) => {
+ const caller = await helper.eth.createAccountWithBalance(donor);
+ const collection = await helper[testCase.mode].mintCollection(alice, {
+ tokenPropertyPermissions: [{
+ key: 'testKey',
+ permission: {
+ mutable: true,
+ collectionAdmin: true,
+ },
+ },
+ {
+ key: 'testKey_1',
+ permission: {
+ mutable: true,
+ collectionAdmin: true,
+ },
+ }],
+ });
+
+
+ await collection.addAdmin(alice, {Ethereum: caller});
+
+ const address = helper.ethAddress.fromCollectionId(collection.collectionId);
+ const contract = await helper.ethNativeContract.collection(address, testCase.mode, caller);
+
+ await expect(contract.methods.deleteProperties(1, ['testKey', 'testKey_1']).call({from: caller})).to.be.rejectedWith('TokenNotFound');
+ }));
});
tests/src/getPropertiesRpc.test.tsdiffbeforeafterboth--- a/tests/src/getPropertiesRpc.test.ts
+++ b/tests/src/getPropertiesRpc.test.ts
@@ -120,3 +120,31 @@
expect(propPermissions).to.be.deep.equal(tokenPropPermissions);
});
});
+
+[
+ {mode: 'nft' as const},
+ {mode: 'rft' as const},
+].map(testCase =>
+ describe('negative properties', () => {
+ let alice: IKeyringPair;
+
+ before(async () => {
+ await usingPlaygrounds(async (_, privateKey) => {
+ alice = await privateKey({url: import.meta.url});
+ });
+ });
+
+ itSub(`[${testCase.mode}] set token property for non-existent token`, async ({helper}) => {
+ const collection = await helper[testCase.mode].mintCollection(alice);
+ await collection.setTokenPropertyPermissions(alice, [{key: 'key', permission: {mutable: true, tokenOwner: true, collectionAdmin: true}}]);
+ await expect(collection.setTokenProperties(alice, 1, [{key: 'key', value: 'value'}])).to.be.rejectedWith('common.TokenNotFound');
+ expect(await collection.getTokenProperties(1, ['key'])).to.be.empty;
+ });
+
+ itSub(`[${testCase.mode}] delete token property for non-existent token`, async ({helper}) => {
+ const collection = await helper[testCase.mode].mintCollection(alice);
+ await collection.setTokenPropertyPermissions(alice, [{key: 'key', permission: {mutable: true, tokenOwner: true, collectionAdmin: true}}]);
+ await expect(collection.deleteTokenProperties(alice, 1, ['key'])).to.be.rejectedWith('common.TokenNotFound');
+ expect(await collection.getTokenProperties(1, ['key'])).to.be.empty;
+ });
+ }));
\ No newline at end of file
tests/src/nesting/tokenProperties.test.tsdiffbeforeafterboth--- a/tests/src/nesting/tokenProperties.test.ts
+++ b/tests/src/nesting/tokenProperties.test.ts
@@ -448,6 +448,29 @@
expectedConsumedSpaceDiff = sizeOfProperty(biggerProp) - sizeOfProperty(smallerProp);
expect(consumedSpace).to.be.equal(sizeOfProperty(biggerProp) - expectedConsumedSpaceDiff);
}));
+
+ itSub('Set sponsored properties', async({helper}) => {
+ const collection = await helper.nft.mintCollection(alice, {tokenPropertyPermissions: [{key: 'k', permission: {tokenOwner: true}}]});
+
+ await collection.setSponsor(alice, alice.address);
+ await collection.confirmSponsorship(alice);
+ await collection.setPermissions(alice, {access: 'AllowList', mintMode: true});
+ await collection.addToAllowList(alice, {Substrate: bob.address});
+ await collection.setLimits(alice, {sponsoredDataRateLimit: {blocks: 30}});
+
+ const token = await collection.mintToken(alice, {Substrate: bob.address});
+
+ const aliceBalanceBefore = await helper.balance.getSubstrate(alice.address);
+ const bobBalanceBefore = await helper.balance.getSubstrate(bob.address);
+
+ await token.setProperties(bob, [{key: 'k', value: 'val'}]);
+
+ const aliceBalanceAfter = await helper.balance.getSubstrate(alice.address);
+ const bobBalanceAfter = await helper.balance.getSubstrate(bob.address);
+
+ expect(bobBalanceAfter).to.be.equal(bobBalanceBefore);
+ expect(aliceBalanceBefore > aliceBalanceAfter).to.be.true;
+ });
});
describe('Negative Integration Test: Token Properties', () => {
@@ -475,6 +498,27 @@
});
});
+ [
+ {mode: 'nft' as const, requiredPallets: [Pallets.NFT]},
+ {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
+ ].map(testCase =>
+ itSub.ifWithPallets(`Forbids adding/deleting properties of a token if token doesn't exist (${testCase.mode.toLocaleUpperCase})`, testCase.requiredPallets, async({helper}) => {
+ const collection = await helper[testCase.mode].mintCollection(alice, {
+ tokenPropertyPermissions: constitution.slice(0, 1).map(({permission}) => ({key: '1', permission})),
+ });
+ const nonExistentToken = collection.getTokenObject(1);
+
+ await expect(
+ nonExistentToken.setProperties(alice, [{key: '1', value: 'Serotonin increase'}]),
+ 'on expecting failure whilst adding a property by alice',
+ ).to.be.rejectedWith(/common\.TokenNotFound/);
+
+ await expect(
+ nonExistentToken.deleteProperties(alice, ['1']),
+ 'on expecting failure whilst deleting a property by alice',
+ ).to.be.rejectedWith(/common\.TokenNotFound/);
+ }));
+
async function mintCollectionWithAllPermissionsAndToken(helper: UniqueHelper, mode: 'NFT' | 'RFT'): Promise<[UniqueNFToken | UniqueRFToken, bigint]> {
const collection = await (mode == 'NFT' ? helper.nft : helper.rft).mintCollection(alice, {
tokenPropertyPermissions: constitution.map(({permission}, i) => ({key: `${i+1}`, permission})),