Skip to content

Commit

Permalink
Feat/non transferable flag (#873)
Browse files Browse the repository at this point in the history
* Add utility flags to NFT pallet

* Add utility flags to SFT pallet

* Add benchmarks to SFT and NFT pallets

* Update benchmarks for pallet-sft on feat/non-transferable-flag

* Update benchmarks for pallet-nft on feat/non-transferable-flag

* Update benchmarks for pallet-sft on feat/non-transferable-flag

---------

Co-authored-by: GitHub Action <[email protected]>
  • Loading branch information
JasonTulp and actions-user authored Aug 25, 2024
1 parent b4b578c commit e7ff168
Show file tree
Hide file tree
Showing 13 changed files with 751 additions and 131 deletions.
14 changes: 14 additions & 0 deletions pallet/common/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,17 @@ pub struct PublicMintInformation {
/// If pricing_details are set, the user will be charged this amount per token
pub pricing_details: Option<(AssetId, Balance)>,
}

// Additional flags on a collection that determine whether tokens within the collection can be transferred, burned, or minted
#[derive(Debug, Clone, Encode, Decode, PartialEq, TypeInfo, Copy, MaxEncodedLen)]
pub struct CollectionUtilityFlags {
pub transferable: bool,
pub burnable: bool,
pub mintable: bool,
}

impl Default for CollectionUtilityFlags {
fn default() -> Self {
Self { transferable: true, burnable: true, mintable: true }
}
}
12 changes: 12 additions & 0 deletions pallet/nft/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,18 @@ benchmarks! {
burn {
let collection_id = build_collection::<T>(None);
}: _(origin::<T>(&account::<T>("Alice")), TokenId::from((collection_id, 0)))

set_utility_flags {
let collection_id = build_collection::<T>(None);
let utility_flags = CollectionUtilityFlags {
transferable: false,
burnable: false,
mintable: false,
};
}: _(origin::<T>(&account::<T>("Alice")), collection_id, utility_flags)
verify {
assert_eq!(UtilityFlags::<T>::get(collection_id), utility_flags)
}
}

impl_benchmark_test_suite!(
Expand Down
5 changes: 5 additions & 0 deletions pallet/nft/src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ impl<T: Config> Pallet<T> {
new_owner: &T::AccountId,
) -> DispatchResult {
ensure!(current_owner != new_owner, Error::<T>::InvalidNewOwner);
ensure!(
<UtilityFlags<T>>::get(collection_id).transferable,
Error::<T>::TransferUtilityBlocked
);

CollectionInfo::<T>::try_mutate(collection_id, |maybe_collection_info| -> DispatchResult {
let collection_info =
Expand Down Expand Up @@ -479,6 +483,7 @@ impl<T: Config> Pallet<T> {
!<TokenLocks<T>>::contains_key((collection_id, serial_number)),
Error::<T>::TokenLocked
);
ensure!(<UtilityFlags<T>>::get(collection_id).burnable, Error::<T>::BurnUtilityBlocked);

// Remove any NFI data associated with this token
T::NFIRequest::on_burn((collection_id, serial_number));
Expand Down
46 changes: 44 additions & 2 deletions pallet/nft/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ use frame_support::{
transactional, PalletId,
};
use seed_pallet_common::{
utils::PublicMintInformation, NFIRequest, OnNewAssetSubscriber, OnTransferSubscriber,
Xls20MintRequest,
utils::{CollectionUtilityFlags, PublicMintInformation},
NFIRequest, OnNewAssetSubscriber, OnTransferSubscriber, Xls20MintRequest,
};
use seed_primitives::{
AssetId, Balance, CollectionUuid, MetadataScheme, OriginChain, ParachainId, RoyaltiesSchedule,
Expand Down Expand Up @@ -159,6 +159,11 @@ pub mod pallet {
#[pallet::storage]
pub type TokenLocks<T> = StorageMap<_, Twox64Concat, TokenId, TokenLockReason>;

/// Map from a collection to additional utility flags
#[pallet::storage]
pub type UtilityFlags<T> =
StorageMap<_, Twox64Concat, CollectionUuid, CollectionUtilityFlags, ValueQuery>;

#[pallet::event]
#[pallet::generate_deposit(pub (super) fn deposit_event)]
pub enum Event<T: Config> {
Expand Down Expand Up @@ -227,6 +232,8 @@ pub mod pallet {
Burn { collection_id: CollectionUuid, serial_number: SerialNumber },
/// Collection has been claimed
CollectionClaimed { account: T::AccountId, collection_id: CollectionUuid },
/// Utility flags were set for a collection
UtilityFlagsSet { collection_id: CollectionUuid, utility_flags: CollectionUtilityFlags },
}

#[pallet::error]
Expand Down Expand Up @@ -274,6 +281,12 @@ pub mod pallet {
CollectionIssuanceNotZero,
/// Token(s) blocked from minting during the bridging process
BlockedMint,
/// Minting has been disabled for tokens within this collection
MintUtilityBlocked,
/// Transfer has been disabled for tokens within this collection
TransferUtilityBlocked,
/// Burning has been disabled for tokens within this collection
BurnUtilityBlocked,
}

#[pallet::call]
Expand Down Expand Up @@ -498,6 +511,8 @@ pub mod pallet {
let who = ensure_signed(origin)?;

ensure!(quantity <= T::MintLimit::get(), Error::<T>::MintLimitExceeded);
// minting flag must be enabled on the collection
ensure!(<UtilityFlags<T>>::get(collection_id).mintable, Error::<T>::MintUtilityBlocked);

let mut collection_info =
<CollectionInfo<T>>::get(collection_id).ok_or(Error::<T>::NoCollectionFound)?;
Expand Down Expand Up @@ -644,6 +659,33 @@ pub mod pallet {
});
Ok(())
}

/// Set utility flags of a collection. This allows restricting certain operations on a
/// collection such as transfer, burn or mint
#[pallet::call_index(12)]
#[pallet::weight(T::WeightInfo::set_utility_flags())]
#[transactional]
pub fn set_utility_flags(
origin: OriginFor<T>,
collection_id: CollectionUuid,
utility_flags: CollectionUtilityFlags,
) -> DispatchResult {
let who = ensure_signed(origin)?;
let collection_info =
<CollectionInfo<T>>::get(collection_id).ok_or(Error::<T>::NoCollectionFound)?;
ensure!(collection_info.is_collection_owner(&who), Error::<T>::NotCollectionOwner);

if utility_flags == CollectionUtilityFlags::default() {
// If the utility flags are default, remove the storage entry
<UtilityFlags<T>>::remove(collection_id);
} else {
// Otherwise, update the storage
<UtilityFlags<T>>::insert(collection_id, utility_flags);
}

Self::deposit_event(Event::<T>::UtilityFlagsSet { collection_id, utility_flags });
Ok(())
}
}
}

Expand Down
181 changes: 181 additions & 0 deletions pallet/nft/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2679,3 +2679,184 @@ mod public_minting {
});
}
}

mod set_utility_flags {
use super::*;

#[test]
fn set_utility_flags_works() {
TestExt::<Test>::default().build().execute_with(|| {
let collection_owner = create_account(10);
let collection_id = setup_collection(collection_owner);
let utility_flags =
CollectionUtilityFlags { transferable: false, burnable: false, mintable: false };

assert_ok!(Nft::set_utility_flags(
RawOrigin::Signed(collection_owner).into(),
collection_id,
utility_flags
));
assert_eq!(UtilityFlags::<Test>::get(collection_id), utility_flags);
System::assert_last_event(
Event::<Test>::UtilityFlagsSet { collection_id, utility_flags }.into(),
);

// Remove flags by setting to default
let utility_flags = CollectionUtilityFlags::default();
assert_ok!(Nft::set_utility_flags(
RawOrigin::Signed(collection_owner).into(),
collection_id,
utility_flags
));
assert!(!UtilityFlags::<Test>::contains_key(collection_id));

System::assert_last_event(
Event::<Test>::UtilityFlagsSet { collection_id, utility_flags }.into(),
);
});
}

#[test]
fn set_utility_flags_not_collection_owner_fails() {
TestExt::<Test>::default().build().execute_with(|| {
let collection_owner = create_account(10);
let collection_id = setup_collection(collection_owner);
let utility_flags =
CollectionUtilityFlags { transferable: false, burnable: false, mintable: false };

assert_noop!(
Nft::set_utility_flags(
RawOrigin::Signed(bob()).into(),
collection_id,
utility_flags
),
Error::<Test>::NotCollectionOwner
);
});
}

#[test]
fn set_utility_flags_no_collection_fails() {
TestExt::<Test>::default().build().execute_with(|| {
let collection_owner = create_account(10);
let collection_id = 1; // No collection
let utility_flags =
CollectionUtilityFlags { transferable: false, burnable: false, mintable: false };

assert_noop!(
Nft::set_utility_flags(
RawOrigin::Signed(collection_owner).into(),
collection_id,
utility_flags
),
Error::<Test>::NoCollectionFound
);
});
}

#[test]
fn set_utility_flags_transferrable_stops_transfer() {
TestExt::<Test>::default().build().execute_with(|| {
let collection_owner = create_account(10);
let collection_id = setup_collection(collection_owner);
let utility_flags =
CollectionUtilityFlags { transferable: false, burnable: true, mintable: true };
assert_ok!(Nft::mint(Some(collection_owner).into(), collection_id, 1, None));

// Disable transfer
assert_ok!(Nft::set_utility_flags(
RawOrigin::Signed(collection_owner).into(),
collection_id,
utility_flags
));
assert_noop!(
Nft::transfer(
RawOrigin::Signed(collection_owner).into(),
collection_id,
BoundedVec::truncate_from(vec![0]),
bob()
),
Error::<Test>::TransferUtilityBlocked
);

// Re-enable transfer
let utility_flags =
CollectionUtilityFlags { transferable: true, burnable: true, mintable: true };
assert_ok!(Nft::set_utility_flags(
RawOrigin::Signed(collection_owner).into(),
collection_id,
utility_flags
));
assert_ok!(Nft::transfer(
RawOrigin::Signed(collection_owner).into(),
collection_id,
BoundedVec::truncate_from(vec![0]),
bob()
));
});
}

#[test]
fn set_utility_flags_burnable_stops_burn() {
TestExt::<Test>::default().build().execute_with(|| {
let collection_owner = create_account(10);
let collection_id = setup_collection(collection_owner);
let utility_flags =
CollectionUtilityFlags { transferable: true, burnable: false, mintable: true };
assert_ok!(Nft::mint(Some(collection_owner).into(), collection_id, 1, None));
let token_id = (collection_id, 0);

// Disable burn
assert_ok!(Nft::set_utility_flags(
RawOrigin::Signed(collection_owner).into(),
collection_id,
utility_flags
));
assert_noop!(
Nft::burn(RawOrigin::Signed(collection_owner).into(), token_id),
Error::<Test>::BurnUtilityBlocked
);

// Re-enable burn
let utility_flags =
CollectionUtilityFlags { transferable: true, burnable: true, mintable: true };
assert_ok!(Nft::set_utility_flags(
RawOrigin::Signed(collection_owner).into(),
collection_id,
utility_flags
));
assert_ok!(Nft::burn(RawOrigin::Signed(collection_owner).into(), token_id));
});
}

#[test]
fn set_utility_flags_mintable_stops_mint() {
TestExt::<Test>::default().build().execute_with(|| {
let collection_owner = create_account(10);
let collection_id = setup_collection(collection_owner);
let utility_flags =
CollectionUtilityFlags { transferable: true, burnable: true, mintable: false };

// Disable mint
assert_ok!(Nft::set_utility_flags(
RawOrigin::Signed(collection_owner).into(),
collection_id,
utility_flags
));
assert_noop!(
Nft::mint(Some(collection_owner).into(), collection_id, 1, None),
Error::<Test>::MintUtilityBlocked
);

// Re-enable mint
let utility_flags =
CollectionUtilityFlags { transferable: true, burnable: true, mintable: true };
assert_ok!(Nft::set_utility_flags(
RawOrigin::Signed(collection_owner).into(),
collection_id,
utility_flags
));
assert_ok!(Nft::mint(Some(collection_owner).into(), collection_id, 1, None));
});
}
}
Loading

0 comments on commit e7ff168

Please sign in to comment.