#![cfg_attr(not(feature = "std"), no_std)]
mod benchmarking;
#[cfg(test)]
mod tests;
mod types;
pub mod weights;
use frame_support::traits::{BalanceStatus, Currency, OnUnbalanced, ReservableCurrency};
use sp_runtime::traits::{AppendZerosInput, Hash, Saturating, StaticLookup, Zero};
use sp_std::prelude::*;
pub use weights::WeightInfo;
pub use pallet::*;
pub use types::{
Data, IdentityField, IdentityFields, IdentityInfo, Judgement, RegistrarIndex, RegistrarInfo,
Registration,
};
type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency<
<T as frame_system::Config>::AccountId,
>>::NegativeImbalance;
type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
#[pallet::config]
pub trait Config: frame_system::Config {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type Currency: ReservableCurrency<Self::AccountId>;
#[pallet::constant]
type BasicDeposit: Get<BalanceOf<Self>>;
#[pallet::constant]
type FieldDeposit: Get<BalanceOf<Self>>;
#[pallet::constant]
type SubAccountDeposit: Get<BalanceOf<Self>>;
#[pallet::constant]
type MaxSubAccounts: Get<u32>;
#[pallet::constant]
type MaxAdditionalFields: Get<u32>;
#[pallet::constant]
type MaxRegistrars: Get<u32>;
type Slashed: OnUnbalanced<NegativeImbalanceOf<Self>>;
type ForceOrigin: EnsureOrigin<Self::RuntimeOrigin>;
type RegistrarOrigin: EnsureOrigin<Self::RuntimeOrigin>;
type WeightInfo: WeightInfo;
}
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);
#[pallet::storage]
#[pallet::getter(fn identity)]
pub(super) type IdentityOf<T: Config> = StorageMap<
_,
Twox64Concat,
T::AccountId,
Registration<BalanceOf<T>, T::MaxRegistrars, T::MaxAdditionalFields>,
OptionQuery,
>;
#[pallet::storage]
#[pallet::getter(fn super_of)]
pub(super) type SuperOf<T: Config> =
StorageMap<_, Blake2_128Concat, T::AccountId, (T::AccountId, Data), OptionQuery>;
#[pallet::storage]
#[pallet::getter(fn subs_of)]
pub(super) type SubsOf<T: Config> = StorageMap<
_,
Twox64Concat,
T::AccountId,
(BalanceOf<T>, BoundedVec<T::AccountId, T::MaxSubAccounts>),
ValueQuery,
>;
#[pallet::storage]
#[pallet::getter(fn registrars)]
pub(super) type Registrars<T: Config> = StorageValue<
_,
BoundedVec<Option<RegistrarInfo<BalanceOf<T>, T::AccountId>>, T::MaxRegistrars>,
ValueQuery,
>;
#[pallet::error]
pub enum Error<T> {
TooManySubAccounts,
NotFound,
NotNamed,
EmptyIndex,
FeeChanged,
NoIdentity,
StickyJudgement,
JudgementGiven,
InvalidJudgement,
InvalidIndex,
InvalidTarget,
TooManyFields,
TooManyRegistrars,
AlreadyClaimed,
NotSub,
NotOwned,
JudgementForDifferentIdentity,
JudgementPaymentFailed,
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
IdentitySet { who: T::AccountId },
IdentityCleared { who: T::AccountId, deposit: BalanceOf<T> },
IdentityKilled { who: T::AccountId, deposit: BalanceOf<T> },
JudgementRequested { who: T::AccountId, registrar_index: RegistrarIndex },
JudgementUnrequested { who: T::AccountId, registrar_index: RegistrarIndex },
JudgementGiven { target: T::AccountId, registrar_index: RegistrarIndex },
RegistrarAdded { registrar_index: RegistrarIndex },
SubIdentityAdded { sub: T::AccountId, main: T::AccountId, deposit: BalanceOf<T> },
SubIdentityRemoved { sub: T::AccountId, main: T::AccountId, deposit: BalanceOf<T> },
SubIdentityRevoked { sub: T::AccountId, main: T::AccountId, deposit: BalanceOf<T> },
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::add_registrar(T::MaxRegistrars::get()))]
pub fn add_registrar(
origin: OriginFor<T>,
account: AccountIdLookupOf<T>,
) -> DispatchResultWithPostInfo {
T::RegistrarOrigin::ensure_origin(origin)?;
let account = T::Lookup::lookup(account)?;
let (i, registrar_count) = <Registrars<T>>::try_mutate(
|registrars| -> Result<(RegistrarIndex, usize), DispatchError> {
registrars
.try_push(Some(RegistrarInfo {
account,
fee: Zero::zero(),
fields: Default::default(),
}))
.map_err(|_| Error::<T>::TooManyRegistrars)?;
Ok(((registrars.len() - 1) as RegistrarIndex, registrars.len()))
},
)?;
Self::deposit_event(Event::RegistrarAdded { registrar_index: i });
Ok(Some(T::WeightInfo::add_registrar(registrar_count as u32)).into())
}
#[pallet::call_index(1)]
#[pallet::weight( T::WeightInfo::set_identity(
T::MaxRegistrars::get(), // R
T::MaxAdditionalFields::get(), // X
))]
pub fn set_identity(
origin: OriginFor<T>,
info: Box<IdentityInfo<T::MaxAdditionalFields>>,
) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
let extra_fields = info.additional.len() as u32;
ensure!(extra_fields <= T::MaxAdditionalFields::get(), Error::<T>::TooManyFields);
let fd = <BalanceOf<T>>::from(extra_fields) * T::FieldDeposit::get();
let mut id = match <IdentityOf<T>>::get(&sender) {
Some(mut id) => {
id.judgements.retain(|j| j.1.is_sticky());
id.info = *info;
id
},
None => Registration {
info: *info,
judgements: BoundedVec::default(),
deposit: Zero::zero(),
},
};
let old_deposit = id.deposit;
id.deposit = T::BasicDeposit::get() + fd;
if id.deposit > old_deposit {
T::Currency::reserve(&sender, id.deposit - old_deposit)?;
}
if old_deposit > id.deposit {
let err_amount = T::Currency::unreserve(&sender, old_deposit - id.deposit);
debug_assert!(err_amount.is_zero());
}
let judgements = id.judgements.len();
<IdentityOf<T>>::insert(&sender, id);
Self::deposit_event(Event::IdentitySet { who: sender });
Ok(Some(T::WeightInfo::set_identity(
judgements as u32, extra_fields, ))
.into())
}
#[pallet::call_index(2)]
#[pallet::weight(T::WeightInfo::set_subs_old(T::MaxSubAccounts::get()) // P: Assume max sub accounts removed.
.saturating_add(T::WeightInfo::set_subs_new(subs.len() as u32)) // S: Assume all subs are new.
)]
pub fn set_subs(
origin: OriginFor<T>,
subs: Vec<(T::AccountId, Data)>,
) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
ensure!(<IdentityOf<T>>::contains_key(&sender), Error::<T>::NotFound);
ensure!(
subs.len() <= T::MaxSubAccounts::get() as usize,
Error::<T>::TooManySubAccounts
);
let (old_deposit, old_ids) = <SubsOf<T>>::get(&sender);
let new_deposit = T::SubAccountDeposit::get() * <BalanceOf<T>>::from(subs.len() as u32);
let not_other_sub =
subs.iter().filter_map(|i| SuperOf::<T>::get(&i.0)).all(|i| i.0 == sender);
ensure!(not_other_sub, Error::<T>::AlreadyClaimed);
if old_deposit < new_deposit {
T::Currency::reserve(&sender, new_deposit - old_deposit)?;
} else if old_deposit > new_deposit {
let err_amount = T::Currency::unreserve(&sender, old_deposit - new_deposit);
debug_assert!(err_amount.is_zero());
}
for s in old_ids.iter() {
<SuperOf<T>>::remove(s);
}
let mut ids = BoundedVec::<T::AccountId, T::MaxSubAccounts>::default();
for (id, name) in subs {
<SuperOf<T>>::insert(&id, (sender.clone(), name));
ids.try_push(id).expect("subs length is less than T::MaxSubAccounts; qed");
}
let new_subs = ids.len();
if ids.is_empty() {
<SubsOf<T>>::remove(&sender);
} else {
<SubsOf<T>>::insert(&sender, (new_deposit, ids));
}
Ok(Some(
T::WeightInfo::set_subs_old(old_ids.len() as u32) .saturating_add(T::WeightInfo::set_subs_new(new_subs as u32)),
)
.into())
}
#[pallet::call_index(3)]
#[pallet::weight(T::WeightInfo::clear_identity(
T::MaxRegistrars::get(), // R
T::MaxSubAccounts::get(), // S
T::MaxAdditionalFields::get(), // X
))]
pub fn clear_identity(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
let (subs_deposit, sub_ids) = <SubsOf<T>>::take(&sender);
let id = <IdentityOf<T>>::take(&sender).ok_or(Error::<T>::NotNamed)?;
let deposit = id.total_deposit() + subs_deposit;
for sub in sub_ids.iter() {
<SuperOf<T>>::remove(sub);
}
let err_amount = T::Currency::unreserve(&sender, deposit);
debug_assert!(err_amount.is_zero());
Self::deposit_event(Event::IdentityCleared { who: sender, deposit });
Ok(Some(T::WeightInfo::clear_identity(
id.judgements.len() as u32, sub_ids.len() as u32, id.info.additional.len() as u32, ))
.into())
}
#[pallet::call_index(4)]
#[pallet::weight(T::WeightInfo::request_judgement(
T::MaxRegistrars::get(), // R
T::MaxAdditionalFields::get(), // X
))]
pub fn request_judgement(
origin: OriginFor<T>,
#[pallet::compact] reg_index: RegistrarIndex,
#[pallet::compact] max_fee: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
let registrars = <Registrars<T>>::get();
let registrar = registrars
.get(reg_index as usize)
.and_then(Option::as_ref)
.ok_or(Error::<T>::EmptyIndex)?;
ensure!(max_fee >= registrar.fee, Error::<T>::FeeChanged);
let mut id = <IdentityOf<T>>::get(&sender).ok_or(Error::<T>::NoIdentity)?;
let item = (reg_index, Judgement::FeePaid(registrar.fee));
match id.judgements.binary_search_by_key(®_index, |x| x.0) {
Ok(i) =>
if id.judgements[i].1.is_sticky() {
return Err(Error::<T>::StickyJudgement.into())
} else {
id.judgements[i] = item
},
Err(i) =>
id.judgements.try_insert(i, item).map_err(|_| Error::<T>::TooManyRegistrars)?,
}
T::Currency::reserve(&sender, registrar.fee)?;
let judgements = id.judgements.len();
let extra_fields = id.info.additional.len();
<IdentityOf<T>>::insert(&sender, id);
Self::deposit_event(Event::JudgementRequested {
who: sender,
registrar_index: reg_index,
});
Ok(Some(T::WeightInfo::request_judgement(judgements as u32, extra_fields as u32))
.into())
}
#[pallet::call_index(5)]
#[pallet::weight(T::WeightInfo::cancel_request(
T::MaxRegistrars::get(), // R
T::MaxAdditionalFields::get(), // X
))]
pub fn cancel_request(
origin: OriginFor<T>,
reg_index: RegistrarIndex,
) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
let mut id = <IdentityOf<T>>::get(&sender).ok_or(Error::<T>::NoIdentity)?;
let pos = id
.judgements
.binary_search_by_key(®_index, |x| x.0)
.map_err(|_| Error::<T>::NotFound)?;
let fee = if let Judgement::FeePaid(fee) = id.judgements.remove(pos).1 {
fee
} else {
return Err(Error::<T>::JudgementGiven.into())
};
let err_amount = T::Currency::unreserve(&sender, fee);
debug_assert!(err_amount.is_zero());
let judgements = id.judgements.len();
let extra_fields = id.info.additional.len();
<IdentityOf<T>>::insert(&sender, id);
Self::deposit_event(Event::JudgementUnrequested {
who: sender,
registrar_index: reg_index,
});
Ok(Some(T::WeightInfo::cancel_request(judgements as u32, extra_fields as u32)).into())
}
#[pallet::call_index(6)]
#[pallet::weight(T::WeightInfo::set_fee(T::MaxRegistrars::get()))] pub fn set_fee(
origin: OriginFor<T>,
#[pallet::compact] index: RegistrarIndex,
#[pallet::compact] fee: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
let registrars = <Registrars<T>>::mutate(|rs| -> Result<usize, DispatchError> {
rs.get_mut(index as usize)
.and_then(|x| x.as_mut())
.and_then(|r| {
if r.account == who {
r.fee = fee;
Some(())
} else {
None
}
})
.ok_or_else(|| DispatchError::from(Error::<T>::InvalidIndex))?;
Ok(rs.len())
})?;
Ok(Some(T::WeightInfo::set_fee(registrars as u32)).into()) }
#[pallet::call_index(7)]
#[pallet::weight(T::WeightInfo::set_account_id(T::MaxRegistrars::get()))] pub fn set_account_id(
origin: OriginFor<T>,
#[pallet::compact] index: RegistrarIndex,
new: AccountIdLookupOf<T>,
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
let new = T::Lookup::lookup(new)?;
let registrars = <Registrars<T>>::mutate(|rs| -> Result<usize, DispatchError> {
rs.get_mut(index as usize)
.and_then(|x| x.as_mut())
.and_then(|r| {
if r.account == who {
r.account = new;
Some(())
} else {
None
}
})
.ok_or_else(|| DispatchError::from(Error::<T>::InvalidIndex))?;
Ok(rs.len())
})?;
Ok(Some(T::WeightInfo::set_account_id(registrars as u32)).into()) }
#[pallet::call_index(8)]
#[pallet::weight(T::WeightInfo::set_fields(T::MaxRegistrars::get()))] pub fn set_fields(
origin: OriginFor<T>,
#[pallet::compact] index: RegistrarIndex,
fields: IdentityFields,
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
let registrars = <Registrars<T>>::mutate(|rs| -> Result<usize, DispatchError> {
rs.get_mut(index as usize)
.and_then(|x| x.as_mut())
.and_then(|r| {
if r.account == who {
r.fields = fields;
Some(())
} else {
None
}
})
.ok_or_else(|| DispatchError::from(Error::<T>::InvalidIndex))?;
Ok(rs.len())
})?;
Ok(Some(T::WeightInfo::set_fields(
registrars as u32, ))
.into())
}
#[pallet::call_index(9)]
#[pallet::weight(T::WeightInfo::provide_judgement(
T::MaxRegistrars::get(), // R
T::MaxAdditionalFields::get(), // X
))]
pub fn provide_judgement(
origin: OriginFor<T>,
#[pallet::compact] reg_index: RegistrarIndex,
target: AccountIdLookupOf<T>,
judgement: Judgement<BalanceOf<T>>,
identity: T::Hash,
) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
let target = T::Lookup::lookup(target)?;
ensure!(!judgement.has_deposit(), Error::<T>::InvalidJudgement);
<Registrars<T>>::get()
.get(reg_index as usize)
.and_then(Option::as_ref)
.filter(|r| r.account == sender)
.ok_or(Error::<T>::InvalidIndex)?;
let mut id = <IdentityOf<T>>::get(&target).ok_or(Error::<T>::InvalidTarget)?;
if T::Hashing::hash_of(&id.info) != identity {
return Err(Error::<T>::JudgementForDifferentIdentity.into())
}
let item = (reg_index, judgement);
match id.judgements.binary_search_by_key(®_index, |x| x.0) {
Ok(position) => {
if let Judgement::FeePaid(fee) = id.judgements[position].1 {
T::Currency::repatriate_reserved(
&target,
&sender,
fee,
BalanceStatus::Free,
)
.map_err(|_| Error::<T>::JudgementPaymentFailed)?;
}
id.judgements[position] = item
},
Err(position) => id
.judgements
.try_insert(position, item)
.map_err(|_| Error::<T>::TooManyRegistrars)?,
}
let judgements = id.judgements.len();
let extra_fields = id.info.additional.len();
<IdentityOf<T>>::insert(&target, id);
Self::deposit_event(Event::JudgementGiven { target, registrar_index: reg_index });
Ok(Some(T::WeightInfo::provide_judgement(judgements as u32, extra_fields as u32))
.into())
}
#[pallet::call_index(10)]
#[pallet::weight(T::WeightInfo::kill_identity(
T::MaxRegistrars::get(), // R
T::MaxSubAccounts::get(), // S
T::MaxAdditionalFields::get(), // X
))]
pub fn kill_identity(
origin: OriginFor<T>,
target: AccountIdLookupOf<T>,
) -> DispatchResultWithPostInfo {
T::ForceOrigin::ensure_origin(origin)?;
let target = T::Lookup::lookup(target)?;
let (subs_deposit, sub_ids) = <SubsOf<T>>::take(&target);
let id = <IdentityOf<T>>::take(&target).ok_or(Error::<T>::NotNamed)?;
let deposit = id.total_deposit() + subs_deposit;
for sub in sub_ids.iter() {
<SuperOf<T>>::remove(sub);
}
T::Slashed::on_unbalanced(T::Currency::slash_reserved(&target, deposit).0);
Self::deposit_event(Event::IdentityKilled { who: target, deposit });
Ok(Some(T::WeightInfo::kill_identity(
id.judgements.len() as u32, sub_ids.len() as u32, id.info.additional.len() as u32, ))
.into())
}
#[pallet::call_index(11)]
#[pallet::weight(T::WeightInfo::add_sub(T::MaxSubAccounts::get()))]
pub fn add_sub(
origin: OriginFor<T>,
sub: AccountIdLookupOf<T>,
data: Data,
) -> DispatchResult {
let sender = ensure_signed(origin)?;
let sub = T::Lookup::lookup(sub)?;
ensure!(IdentityOf::<T>::contains_key(&sender), Error::<T>::NoIdentity);
ensure!(!SuperOf::<T>::contains_key(&sub), Error::<T>::AlreadyClaimed);
SubsOf::<T>::try_mutate(&sender, |(ref mut subs_deposit, ref mut sub_ids)| {
ensure!(
sub_ids.len() < T::MaxSubAccounts::get() as usize,
Error::<T>::TooManySubAccounts
);
let deposit = T::SubAccountDeposit::get();
T::Currency::reserve(&sender, deposit)?;
SuperOf::<T>::insert(&sub, (sender.clone(), data));
sub_ids.try_push(sub.clone()).expect("sub ids length checked above; qed");
*subs_deposit = subs_deposit.saturating_add(deposit);
Self::deposit_event(Event::SubIdentityAdded { sub, main: sender.clone(), deposit });
Ok(())
})
}
#[pallet::call_index(12)]
#[pallet::weight(T::WeightInfo::rename_sub(T::MaxSubAccounts::get()))]
pub fn rename_sub(
origin: OriginFor<T>,
sub: AccountIdLookupOf<T>,
data: Data,
) -> DispatchResult {
let sender = ensure_signed(origin)?;
let sub = T::Lookup::lookup(sub)?;
ensure!(IdentityOf::<T>::contains_key(&sender), Error::<T>::NoIdentity);
ensure!(SuperOf::<T>::get(&sub).map_or(false, |x| x.0 == sender), Error::<T>::NotOwned);
SuperOf::<T>::insert(&sub, (sender, data));
Ok(())
}
#[pallet::call_index(13)]
#[pallet::weight(T::WeightInfo::remove_sub(T::MaxSubAccounts::get()))]
pub fn remove_sub(origin: OriginFor<T>, sub: AccountIdLookupOf<T>) -> DispatchResult {
let sender = ensure_signed(origin)?;
ensure!(IdentityOf::<T>::contains_key(&sender), Error::<T>::NoIdentity);
let sub = T::Lookup::lookup(sub)?;
let (sup, _) = SuperOf::<T>::get(&sub).ok_or(Error::<T>::NotSub)?;
ensure!(sup == sender, Error::<T>::NotOwned);
SuperOf::<T>::remove(&sub);
SubsOf::<T>::mutate(&sup, |(ref mut subs_deposit, ref mut sub_ids)| {
sub_ids.retain(|x| x != &sub);
let deposit = T::SubAccountDeposit::get().min(*subs_deposit);
*subs_deposit -= deposit;
let err_amount = T::Currency::unreserve(&sender, deposit);
debug_assert!(err_amount.is_zero());
Self::deposit_event(Event::SubIdentityRemoved { sub, main: sender, deposit });
});
Ok(())
}
#[pallet::call_index(14)]
#[pallet::weight(T::WeightInfo::quit_sub(T::MaxSubAccounts::get()))]
pub fn quit_sub(origin: OriginFor<T>) -> DispatchResult {
let sender = ensure_signed(origin)?;
let (sup, _) = SuperOf::<T>::take(&sender).ok_or(Error::<T>::NotSub)?;
SubsOf::<T>::mutate(&sup, |(ref mut subs_deposit, ref mut sub_ids)| {
sub_ids.retain(|x| x != &sender);
let deposit = T::SubAccountDeposit::get().min(*subs_deposit);
*subs_deposit -= deposit;
let _ =
T::Currency::repatriate_reserved(&sup, &sender, deposit, BalanceStatus::Free);
Self::deposit_event(Event::SubIdentityRevoked {
sub: sender,
main: sup.clone(),
deposit,
});
});
Ok(())
}
}
}
impl<T: Config> Pallet<T> {
pub fn subs(who: &T::AccountId) -> Vec<(T::AccountId, Data)> {
SubsOf::<T>::get(who)
.1
.into_iter()
.filter_map(|a| SuperOf::<T>::get(&a).map(|x| (a, x.1)))
.collect()
}
pub fn has_identity(who: &T::AccountId, fields: u64) -> bool {
IdentityOf::<T>::get(who)
.map_or(false, |registration| (registration.info.fields().0.bits() & fields) == fields)
}
}