Newer
Older
// Copyright 2021 Axiom-Team
//
// This file is part of Substrate-Libre-Currency.
//
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Substrate-Libre-Currency is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>.
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
/*#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;*/
pub use pallet::*;
use sp_runtime::traits::{AtLeast32BitUnsigned, One, Saturating, Zero};
use sp_std::fmt::Debug;
use sp_std::prelude::*;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_support::traits::StorageVersion;
/// The current storage version.
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
#[pallet::storage_version(STORAGE_VERSION)]
pub struct Pallet<T>(_);
// CONFIG //
#[pallet::config]
pub trait Config: frame_system::Config {
#[pallet::constant]
/// Period during which the owner can confirm the new identity.
type ConfirmPeriod: Get<Self::BlockNumber>;
/// Because this pallet emits events, it depends on the runtime's definition of an event.
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
/// Management of the authorizations of the different calls. (The default implementation only allows root)
type EnsureIdtyCallAllowed: EnsureIdtyCallAllowed<Self>;
/// Minimum duration between the creation of 2 identities by the same creator
type IdtyCreationPeriod: Get<Self::BlockNumber>;
/// A short identity index.
type IdtyIndex: Parameter
+ Member
+ AtLeast32BitUnsigned
+ Codec
+ Default
+ Copy
+ MaybeSerializeDeserialize
+ Debug
+ MaxEncodedLen;
/// Handle logic to validate an identity name
type IdtyNameValidator: IdtyNameValidator;
/// Origin allowed to validate identity
type IdtyValidationOrigin: EnsureOrigin<Self::Origin>;
///
type IsMember: sp_runtime::traits::IsMember<Self::IdtyIndex>;
/// Maximum period with disabled status, after this period, the identity is permanently
/// deleted
type MaxDisabledPeriod: Get<Self::BlockNumber>;
#[cfg_attr(feature = "std", derive(serde::Deserialize, serde::Serialize))]
#[derive(Encode, Decode, Clone, PartialEq, Eq)]
pub struct GenesisIdty<T: Config> {
pub index: T::IdtyIndex,
pub name: IdtyName,
pub value: IdtyValue<T::BlockNumber, T::AccountId>,
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
pub identities: Vec<GenesisIdty<T>>,
}
#[cfg(feature = "std")]
impl<T: Config> Default for GenesisConfig<T> {
fn default() -> Self {
Self {
identities: Default::default(),
}
}
}
#[pallet::genesis_build]
impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {
fn build(&self) {
let mut names = sp_std::collections::btree_set::BTreeSet::new();
assert!(idty.value.removable_on == T::BlockNumber::zero());
names.insert(idty.name.clone());
let mut identities = self.identities.clone();
identities.sort_unstable_by(|a, b| a.index.cmp(&b.index));
for idty in identities.into_iter() {
let idty_index = Pallet::<T>::get_next_idty_index();
if idty.value.removable_on > T::BlockNumber::zero() {
<IdentitiesRemovableOn<T>>::append(
idty.value.removable_on,
(idty_index, idty.value.status),
<Identities<T>>::insert(idty_index, idty.value.clone());
IdentitiesNames::<T>::insert(idty.name.clone(), ());
IdentityIndexOf::<T>::insert(idty.value.owner_key, idty_index);
// STORAGE //
#[pallet::storage]
#[pallet::getter(fn identity)]
pub type Identities<T: Config> = CountedStorageMap<
_,
Twox64Concat,
T::IdtyIndex,
IdtyValue<T::BlockNumber, T::AccountId>,
OptionQuery,
>;
#[pallet::storage]
#[pallet::getter(fn identity_index_of)]
pub type IdentityIndexOf<T: Config> =
StorageMap<_, Blake2_128, T::AccountId, T::IdtyIndex, OptionQuery>;
#[pallet::storage]
#[pallet::getter(fn identity_by_did)]
pub type IdentitiesNames<T: Config> = StorageMap<_, Blake2_128, IdtyName, (), OptionQuery>;
#[pallet::storage]
pub(super) type NextIdtyIndex<T: Config> = StorageValue<_, T::IdtyIndex, ValueQuery>;
/// Identities by removed block
#[pallet::storage]
#[pallet::getter(fn removable_on)]
pub type IdentitiesRemovableOn<T: Config> =
StorageMap<_, Twox64Concat, T::BlockNumber, Vec<(T::IdtyIndex, IdtyStatus)>, ValueQuery>;
// HOOKS //
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(n: T::BlockNumber) -> Weight {
}
}
// EVENTS //
// Pallets use events to inform users when important changes are made.
// https://substrate.dev/docs/en/knowledgebase/runtime/events
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// A new identity has been created
/// [idty_index, owner_key]
IdtyCreated(T::IdtyIndex, T::AccountId),
/// [idty_index, owner_key, name]
IdtyConfirmed(T::IdtyIndex, T::AccountId, IdtyName),
}
// CALLS //
// Dispatchable functions allows users to interact with the pallet and invoke state changes.
// These functions materialize as "extrinsics", which are often compared to transactions.
// Dispatchable functions must be annotated with a weight and must return a DispatchResult.
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::weight(0)]
pub fn create_identity(
origin: OriginFor<T>,
owner_key: T::AccountId,
) -> DispatchResultWithPostInfo {
// Verification phase //
let who = ensure_signed(origin)?;
let creator =
IdentityIndexOf::<T>::try_get(&who).map_err(|_| Error::<T>::IdtyIndexNotFound)?;
Identities::<T>::try_get(&creator).map_err(|_| Error::<T>::IdtyNotFound)?;
if IdentityIndexOf::<T>::contains_key(&owner_key) {
return Err(Error::<T>::IdtyAlreadyCreated.into());
}
if !T::EnsureIdtyCallAllowed::can_create_identity(creator) {
return Err(Error::<T>::CreatorNotAllowedToCreateIdty.into());
}
let block_number = frame_system::pallet::Pallet::<T>::block_number();
if creator_idty_val.next_creatable_identity_on > block_number {
return Err(Error::<T>::NotRespectIdtyCreationPeriod.into());
}
frame_system::Pallet::<T>::inc_sufficients(&owner_key);
<Identities<T>>::mutate_exists(creator, |idty_val_opt| {
if let Some(ref mut idty_val) = idty_val_opt {
idty_val.next_creatable_identity_on =
block_number + T::IdtyCreationPeriod::get();
}
});
let removable_on = block_number + T::ConfirmPeriod::get();
next_creatable_identity_on: T::BlockNumber::zero(),
owner_key: owner_key.clone(),
removable_on,
status: IdtyStatus::Created,
IdentitiesRemovableOn::<T>::append(removable_on, (idty_index, IdtyStatus::Created));
IdentityIndexOf::<T>::insert(owner_key.clone(), idty_index);
Self::deposit_event(Event::IdtyCreated(idty_index, owner_key));
T::OnIdtyChange::on_idty_change(idty_index, IdtyEvent::Created { creator });
Ok(().into())
}
#[pallet::weight(0)]
pub fn confirm_identity(
origin: OriginFor<T>,
let idty_index =
IdentityIndexOf::<T>::try_get(&who).map_err(|_| Error::<T>::IdtyIndexNotFound)?;
let mut idty_value =
Identities::<T>::try_get(idty_index).map_err(|_| Error::<T>::IdtyNotFound)?;
if idty_value.status != IdtyStatus::Created {
return Err(Error::<T>::IdtyAlreadyConfirmed.into());
}
if !T::IdtyNameValidator::validate(&idty_name) {
return Err(Error::<T>::IdtyNameInvalid.into());
}
if <IdentitiesNames<T>>::contains_key(&idty_name) {
return Err(Error::<T>::IdtyNameAlreadyExist.into());
}
if !T::EnsureIdtyCallAllowed::can_confirm_identity(idty_index, who.clone()) {
return Err(Error::<T>::NotAllowedToConfirmIdty.into());
// Apply phase //
idty_value.status = IdtyStatus::ConfirmedByOwner;
<Identities<T>>::insert(idty_index, idty_value);
<IdentitiesNames<T>>::insert(idty_name.clone(), ());
Self::deposit_event(Event::IdtyConfirmed(idty_index, who, idty_name));
T::OnIdtyChange::on_idty_change(idty_index, IdtyEvent::Confirmed);
Ok(().into())
}
#[pallet::weight(0)]
pub fn validate_identity(
origin: OriginFor<T>,
idty_index: T::IdtyIndex,
T::IdtyValidationOrigin::ensure_origin(origin)?;
let mut idty_value =
Identities::<T>::try_get(idty_index).map_err(|_| Error::<T>::IdtyNotFound)?;
match idty_value.status {
IdtyStatus::Created => return Err(Error::<T>::IdtyNotConfirmedByOwner.into()),
if !T::EnsureIdtyCallAllowed::can_validate_identity(idty_index) {
return Err(Error::<T>::NotAllowedToValidateIdty.into());
IdtyStatus::Validated => return Err(Error::<T>::IdtyAlreadyValidated.into()),
// Apply phase //
idty_value.removable_on = T::BlockNumber::zero();
idty_value.status = IdtyStatus::Validated;
<Identities<T>>::insert(idty_index, idty_value);
Self::deposit_event(Event::IdtyValidated(idty_index));
T::OnIdtyChange::on_idty_change(idty_index, IdtyEvent::Validated);
Ok(().into())
#[pallet::weight(0)]
pub fn remove_identity(
origin: OriginFor<T>,
idty_index: T::IdtyIndex,
) -> DispatchResultWithPostInfo {
ensure_root(origin)?;
Self::do_remove_identity(idty_index);
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
if let Some(idty_name) = idty_name {
<IdentitiesNames<T>>::remove(idty_name);
}
Ok(().into())
}
#[pallet::weight(0)]
pub fn prune_item_identities_names(
origin: OriginFor<T>,
names: Vec<IdtyName>,
) -> DispatchResultWithPostInfo {
ensure_root(origin)?;
for name in names {
<IdentitiesNames<T>>::remove(name);
}
Ok(().into())
}
#[pallet::weight(0)]
pub fn prune_item_identity_index_of(
origin: OriginFor<T>,
accounts_ids: Vec<T::AccountId>,
) -> DispatchResultWithPostInfo {
ensure_root(origin)?;
accounts_ids
.into_iter()
.filter(|account_id| {
if let Ok(idty_index) = IdentityIndexOf::<T>::try_get(&account_id) {
!Identities::<T>::contains_key(&idty_index)
} else {
false
}
})
.for_each(IdentityIndexOf::<T>::remove);
}
// ERRORS //
#[pallet::error]
pub enum Error<T> {
/// Creator not allowed to create identities
CreatorNotAllowedToCreateIdty,
/// Identity already created
IdtyAlreadyCreated,
/// Identity already validated
IdtyAlreadyValidated,
/// You are not allowed to create a new identity now
IdtyCreationNotAllowed,
/// Identity name already exist
IdtyNameAlreadyExist,
/// Idty name invalid
IdtyNameInvalid,
/// Identity not confirmed by owner
IdtyNotConfirmedByOwner,
/// Identity not found
IdtyNotFound,
/// Idty not member
IdtyNotMember,
/// Identity not yet renewable
IdtyNotYetRenewable,
/// Not allowed to confirm identity
NotAllowedToConfirmIdty,
/// Not allowed to validate identity
NotAllowedToValidateIdty,
/// Not same identity name
NotSameIdtyName,
/// Right already added
RightAlreadyAdded,
/// Right not exist
RightNotExist,
/// Not respect IdtyCreationPeriod
NotRespectIdtyCreationPeriod,
// PUBLIC FUNCTIONS //
pub fn identities_count() -> u32 {
Identities::<T>::count()
}
// INTERNAL FUNCTIONS //
impl<T: Config> Pallet<T> {
pub(super) fn do_remove_identity(idty_index: T::IdtyIndex) -> Weight {
if let Some(idty_val) = Identities::<T>::take(idty_index) {
frame_system::Pallet::<T>::dec_sufficients(&idty_val.owner_key);
}
T::OnIdtyChange::on_idty_change(idty_index, IdtyEvent::Removed);
0
}
fn get_next_idty_index() -> T::IdtyIndex {
if let Ok(next_index) = <NextIdtyIndex<T>>::try_get() {
<NextIdtyIndex<T>>::put(next_index.saturating_add(T::IdtyIndex::one()));
next_index
} else {
<NextIdtyIndex<T>>::put(T::IdtyIndex::one() + T::IdtyIndex::one());
T::IdtyIndex::one()
fn prune_identities(block_number: T::BlockNumber) -> Weight {
for (idty_index, idty_status) in IdentitiesRemovableOn::<T>::take(block_number) {
if let Ok(idty_val) = <Identities<T>>::try_get(idty_index) {
if idty_val.removable_on == block_number && idty_val.status == idty_status {
total_weight += Self::do_remove_identity(idty_index)