Skip to content
Snippets Groups Projects
lib.rs 17.66 KiB
// 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)]
#![allow(clippy::type_complexity)]

pub mod traits;
mod types;

#[cfg(test)]
mod mock;

#[cfg(test)]
mod tests;

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

pub use pallet::*;
pub use types::*;

use crate::traits::*;
use codec::Codec;
use frame_support::dispatch::Weight;
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;
    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)]
    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>;
        ///  Identity custom data
        type IdtyData: Parameter + Member + MaybeSerializeDeserialize + Debug + Default;
        ///  Identity custom data provider
        type IdtyDataProvider: ProvideIdtyData<Self>;
        /// 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>;
        /// On identity confirmed by it's owner
        type OnIdtyChange: OnIdtyChange<Self>;
        #[pallet::constant]
        /// Maximum period with disabled status, after this period, the identity is permanently
        /// deleted
        type MaxDisabledPeriod: Get<Self::BlockNumber>;
    }

    // GENESIS STUFF //
    #[pallet::genesis_config]
    pub struct GenesisConfig<T: Config> {
        pub identities: Vec<IdtyValue<T::AccountId, T::BlockNumber, T::IdtyData>>,
    }

    #[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();
            for idty_value in &self.identities {
                assert!(
                    !names.contains(&idty_value.name),
                    "Idty name {:?} is present twice",
                    &idty_value.name
                );
                assert!(idty_value.removable_on == T::BlockNumber::zero());
                names.insert(idty_value.name.clone());
            }

            // We need to sort identities to ensure determinisctic result
            let mut identities = self.identities.clone();
            identities.sort_by(|idty_val_1, idty_val_2| idty_val_1.name.cmp(&idty_val_2.name));

            <IdentitiesCount<T>>::put(self.identities.len() as u64);
            for idty_value in &identities {
                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);
            }
        }
    }

    // STORAGE //

    /// Identities
    #[pallet::storage]
    #[pallet::getter(fn identity)]
    pub type Identities<T: Config> = StorageMap<
        _,
        Blake2_128Concat,
        T::IdtyIndex,
        IdtyValue<T::AccountId, T::BlockNumber, T::IdtyData>,
        OptionQuery,
    >;

    /// IdentitiesByDid
    #[pallet::storage]
    #[pallet::getter(fn identity_by_did)]
    pub type IdentitiesByDid<T: Config> =
        StorageMap<_, Blake2_128Concat, IdtyName, T::IdtyIndex, ValueQuery>;

    #[pallet::storage]
    pub(super) type NextIdtyIndex<T: Config> = StorageValue<_, T::IdtyIndex, ValueQuery>;

    #[pallet::storage]
    #[pallet::getter(fn identities_count)]
    pub(super) type IdentitiesCount<T: Config> = StorageValue<_, u64, ValueQuery>;

    /// Identities by removed block
    #[pallet::storage]
    #[pallet::getter(fn removable_on)]
    pub type IdentitiesRemovableOn<T: Config> = StorageMap<
        _,
        Blake2_128Concat,
        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 {
            if n > T::BlockNumber::zero() {
                Self::prune_identities(n)
            } else {
                0
            }
        }
    }

    // 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, owner_key]
        IdtyCreated(IdtyName, T::AccountId),
        /// An identity has been confirmed by it's owner
        /// [idty]
        IdtyConfirmed(IdtyName),
        /// An identity has been validated
        /// [idty]
        IdtyValidated(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>,
            creator: T::IdtyIndex,
            idty_name: IdtyName,
            owner_key: T::AccountId,
        ) -> DispatchResultWithPostInfo {
            // Verification phase //
            let who = ensure_signed(origin)?;

            let creator_idty_val =
                Identities::<T>::try_get(&creator).map_err(|_| Error::<T>::CreatorNotExist)?;

            if who != creator_idty_val.owner_key {
                return Err(Error::<T>::RequireToBeOwner.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());
            }

            if !T::IdtyNameValidator::validate(&idty_name) {
                return Err(Error::<T>::IdtyNameInvalid.into());
            }
            if <IdentitiesByDid<T>>::contains_key(&idty_name) {
                return Err(Error::<T>::IdtyNameAlreadyExist.into());
            }

            // Apply phase //

            <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 idty_data =
                T::IdtyDataProvider::provide_identity_data(creator, &idty_name, &owner_key);

            let removable_on = block_number + T::ConfirmPeriod::get();

            let idty_index = Self::get_next_idty_index();
            <Identities<T>>::insert(
                idty_index,
                IdtyValue {
                    name: idty_name.clone(),
                    next_creatable_identity_on: T::BlockNumber::zero(),
                    owner_key: owner_key.clone(),
                    removable_on,
                    status: IdtyStatus::Created,
                    data: idty_data,
                },
            );
            <IdentitiesByDid<T>>::insert(idty_name.clone(), idty_index);
            IdentitiesRemovableOn::<T>::append(removable_on, (idty_index, IdtyStatus::Created));
            Self::inc_identities_counter();
            Self::deposit_event(Event::IdtyCreated(idty_name, owner_key));
            T::OnIdtyChange::on_idty_change(idty_index, IdtyEvent::Created { creator });
            Ok(().into())
        }
        #[pallet::weight(0)]
        pub fn confirm_identity(
            origin: OriginFor<T>,
            idty_name: IdtyName,
            idty_index: T::IdtyIndex,
        ) -> DispatchResultWithPostInfo {
            let who = ensure_signed(origin)?;

            if let Ok(mut idty_value) = <Identities<T>>::try_get(idty_index) {
                if who == idty_value.owner_key {
                    if idty_value.status != IdtyStatus::Created {
                        return Err(Error::<T>::IdtyAlreadyConfirmed.into());
                    }
                    if idty_value.name != idty_name {
                        return Err(Error::<T>::NotSameIdtyName.into());
                    }
                    if !T::EnsureIdtyCallAllowed::can_confirm_identity(idty_index) {
                        return Err(Error::<T>::NotAllowedToConfirmIdty.into());
                    }

                    idty_value.status = IdtyStatus::ConfirmedByOwner;

                    <Identities<T>>::insert(idty_index, idty_value);
                    Self::deposit_event(Event::IdtyConfirmed(idty_name));
                    T::OnIdtyChange::on_idty_change(idty_index, IdtyEvent::Confirmed);
                    Ok(().into())
                } else {
                    Err(Error::<T>::RequireToBeOwner.into())
                }
            } else {
                Err(Error::<T>::IdtyNotFound.into())
            }
        }
        #[pallet::weight(0)]
        pub fn validate_identity(
            origin: OriginFor<T>,
            idty_index: T::IdtyIndex,
        ) -> DispatchResultWithPostInfo {
            T::IdtyValidationOrigin::ensure_origin(origin)?;

            if let Ok(mut idty_value) = <Identities<T>>::try_get(idty_index) {
                match idty_value.status {
                    IdtyStatus::Created => Err(Error::<T>::IdtyNotConfirmedByOwner.into()),
                    IdtyStatus::ConfirmedByOwner | IdtyStatus::Disabled => {
                        if !T::EnsureIdtyCallAllowed::can_validate_identity(idty_index) {
                            return Err(Error::<T>::NotAllowedToValidateIdty.into());
                        }

                        idty_value.removable_on = T::BlockNumber::zero();
                        idty_value.status = IdtyStatus::Validated;
                        let name = idty_value.name.clone();

                        <Identities<T>>::insert(idty_index, idty_value);
                        Self::deposit_event(Event::IdtyValidated(name));
                        T::OnIdtyChange::on_idty_change(idty_index, IdtyEvent::Validated);
                        Ok(().into())
                    }
                    IdtyStatus::Validated => Err(Error::<T>::IdtyAlreadyValidated.into()),
                }
            } else {
                Err(Error::<T>::IdtyNotFound.into())
            }
        }
        #[pallet::weight(0)]
        pub fn disable_identity(
            origin: OriginFor<T>,
            idty_index: T::IdtyIndex,
        ) -> DispatchResultWithPostInfo {
            // Verify phase
            ensure_root(origin)?;

            if !Identities::<T>::contains_key(idty_index) {
                return Err(Error::<T>::IdtyNotFound.into());
            }

            // Apply phase
            let block_number = frame_system::pallet::Pallet::<T>::block_number();
            let removable_on = block_number + T::MaxDisabledPeriod::get();
            Identities::<T>::mutate_exists(idty_index, |idty_value_opt| {
                if let Some(idty_value) = idty_value_opt {
                    idty_value.status = IdtyStatus::Disabled;
                    idty_value.removable_on = removable_on;
                }
            });
            <IdentitiesRemovableOn<T>>::append(removable_on, (idty_index, IdtyStatus::Disabled));
            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);

            Ok(().into())
        }
    }

    // ERRORS //

    #[pallet::error]
    pub enum Error<T> {
        /// Creator not exist
        CreatorNotExist,
        /// Creator not allowed to create identities
        CreatorNotAllowedToCreateIdty,
        /// Identity already confirmed
        IdtyAlreadyConfirmed,
        /// 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 validated
        IdtyNotValidated,
        /// Identity not yet renewable
        IdtyNotYetRenewable,
        /// Not allowed to confirm identity
        NotAllowedToConfirmIdty,
        /// Not allowed to validate identity
        NotAllowedToValidateIdty,
        /// Not same identity name
        NotSameIdtyName,
        /// This operation requires to be the owner of the identity
        RequireToBeOwner,
        /// Right already added
        RightAlreadyAdded,
        /// Right not exist
        RightNotExist,
        /// Not respect IdtyCreationPeriod
        NotRespectIdtyCreationPeriod,
    }

    // PUBLIC FUNCTIONS //

    impl<T: Config> Pallet<T> {
        pub fn set_idty_data(idty_index: T::IdtyIndex, idty_data: T::IdtyData) {
            Identities::<T>::mutate_exists(idty_index, |idty_val_opt| {
                if let Some(ref mut idty_val) = idty_val_opt {
                    idty_val.data = idty_data;
                }
            });
        }
    }

    // INTERNAL FUNCTIONS //

    impl<T: Config> Pallet<T> {
        fn dec_identities_counter() {
            if let Ok(counter) = <IdentitiesCount<T>>::try_get() {
                <IdentitiesCount<T>>::put(counter.saturating_sub(1));
            } else {
                panic!("storage corrupted")
            }
        }
        pub(super) fn do_remove_identity(idty_index: T::IdtyIndex) -> Weight {
            if let Some(idty_val) = <Identities<T>>::take(idty_index) {
                <IdentitiesByDid<T>>::remove(idty_val.name);
            }
            Self::dec_identities_counter();
            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 inc_identities_counter() {
            if let Ok(counter) = <IdentitiesCount<T>>::try_get() {
                <IdentitiesCount<T>>::put(counter.saturating_add(1));
            } else {
                <IdentitiesCount<T>>::put(1);
            }
        }
        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)
                    }
                }
            }

            total_weight
        }
    }
}