#![cfg_attr(not(feature = "std"), no_std)]
pub use pallet::*;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
mod functions;
pub mod types;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use crate::types::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use sp_runtime::Permill;
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
use pallet_rbac::types::RoleBasedAccessControl;
#[pallet::config]
pub trait Config: frame_system::Config + pallet_uniques::Config {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type RemoveOrigin: EnsureOrigin<Self::RuntimeOrigin>;
#[pallet::constant]
type ChildMaxLen: Get<u32>;
#[pallet::constant]
type MaxParentsInCollection: Get<u32>;
type Rbac: RoleBasedAccessControl<Self::AccountId>;
}
#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
FruniqueCollectionCreated(T::AccountId, T::CollectionId),
FruniqueCreated(T::AccountId, T::AccountId, T::CollectionId, T::ItemId),
FruniqueDivided(T::AccountId, T::AccountId, T::CollectionId, T::ItemId),
FruniqueVerified(T::AccountId, CollectionId, ItemId),
InvitedToCollaborate(T::AccountId, T::AccountId, T::CollectionId),
NextFrunique(u32),
}
#[pallet::error]
pub enum Error<T> {
NoPermission,
NotAdmin,
StorageOverflow,
NotYetImplemented,
FruniqueCntOverflow,
NotAFrunique,
KeyTooLong,
ValueTooLong,
AttributesEmpty,
CollectionNotFound,
ExceedMaxPercentage,
ParentNotFound,
FruniqueNotFound,
MaxNumberOfChildrenReached,
CollectionAlreadyExists,
FruniqueAlreadyExists,
FruniqueAlreadyVerified,
FruniqueRootsOverflow,
ParentFrozen,
ParentAlreadyRedeemed,
FruniqueFrozen,
FruniqueAlreadyRedeemed,
UserNotInCollection,
NotAuthorized,
}
#[pallet::storage]
#[pallet::getter(fn freezer)]
pub(super) type Freezer<T: Config> = StorageValue<
_,
T::AccountId, >;
#[pallet::storage]
#[pallet::getter(fn next_collection)]
pub(super) type NextCollection<T: Config> = StorageValue<
_,
CollectionId, ValueQuery,
>;
#[pallet::storage]
#[pallet::getter(fn next_frunique)]
pub(super) type NextFrunique<T: Config> = StorageMap<
_,
Blake2_128Concat,
T::CollectionId,
ItemId, ValueQuery,
>;
#[pallet::storage]
#[pallet::getter(fn frunique_info)]
pub(super) type FruniqueInfo<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
T::CollectionId,
Blake2_128Concat,
T::ItemId,
FruniqueData<T>,
OptionQuery,
>;
#[pallet::storage]
#[pallet::getter(fn frunique_roots)]
pub(super) type FruniqueRoots<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
T::CollectionId,
Blake2_128Concat,
T::ItemId,
bool,
OptionQuery,
>;
#[pallet::storage]
#[pallet::getter(fn frunique_verified)]
pub(super) type FruniqueVerified<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
T::CollectionId,
Blake2_128Concat,
T::ItemId,
bool,
OptionQuery,
>;
#[pallet::storage]
#[pallet::getter(fn frunique_redeemed)]
pub(super) type FruniqueRedeemed<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
T::CollectionId,
Blake2_128Concat,
T::ItemId,
bool,
OptionQuery,
>;
#[pallet::call]
impl<T: Config> Pallet<T>
where
T: pallet_uniques::Config<CollectionId = CollectionId, ItemId = ItemId>,
{
#[pallet::call_index(1)]
#[pallet::weight(Weight::from_ref_time(10_000) + T::DbWeight::get().writes(10))]
pub fn initial_setup(origin: OriginFor<T>, freezer: T::AccountId) -> DispatchResult {
T::RemoveOrigin::ensure_origin(origin.clone())?;
<Freezer<T>>::put(freezer);
Self::do_initial_setup()?;
Ok(())
}
#[pallet::call_index(2)]
#[pallet::weight(Weight::from_ref_time(10_000) + T::DbWeight::get().writes(1))]
pub fn create_collection(
origin: OriginFor<T>,
metadata: CollectionDescription<T>,
) -> DispatchResult {
let admin: T::AccountId = ensure_signed(origin.clone())?;
Self::do_create_collection(origin, metadata, admin.clone())?;
let next_collection_id: u32 = Self::next_collection();
Self::deposit_event(Event::FruniqueCollectionCreated(admin, next_collection_id));
Ok(())
}
#[pallet::call_index(3)]
#[pallet::weight(Weight::from_ref_time(10_000) + T::DbWeight::get().writes(1))]
pub fn set_attributes(
origin: OriginFor<T>,
class_id: T::CollectionId,
instance_id: T::ItemId,
attributes: Attributes<T>,
) -> DispatchResult {
ensure!(Self::instance_exists(&class_id, &instance_id), Error::<T>::FruniqueNotFound);
let admin = Self::admin_of(&class_id, &instance_id);
let signer = core::prelude::v1::Some(ensure_signed(origin.clone())?);
ensure!(signer == admin, Error::<T>::NotAdmin);
ensure!(!attributes.is_empty(), Error::<T>::AttributesEmpty);
for attribute in &attributes {
Self::set_attribute(
origin.clone(),
&class_id.clone(),
Self::u32_to_instance_id(instance_id),
attribute.0.clone(),
attribute.1.clone(),
)?;
}
Ok(())
}
#[pallet::call_index(4)]
#[pallet::weight(Weight::from_ref_time(10_000) + T::DbWeight::get().writes(4))]
pub fn spawn(
origin: OriginFor<T>,
class_id: CollectionId,
metadata: CollectionDescription<T>,
attributes: Option<Attributes<T>>,
parent_info_call: Option<ParentInfoCall<T>>,
) -> DispatchResult {
ensure!(Self::collection_exists(&class_id), Error::<T>::CollectionNotFound);
let user: T::AccountId = ensure_signed(origin.clone())?;
ensure!(
Self::is_authorized(user.clone(), class_id, Permission::Mint).is_ok(),
Error::<T>::UserNotInCollection
);
let owner = user.clone();
if let Some(parent_info_call) = parent_info_call.clone() {
ensure!(
Self::collection_exists(&parent_info_call.collection_id),
Error::<T>::CollectionNotFound
);
ensure!(
Self::instance_exists(&parent_info_call.collection_id, &parent_info_call.parent_id),
Error::<T>::ParentNotFound
);
ensure!(
!<FruniqueInfo<T>>::try_get(parent_info_call.collection_id, parent_info_call.parent_id)
.unwrap()
.redeemed,
Error::<T>::ParentAlreadyRedeemed
);
ensure!(
Self::is_authorized(user.clone(), parent_info_call.collection_id, Permission::Mint)
.is_ok(),
Error::<T>::UserNotInCollection
);
let parent_info = ParentInfo {
collection_id: parent_info_call.collection_id,
parent_id: parent_info_call.parent_id,
parent_weight: Permill::from_percent(parent_info_call.parent_percentage),
is_hierarchical: parent_info_call.is_hierarchical,
};
Self::do_spawn(class_id, owner, metadata, attributes, Some(parent_info))?;
return Ok(());
};
Self::do_spawn(class_id, owner, metadata, attributes, None)?;
Ok(())
}
#[pallet::call_index(5)]
#[pallet::weight(Weight::from_ref_time(10_000) + T::DbWeight::get().writes(1))]
pub fn verify(
origin: OriginFor<T>,
class_id: CollectionId,
instance_id: ItemId,
) -> DispatchResult {
ensure!(Self::instance_exists(&class_id, &instance_id), Error::<T>::FruniqueNotFound);
let caller: T::AccountId = ensure_signed(origin.clone())?;
ensure!(
Self::is_authorized(caller.clone(), class_id, Permission::Verify).is_ok(),
Error::<T>::NotAuthorized
);
<FruniqueInfo<T>>::try_mutate::<_, _, _, DispatchError, _>(
class_id,
instance_id,
|frunique_data| -> DispatchResult {
let frunique = frunique_data.as_mut().ok_or(Error::<T>::FruniqueNotFound)?;
if frunique.verified == true || frunique.verified_by.is_some() {
return Err(Error::<T>::FruniqueAlreadyVerified.into());
}
frunique.verified = true;
frunique.verified_by = Some(caller.clone());
Ok(())
},
)?;
<FruniqueVerified<T>>::insert(class_id, instance_id, true);
Self::deposit_event(Event::FruniqueVerified(caller, class_id, instance_id));
Ok(())
}
#[pallet::call_index(6)]
#[pallet::weight(Weight::from_ref_time(10_000) + T::DbWeight::get().writes(1))]
pub fn invite(
origin: OriginFor<T>,
class_id: CollectionId,
invitee: T::AccountId,
) -> DispatchResult {
let owner: T::AccountId = ensure_signed(origin.clone())?;
Self::insert_auth_in_frunique_collection(
invitee.clone(),
class_id,
FruniqueRole::Collaborator,
)?;
Self::deposit_event(Event::InvitedToCollaborate(owner, invitee, class_id));
Ok(())
}
#[pallet::call_index(7)]
#[pallet::weight(Weight::from_ref_time(10_000) + T::DbWeight::get().writes(1))]
pub fn force_set_counter(
origin: OriginFor<T>,
class_id: T::CollectionId,
instance_id: Option<T::ItemId>,
) -> DispatchResult {
T::RemoveOrigin::ensure_origin(origin)?;
if let Some(instance_id) = instance_id {
ensure!(!Self::instance_exists(&class_id, &instance_id), Error::<T>::FruniqueAlreadyExists);
<NextFrunique<T>>::insert(class_id, instance_id);
} else {
ensure!(!Self::collection_exists(&class_id), Error::<T>::CollectionAlreadyExists);
<NextCollection<T>>::set(class_id);
}
Ok(())
}
#[pallet::call_index(8)]
#[pallet::weight(Weight::from_ref_time(10_000) + T::DbWeight::get().writes(1))]
pub fn force_destroy_collection(
origin: OriginFor<T>,
class_id: T::CollectionId,
witness: pallet_uniques::DestroyWitness,
maybe_check_owner: Option<T::AccountId>,
) -> DispatchResult {
T::RemoveOrigin::ensure_origin(origin)?;
ensure!(Self::collection_exists(&class_id), Error::<T>::CollectionNotFound);
pallet_uniques::Pallet::<T>::do_destroy_collection(class_id, witness, maybe_check_owner)?;
Ok(())
}
#[pallet::call_index(9)]
#[pallet::weight(Weight::from_ref_time(10_000) + T::DbWeight::get().writes(1))]
pub fn kill_storage(origin: OriginFor<T>) -> DispatchResult {
T::RemoveOrigin::ensure_origin(origin.clone())?;
<Freezer<T>>::kill();
<NextCollection<T>>::put(0);
let _ = <NextFrunique<T>>::clear(1000, None);
let _ = <FruniqueVerified<T>>::clear(1000, None);
let _ = <FruniqueRoots<T>>::clear(1000, None);
let _ = <FruniqueRedeemed<T>>::clear(1000, None);
let _ = <FruniqueInfo<T>>::clear(1000, None);
T::Rbac::remove_pallet_storage(Self::pallet_id())?;
Ok(())
}
#[pallet::call_index(10)]
#[pallet::weight(Weight::from_ref_time(10_000) + T::DbWeight::get().writes(1))]
pub fn spam_spawning(
origin: OriginFor<T>,
number_of_classes: u32,
number_of_instances: u32,
) -> DispatchResult {
let _ = ensure_signed(origin.clone())?;
for i in 0..number_of_classes {
Self::create_collection(origin.clone(), Self::dummy_description());
for j in 0..number_of_instances {
Self::spawn(
origin.clone(),
i,
Self::dummy_description(),
Some(Self::dummy_attributes()),
None,
);
}
}
Ok(())
}
}
}