Skip to content
Snippets Groups Projects
lib.rs 16.9 KiB
Newer Older
Éloïs's avatar
Éloïs committed
// 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.
Éloïs's avatar
Éloïs committed
//
// 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)]
#![allow(clippy::type_complexity)]
Éloïs's avatar
Éloïs committed

pub mod traits;
mod types;
Éloïs's avatar
Éloïs committed

#[cfg(test)]
mod mock;

#[cfg(test)]
mod tests;

/*#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;*/

pub use pallet::*;
pub use types::*;
Éloïs's avatar
Éloïs committed

use crate::traits::*;
Éloïs's avatar
Éloïs committed
use codec::Codec;
Éloïs's avatar
Éloïs committed
use frame_support::dispatch::Weight;
Éloïs's avatar
Éloïs committed
use sp_runtime::traits::{AtLeast32BitUnsigned, One, Saturating, Zero};
use sp_std::fmt::Debug;
Éloïs's avatar
Éloïs committed
use sp_std::prelude::*;

#[frame_support::pallet]
pub mod pallet {
    use super::*;
    use frame_support::pallet_prelude::*;
    use frame_support::traits::StorageVersion;
Éloïs's avatar
Éloïs committed
    use frame_system::pallet_prelude::*;

    /// 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)]
    #[pallet::without_storage_info]
    pub struct Pallet<T>(_);

    // CONFIG //
Éloïs's avatar
Éloïs committed

    #[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>;
Éloïs's avatar
Éloïs committed
        /// A short identity index.
        type IdtyIndex: Parameter
            + Member
            + AtLeast32BitUnsigned
            + Codec
            + Default
            + Copy
            + MaybeSerializeDeserialize
            + Debug
            + MaxEncodedLen;
Éloïs's avatar
Éloïs committed
        /// Handle logic to validate an identity name
        type IdtyNameValidator: IdtyNameValidator;
Éloïs's avatar
Éloïs committed
        /// Origin allowed to validate identity
        type IdtyValidationOrigin: EnsureOrigin<Self::Origin>;
        ///
        type IsMember: sp_runtime::traits::IsMember<Self::IdtyIndex>;
Éloïs's avatar
Éloïs committed
        /// On identity confirmed by it's owner
        type OnIdtyChange: OnIdtyChange<Self>;
Éloïs's avatar
Éloïs committed
        #[pallet::constant]
        /// Maximum period with disabled status, after this period, the identity is permanently
        /// deleted
        type MaxDisabledPeriod: Get<Self::BlockNumber>;
Éloïs's avatar
Éloïs committed
    }
    // GENESIS STUFF //
    #[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>,
Éloïs's avatar
Éloïs committed
    #[pallet::genesis_config]
    pub struct GenesisConfig<T: Config> {
        pub identities: Vec<GenesisIdty<T>>,
Éloïs's avatar
Éloïs committed
    }

    #[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) {
Éloïs's avatar
Éloïs committed
            let mut names = sp_std::collections::btree_set::BTreeSet::new();
            for idty in &self.identities {
                    !names.contains(&idty.name),
Éloïs's avatar
Éloïs committed
                    "Idty name {:?} is present twice",
                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),
Éloïs's avatar
Éloïs committed
                }
                <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,
    >;
Éloïs's avatar
Éloïs committed

    #[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)]
Éloïs's avatar
Éloïs committed
    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>;
Éloïs's avatar
Éloïs committed
    // HOOKS //

    #[pallet::hooks]
    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
        fn on_initialize(n: T::BlockNumber) -> Weight {
            if n > T::BlockNumber::zero() {
                Self::prune_identities(n)
Éloïs's avatar
Éloïs committed
        }
    }

    // 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
Éloïs's avatar
Éloïs committed
        /// [idty_index, owner_key]
        IdtyCreated(T::IdtyIndex, T::AccountId),
Éloïs's avatar
Éloïs committed
        /// An identity has been confirmed by it's owner
Éloïs's avatar
Éloïs committed
        /// [idty_index, owner_key, name]
        IdtyConfirmed(T::IdtyIndex, T::AccountId, IdtyName),
Éloïs's avatar
Éloïs committed
        /// An identity has been validated
Éloïs's avatar
Éloïs committed
        /// [idty_index]
        IdtyValidated(T::IdtyIndex),
Éloïs's avatar
Éloïs committed
    }

    // 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)?;

Éloïs's avatar
Éloïs committed
            let creator =
                IdentityIndexOf::<T>::try_get(&who).map_err(|_| Error::<T>::IdtyIndexNotFound)?;
            let creator_idty_val =
Éloïs's avatar
Éloïs committed
                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();
                }
            });

Éloïs's avatar
Éloïs committed
            let removable_on = block_number + T::ConfirmPeriod::get();

Éloïs's avatar
Éloïs committed
            let idty_index = Self::get_next_idty_index();
Éloïs's avatar
Éloïs committed
            <Identities<T>>::insert(
Éloïs's avatar
Éloïs committed
                IdtyValue {
                    next_creatable_identity_on: T::BlockNumber::zero(),
                    owner_key: owner_key.clone(),
                    removable_on,
                    status: IdtyStatus::Created,
Éloïs's avatar
Éloïs committed
                },
            );
            IdentitiesRemovableOn::<T>::append(removable_on, (idty_index, IdtyStatus::Created));
Éloïs's avatar
Éloïs committed
            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 });
Éloïs's avatar
Éloïs committed
            Ok(().into())
        }
        #[pallet::weight(0)]
        pub fn confirm_identity(
            origin: OriginFor<T>,
Éloïs's avatar
Éloïs committed
            idty_name: IdtyName,
Éloïs's avatar
Éloïs committed
        ) -> DispatchResultWithPostInfo {
Éloïs's avatar
Éloïs committed
            // Verification phase //
Éloïs's avatar
Éloïs committed
            let who = ensure_signed(origin)?;

Éloïs's avatar
Éloïs committed
            let idty_index =
                IdentityIndexOf::<T>::try_get(&who).map_err(|_| Error::<T>::IdtyIndexNotFound)?;
Éloïs's avatar
Éloïs committed

Éloïs's avatar
Éloïs committed
            let mut idty_value =
                Identities::<T>::try_get(idty_index).map_err(|_| Error::<T>::IdtyNotFound)?;
Éloïs's avatar
Éloïs committed

Éloïs's avatar
Éloïs committed
            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());
Éloïs's avatar
Éloïs committed
            }
Éloïs's avatar
Éloïs committed

            // 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())
Éloïs's avatar
Éloïs committed
        }
        #[pallet::weight(0)]
        pub fn validate_identity(
            origin: OriginFor<T>,
Éloïs's avatar
Éloïs committed
        ) -> DispatchResultWithPostInfo {
Éloïs's avatar
Éloïs committed
            // Verification phase //
            T::IdtyValidationOrigin::ensure_origin(origin)?;
Éloïs's avatar
Éloïs committed

Éloïs's avatar
Éloïs committed
            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()),
                IdtyStatus::ConfirmedByOwner => {
Éloïs's avatar
Éloïs committed
                    if !T::EnsureIdtyCallAllowed::can_validate_identity(idty_index) {
                        return Err(Error::<T>::NotAllowedToValidateIdty.into());
Éloïs's avatar
Éloïs committed
                    }
                }
Éloïs's avatar
Éloïs committed
                IdtyStatus::Validated => return Err(Error::<T>::IdtyAlreadyValidated.into()),
Éloïs's avatar
Éloïs committed
            }
Éloïs's avatar
Éloïs committed

            // 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())
Éloïs's avatar
Éloïs committed
        }
        #[pallet::weight(0)]
        pub fn remove_identity(
            origin: OriginFor<T>,
            idty_index: T::IdtyIndex,
Éloïs's avatar
Éloïs committed
            idty_name: Option<IdtyName>,
        ) -> DispatchResultWithPostInfo {
            ensure_root(origin)?;

            Self::do_remove_identity(idty_index);
Éloïs's avatar
Éloïs committed
            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);
Éloïs's avatar
Éloïs committed
    }

    // ERRORS //

    #[pallet::error]
    pub enum Error<T> {
        /// Creator not allowed to create identities
        CreatorNotAllowedToCreateIdty,
Éloïs's avatar
Éloïs committed
        /// Identity already confirmed
        IdtyAlreadyConfirmed,
        /// Identity already created
        IdtyAlreadyCreated,
Éloïs's avatar
Éloïs committed
        /// Identity already validated
        IdtyAlreadyValidated,
        /// You are not allowed to create a new identity now
        IdtyCreationNotAllowed,
Éloïs's avatar
Éloïs committed
        /// Identity index not found
        IdtyIndexNotFound,
Éloïs's avatar
Éloïs committed
        /// Identity name already exist
        IdtyNameAlreadyExist,
        /// Idty name invalid
        IdtyNameInvalid,
Éloïs's avatar
Éloïs committed
        /// Identity not confirmed by owner
        IdtyNotConfirmedByOwner,
        /// Identity not found
        IdtyNotFound,
        /// Idty not member
        IdtyNotMember,
Éloïs's avatar
Éloïs committed
        /// Identity not validated
        IdtyNotValidated,
        /// Identity not yet renewable
        IdtyNotYetRenewable,
        /// Not allowed to confirm identity
        NotAllowedToConfirmIdty,
        /// Not allowed to validate identity
        NotAllowedToValidateIdty,
        /// Not same identity name
        NotSameIdtyName,
Éloïs's avatar
Éloïs committed
        /// Right already added
        RightAlreadyAdded,
        /// Right not exist
        RightNotExist,
        /// Not respect IdtyCreationPeriod
        NotRespectIdtyCreationPeriod,
Éloïs's avatar
Éloïs committed

    impl<T: Config> Pallet<T> {
        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
        }
Éloïs's avatar
Éloïs committed
        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()
Éloïs's avatar
Éloïs committed
        }
        fn prune_identities(block_number: T::BlockNumber) -> Weight {
            let mut total_weight: Weight = 0;

            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)
Éloïs's avatar
Éloïs committed

impl<T: Config> sp_runtime::traits::Convert<T::AccountId, Option<T::IdtyIndex>> for Pallet<T> {
    fn convert(account_id: T::AccountId) -> Option<T::IdtyIndex> {
        Self::identity_index_of(account_id)
    }
}