diff --git a/Cargo.lock b/Cargo.lock index 257ac22dc..5a1d69bea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1271,6 +1271,7 @@ dependencies = [ "pallet-indices", "pallet-insecure-randomness-collective-flip", "pallet-multisig", + "pallet-namespace", "pallet-network-membership", "pallet-network-score", "pallet-node-authorization", @@ -1405,6 +1406,7 @@ dependencies = [ "pallet-insecure-randomness-collective-flip", "pallet-membership", "pallet-multisig", + "pallet-namespace", "pallet-network-membership", "pallet-network-score", "pallet-node-authorization", @@ -1931,6 +1933,7 @@ dependencies = [ "pallet-insecure-randomness-collective-flip", "pallet-membership", "pallet-multisig", + "pallet-namespace", "pallet-network-membership", "pallet-network-score", "pallet-node-authorization", @@ -6483,6 +6486,26 @@ dependencies = [ "sp-runtime", ] +[[package]] +name = "pallet-namespace" +version = "0.9.4" +dependencies = [ + "bitflags 1.3.2", + "cord-identifier", + "cord-primitives", + "cord-utilities", + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-keystore", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-network-membership" version = "0.9.4" diff --git a/Cargo.toml b/Cargo.toml index 6b47e30d0..ad48ebe96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ members = [ "node/testing", "pallets/asset", "pallets/chain-space", + "pallets/namespace", "pallets/did", "pallets/did-name", "pallets/identity", @@ -155,6 +156,7 @@ pallet-transaction-weight-runtime-api = { path = "runtimes/common/api/weight", d pallet-registries = { path = "pallets/registries", default-features = false } pallet-entries = { path = "pallets/entries", default-features = false } pallet-schema-accounts = { path = "pallets/schema-accounts", default-features = false } +pallet-namespace = { path = 'pallets/namespace', default-features = false } # substrate dependencies diff --git a/pallets/namespace/Cargo.toml b/pallets/namespace/Cargo.toml new file mode 100644 index 000000000..006fec14c --- /dev/null +++ b/pallets/namespace/Cargo.toml @@ -0,0 +1,73 @@ +[package] +name = 'pallet-namespace' +description = 'Manage Name-Spaces.' +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ['x86_64-unknown-linux-gnu'] + +[dev-dependencies] +sp-core = { features = ["std"], workspace = true } +sp-keystore = { features = ["std"], workspace = true } +cord-utilities = { features = ["mock"], workspace = true } + +[dependencies] +codec = { features = ["derive"], workspace = true } +scale-info = { features = ["derive"], workspace = true } +bitflags = { workspace = true } + +# Internal dependencies +cord-primitives = { workspace = true } +cord-utilities = { workspace = true } +identifier = { workspace = true } + +# Substrate dependencies +frame-benchmarking = { optional = true, workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +sp-core = { optional = true, workspace = true } +sp-io = { optional = true, workspace = true } +sp-keystore = { optional = true, workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } + + +[features] +default = ['std'] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "cord-utilities/runtime-benchmarks", +] +std = [ + "codec/std", + "identifier/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "cord-primitives/std", + "cord-utilities/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-keystore/std", + "sp-runtime/std", + "sp-std/std", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "identifier/try-runtime", + "cord-utilities/try-runtime", + "sp-runtime/try-runtime" +] diff --git a/pallets/namespace/src/lib.rs b/pallets/namespace/src/lib.rs new file mode 100644 index 000000000..599a84201 --- /dev/null +++ b/pallets/namespace/src/lib.rs @@ -0,0 +1,1135 @@ +// This file is part of CORD – https://cord.network + +// Copyright (C) Dhiway Networks Pvt. Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// CORD is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// CORD 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 General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with CORD. If not, see . +// + +//! # NameSpace Pallet +//! +//! The NameSpace pallet provides a framework for creating and managing +//! isolated namespaces within the CORD blockchain that can be governed and +//! moderated with a fine-grained permission system. It allows for the creation, +//! approval, and archival of namespaces, as well as the management of delegates +//! within these namespaces. +//! +//! ## Overview +//! +//! The NameSpace pallet allows for the creation of distinct namespaces on the CORD +//! blockchain, each with its own set of rules and governance. These namespaces can +//! be used to manage different ecosystems or communities within the larger +//! blockchain environment. NameSpaces are created with a unique identifier and can +//! be managed by appointed delegates. +//! +//! ## Interface +//! +//! The pallet provides dispatchable functions for namespace management: +//! +//! - `create`: Initializes a new namespace with a unique identifier. +//! - `approve`: Approves a namespace for use, setting its capacity and governance status. (TODO: +//! Remove approve) +//! - `archive`: Marks a namespace as archived, effectively freezing its state. +//! - `restore`: Unarchives a namespace, returning it to active status. +//! - `add_delegate`: Adds a delegate to a namespace, granting them specific permissions. +//! - `add_admin_delegate`: Adds an admin delegate to a namespace, granting them administrative +//! permissions. +//! - `add_audit_delegate`: Adds an audit delegate to a namespace, granting them audit permissions. +//! - `remove_delegate`: Removes a delegate from a namespace, revoking their permissions. +//! +//! ## Permissions +//! +//! The pallet uses a permissions system to manage the actions that delegates +//! can perform within a namespace. Permissions are granular and can be assigned to +//! different roles, such as an admin or a regular delegate. +//! +//! ## Data Privacy +//! +//! The NameSpace pallet is designed with data privacy as a core consideration. +//! It does not directly store any personal or sensitive information on-chain. +//! Instead, it manages references to off-chain data, ensuring that the +//! blockchain layer remains compliant with data privacy regulations. Users and +//! developers are responsible for ensuring that the off-chain data handling +//! processes adhere to the applicable laws and standards. +//! +//! ## Usage +//! +//! The NameSpace pallet can be used by other pallets to create +//! compartmentalized and governed sections of the blockchain. This is +//! particularly useful for applications that require distinct governance models +//! or privacy settings within a shared ecosystem. +//! +//! ## Governance Integration +//! +//! The NameSpace pallet is integrated with on-chain governance pallets to +//! allow namespace administrators and delegates to propose changes, vote on +//! initiatives, or manage the namespace in accordance with the collective decisions +//! of its members. +//! +//! ## Examples +//! +//! - Creating a new namespace for a community-driven project. +//! - Approving a namespace for official use after meeting certain criteria. (TODO: Remove this +//! line) +//! - Archiving a namespace that is no longer active or has violated terms of use. +//! - Adding delegates to a namespace to ensure ongoing compliance with governance standards. + +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(clippy::unused_unit)] + +#[cfg(any(feature = "mock", test))] +pub mod mock; + +#[cfg(test)] +mod tests; + +use frame_support::{ensure, storage::types::StorageMap, BoundedVec}; +pub mod types; +pub use crate::{pallet::*, types::*}; +use codec::Encode; +use frame_system::WeightInfo; +use identifier::{ + types::{CallTypeOf, IdentifierTypeOf, Timepoint}, + EventEntryOf, +}; +use sp_runtime::traits::{Hash, UniqueSaturatedInto}; + +/// Type of a namespace creator. +pub type NameSpaceCreatorOf = ::AccountId; + +/// Namespace Identifier +pub type NameSpaceIdOf = Ss58Identifier; + +/// Registry Identifier +pub type RegistryIdOf = Ss58Identifier; + +/// Authorization Identifier +pub type AuthorizationIdOf = Ss58Identifier; + +/// Namespace input code +pub type NameSpaceCodeOf = ::Hash; + +/// Registry Identifier mapped to a Namespace. +pub type MaxRegistriesOf = ::MaxNameSpaceDelegates; + +/// Type of on-chain Namespace details +pub type NameSpaceDetailsOf = NameSpaceDetails< + NameSpaceCodeOf, + NameSpaceCreatorOf, + StatusOf, + BoundedVec>, +>; + +/// Type of Namespace Authorization details +pub type NameSpaceAuthorizationOf = + NameSpaceAuthorization, Permissions>; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + pub use cord_primitives::{IsPermissioned, StatusOf}; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + pub use identifier::{IdentifierCreator, IdentifierTimeline, IdentifierType, Ss58Identifier}; + + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + + #[pallet::config] + pub trait Config: frame_system::Config + identifier::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + type ChainSpaceOrigin: EnsureOrigin; + type NetworkPermission: IsPermissioned; + + #[pallet::constant] + type MaxNameSpaceDelegates: Get; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + } + + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(_); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + /// Namespace information stored on chain. + /// It maps from an identifier to its details. + #[pallet::storage] + pub type NameSpaces = + StorageMap<_, Blake2_128Concat, NameSpaceIdOf, NameSpaceDetailsOf, OptionQuery>; + + /// Namespace authorizations stored on-chain. + /// It maps from an identifier to delegates. + #[pallet::storage] + pub type Authorizations = StorageMap< + _, + Blake2_128Concat, + AuthorizationIdOf, + NameSpaceAuthorizationOf, + OptionQuery, + >; + + /// Namespace delegates stored on chain. + /// It maps from an identifier to a bounded vec of delegates and + /// permissions. + #[pallet::storage] + pub(super) type Delegates = StorageMap< + _, + Blake2_128Concat, + NameSpaceIdOf, + BoundedVec, T::MaxNameSpaceDelegates>, + ValueQuery, + >; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A new namespace authorization has been added. + /// \[namespace identifier, authorization, delegate\] + Authorization { + namespace: NameSpaceIdOf, + authorization: AuthorizationIdOf, + delegate: NameSpaceCreatorOf, + }, + /// A namespace authorization has been removed. + /// \[namespace identifier, authorization, ] + Deauthorization { namespace: NameSpaceIdOf, authorization: AuthorizationIdOf }, + /// A new namespace has been created. + /// \[namespace identifier, creator, authorization\] + Create { + namespace: NameSpaceIdOf, + creator: NameSpaceCreatorOf, + authorization: AuthorizationIdOf, + }, + /// A new namespace has been approved. + /// \[namespace identifier \] + Approve { namespace: NameSpaceIdOf }, + /// A namespace has been archived. + /// \[namespace identifier, authority\] + Archive { namespace: NameSpaceIdOf, authority: NameSpaceCreatorOf }, + /// A namespace has been restored. + /// \[namespace identifier, authority\] + Restore { namespace: NameSpaceIdOf, authority: NameSpaceCreatorOf }, + /// A namespace has been restored. + /// \[namespace identifier, \] + Revoke { namespace: NameSpaceIdOf }, + /// A namespace approval has been revoked. + /// \[namespace identifier, \] + ApprovalRevoke { namespace: NameSpaceIdOf }, + /// A namespace approval has been restored. + /// \[namespace identifier, \] + ApprovalRestore { namespace: NameSpaceIdOf }, + } + + #[pallet::error] + #[derive(PartialEq)] + pub enum Error { + /// NameSpace identifier is not unique + NameSpaceAlreadyAnchored, + /// NameSpace identifier not found + NameSpaceNotFound, + /// Only when the author is not the controller or delegate. + UnauthorizedOperation, + /// Invalid Identifier + InvalidIdentifier, + /// Invalid Identifier Length + InvalidIdentifierLength, + /// Invalid Identifier Prefix + InvalidIdentifierPrefix, + /// Archived NameSpace + ArchivedNameSpace, + /// NameSpace not Archived + NameSpaceNotArchived, + /// NameSpace delegation limit exceeded + NameSpaceDelegatesLimitExceeded, + /// Empty transaction. + EmptyTransaction, + /// Authority already added + DelegateAlreadyAdded, + /// Authorization Id not found + AuthorizationNotFound, + /// Delegate not found. + DelegateNotFound, + /// NameSpace already approved + NameSpaceAlreadyApproved, + /// NameSpace not approved. + NameSpaceNotApproved, + } + + #[pallet::call] + impl Pallet { + /// Adds a delegate with the ability to assert new entries to a namespace. + /// + /// The `ASSERT` permission allows the delegate to sign and add new + /// entries within the namespace. This function is called to grant a + /// delegate this specific permission. It checks that the caller has the + /// necessary authorization (admin rights) to add a delegate to the + /// namespace. If the caller is authorized, the delegate is added with the + /// `ASSERT` permission using the `space_delegate_addition` + /// internal function. + /// + /// # Parameters + /// - `origin`: The origin of the call, which must be signed by an admin of the namespace. + /// - `namespace_id`: The identifier of the namespace to which the delegate is being added. + /// - `delegate`: The identifier of the delegate being added to the namespace. + /// - `authorization`: The authorization ID used to validate the addition. + /// + /// # Returns + /// Returns `Ok(())` if the delegate was successfully added with + /// `ASSERT` permission, or an `Err` with an appropriate error if the + /// operation fails. + /// + /// # Errors + /// - `UnauthorizedOperation`: If the caller is not an admin of the namespace. + /// - Propagates errors from `space_delegate_addition` if it fails. + #[pallet::call_index(0)] + #[pallet::weight({0})] + pub fn add_delegate( + origin: OriginFor, + namespace_id: NameSpaceIdOf, + delegate: NameSpaceCreatorOf, + authorization: AuthorizationIdOf, + ) -> DispatchResult { + let creator = ensure_signed(origin)?; + let auth_space_id = + Self::ensure_authorization_delegator_origin(&authorization, &creator)?; + ensure!(auth_space_id == namespace_id, Error::::UnauthorizedOperation); + + let permissions = Permissions::ASSERT; + Self::space_delegate_addition(auth_space_id, delegate, creator, permissions)?; + + Ok(()) + } + + /// Adds an administrative delegate to a namespace. + /// + /// The `ADMIN` permission grants the delegate extensive control over + /// the namespace, including the ability to manage other delegates and + /// change namespace configurations. This function is called to + /// grant a delegate these administrative privileges. It verifies that + /// the caller has the necessary authorization (admin rights) to add an + /// admin delegate to the namespace. If the caller is authorized, + /// the delegate is added with the `ADMIN` permission using the + /// `space_delegate_addition` internal function. + /// + /// # Parameters + /// - `origin`: The origin of the call, which must be signed by an existing admin of the + /// namespace. + /// - `namespace_id`: The identifier of the namespace to which the admin delegate is being + /// added. + /// - `delegate`: The identifier of the delegate being granted admin permissions. + /// - `authorization`: The authorization ID used to validate the addition. + /// + /// # Returns + /// Returns `Ok(())` if the admin delegate was successfully added, or an + /// `Err` with an appropriate error if the operation fails. + /// + /// # Errors + /// - `UnauthorizedOperation`: If the caller is not an admin of the namespace. + /// - Propagates errors from `space_delegate_addition` if it fails. + #[pallet::call_index(1)] + #[pallet::weight({0})] + pub fn add_admin_delegate( + origin: OriginFor, + namespace_id: NameSpaceIdOf, + delegate: NameSpaceCreatorOf, + authorization: AuthorizationIdOf, + ) -> DispatchResult { + let creator = ensure_signed(origin)?; + let auth_space_id = Self::ensure_authorization_admin_origin(&authorization, &creator)?; + + ensure!(auth_space_id == namespace_id, Error::::UnauthorizedOperation); + + let permissions = Permissions::ADMIN; + Self::space_delegate_addition(auth_space_id, delegate, creator, permissions)?; + + Ok(()) + } + + /// Adds an audit delegate to a namespace. + /// + /// The `AUDIT` permission grants the delegate the ability to perform + /// oversight and compliance checks within the namespace. This function is + /// used to assign a delegate these audit privileges. It ensures that + /// the caller has the necessary authorization (admin rights) to add an + /// audit delegate to the namespace. If the caller is authorized, the + /// delegate is added with the `AUDIT` permission using the + /// `space_delegate_addition` internal function. + /// + /// # Parameters + /// - `origin`: The origin of the call, which must be signed by an existing admin of the + /// namespace. + /// - `namespace_id`: The identifier of the namespace to which the audit delegate is being + /// added. + /// - `delegate`: The identifier of the delegate being granted audit permissions. + /// - `authorization`: The authorization ID used to validate the addition. + /// + /// # Returns + /// Returns `Ok(())` if the audit delegate was successfully added, or an + /// `Err` with an appropriate error if the operation fails. + #[pallet::call_index(2)] + #[pallet::weight({0})] + pub fn add_delegator( + origin: OriginFor, + namespace_id: NameSpaceIdOf, + delegate: NameSpaceCreatorOf, + authorization: AuthorizationIdOf, + ) -> DispatchResult { + let creator = ensure_signed(origin)?; + let auth_space_id = Self::ensure_authorization_admin_origin(&authorization, &creator)?; + + ensure!(auth_space_id == namespace_id, Error::::UnauthorizedOperation); + + let permissions = Permissions::DELEGATE; + Self::space_delegate_addition(auth_space_id, delegate, creator, permissions)?; + + Ok(()) + } + + /// Removes a delegate from a specified namespace. + /// + /// This function will remove an existing delegate from a namespace, given + /// the namespace ID and the delegate's authorization ID. It checks that the + /// namespace exists, is not archived, is approved, and that the provided + /// authorization corresponds to a delegate of the namespace. It also + /// verifies that the caller has the authority to remove a delegate. + /// + /// # Parameters + /// - `origin`: The origin of the transaction, which must be signed by the creator or an + /// admin. + /// - `namespace_id`: The identifier of the namespace from which the delegate is being + /// removed. + /// - `remove_authorization`: The authorization ID of the delegate to be removed. + /// - `authorization`: An identifier for the authorization being used to validate the + /// removal. + /// + /// # Returns + /// - `DispatchResult`: This function returns `Ok(())` if the delegate is successfully + /// removed, or an error (`DispatchError`) if any of the checks fail. + /// + /// # Errors + /// - `AuthorizationNotFound`: If the provided `remove_authorization` does not exist. + /// - `UnauthorizedOperation`: If the origin is not authorized to remove a delegate from the + /// namespace. + /// - `NameSpaceNotFound`: If the specified namespace ID does not correspond to an existing + /// namespace. + /// - `ArchivedNameSpace`: If the namespace is archived and no longer active. + /// - `NameSpaceNotApproved`: If the namespace has not been approved for use. + /// - `DelegateNotFound`: If the delegate specified by `remove_authorization` is not found + /// in the namespace. + /// + /// # Events + /// + /// - `Deauthorization`: Emitted when a delegate is successfully removed from a namespace. + /// The event includes the namespace ID and the authorization ID of the removed delegate. + #[pallet::call_index(3)] + #[pallet::weight({0})] + pub fn remove_delegate( + origin: OriginFor, + namespace_id: NameSpaceIdOf, + remove_authorization: AuthorizationIdOf, + authorization: AuthorizationIdOf, + ) -> DispatchResult { + let creator = ensure_signed(origin)?; + let auth_space_id = + Self::ensure_authorization_admin_remove_origin(&authorization, &creator)?; + + ensure!(auth_space_id == namespace_id, Error::::UnauthorizedOperation); + + // Ensure the authorization exists and retrieve its details. + let authorization_details = Authorizations::::get(&remove_authorization) + .ok_or(Error::::AuthorizationNotFound)?; + + let mut delegates = Delegates::::get(&namespace_id); + if let Some(index) = delegates.iter().position(|d| d == &authorization_details.delegate) + { + delegates.remove(index); + Delegates::::insert(&namespace_id, delegates); + + Authorizations::::remove(&remove_authorization); + + Self::update_activity( + &namespace_id, + IdentifierTypeOf::Auth, + CallTypeOf::Deauthorization, + )?; + + Self::deposit_event(Event::Deauthorization { + namespace: namespace_id, + authorization: remove_authorization, + }); + + Ok(()) + } else { + Err(Error::::DelegateNotFound.into()) + } + } + + /// Creates a new namespace with a unique identifier based on the provided + /// namespace code and the creator's identity. + /// + /// This function generates a unique identifier for the namespace by hashing + /// the encoded namespace code and creator's identifier. It ensures that the + /// generated namespace identifier is not already in use. An authorization + /// ID is also created for the new namespace, which is used to manage + /// delegations. The creator is automatically added as a delegate with + /// all permissions. + /// + /// # Parameters + /// - `origin`: The origin of the transaction, which must be signed by the creator. + /// - `space_code`: A unique code representing the namespace to be created. + /// + /// # Returns + /// - `DispatchResult`: Returns `Ok(())` if the namespace is successfully created, or an + /// error (`DispatchError`) if: + /// - The generated namespace identifier is already in use. + /// - The generated authorization ID is of invalid length. + /// - The namespace delegates limit is exceeded. + /// + /// # Errors + /// - `InvalidIdentifierLength`: If the generated identifiers for the namespace or + /// authorization are of invalid length. + /// - `NameSpaceAlreadyAnchored`: If the namespace identifier is already in use. + /// - `NameSpaceDelegatesLimitExceeded`: If the namespace exceeds the limit of allowed + /// delegates. + /// + /// # Events + /// - `Create`: Emitted when a new namespace is successfully created. It includes the + /// namespace identifier, the creator's identifier, and the authorization ID. + #[pallet::call_index(4)] + #[pallet::weight({0})] + pub fn create(origin: OriginFor, namespace_code: NameSpaceCodeOf) -> DispatchResult { + let creator = ensure_signed(origin)?; + + // Id Digest = concat (H(, + // )) + let id_digest = ::Hashing::hash( + &[&namespace_code.encode()[..], &creator.encode()[..]].concat()[..], + ); + + let identifier = + Ss58Identifier::create_identifier(&id_digest.encode()[..], IdentifierType::Space) + .map_err(|_| Error::::InvalidIdentifierLength)?; + + ensure!( + !>::contains_key(&identifier), + Error::::NameSpaceAlreadyAnchored + ); + + // Construct the authorization_id from the provided parameters. + // Id Digest = concat (H(, + // )) + let auth_id_digest = T::Hashing::hash( + &[&identifier.encode()[..], &creator.encode()[..], &creator.encode()[..]].concat() + [..], + ); + + let authorization_id = Ss58Identifier::create_identifier( + &auth_id_digest.encode(), + IdentifierType::Authorization, + ) + .map_err(|_| Error::::InvalidIdentifierLength)?; + + let mut delegates: BoundedVec, T::MaxNameSpaceDelegates> = + BoundedVec::default(); + delegates + .try_push(creator.clone()) + .map_err(|_| Error::::NameSpaceDelegatesLimitExceeded)?; + + Delegates::::insert(&identifier, delegates); + + Authorizations::::insert( + &authorization_id, + NameSpaceAuthorizationOf:: { + namespace_id: identifier.clone(), + delegate: creator.clone(), + permissions: Permissions::all(), + delegator: creator.clone(), + }, + ); + + let approved = !T::NetworkPermission::is_permissioned(); + + >::insert( + &identifier, + NameSpaceDetailsOf:: { + code: namespace_code, + creator: creator.clone(), + approved, + archive: false, + registry_id: Some(BoundedVec::default()), + }, + ); + + Self::update_activity(&identifier, IdentifierTypeOf::ChainSpace, CallTypeOf::Genesis) + .map_err(Error::::from)?; + + Self::deposit_event(Event::Create { + namespace: identifier, + creator, + authorization: authorization_id, + }); + + Ok(()) + } + + /// Approves a namespace and sets its capacity. + /// + /// This function can only be called by a council or root origin, + /// reflecting its privileged nature. It is used to approve a namespace that + /// has been previously created, setting its transaction capacity and + /// marking it as approved. It ensures that the namespace exists, is not + /// archived, and has not already been approved. + /// + /// # Parameters + /// - `origin`: The origin of the transaction, which must be a council or root origin. + /// - `namespace_id`: The identifier of the namespace to be approved. + /// - `txn_capacity`: The transaction capacity to be set for the namespace. + /// + /// # Returns + /// - `DispatchResult`: Returns `Ok(())` if the namespace is successfully approved, or an + /// error (`DispatchError`) if: + /// - The origin is not a council or root origin. + /// - The namespace does not exist. + /// - The namespace is archived. + /// - The namespace is already approved. + /// + /// # Errors + /// - `BadOrigin`: If the call does not come from a council or root origin. + /// - `NameSpaceNotFound`: If the specified namespace ID does not correspond to an existing + /// namespace. + /// - `ArchivedNameSpace`: If the namespace is archived and no longer active. + /// - `NameSpaceAlreadyApproved`: If the namespace has already been approved. + /// + /// # Events + /// - `Approve`: Emitted when a namespace is successfully approved. It includes the + /// namespace identifier. + /// + /// # Security Considerations + /// Due to the privileged nature of this function, callers must ensure + /// that they have the appropriate authority. Misuse can lead to + /// unauthorized approval of namespaces, which may have security + /// implications. + #[pallet::call_index(5)] + #[pallet::weight({0})] + pub fn approve(origin: OriginFor, namespace_id: NameSpaceIdOf) -> DispatchResult { + // TODO: Below should be root + let _creator = ensure_signed(origin)?; + + let namespace_details = + NameSpaces::::get(&namespace_id).ok_or(Error::::NameSpaceNotFound)?; + ensure!(!namespace_details.archive, Error::::ArchivedNameSpace); + ensure!(!namespace_details.approved, Error::::NameSpaceAlreadyApproved); + + >::insert( + &namespace_id, + NameSpaceDetailsOf:: { approved: true, ..namespace_details }, + ); + + // TODO: Add Namespace in Identifier types and update all activities. + Self::update_activity( + &namespace_id, + IdentifierTypeOf::ChainSpace, + CallTypeOf::Approved, + ) + .map_err(Error::::from)?; + + Self::deposit_event(Event::Approve { namespace: namespace_id }); + + Ok(()) + } + + /// Archives a namespace, rendering it inactive. + /// + /// This function marks a namespace as archived based on the provided namespace + /// ID. It checks that the namespace exists, is not already archived, and is + /// approved. Additionally, it verifies that the caller has the + /// authority to archive the namespace, as indicated by the provided + /// authorization ID. + /// + /// # Parameters + /// - `origin`: The origin of the transaction, which must be signed by the creator or an + /// admin with the appropriate authority. + /// - `namespace_id`: The identifier of the namespace to be archived. + /// - `authorization`: An identifier for the authorization being used to validate the + /// archival. + /// + /// # Returns + /// - `DispatchResult`: Returns `Ok(())` if the namespace is successfully archived, or an + /// error (`DispatchError`) if: + /// - The namespace does not exist. + /// - `ArchivedNameSpace`: If the namespace is already archived. + /// - `NameSpaceNotApproved`: If the namespace has not been approved for use. + /// - `UnauthorizedOperation`: If the caller does not have the authority to archive the + /// namespace. + /// + /// # Errors + /// - `NameSpaceNotFound`: If the specified namespace ID does not correspond to an existing + /// namespace. + /// - `ArchivedNameSpace`: If the namespace is already archived. + /// - `NameSpaceNotApproved`: If the namespace has not been approved for use. + /// - `UnauthorizedOperation`: If the caller is not authorized to archive the namespace. + /// + /// # Events + /// - `Archive`: Emitted when a namespace is successfully archived. It includes the + /// namespace ID and the authority who performed the archival. + #[pallet::call_index(6)] + #[pallet::weight({0})] + pub fn archive( + origin: OriginFor, + namespace_id: NameSpaceIdOf, + authorization: AuthorizationIdOf, + ) -> DispatchResult { + let creator = ensure_signed(origin)?; + let auth_space_id = Self::ensure_authorization_admin_origin(&authorization, &creator)?; + + ensure!(auth_space_id == namespace_id, Error::::UnauthorizedOperation); + + let namespace_details = + NameSpaces::::get(&namespace_id).ok_or(Error::::NameSpaceNotFound)?; + ensure!(!namespace_details.archive, Error::::ArchivedNameSpace); + ensure!(namespace_details.approved, Error::::NameSpaceNotApproved); + + >::insert( + &namespace_id, + NameSpaceDetailsOf:: { archive: true, ..namespace_details }, + ); + + Self::update_activity(&namespace_id, IdentifierTypeOf::ChainSpace, CallTypeOf::Archive) + .map_err(Error::::from)?; + + Self::deposit_event(Event::Archive { namespace: namespace_id, authority: creator }); + + Ok(()) + } + + /// Restores an archived namespace, making it active again. + /// + /// This function unarchives a namespace based on the provided namespace ID. It + /// checks that the namespace exists, is currently archived, and is + /// approved. It also verifies that the caller has the authority to + /// restore the namespace, as indicated by the provided authorization ID. + /// + /// # Parameters + /// - `origin`: The origin of the transaction, which must be signed by the creator or an + /// admin with the appropriate authority. + /// - `namespace_id`: The identifier of the namespace to be restored. + /// - `authorization`: An identifier for the authorization being used to validate the + /// restoration. + /// + /// # Returns + /// - `DispatchResult`: Returns `Ok(())` if the namespace is successfully restored, or an + /// error (`DispatchError`) if: + /// - The namespace does not exist. + /// - The namespace is not archived. + /// - The namespace is not approved. + /// - The caller does not have the authority to restore the namespace. + /// + /// # Errors + /// - `NameSpaceNotFound`: If the specified namespace ID does not correspond to an existing + /// namespace. + /// - `NameSpaceNotArchived`: If the namespace is not currently archived. + /// - `NameSpaceNotApproved`: If the namespace has not been approved for use. + /// - `UnauthorizedOperation`: If the caller is not authorized to restore the namespace. + /// + /// # Events + /// - `Restore`: Emitted when a namespace is successfully restored. It includes the + /// namespace ID and the authority who performed the restoration. + #[pallet::call_index(7)] + #[pallet::weight({0})] + pub fn restore( + origin: OriginFor, + namespace_id: NameSpaceIdOf, + authorization: AuthorizationIdOf, + ) -> DispatchResult { + let creator = ensure_signed(origin)?; + let auth_space_id = + Self::ensure_authorization_restore_origin(&authorization, &creator)?; + + ensure!(auth_space_id == namespace_id, Error::::UnauthorizedOperation); + + let namespace_details = + NameSpaces::::get(&namespace_id).ok_or(Error::::NameSpaceNotFound)?; + ensure!(namespace_details.archive, Error::::NameSpaceNotArchived); + ensure!(namespace_details.approved, Error::::NameSpaceNotApproved); + + >::insert( + &namespace_id, + NameSpaceDetailsOf:: { archive: false, ..namespace_details }, + ); + + Self::update_activity(&namespace_id, IdentifierTypeOf::ChainSpace, CallTypeOf::Restore) + .map_err(Error::::from)?; + + Self::deposit_event(Event::Restore { namespace: namespace_id, authority: creator }); + + Ok(()) + } + + /// Revokes approval for a specified namespace. + /// + /// This function can be executed by an authorized origin, as determined + /// by `ChainSpaceOrigin`. It is designed to change the status of a + /// given namespace, referred to by `namespace_id`, to unapproved. + /// The revocation is only allowed if the namespace is currently approved, + /// and not archived. + /// + /// # Parameters + /// - `origin`: The transaction's origin, which must satisfy the `ChainSpaceOrigin` policy. + /// - `namespace_id`: The identifier of the namespace whose approval status is being + /// revoked. + /// + /// # Errors + /// - Returns `NameSpaceNotFound` if no namespace corresponds to the provided `space_id`. + /// - Returns `ArchivedNameSpace` if the namespace is archived, in which case its status + /// cannot be altered. + /// - Returns `NameSpaceNotApproved` if the namespace is already unapproved. + /// + /// # Events + /// - Emits `Revoke` when the namespace's approved status is successfully revoked. + #[pallet::call_index(10)] + #[pallet::weight({0})] + pub fn approval_revoke( + origin: OriginFor, + namespace_id: NameSpaceIdOf, + ) -> DispatchResult { + let _creator = ensure_signed(origin)?; + + let namespace_details = + NameSpaces::::get(&namespace_id).ok_or(Error::::NameSpaceNotFound)?; + ensure!(!namespace_details.archive, Error::::ArchivedNameSpace); + ensure!(namespace_details.approved, Error::::NameSpaceNotApproved); + + >::insert( + &namespace_id, + NameSpaceDetailsOf:: { approved: false, ..namespace_details }, + ); + + Self::update_activity( + &namespace_id, + IdentifierTypeOf::ChainSpace, + CallTypeOf::CouncilRevoke, + ) + .map_err(Error::::from)?; + + Self::deposit_event(Event::ApprovalRevoke { namespace: namespace_id }); + + Ok(()) + } + + #[pallet::call_index(11)] + #[pallet::weight({0})] + pub fn approval_restore( + origin: OriginFor, + namespace_id: NameSpaceIdOf, + ) -> DispatchResult { + let _creator = ensure_signed(origin)?; + + let namespace_details = + NameSpaces::::get(&namespace_id).ok_or(Error::::NameSpaceNotFound)?; + ensure!(!namespace_details.archive, Error::::ArchivedNameSpace); + ensure!(!namespace_details.approved, Error::::NameSpaceAlreadyApproved); + + >::insert( + &namespace_id, + NameSpaceDetailsOf:: { approved: true, ..namespace_details }, + ); + + Self::update_activity( + &namespace_id, + IdentifierTypeOf::ChainSpace, + CallTypeOf::CouncilRestore, + ) + .map_err(Error::::from)?; + + Self::deposit_event(Event::ApprovalRestore { namespace: namespace_id }); + + Ok(()) + } + } +} + +impl Pallet { + /// Adds a delegate to a namespace with specified permissions. + /// + /// This function will add a new delegate to a namespace, given the namespace's ID, + /// the delegate's information, and the required permissions. It constructs + /// an authorization ID based on the namespace ID, delegate, and creator, + /// ensuring that the delegate is not already added. It also checks that the + /// namespace is not archived, is approved, and has not exceeded its capacity. + fn space_delegate_addition( + namespace_id: NameSpaceIdOf, + delegate: NameSpaceCreatorOf, + creator: NameSpaceCreatorOf, + permissions: Permissions, + ) -> Result<(), Error> { + // Id Digest = concat (H(, + // , )) + let id_digest = T::Hashing::hash( + &[&namespace_id.encode()[..], &delegate.encode()[..], &creator.encode()[..]].concat()[..], + ); + + let delegate_authorization_id = + Ss58Identifier::create_identifier(&id_digest.encode(), IdentifierType::Authorization) + .map_err(|_| Error::::InvalidIdentifierLength)?; + + ensure!( + !Authorizations::::contains_key(&delegate_authorization_id), + Error::::DelegateAlreadyAdded + ); + + let mut delegates = Delegates::::get(&namespace_id); + delegates + .try_push(delegate.clone()) + .map_err(|_| Error::::NameSpaceDelegatesLimitExceeded)?; + Delegates::::insert(&namespace_id, delegates); + + Authorizations::::insert( + &delegate_authorization_id, + NameSpaceAuthorizationOf:: { + namespace_id: namespace_id.clone(), + delegate: delegate.clone(), + permissions, + delegator: creator, + }, + ); + + Self::update_activity(&namespace_id, IdentifierTypeOf::Auth, CallTypeOf::Authorization) + .map_err(Error::::from)?; + + Self::deposit_event(Event::Authorization { + namespace: namespace_id, + authorization: delegate_authorization_id, + delegate, + }); + + Ok(()) + } + + /// Checks if a given entity is a delegate for the specified namespace. + /// + /// This function retrieves the list of delegates for a namespace and determines + /// whether the specified delegate is among them. It is a read-only + /// operation and does not modify the state. + pub fn is_a_delegate(tx_id: &NameSpaceIdOf, delegate: NameSpaceCreatorOf) -> bool { + >::get(tx_id).iter().any(|d| d == &delegate) + } + + /// Verifies if a given delegate has a specific authorization. + /// + /// This function checks if the provided delegate is associated with the + /// given authorization ID and has the 'ASSERT' permission. + pub fn ensure_authorization_origin( + authorization_id: &AuthorizationIdOf, + delegate: &NameSpaceCreatorOf, + ) -> Result> { + let d = + >::get(authorization_id).ok_or(Error::::AuthorizationNotFound)?; + + ensure!(d.delegate == *delegate, Error::::UnauthorizedOperation); + + // TODO: Update all similar function names. + Self::validate_space_for_transaction(&d.namespace_id)?; + + ensure!(d.permissions.contains(Permissions::ASSERT), Error::::UnauthorizedOperation); + + Ok(d.namespace_id) + } + + pub fn ensure_authorization_restore_origin( + authorization_id: &AuthorizationIdOf, + delegate: &NameSpaceCreatorOf, + ) -> Result> { + let d = + >::get(authorization_id).ok_or(Error::::AuthorizationNotFound)?; + + ensure!(d.delegate == *delegate, Error::::UnauthorizedOperation); + + Self::validate_space_for_restore_transaction(&d.namespace_id)?; + + ensure!(d.permissions.contains(Permissions::ADMIN), Error::::UnauthorizedOperation); + + Ok(d.namespace_id) + } + + /// Checks if a given delegate is an admin for the namespace associated with the + /// authorization ID. + /// + /// This function verifies whether the specified delegate is the admin of + /// the namespace by checking the 'ADMIN' permission within the authorization + /// tied to the provided authorization ID. + pub fn ensure_authorization_admin_origin( + authorization_id: &AuthorizationIdOf, + delegate: &NameSpaceCreatorOf, + ) -> Result> { + let d = + >::get(authorization_id).ok_or(Error::::AuthorizationNotFound)?; + + ensure!(d.delegate == *delegate, Error::::UnauthorizedOperation); + + Self::validate_space_for_transaction(&d.namespace_id)?; + + ensure!(d.permissions.contains(Permissions::ADMIN), Error::::UnauthorizedOperation); + + Ok(d.namespace_id) + } + + /// Ensures that the given delegate is authorized to perform an audit + /// operation on a namespace. + /// + /// This function checks whether the provided `authorization_id` corresponds + /// to an existing authorization and whether the delegate associated with + /// that authorization is allowed to perform audit operations. It also + /// increments usage and validates the namespace for transactions. + pub fn ensure_authorization_delegator_origin( + authorization_id: &AuthorizationIdOf, + delegate: &NameSpaceCreatorOf, + ) -> Result> { + let d = + >::get(authorization_id).ok_or(Error::::AuthorizationNotFound)?; + + ensure!(d.delegate == *delegate, Error::::UnauthorizedOperation); + + Self::validate_space_for_transaction(&d.namespace_id)?; + + ensure!( + d.permissions.contains(Permissions::DELEGATE | Permissions::ADMIN), + Error::::UnauthorizedOperation + ); + + Ok(d.namespace_id) + } + + /// Checks if a given delegate is an admin for the namespace associated with the + /// authorization ID. + /// + /// This function verifies whether the specified delegate is the admin of + /// the namespace by checking the 'ADMIN' permission within the authorization + /// tied to the provided authorization ID. + pub fn ensure_authorization_admin_remove_origin( + authorization_id: &AuthorizationIdOf, + delegate: &NameSpaceCreatorOf, + ) -> Result> { + let d = + >::get(authorization_id).ok_or(Error::::AuthorizationNotFound)?; + + ensure!(d.delegate == *delegate, Error::::UnauthorizedOperation); + + Self::validate_space_for_transaction(&d.namespace_id)?; + + ensure!(d.permissions.contains(Permissions::ADMIN), Error::::UnauthorizedOperation); + + Ok(d.namespace_id) + } + + /// Validates that a namespace is eligible for a new transaction. + /// + /// This function ensures that a namespace is not archived, is approved, and has + /// not exceeded its capacity limit before allowing a new transaction to be + /// recorded. It is a critical check that enforces the integrity and + /// constraints of namespace usage on the chain. + pub fn validate_space_for_transaction(namespace_id: &NameSpaceIdOf) -> Result<(), Error> { + let namespace_details = + NameSpaces::::get(namespace_id).ok_or(Error::::NameSpaceNotFound)?; + + // Ensure the namespace is not archived. + ensure!(!namespace_details.archive, Error::::ArchivedNameSpace); + + // Ensure the namespace is approved for transactions. + ensure!(namespace_details.approved, Error::::NameSpaceNotApproved); + + Ok(()) + } + + /// Validates a namespace for restore transactions. + /// + /// This function checks that the specified namespace is approved and has not + /// exceeded its capacity limit. It is designed to be called before + /// performing any administrative actions on a namespace to ensure + /// that the namespace is in a proper state for such transactions. + pub fn validate_space_for_restore_transaction( + namespace_id: &NameSpaceIdOf, + ) -> Result<(), Error> { + let namespace_details = + NameSpaces::::get(namespace_id).ok_or(Error::::NameSpaceNotFound)?; + + // Ensure the namespace is archived. + ensure!(namespace_details.archive, Error::::NameSpaceNotArchived); + + // Ensure the namespace is approved for transactions. + ensure!(namespace_details.approved, Error::::NameSpaceNotApproved); + + Ok(()) + } + + /// Validates that a namespace can accommodate a batch of new entries without + /// exceeding its capacity. + /// + /// This function ensures that a namespace is not archived, is approved, and has + /// enough remaining capacity to accommodate a specified number of new + /// entries. It is a critical check that enforces the integrity and + /// constraints of namespace usage on the chain, especially when dealing + /// with batch operations. + pub fn validate_space_for_transaction_entries( + namespace_id: &NameSpaceIdOf, + _entries: u16, + ) -> Result<(), Error> { + let namespace_details = + NameSpaces::::get(namespace_id).ok_or(Error::::NameSpaceNotFound)?; + + // Ensure the namespace is not archived. + ensure!(!namespace_details.archive, Error::::ArchivedNameSpace); + + // Ensure the namespace is approved for adding new entries. + ensure!(namespace_details.approved, Error::::NameSpaceNotApproved); + + Ok(()) + } + + /// Updates the global timeline with a new activity event for a namespace. + /// + /// This function is an internal mechanism that logs each significant change + /// to a namespace on the global timeline. It is automatically called by the + /// system whenever an update to a namespace occurs, capturing the type of + /// activity and the precise time at which it happened. This automated + /// tracking is crucial for maintaining a consistent and auditable record of + /// all namespace-related activities. + pub fn update_activity( + tx_id: &NameSpaceIdOf, + tx_type: IdentifierTypeOf, + tx_action: CallTypeOf, + ) -> Result<(), Error> { + let tx_moment = Self::timepoint(); + + let tx_entry = EventEntryOf { action: tx_action, location: tx_moment }; + let _ = IdentifierTimeline::update_timeline::(tx_id, tx_type, tx_entry); + Ok(()) + } + + /// Retrieves the current timepoint. + /// + /// This function returns a `Timepoint` structure containing the current + /// block number and extrinsic index. It is typically used in conjunction + /// with `update_activity` to record when an event occurred. + pub fn timepoint() -> Timepoint { + Timepoint { + height: frame_system::Pallet::::block_number().unique_saturated_into(), + index: frame_system::Pallet::::extrinsic_index().unwrap_or_default(), + } + } +} diff --git a/pallets/namespace/src/mock.rs b/pallets/namespace/src/mock.rs new file mode 100644 index 000000000..42fce696e --- /dev/null +++ b/pallets/namespace/src/mock.rs @@ -0,0 +1,119 @@ +// This file is part of CORD – https://cord.network + +// Copyright (C) Dhiway Networks Pvt. Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// CORD is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// CORD 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 General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with CORD. If not, see . + +use crate as pallet_namespace; +use cord_utilities::mock::{mock_origin, SubjectId}; +use frame_support::{derive_impl, parameter_types}; +use pallet_namespace::IsPermissioned; + +use frame_system::EnsureRoot; +use sp_runtime::{ + traits::{IdentifyAccount, IdentityLookup, Verify}, + BuildStorage, MultiSignature, +}; + +type Signature = MultiSignature; +type AccountPublic = ::Signer; +pub type AccountId = ::AccountId; +pub(crate) type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test { + System: frame_system, + NameSpace: pallet_namespace, + Identifier: identifier, + MockOrigin: mock_origin, + } +); + +parameter_types! { + pub const SS58Prefix: u8 = 29; +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Block = Block; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type SS58Prefix = SS58Prefix; +} + +impl mock_origin::Config for Test { + type RuntimeOrigin = RuntimeOrigin; + type AccountId = AccountId; + type SubjectId = SubjectId; +} + +pub struct NetworkPermission; +impl IsPermissioned for NetworkPermission { + fn is_permissioned() -> bool { + true + } +} + +parameter_types! { + #[derive(Debug, Clone)] + pub const MaxNameSpaceDelegates: u32 = 5u32; +} + +impl pallet_namespace::Config for Test { + type RuntimeEvent = RuntimeEvent; + type ChainSpaceOrigin = EnsureRoot; + type NetworkPermission = NetworkPermission; + type MaxNameSpaceDelegates = MaxNameSpaceDelegates; + type WeightInfo = (); +} + +parameter_types! { + pub const MaxEventsHistory: u32 = 6u32; +} + +impl identifier::Config for Test { + type MaxEventsHistory = MaxEventsHistory; +} + +parameter_types! { + storage NameSpaceEvents: u32 = 0; +} + +/// All events of this pallet. +pub fn space_events_since_last_call() -> Vec> { + let events = System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let RuntimeEvent::NameSpace(inner) = e { Some(inner) } else { None }) + .collect::>(); + let already_seen = NameSpaceEvents::get(); + NameSpaceEvents::set(&(events.len() as u32)); + events.into_iter().skip(already_seen as usize).collect() +} + +#[allow(dead_code)] +pub(crate) fn new_test_ext() -> sp_io::TestExternalities { + let t: sp_runtime::Storage = + frame_system::GenesisConfig::::default().build_storage().unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + #[cfg(feature = "runtime-benchmarks")] + let keystore = sp_keystore::testing::MemoryKeystore::new(); + #[cfg(feature = "runtime-benchmarks")] + ext.register_extension(sp_keystore::KeystoreExt(sp_std::sync::Arc::new(keystore))); + ext.execute_with(|| System::set_block_number(1)); + ext +} diff --git a/pallets/namespace/src/tests.rs b/pallets/namespace/src/tests.rs new file mode 100644 index 000000000..42ac48e97 --- /dev/null +++ b/pallets/namespace/src/tests.rs @@ -0,0 +1,57 @@ +use super::*; +use crate::mock::*; +use codec::Encode; +use frame_support::assert_ok; +use sp_runtime::traits::Hash; +use sp_std::prelude::*; + +pub fn generate_namespace_id(digest: &NameSpaceCodeOf) -> NameSpaceIdOf { + Ss58Identifier::create_identifier(&(digest).encode()[..], IdentifierType::Space).unwrap() +} + +pub fn generate_authorization_id(digest: &NameSpaceCodeOf) -> AuthorizationIdOf { + Ss58Identifier::create_identifier(&(digest).encode()[..], IdentifierType::Authorization) + .unwrap() +} + +pub(crate) const ACCOUNT_00: AccountId = AccountId::new([1u8; 32]); +pub(crate) const ACCOUNT_01: AccountId = AccountId::new([2u8; 32]); + +//TEST FUNCTION FOR ADD DELEGATE +#[test] +fn add_delegate_should_succeed() { + let creator = ACCOUNT_00; + let delegate = ACCOUNT_01; + let space = [2u8; 256].to_vec(); + let space_digest = ::Hashing::hash(&space.encode()[..]); + + let id_digest = ::Hashing::hash( + &[&space_digest.encode()[..], &creator.encode()[..]].concat()[..], + ); + + let space_id: NameSpaceIdOf = generate_namespace_id::(&id_digest); + + let auth_id_digest = ::Hashing::hash( + &[&space_id.encode()[..], &creator.encode()[..], &creator.encode()[..]].concat()[..], + ); + + let authorization_id: AuthorizationIdOf = generate_authorization_id::(&auth_id_digest); + new_test_ext().execute_with(|| { + assert_ok!(NameSpace::create( + frame_system::RawOrigin::Signed(creator.clone()).into(), + space_digest, + )); + + assert_ok!(NameSpace::approve( + frame_system::RawOrigin::Signed(creator.clone()).into(), + space_id.clone(), + )); + + assert_ok!(NameSpace::add_delegate( + frame_system::RawOrigin::Signed(creator.clone()).into(), + space_id, + delegate.clone(), + authorization_id, + )); + }); +} diff --git a/pallets/namespace/src/types.rs b/pallets/namespace/src/types.rs new file mode 100644 index 000000000..fe8381b3a --- /dev/null +++ b/pallets/namespace/src/types.rs @@ -0,0 +1,109 @@ +// This file is part of CORD – https://cord.network + +// Copyright (C) Dhiway Networks Pvt. Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// CORD is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// CORD 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 General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with CORD. If not, see . +// +//! # Space Management Module Types +//! +//! This module defines types used for managing spaces within the blockchain, +//! including permissions, space details, and space authorizations. + +use bitflags::bitflags; +use codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::RuntimeDebug; + +bitflags! { + #[derive(Encode, Decode, TypeInfo, MaxEncodedLen)] + pub struct Permissions: u32 { + const ASSERT = 0b0000_0001; + const DELEGATE = 0b0000_0010; + const ADMIN = 0b0000_0100; + + } +} + +impl Permissions { + // Encodes the permission bitflags into a 4-byte array. + /// + /// This method is useful for serialization and storage purposes, as it + /// converts the internal representation of the permissions into a format + /// that can be easily stored and transmitted. + /// + /// Returns a `[u8; 4]` representing the encoded permissions. + pub fn as_u8(self) -> [u8; 4] { + let x: u32 = self.bits; + let b1: u8 = ((x >> 24) & 0xff) as u8; + let b2: u8 = ((x >> 16) & 0xff) as u8; + let b3: u8 = ((x >> 8) & 0xff) as u8; + let b4: u8 = (x & 0xff) as u8; + [b4, b3, b2, b1] + } +} + +impl Default for Permissions { + /// Provides a default for the Permissions struct. + /// + /// By default, the `ASSERT` permission is granted + fn default() -> Self { + Permissions::ASSERT + } +} + +/// Details of an on-chain namespace. +/// +/// This structure holds metadata about a namespace, including its identifier, +/// creator, capacity, and current usage. It also tracks the approval and +/// archival status of the namespace. +/// +/// ## Fields +/// +/// - `code`: The unique code or identifier for the namespace. +/// - `creator`: The account or entity that created the namespace. +/// - `txn_capacity`: The maximum allowed transactions within the namespace. A value of zero denotes +/// unlimited capacity. +/// - `txn_count`: The current usage of the namespace's capacity. +/// - `approved`: Indicates whether the namespace has been approved by the appropriate governance +/// body. +/// - `archive`: Indicates whether the namespace is currently archived. +#[derive(Encode, Decode, Clone, MaxEncodedLen, RuntimeDebug, PartialEq, Eq, TypeInfo)] +pub struct NameSpaceDetails { + pub code: NameSpaceCodeOf, + pub creator: NameSpaceCreatorOf, + pub approved: StatusOf, + pub archive: StatusOf, + pub registry_id: Option, +} + +/// Authorization details for a namespace delegate. +/// +/// This structure defines the permissions granted to a delegate within a namespace, +/// as well as the delegator who granted these permissions. It is used to manage +/// and verify the actions that delegates are allowed to perform within a namespace. +/// +/// ## Fields +/// +/// - `namespace_id`: The identifier of the namespace to which the authorization applies. +/// - `delegate`: The entity that has been granted permissions within the namespace. +/// - `permissions`: The specific permissions granted to the delegate. +/// - `delegator`: The entity that granted the permissions to the delegates +#[derive(Encode, Decode, Clone, MaxEncodedLen, RuntimeDebug, PartialEq, Eq, TypeInfo)] +pub struct NameSpaceAuthorization { + pub namespace_id: NameSpaceIdOf, + pub delegate: NameSpaceCreatorOf, + pub permissions: Permissions, + pub delegator: NameSpaceCreatorOf, +} diff --git a/runtimes/braid/Cargo.toml b/runtimes/braid/Cargo.toml index 80d429ce7..c66a467c1 100644 --- a/runtimes/braid/Cargo.toml +++ b/runtimes/braid/Cargo.toml @@ -54,6 +54,7 @@ pallet-session-benchmarking = { workspace = true } pallet-registries = { workspace = true } pallet-entries = { workspace = true } pallet-schema-accounts = { workspace = true } +pallet-namespace = { workspace = true } # Internal runtime API (with default disabled) pallet-did-runtime-api = { workspace = true } @@ -171,6 +172,7 @@ std = [ "pallet-registries/std", "pallet-entries/std", "pallet-schema-accounts/std", + "pallet-namespace/std", "pallet-network-score/std", "pallet-network-membership/std", "pallet-runtime-upgrade/std", @@ -222,6 +224,7 @@ runtime-benchmarks = [ "pallet-schema/runtime-benchmarks", "pallet-statement/runtime-benchmarks", "pallet-chain-space/runtime-benchmarks", + "pallet-namespace/runtime-benchmarks", "pallet-network-membership/runtime-benchmarks", "hex-literal", "pallet-sudo/runtime-benchmarks", @@ -262,6 +265,7 @@ try-runtime = [ "pallet-babe/try-runtime", "pallet-schema/try-runtime", "pallet-chain-space/try-runtime", + "pallet-namespace/try-runtime", "pallet-statement/try-runtime", "pallet-did/try-runtime", "pallet-did-name/try-runtime", diff --git a/runtimes/braid/src/lib.rs b/runtimes/braid/src/lib.rs index 2300e90d0..194627f2f 100644 --- a/runtimes/braid/src/lib.rs +++ b/runtimes/braid/src/lib.rs @@ -762,6 +762,18 @@ parameter_types! { pub const MaxRegistryDelegates: u32 = 10_000; } +parameter_types! { + pub const MaxNameSpaceDelegates: u32 = 10_000; +} + +impl pallet_namespace::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ChainSpaceOrigin = EnsureRoot; + type NetworkPermission = NetworkParameters; + type MaxNameSpaceDelegates = MaxNameSpaceDelegates; + type WeightInfo = (); +} + impl pallet_registries::Config for Runtime { type RuntimeEvent = RuntimeEvent; type MaxRegistryDelegates = MaxRegistryDelegates; @@ -998,6 +1010,9 @@ mod runtime { #[runtime::pallet_index(63)] pub type SchemaAccounts = pallet_schema_accounts; + #[runtime::pallet_index(64)] + pub type NameSpace = pallet_namespace; + #[runtime::pallet_index(255)] pub type Sudo = pallet_sudo; } diff --git a/runtimes/loom/Cargo.toml b/runtimes/loom/Cargo.toml index d304af2c7..594e98414 100644 --- a/runtimes/loom/Cargo.toml +++ b/runtimes/loom/Cargo.toml @@ -44,6 +44,7 @@ pallet-did-name = { workspace = true } pallet-schema = { workspace = true } pallet-config = { workspace = true } pallet-chain-space = { workspace = true } +pallet-namespace = { workspace = true } pallet-statement = { workspace = true } pallet-network-membership = { workspace = true } pallet-runtime-upgrade = { workspace = true } @@ -176,6 +177,7 @@ std = [ "pallet-did-name/std", "pallet-schema/std", "pallet-chain-space/std", + "pallet-namespace/std", "pallet-statement/std", "pallet-network-score/std", "pallet-network-membership/std", @@ -234,6 +236,7 @@ runtime-benchmarks = [ "pallet-schema/runtime-benchmarks", "pallet-statement/runtime-benchmarks", "pallet-chain-space/runtime-benchmarks", + "pallet-namespace/runtime-benchmarks", "pallet-network-membership/runtime-benchmarks", "hex-literal", "pallet-sudo/runtime-benchmarks", @@ -279,6 +282,7 @@ try-runtime = [ "pallet-babe/try-runtime", "pallet-schema/try-runtime", "pallet-chain-space/try-runtime", + "pallet-namespace/try-runtime", "pallet-statement/try-runtime", "pallet-did/try-runtime", "pallet-did-name/try-runtime", diff --git a/runtimes/loom/src/lib.rs b/runtimes/loom/src/lib.rs index 776066f05..05b530a46 100644 --- a/runtimes/loom/src/lib.rs +++ b/runtimes/loom/src/lib.rs @@ -894,6 +894,18 @@ impl pallet_chain_space::Config for Runtime { type WeightInfo = weights::pallet_chain_space::WeightInfo; } +parameter_types! { + pub const MaxNameSpaceDelegates: u32 = 10_000; +} + +impl pallet_namespace::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ChainSpaceOrigin = EnsureRoot; + type NetworkPermission = NetworkParameters; + type MaxNameSpaceDelegates = MaxNameSpaceDelegates; + type WeightInfo = (); +} + parameter_types! { pub const MaxRegistryBlobSize: u32 = 4 * 1024; pub const MaxEncodedInputLength: u32 = 30; @@ -1157,6 +1169,9 @@ mod runtime { #[runtime::pallet_index(63)] pub type SchemaAccounts = pallet_schema_accounts; + #[runtime::pallet_index(64)] + pub type NameSpace = pallet_namespace; + #[runtime::pallet_index(254)] pub type RootTesting = pallet_root_testing; diff --git a/runtimes/weave/Cargo.toml b/runtimes/weave/Cargo.toml index 121db777e..edc309f8f 100644 --- a/runtimes/weave/Cargo.toml +++ b/runtimes/weave/Cargo.toml @@ -44,6 +44,7 @@ pallet-did-name = { workspace = true } pallet-schema = { workspace = true } pallet-config = { workspace = true } pallet-chain-space = { workspace = true } +pallet-namespace = { workspace = true } pallet-statement = { workspace = true } pallet-network-membership = { workspace = true } pallet-runtime-upgrade = { workspace = true } @@ -175,6 +176,7 @@ std = [ "pallet-did-name/std", "pallet-schema/std", "pallet-chain-space/std", + "pallet-namespace/std", "pallet-statement/std", "pallet-network-score/std", "pallet-network-membership/std", @@ -233,6 +235,7 @@ runtime-benchmarks = [ "pallet-schema/runtime-benchmarks", "pallet-statement/runtime-benchmarks", "pallet-chain-space/runtime-benchmarks", + "pallet-namespace/runtime-benchmarks", "pallet-network-membership/runtime-benchmarks", "hex-literal", "pallet-sudo/runtime-benchmarks", @@ -278,6 +281,7 @@ try-runtime = [ "pallet-babe/try-runtime", "pallet-schema/try-runtime", "pallet-chain-space/try-runtime", + "pallet-namespace/try-runtime", "pallet-statement/try-runtime", "pallet-did/try-runtime", "pallet-did-name/try-runtime", diff --git a/runtimes/weave/src/lib.rs b/runtimes/weave/src/lib.rs index 6b0dcd09e..60a5b58b1 100644 --- a/runtimes/weave/src/lib.rs +++ b/runtimes/weave/src/lib.rs @@ -894,6 +894,18 @@ impl pallet_chain_space::Config for Runtime { type WeightInfo = weights::pallet_chain_space::WeightInfo; } +parameter_types! { + pub const MaxNameSpaceDelegates: u32 = 10_000; +} + +impl pallet_namespace::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ChainSpaceOrigin = EnsureRoot; + type NetworkPermission = NetworkParameters; + type MaxNameSpaceDelegates = MaxNameSpaceDelegates; + type WeightInfo = (); +} + parameter_types! { pub const MaxRegistryBlobSize: u32 = 4 * 1024; pub const MaxEncodedInputLength: u32 = 30; @@ -1154,6 +1166,9 @@ mod runtime { #[runtime::pallet_index(63)] pub type SchemaAccounts = pallet_schema_accounts; + #[runtime::pallet_index(64)] + pub type NameSpace = pallet_namespace; + #[runtime::pallet_index(255)] pub type Sudo = pallet_sudo; }