#![cfg_attr(not(feature = "std"), no_std)]
mod default_weights;
pub mod migrations;
pub mod web3_name;
#[cfg(any(test, feature = "runtime-benchmarks"))]
mod mock;
#[cfg(any(test, feature = "try-runtime"))]
mod try_state;
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub use crate::{default_weights::WeightInfo, pallet::*};
#[frame_support::pallet]
pub mod pallet {
use frame_support::{
pallet_prelude::*,
sp_runtime::SaturatedConversion,
traits::{
fungible::{Inspect, InspectHold, MutateHold},
StorageVersion,
},
Blake2_128Concat,
};
use frame_system::pallet_prelude::*;
use parity_scale_codec::FullCodec;
use sp_runtime::DispatchError;
use sp_std::{fmt::Debug, vec::Vec};
use kilt_support::{
traits::{BalanceMigrationManager, CallSources, StorageDepositCollector},
Deposit,
};
use super::WeightInfo;
use crate::web3_name::Web3NameOwnership;
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
pub type Web3NameOwnerOf<T> = <T as Config>::Web3NameOwner;
pub type Web3NameInput<T> = BoundedVec<u8, <T as Config>::MaxNameLength>;
pub type Web3NameOf<T> = <T as Config>::Web3Name;
pub type Web3OwnershipOf<T> =
Web3NameOwnership<Web3NameOwnerOf<T>, Deposit<AccountIdOf<T>, BalanceOf<T>>, BlockNumberFor<T>>;
pub(crate) type BalanceMigrationManagerOf<T> = <T as Config>::BalanceMigrationManager;
pub(crate) type CurrencyOf<T> = <T as Config>::Currency;
pub type BalanceOf<T> = <CurrencyOf<T> as Inspect<AccountIdOf<T>>>::Balance;
#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
pub struct Pallet<T>(_);
#[pallet::storage]
#[pallet::getter(fn owner)]
pub type Owner<T> = StorageMap<_, Blake2_128Concat, Web3NameOf<T>, Web3OwnershipOf<T>>;
#[pallet::storage]
#[pallet::getter(fn names)]
pub type Names<T> = StorageMap<_, Blake2_128Concat, Web3NameOwnerOf<T>, Web3NameOf<T>>;
#[pallet::storage]
#[pallet::getter(fn is_banned)]
pub type Banned<T> = StorageMap<_, Blake2_128Concat, Web3NameOf<T>, ()>;
#[pallet::composite_enum]
pub enum HoldReason {
Deposit,
}
#[pallet::config]
pub trait Config: frame_system::Config {
type BanOrigin: EnsureOrigin<Self::RuntimeOrigin>;
type OwnerOrigin: EnsureOrigin<<Self as frame_system::Config>::RuntimeOrigin, Success = Self::OriginSuccess>;
type OriginSuccess: CallSources<AccountIdOf<Self>, Web3NameOwnerOf<Self>>;
type RuntimeHoldReason: From<HoldReason>;
type Currency: MutateHold<AccountIdOf<Self>, Reason = Self::RuntimeHoldReason>;
#[pallet::constant]
type Deposit: Get<BalanceOf<Self>>;
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
#[pallet::constant]
type MinNameLength: Get<u32>;
#[pallet::constant]
type MaxNameLength: Get<u32>;
type Web3Name: FullCodec
+ Debug
+ PartialEq
+ Clone
+ TypeInfo
+ TryFrom<Vec<u8>, Error = Error<Self>>
+ MaxEncodedLen
+ Ord;
type Web3NameOwner: Parameter + MaxEncodedLen;
type WeightInfo: WeightInfo;
type BalanceMigrationManager: BalanceMigrationManager<AccountIdOf<Self>, BalanceOf<Self>>;
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
Web3NameClaimed {
owner: Web3NameOwnerOf<T>,
name: Web3NameOf<T>,
},
Web3NameReleased {
owner: Web3NameOwnerOf<T>,
name: Web3NameOf<T>,
},
Web3NameBanned { name: Web3NameOf<T> },
Web3NameUnbanned { name: Web3NameOf<T> },
}
#[pallet::error]
pub enum Error<T> {
InsufficientFunds,
AlreadyExists,
NotFound,
OwnerAlreadyExists,
OwnerNotFound,
Banned,
NotBanned,
AlreadyBanned,
NotAuthorized,
TooShort,
TooLong,
InvalidCharacter,
}
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
#[cfg(feature = "try-runtime")]
fn try_state(_n: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
crate::try_state::do_try_state::<T>()
}
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(<T as Config>::WeightInfo::claim(name.len().saturated_into()))]
pub fn claim(origin: OriginFor<T>, name: Web3NameInput<T>) -> DispatchResult {
let origin = T::OwnerOrigin::ensure_origin(origin)?;
let payer = origin.sender();
let owner = origin.subject();
let decoded_name = Self::check_claiming_preconditions(name, &owner, &payer)?;
Self::register_name(decoded_name, owner, payer)?;
Ok(())
}
#[pallet::call_index(1)]
#[pallet::weight(<T as Config>::WeightInfo::release_by_owner())]
pub fn release_by_owner(origin: OriginFor<T>) -> DispatchResult {
let origin = T::OwnerOrigin::ensure_origin(origin)?;
let owner = origin.subject();
let owned_name = Self::check_releasing_preconditions(&owner)?;
Self::unregister_name(&owned_name)?;
Ok(())
}
#[pallet::call_index(2)]
#[pallet::weight(<T as Config>::WeightInfo::reclaim_deposit(name.len().saturated_into()))]
pub fn reclaim_deposit(origin: OriginFor<T>, name: Web3NameInput<T>) -> DispatchResult {
let caller = ensure_signed(origin)?;
let decoded_name = Self::check_reclaim_deposit_preconditions(name, &caller)?;
Self::unregister_name(&decoded_name)?;
Ok(())
}
#[pallet::call_index(3)]
#[pallet::weight(<T as Config>::WeightInfo::ban(name.len().saturated_into()))]
pub fn ban(origin: OriginFor<T>, name: Web3NameInput<T>) -> DispatchResult {
T::BanOrigin::ensure_origin(origin)?;
let (decoded_name, is_claimed) = Self::check_banning_preconditions(name)?;
if is_claimed {
Self::unregister_name(&decoded_name)?;
}
Self::ban_name(&decoded_name);
Self::deposit_event(Event::<T>::Web3NameBanned { name: decoded_name });
Ok(())
}
#[pallet::call_index(4)]
#[pallet::weight(<T as Config>::WeightInfo::unban(name.len().saturated_into()))]
pub fn unban(origin: OriginFor<T>, name: Web3NameInput<T>) -> DispatchResult {
T::BanOrigin::ensure_origin(origin)?;
let decoded_name = Self::check_unbanning_preconditions(name)?;
Self::unban_name(&decoded_name);
Self::deposit_event(Event::<T>::Web3NameUnbanned { name: decoded_name });
Ok(())
}
#[pallet::call_index(5)]
#[pallet::weight(<T as Config>::WeightInfo::change_deposit_owner())]
pub fn change_deposit_owner(origin: OriginFor<T>) -> DispatchResult {
let source = <T as Config>::OwnerOrigin::ensure_origin(origin)?;
let w3n_owner = source.subject();
let name = Names::<T>::get(&w3n_owner).ok_or(Error::<T>::NotFound)?;
Web3NameStorageDepositCollector::<T>::change_deposit_owner::<BalanceMigrationManagerOf<T>>(
&name,
source.sender(),
)?;
Ok(())
}
#[pallet::call_index(6)]
#[pallet::weight(<T as Config>::WeightInfo::update_deposit())]
pub fn update_deposit(origin: OriginFor<T>, name_input: Web3NameInput<T>) -> DispatchResult {
let source = ensure_signed(origin)?;
let name = Web3NameOf::<T>::try_from(name_input.into_inner()).map_err(DispatchError::from)?;
let w3n_entry = Owner::<T>::get(&name).ok_or(Error::<T>::NotFound)?;
ensure!(w3n_entry.deposit.owner == source, Error::<T>::NotAuthorized);
Web3NameStorageDepositCollector::<T>::update_deposit::<BalanceMigrationManagerOf<T>>(&name)?;
Ok(())
}
}
impl<T: Config> Pallet<T> {
fn check_claiming_preconditions(
name_input: Web3NameInput<T>,
owner: &Web3NameOwnerOf<T>,
deposit_payer: &AccountIdOf<T>,
) -> Result<Web3NameOf<T>, DispatchError> {
let name = Web3NameOf::<T>::try_from(name_input.into_inner()).map_err(DispatchError::from)?;
ensure!(!Names::<T>::contains_key(owner), Error::<T>::OwnerAlreadyExists);
ensure!(!Owner::<T>::contains_key(&name), Error::<T>::AlreadyExists);
ensure!(!Banned::<T>::contains_key(&name), Error::<T>::Banned);
ensure!(
<T::Currency as InspectHold<AccountIdOf<T>>>::can_hold(
&HoldReason::Deposit.into(),
deposit_payer,
T::Deposit::get()
),
Error::<T>::InsufficientFunds
);
Ok(name)
}
pub fn register_name(
name: Web3NameOf<T>,
owner: Web3NameOwnerOf<T>,
deposit_payer: AccountIdOf<T>,
) -> DispatchResult {
let block_number = frame_system::Pallet::<T>::block_number();
let deposit = Web3NameStorageDepositCollector::<T>::create_deposit(deposit_payer, T::Deposit::get())?;
<T as Config>::BalanceMigrationManager::exclude_key_from_migration(&Owner::<T>::hashed_key_for(&name));
Names::<T>::insert(&owner, name.clone());
Owner::<T>::insert(
&name,
Web3OwnershipOf::<T> {
owner: owner.clone(),
claimed_at: block_number,
deposit,
},
);
Self::deposit_event(Event::<T>::Web3NameClaimed { owner, name });
Ok(())
}
fn check_releasing_preconditions(owner: &Web3NameOwnerOf<T>) -> Result<Web3NameOf<T>, DispatchError> {
let name = Names::<T>::get(owner).ok_or(Error::<T>::OwnerNotFound)?;
Ok(name)
}
fn check_reclaim_deposit_preconditions(
name_input: Web3NameInput<T>,
caller: &AccountIdOf<T>,
) -> Result<Web3NameOf<T>, DispatchError> {
let name = Web3NameOf::<T>::try_from(name_input.into_inner()).map_err(DispatchError::from)?;
let Web3NameOwnership { deposit, .. } = Owner::<T>::get(&name).ok_or(Error::<T>::NotFound)?;
ensure!(caller == &deposit.owner, Error::<T>::NotAuthorized);
Ok(name)
}
fn unregister_name(name: &Web3NameOf<T>) -> Result<Web3OwnershipOf<T>, DispatchError> {
let name_ownership = Owner::<T>::take(name).unwrap();
Names::<T>::remove(&name_ownership.owner);
let is_key_migrated =
<T as Config>::BalanceMigrationManager::is_key_migrated(&Owner::<T>::hashed_key_for(name));
if is_key_migrated {
Web3NameStorageDepositCollector::<T>::free_deposit(name_ownership.clone().deposit)?;
} else {
<T as Config>::BalanceMigrationManager::release_reserved_deposit(
&name_ownership.deposit.owner,
&name_ownership.deposit.amount,
)
}
Self::deposit_event(Event::<T>::Web3NameReleased {
owner: name_ownership.owner.clone(),
name: name.clone(),
});
Ok(name_ownership)
}
fn check_banning_preconditions(name_input: Web3NameInput<T>) -> Result<(Web3NameOf<T>, bool), DispatchError> {
let name = Web3NameOf::<T>::try_from(name_input.into_inner()).map_err(DispatchError::from)?;
ensure!(!Banned::<T>::contains_key(&name), Error::<T>::AlreadyBanned);
let is_claimed = Owner::<T>::contains_key(&name);
Ok((name, is_claimed))
}
pub(crate) fn ban_name(name: &Web3NameOf<T>) {
Banned::<T>::insert(name, ());
}
fn check_unbanning_preconditions(name_input: Web3NameInput<T>) -> Result<Web3NameOf<T>, DispatchError> {
let name = Web3NameOf::<T>::try_from(name_input.into_inner()).map_err(DispatchError::from)?;
ensure!(Banned::<T>::contains_key(&name), Error::<T>::NotBanned);
Ok(name)
}
fn unban_name(name: &Web3NameOf<T>) {
Banned::<T>::remove(name);
}
}
pub(crate) struct Web3NameStorageDepositCollector<T: Config>(PhantomData<T>);
impl<T: Config> StorageDepositCollector<AccountIdOf<T>, T::Web3Name, T::RuntimeHoldReason>
for Web3NameStorageDepositCollector<T>
{
type Currency = T::Currency;
type Reason = HoldReason;
fn get_hashed_key(key: &T::Web3Name) -> Result<sp_std::vec::Vec<u8>, DispatchError> {
Ok(Owner::<T>::hashed_key_for(key))
}
fn reason() -> Self::Reason {
HoldReason::Deposit
}
fn deposit(
key: &T::Web3Name,
) -> Result<Deposit<AccountIdOf<T>, <Self::Currency as Inspect<AccountIdOf<T>>>::Balance>, DispatchError> {
let w3n_entry = Owner::<T>::get(key).ok_or(Error::<T>::NotFound)?;
Ok(w3n_entry.deposit)
}
fn deposit_amount(_key: &T::Web3Name) -> <Self::Currency as Inspect<AccountIdOf<T>>>::Balance {
T::Deposit::get()
}
fn store_deposit(
key: &T::Web3Name,
deposit: Deposit<AccountIdOf<T>, <Self::Currency as Inspect<AccountIdOf<T>>>::Balance>,
) -> Result<(), DispatchError> {
let w3n_entry = Owner::<T>::get(key).ok_or(Error::<T>::NotFound)?;
Owner::<T>::insert(key, Web3OwnershipOf::<T> { deposit, ..w3n_entry });
Ok(())
}
}
}