diff --git a/Cargo.lock b/Cargo.lock index 8915054d6e..abcc85ce85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2535,10 +2535,13 @@ dependencies = [ "frame-system-rpc-runtime-api", "kilt-dip-support", "kilt-runtime-api-dip-provider", + "kilt-support", + "log", "pallet-aura", "pallet-authorship", "pallet-balances", "pallet-collator-selection", + "pallet-deposit-storage", "pallet-did-lookup", "pallet-dip-provider", "pallet-session", @@ -6442,6 +6445,21 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-deposit-storage" +version = "1.12.0-dev" +dependencies = [ + "frame-support", + "frame-system", + "kilt-support", + "log", + "pallet-dip-provider", + "parity-scale-codec", + "scale-info", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-did-lookup" version = "1.12.0-dev" @@ -6491,7 +6509,6 @@ dependencies = [ "frame-system", "parity-scale-codec", "scale-info", - "sp-io", "sp-std", ] diff --git a/Cargo.toml b/Cargo.toml index 00b1e3e7c8..afd7e6a757 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ ctype = {path = "pallets/ctype", default-features = false} delegation = {path = "pallets/delegation", default-features = false} did = {path = "pallets/did", default-features = false} pallet-configuration = {path = "pallets/pallet-configuration", default-features = false} +pallet-deposit-storage = {path = "pallets/pallet-deposit-storage", default-features = false} pallet-dip-consumer = {path = "pallets/pallet-dip-consumer", default-features = false} pallet-dip-provider = {path = "pallets/pallet-dip-provider", default-features = false} pallet-did-lookup = {path = "pallets/pallet-did-lookup", default-features = false} diff --git a/crates/kilt-dip-support/Cargo.toml b/crates/kilt-dip-support/Cargo.toml index 8941e0e46d..b9ba5ff712 100644 --- a/crates/kilt-dip-support/Cargo.toml +++ b/crates/kilt-dip-support/Cargo.toml @@ -28,9 +28,9 @@ scale-info = {workspace = true, features = ["derive"]} # Substrate dependencies frame-system.workspace = true frame-support.workspace = true -sp-runtime.workspace = true sp-core.workspace = true sp-io.workspace = true +sp-runtime.workspace = true sp-state-machine.workspace = true sp-std.workspace = true sp-trie.workspace = true @@ -51,6 +51,7 @@ sp-io = { workspace = true, features = ["std"] } default = ["std"] std = [ "hash-db/std", + "log/std", "did/std", "pallet-dip-consumer/std", "pallet-dip-provider/std", @@ -59,9 +60,9 @@ std = [ "scale-info/std", "frame-system/std", "frame-support/std", - "sp-runtime/std", "sp-core/std", "sp-io/std", + "sp-runtime/std", "sp-state-machine/std", "sp-std/std", "sp-trie/std", diff --git a/dip-template/runtimes/dip-provider/Cargo.toml b/dip-template/runtimes/dip-provider/Cargo.toml index dff25abda3..19da12ee26 100644 --- a/dip-template/runtimes/dip-provider/Cargo.toml +++ b/dip-template/runtimes/dip-provider/Cargo.toml @@ -14,13 +14,16 @@ version.workspace = true substrate-wasm-builder.workspace = true [dependencies] +log.workspace = true parity-scale-codec = {workspace = true, features = ["derive"]} scale-info = {workspace = true, features = ["derive"]} # DIP did.workspace = true +kilt-support.workspace = true kilt-dip-support.workspace = true kilt-runtime-api-dip-provider.workspace = true +pallet-deposit-storage.workspace = true pallet-did-lookup.workspace = true pallet-dip-provider.workspace = true pallet-web3-names.workspace = true @@ -66,11 +69,14 @@ default = [ "std", ] std = [ + "log/std", "parity-scale-codec/std", "scale-info/std", "did/std", + "kilt-support/std", "kilt-dip-support/std", "kilt-runtime-api-dip-provider/std", + "pallet-deposit-storage/std", "pallet-did-lookup/std", "pallet-dip-provider/std", "pallet-web3-names/std", diff --git a/dip-template/runtimes/dip-provider/src/dip.rs b/dip-template/runtimes/dip-provider/src/dip.rs index 720304f30b..12ac28a33b 100644 --- a/dip-template/runtimes/dip-provider/src/dip.rs +++ b/dip-template/runtimes/dip-provider/src/dip.rs @@ -17,6 +17,7 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org use did::{DidRawOrigin, EnsureDidOrigin, KeyIdOf}; +use frame_system::EnsureSigned; use pallet_did_lookup::linkable_account::LinkableAccountId; use pallet_dip_provider::{traits::IdentityProvider, IdentityCommitmentVersion}; use parity_scale_codec::{Decode, Encode}; @@ -25,24 +26,114 @@ use runtime_common::dip::{ merkle::{DidMerkleProofError, DidMerkleRootGenerator}, }; use scale_info::TypeInfo; +use sp_core::ConstU32; use sp_std::vec::Vec; -use crate::{AccountId, DidIdentifier, Hash, Runtime, RuntimeEvent}; +use crate::{ + deposit::{DepositHooks, DepositNamespaces}, + AccountId, Balances, DidIdentifier, Hash, Runtime, RuntimeEvent, RuntimeHoldReason, +}; + +pub mod runtime_api { + use super::*; + + #[derive(Encode, Decode, TypeInfo)] + pub struct DipProofRequest { + pub(crate) identifier: DidIdentifier, + pub(crate) version: IdentityCommitmentVersion, + pub(crate) keys: Vec>, + pub(crate) accounts: Vec, + pub(crate) should_include_web3_name: bool, + } + + #[derive(Encode, Decode, TypeInfo)] + pub enum DipProofError { + IdentityNotFound, + IdentityProviderError( as IdentityProvider>::Error), + MerkleProofError(DidMerkleProofError), + } +} + +pub mod deposit { + use super::*; + + use crate::{Balance, UNIT}; + + use frame_support::traits::Get; + use pallet_deposit_storage::{ + traits::DepositStorageHooks, DepositEntryOf, DepositKeyOf, FixedDepositCollectorViaDepositsPallet, + }; + use parity_scale_codec::MaxEncodedLen; + use sp_core::{ConstU128, RuntimeDebug}; + + #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq, Eq, RuntimeDebug)] + pub enum DepositNamespaces { + DipProvider, + } -#[derive(Encode, Decode, TypeInfo)] -pub struct RuntimeApiDipProofRequest { - pub(crate) identifier: DidIdentifier, - pub(crate) version: IdentityCommitmentVersion, - pub(crate) keys: Vec>, - pub(crate) accounts: Vec, - pub(crate) should_include_web3_name: bool, + pub struct DipProviderDepositNamespace; + + impl Get for DipProviderDepositNamespace { + fn get() -> DepositNamespaces { + DepositNamespaces::DipProvider + } + } + + pub const DEPOSIT_AMOUNT: Balance = 2 * UNIT; + + pub type DepositCollectorHooks = + FixedDepositCollectorViaDepositsPallet>; + + pub enum CommitmentDepositRemovalHookError { + DecodeKey, + Internal, + } + + impl From for u16 { + fn from(value: CommitmentDepositRemovalHookError) -> Self { + match value { + CommitmentDepositRemovalHookError::DecodeKey => 0, + CommitmentDepositRemovalHookError::Internal => u16::MAX, + } + } + } + + pub struct DepositHooks; + + impl DepositStorageHooks for DepositHooks { + type Error = CommitmentDepositRemovalHookError; + + fn on_deposit_reclaimed( + _namespace: &::Namespace, + key: &DepositKeyOf, + _deposit: DepositEntryOf, + ) -> Result<(), Self::Error> { + let (identifier, commitment_version) = <(DidIdentifier, IdentityCommitmentVersion)>::decode(&mut &key[..]) + .map_err(|_| CommitmentDepositRemovalHookError::DecodeKey)?; + pallet_dip_provider::Pallet::::delete_identity_commitment_storage_entry( + &identifier, + commitment_version, + ) + .map_err(|_| { + log::error!( + "Should not fail to remove commitment for identifier {:#?} and version {commitment_version}", + identifier + ); + CommitmentDepositRemovalHookError::Internal + })?; + Ok(()) + } + } } -#[derive(Encode, Decode, TypeInfo)] -pub enum RuntimeApiDipProofError { - IdentityNotFound, - IdentityProviderError( as IdentityProvider>::Error), - MerkleProofError(DidMerkleProofError), +impl pallet_deposit_storage::Config for Runtime { + type CheckOrigin = EnsureSigned; + type Currency = Balances; + type DepositHooks = DepositHooks; + type MaxKeyLength = ConstU32<256>; + type Namespace = DepositNamespaces; + type RuntimeEvent = RuntimeEvent; + type RuntimeHoldReason = RuntimeHoldReason; } impl pallet_dip_provider::Config for Runtime { @@ -54,5 +145,6 @@ impl pallet_dip_provider::Config for Runtime { type IdentityCommitmentGeneratorError = DidMerkleProofError; type IdentityProvider = LinkedDidInfoProviderOf; type IdentityProviderError = as IdentityProvider>::Error; + type ProviderHooks = deposit::DepositCollectorHooks; type RuntimeEvent = RuntimeEvent; } diff --git a/dip-template/runtimes/dip-provider/src/lib.rs b/dip-template/runtimes/dip-provider/src/lib.rs index cc8320d55e..94e58c36c4 100644 --- a/dip-template/runtimes/dip-provider/src/lib.rs +++ b/dip-template/runtimes/dip-provider/src/lib.rs @@ -51,7 +51,7 @@ use frame_system::{ }; use pallet_balances::AccountData; use pallet_collator_selection::IdentityCollator; -use pallet_dip_provider::traits::IdentityProvider; +use pallet_dip_provider::{traits::IdentityProvider, IdentityProviderOf}; use pallet_session::{FindAccountFromAuthorIndex, PeriodicSessions}; use pallet_transaction_payment::{CurrencyAdapter, FeeDetails, RuntimeDispatchInfo}; use runtime_common::dip::merkle::{CompleteMerkleProof, DidMerkleProofOf, DidMerkleRootGenerator}; @@ -137,7 +137,8 @@ construct_runtime!( Web3Names: pallet_web3_names = 32, // DIP - DipProvider: pallet_dip_provider = 40, + DepositStorage: pallet_deposit_storage = 40, + DipProvider: pallet_dip_provider = 41, } ); @@ -568,14 +569,12 @@ impl_runtime_apis! { } } - impl kilt_runtime_api_dip_provider::DipProvider>, RuntimeApiDipProofError> for Runtime { - fn generate_proof(request: RuntimeApiDipProofRequest) -> Result>, RuntimeApiDipProofError> { - let linked_did_info = match ::IdentityProvider::retrieve(&request.identifier) { - Ok(Some(linked_did_info)) => Ok(linked_did_info), - Ok(None) => Err(RuntimeApiDipProofError::IdentityNotFound), - Err(e) => Err(RuntimeApiDipProofError::IdentityProviderError(e)) - }?; - DidMerkleRootGenerator::::generate_proof(&linked_did_info, request.version, request.keys.iter(), request.should_include_web3_name, request.accounts.iter()).map_err(RuntimeApiDipProofError::MerkleProofError) + impl kilt_runtime_api_dip_provider::DipProvider>, runtime_api::DipProofError> for Runtime { + fn generate_proof(request: runtime_api::DipProofRequest) -> Result>, runtime_api::DipProofError> { + let maybe_identity_details = IdentityProviderOf::::retrieve(&request.identifier).map_err(runtime_api::DipProofError::IdentityProviderError)?; + let identity_details = maybe_identity_details.ok_or(runtime_api::DipProofError::IdentityNotFound)?; + + DidMerkleRootGenerator::::generate_proof(&identity_details, request.version, request.keys.iter(), request.should_include_web3_name, request.accounts.iter()).map_err(runtime_api::DipProofError::MerkleProofError) } } } diff --git a/out.txt b/out.txt deleted file mode 100644 index dc1d6e62d6..0000000000 --- a/out.txt +++ /dev/null @@ -1 +0,0 @@ -pallet-dip-consumer v1.12.0-dev (/home/antonio/Developer/kilt-node/pallets/pallet-dip-consumer) diff --git a/pallets/pallet-deposit-storage/Cargo.toml b/pallets/pallet-deposit-storage/Cargo.toml new file mode 100644 index 0000000000..914806a69e --- /dev/null +++ b/pallets/pallet-deposit-storage/Cargo.toml @@ -0,0 +1,41 @@ +[package] +authors.workspace = true +documentation.workspace = true +edition.workspace = true +homepage.workspace = true +license-file.workspace = true +readme.workspace = true +repository.workspace = true +version.workspace = true +name = "pallet-deposit-storage" +description = "Stores all deposits under a single pallet, with suport for namespacing different deposit contexts." + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +# Substrate dependencies +frame-support.workspace = true +frame-system.workspace = true +kilt-support.workspace = true +pallet-dip-provider.workspace = true +parity-scale-codec = {workspace = true, features = ["derive"]} +scale-info = {workspace = true, features = ["derive"]} +sp-runtime.workspace = true +sp-std.workspace = true + +log.workspace = true + +[features] +default = ["std"] +std = [ + "frame-support/std", + "frame-system/std", + "kilt-support/std", + "pallet-dip-provider/std", + "parity-scale-codec/std", + "scale-info/std", + "sp-runtime/std", + "sp-std/std", + "log/std", +] diff --git a/pallets/pallet-deposit-storage/src/deposit.rs b/pallets/pallet-deposit-storage/src/deposit.rs new file mode 100644 index 0000000000..a76acce619 --- /dev/null +++ b/pallets/pallet-deposit-storage/src/deposit.rs @@ -0,0 +1,169 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2023 BOTLabs GmbH + +// The KILT Blockchain 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. + +// The KILT Blockchain 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 this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use frame_support::{ + sp_runtime::DispatchError, + traits::{ + fungible::{hold::Mutate, Inspect}, + tokens::Precision, + }, +}; +use kilt_support::Deposit; +use pallet_dip_provider::{traits::ProviderHooks as DipProviderHooks, IdentityCommitmentVersion}; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::traits::Get; +use sp_std::marker::PhantomData; + +use crate::{BalanceOf, Config, Error, HoldReason, Pallet}; + +#[derive(Clone, Debug, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, TypeInfo, MaxEncodedLen)] +pub struct DepositEntry { + pub(crate) deposit: Deposit, + pub(crate) reason: Reason, +} + +pub struct FixedDepositCollectorViaDepositsPallet( + PhantomData<(DepositsNamespace, FixedDepositAmount)>, +); + +pub enum FixedDepositCollectorViaDepositsPalletError { + DepositAlreadyTaken, + DepositNotFound, + FailedToHold, + FailedToRelease, + Internal, +} + +impl From for u16 { + fn from(value: FixedDepositCollectorViaDepositsPalletError) -> Self { + match value { + FixedDepositCollectorViaDepositsPalletError::DepositAlreadyTaken => 0, + FixedDepositCollectorViaDepositsPalletError::DepositNotFound => 1, + FixedDepositCollectorViaDepositsPalletError::FailedToHold => 2, + FixedDepositCollectorViaDepositsPalletError::FailedToRelease => 3, + FixedDepositCollectorViaDepositsPalletError::Internal => u16::MAX, + } + } +} + +impl DipProviderHooks + for FixedDepositCollectorViaDepositsPallet +where + Runtime: pallet_dip_provider::Config + Config, + DepositsNamespace: Get, + FixedDepositAmount: Get>, +{ + type Error = u16; + + fn on_identity_committed( + identifier: &Runtime::Identifier, + submitter: &Runtime::AccountId, + _commitment: &Runtime::IdentityCommitment, + version: IdentityCommitmentVersion, + ) -> Result<(), Self::Error> { + let namespace = DepositsNamespace::get(); + let key = (identifier, version).encode().try_into().map_err(|_| { + log::error!( + "Failed to convert tuple ({:#?}, {version}) to BoundedVec with max length {}", + identifier, + Runtime::MaxKeyLength::get() + ); + FixedDepositCollectorViaDepositsPalletError::Internal + })?; + let deposit_entry = DepositEntry { + deposit: Deposit { + amount: FixedDepositAmount::get(), + owner: submitter.clone(), + }, + reason: HoldReason::Deposit.into(), + }; + Pallet::::add_deposit(namespace, key, deposit_entry).map_err(|e| match e { + pallet_error if pallet_error == DispatchError::from(Error::::DepositExisting) => { + FixedDepositCollectorViaDepositsPalletError::DepositAlreadyTaken + } + _ => { + log::error!( + "Error {:#?} should not be generated inside `on_identity_committed` hook.", + e + ); + FixedDepositCollectorViaDepositsPalletError::Internal + } + })?; + Ok(()) + } + + fn on_commitment_removed( + identifier: &Runtime::Identifier, + _submitter: &Runtime::AccountId, + _commitment: &Runtime::IdentityCommitment, + version: pallet_dip_provider::IdentityCommitmentVersion, + ) -> Result<(), Self::Error> { + let namespace = DepositsNamespace::get(); + let key = (identifier, version).encode().try_into().map_err(|_| { + log::error!( + "Failed to convert tuple ({:#?}, {version}) to BoundedVec with max length {}", + identifier, + Runtime::MaxKeyLength::get() + ); + FixedDepositCollectorViaDepositsPalletError::Internal + })?; + Pallet::::remove_deposit(&namespace, &key, None).map_err(|e| match e { + pallet_error if pallet_error == DispatchError::from(Error::::DepositNotFound) => { + FixedDepositCollectorViaDepositsPalletError::DepositNotFound + } + _ => { + log::error!( + "Error {:#?} should not be generated inside `on_commitment_removed` hook.", + e + ); + FixedDepositCollectorViaDepositsPalletError::Internal + } + })?; + Ok(()) + } +} + +// Taken from dip_support logic, not to make that pub +pub(crate) fn reserve_deposit>( + account: Account, + deposit_amount: Currency::Balance, + reason: &Currency::Reason, +) -> Result, DispatchError> { + Currency::hold(reason, &account, deposit_amount)?; + Ok(Deposit { + owner: account, + amount: deposit_amount, + }) +} + +// Taken from dip_support logic, not to make that pub +pub(crate) fn free_deposit>( + deposit: &Deposit, + reason: &Currency::Reason, +) -> Result<>::Balance, DispatchError> { + let result = Currency::release(reason, &deposit.owner, deposit.amount, Precision::BestEffort); + debug_assert!( + result == Ok(deposit.amount), + "Released deposit amount does not match with expected amount. Expected: {:?}, Released amount: {:?} Error: {:?}", + deposit.amount, + result.ok(), + result.err(), + ); + result +} diff --git a/pallets/pallet-deposit-storage/src/lib.rs b/pallets/pallet-deposit-storage/src/lib.rs new file mode 100644 index 0000000000..2674923442 --- /dev/null +++ b/pallets/pallet-deposit-storage/src/lib.rs @@ -0,0 +1,166 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2023 BOTLabs GmbH + +// The KILT Blockchain 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. + +// The KILT Blockchain 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 this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +#![cfg_attr(not(feature = "std"), no_std)] +#![recursion_limit = "256"] + +mod deposit; +pub mod traits; + +pub use deposit::FixedDepositCollectorViaDepositsPallet; +pub use pallet::*; + +#[frame_support::pallet(dev_mode)] +pub mod pallet { + use crate::{ + deposit::{free_deposit, reserve_deposit, DepositEntry}, + traits::DepositStorageHooks, + }; + + use super::*; + + use frame_support::{ + pallet_prelude::*, + traits::{ + fungible::{hold::Mutate, Inspect}, + EnsureOrigin, + }, + }; + use frame_system::pallet_prelude::*; + use parity_scale_codec::FullCodec; + use scale_info::TypeInfo; + use sp_runtime::DispatchError; + use sp_std::fmt::Debug; + + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); + + pub type AccountIdOf = ::AccountId; + pub type BalanceOf = <::Currency as Inspect>>::Balance; + pub type DepositKeyOf = BoundedVec::MaxKeyLength>; + pub type DepositEntryOf = DepositEntry, BalanceOf, ::RuntimeHoldReason>; + + #[pallet::config] + pub trait Config: frame_system::Config { + #[pallet::constant] + type MaxKeyLength: Get; + + type CheckOrigin: EnsureOrigin; + type Currency: Mutate; + type DepositHooks: DepositStorageHooks; + type Namespace: Parameter; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + type RuntimeHoldReason: From + Clone + PartialEq + Debug + FullCodec + MaxEncodedLen + TypeInfo; + } + + #[pallet::composite_enum] + pub enum HoldReason { + Deposit, + } + + #[pallet::error] + pub enum Error { + DepositNotFound, + DepositExisting, + Unauthorized, + Hook(u16), + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + DepositAdded { + namespace: T::Namespace, + key: DepositKeyOf, + deposit_entry: DepositEntryOf, + }, + DepositReclaimed { + namespace: T::Namespace, + key: DepositKeyOf, + deposit_entry: DepositEntryOf, + }, + } + + // Double map (namespace, key) -> deposit + #[pallet::storage] + #[pallet::getter(fn deposits)] + pub(crate) type Deposits = + StorageDoubleMap<_, Twox64Concat, ::Namespace, Twox64Concat, DepositKeyOf, DepositEntryOf>; + + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(_); + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + // TODO: Update weight + #[pallet::weight(0)] + pub fn reclaim_deposit(origin: OriginFor, namespace: T::Namespace, key: DepositKeyOf) -> DispatchResult { + let dispatcher = T::CheckOrigin::ensure_origin(origin)?; + + let deposit = Self::remove_deposit(&namespace, &key, Some(&dispatcher))?; + T::DepositHooks::on_deposit_reclaimed(&namespace, &key, deposit).map_err(|e| Error::::Hook(e.into()))?; + Ok(()) + } + } + + impl Pallet { + pub fn add_deposit(namespace: T::Namespace, key: DepositKeyOf, entry: DepositEntryOf) -> DispatchResult { + Deposits::::try_mutate(&namespace, &key, |deposit_entry| match deposit_entry { + Some(_) => Err(DispatchError::from(Error::::DepositExisting)), + None => { + reserve_deposit::, T::Currency>( + entry.deposit.owner.clone(), + entry.deposit.amount, + &entry.reason, + )?; + Self::deposit_event(Event::::DepositAdded { + namespace: namespace.clone(), + key: key.clone(), + deposit_entry: entry.clone(), + }); + *deposit_entry = Some(entry); + Ok(()) + } + })?; + Ok(()) + } + + pub fn remove_deposit( + namespace: &T::Namespace, + key: &DepositKeyOf, + expected_owner: Option<&AccountIdOf>, + ) -> Result, DispatchError> { + let existing_entry = Deposits::::take(namespace, key).ok_or(Error::::DepositNotFound)?; + if let Some(expected_owner) = expected_owner { + ensure!( + existing_entry.deposit.owner == *expected_owner, + Error::::Unauthorized + ); + } + free_deposit::, T::Currency>(&existing_entry.deposit, &existing_entry.reason)?; + Self::deposit_event(Event::::DepositReclaimed { + namespace: namespace.clone(), + key: key.clone(), + deposit_entry: existing_entry.clone(), + }); + Ok(existing_entry) + } + } +} diff --git a/pallets/pallet-deposit-storage/src/traits.rs b/pallets/pallet-deposit-storage/src/traits.rs new file mode 100644 index 0000000000..f514eee108 --- /dev/null +++ b/pallets/pallet-deposit-storage/src/traits.rs @@ -0,0 +1,49 @@ +// KILT Blockchain – https://botlabs.org +// Copyright (C) 2019-2023 BOTLabs GmbH + +// The KILT Blockchain 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. + +// The KILT Blockchain 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 this program. If not, see . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +use crate::{Config, DepositEntryOf, DepositKeyOf}; + +pub trait DepositStorageHooks +where + Runtime: Config, +{ + type Error: Into; + + fn on_deposit_reclaimed( + namespace: &Runtime::Namespace, + key: &DepositKeyOf, + deposit: DepositEntryOf, + ) -> Result<(), Self::Error>; +} + +pub struct NoopDepositStorageHooks; + +impl DepositStorageHooks for NoopDepositStorageHooks +where + Runtime: Config, +{ + type Error = u16; + + fn on_deposit_reclaimed( + _namespace: &Runtime::Namespace, + _key: &DepositKeyOf, + _deposit: DepositEntryOf, + ) -> Result<(), Self::Error> { + Ok(()) + } +} diff --git a/pallets/pallet-dip-provider/Cargo.toml b/pallets/pallet-dip-provider/Cargo.toml index 8c078fbc52..5e22e357a1 100644 --- a/pallets/pallet-dip-provider/Cargo.toml +++ b/pallets/pallet-dip-provider/Cargo.toml @@ -19,7 +19,6 @@ frame-support.workspace = true frame-system.workspace = true parity-scale-codec = {workspace = true, features = ["derive"]} scale-info = {workspace = true, features = ["derive"]} -sp-io.workspace = true sp-std.workspace = true [features] diff --git a/pallets/pallet-dip-provider/src/lib.rs b/pallets/pallet-dip-provider/src/lib.rs index 826fd170ac..f01b78c52e 100644 --- a/pallets/pallet-dip-provider/src/lib.rs +++ b/pallets/pallet-dip-provider/src/lib.rs @@ -31,23 +31,17 @@ pub mod pallet { use frame_support::{pallet_prelude::*, traits::EnsureOrigin}; use frame_system::pallet_prelude::*; use parity_scale_codec::FullCodec; - use sp_io::MultiRemovalResults; use sp_std::fmt::Debug; - use crate::traits::{IdentityCommitmentGenerator, IdentityProvider, SubmitterInfo}; + use crate::traits::{IdentityCommitmentGenerator, IdentityProvider, ProviderHooks, SubmitterInfo}; + pub type IdentityProviderOf = ::IdentityProvider; pub type IdentityOf = <::IdentityProvider as IdentityProvider<::Identifier>>::Success; pub type IdentityCommitmentVersion = u16; pub const LATEST_COMMITMENT_VERSION: IdentityCommitmentVersion = 0; const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); - #[derive(Encode, Decode, RuntimeDebug, TypeInfo, Clone, PartialEq)] - pub enum VersionOrLimit { - Version(IdentityCommitmentVersion), - Limit(u32), - } - #[pallet::config] pub trait Config: frame_system::Config { type CommitOriginCheck: EnsureOrigin; @@ -63,6 +57,7 @@ pub mod pallet { type IdentityCommitmentGeneratorError: Into; type IdentityProvider: IdentityProvider; type IdentityProviderError: Into; + type ProviderHooks: ProviderHooks; type RuntimeEvent: From> + IsType<::RuntimeEvent>; } @@ -104,6 +99,7 @@ pub mod pallet { LimitTooLow, IdentityProvider(u16), IdentityCommitmentGenerator(u16), + Hook(u16), } #[pallet::call] @@ -116,27 +112,43 @@ pub mod pallet { identifier: T::Identifier, version: Option, ) -> DispatchResult { - // TODO: use dispatcher to get deposit - let _dispatcher = + let dispatcher = T::CommitOriginCheck::ensure_origin(origin).map(|e: ::CommitOrigin| e.submitter())?; let commitment_version = version.unwrap_or(LATEST_COMMITMENT_VERSION); let commitment = match T::IdentityProvider::retrieve(&identifier) { + Ok(None) => Err(Error::::IdentityNotFound), + Err(error) => Err(Error::::IdentityProvider(error.into())), Ok(Some(identity)) => { T::IdentityCommitmentGenerator::generate_commitment(&identifier, &identity, commitment_version) .map_err(|error| Error::::IdentityCommitmentGenerator(error.into())) } - Ok(None) => Err(Error::::IdentityNotFound), - Err(error) => Err(Error::::IdentityProvider(error.into())), }?; - // TODO: Take deposit (once 0.9.42 PR is merged into develop) - IdentityCommitments::::insert(&identifier, commitment_version, commitment.clone()); - Self::deposit_event(Event::::IdentityCommitted { - identifier, - commitment, - version: commitment_version, - }); + IdentityCommitments::::try_mutate(&identifier, commitment_version, |commitment_entry| { + if let Some(old_commitment) = commitment_entry { + T::ProviderHooks::on_commitment_removed( + &identifier, + &dispatcher, + old_commitment, + commitment_version, + ) + .map_err(|e| Error::::Hook(e.into()))?; + Self::deposit_event(Event::::VersionedIdentityDeleted { + identifier: identifier.clone(), + version: commitment_version, + }); + } + T::ProviderHooks::on_identity_committed(&identifier, &dispatcher, &commitment, commitment_version) + .map_err(|e| Error::::Hook(e.into()))?; + *commitment_entry = Some(commitment.clone()); + Self::deposit_event(Event::::IdentityCommitted { + identifier: identifier.clone(), + commitment, + version: commitment_version, + }); + Ok::<_, Error>(()) + })?; Ok(()) } @@ -146,35 +158,30 @@ pub mod pallet { pub fn delete_identity_commitment( origin: OriginFor, identifier: T::Identifier, - version_or_limit: VersionOrLimit, + version: Option, ) -> DispatchResult { - let _dispatcher = + let dispatcher = T::CommitOriginCheck::ensure_origin(origin).map(|e: ::CommitOrigin| e.submitter())?; - match version_or_limit { - VersionOrLimit::Version(version) => { - let commitment = IdentityCommitments::::take(&identifier, version); - match commitment { - Some(_) => Err(Error::::IdentityNotFound), - None => { - Self::deposit_event(Event::::VersionedIdentityDeleted { identifier, version }); - Ok(()) - } - } - } - VersionOrLimit::Limit(limit) => { - let MultiRemovalResults { maybe_cursor, .. } = - IdentityCommitments::::clear_prefix(&identifier, limit, None); - match maybe_cursor { - Some(_) => Err(Error::::LimitTooLow), - None => { - Self::deposit_event(Event::::IdentityDeleted { identifier }); - Ok(()) - } - } - } - }?; + let commitment_version = version.unwrap_or(LATEST_COMMITMENT_VERSION); + let commitment = Self::delete_identity_commitment_storage_entry(&identifier, commitment_version)?; + T::ProviderHooks::on_commitment_removed(&identifier, &dispatcher, &commitment, commitment_version) + .map_err(|e| Error::::Hook(e.into()))?; Ok(()) } } + + impl Pallet { + pub fn delete_identity_commitment_storage_entry( + identifier: &T::Identifier, + version: IdentityCommitmentVersion, + ) -> Result { + let commitment = IdentityCommitments::::take(identifier, version).ok_or(Error::::IdentityNotFound)?; + Self::deposit_event(Event::::VersionedIdentityDeleted { + identifier: identifier.clone(), + version, + }); + Ok(commitment) + } + } } diff --git a/pallets/pallet-dip-provider/src/traits.rs b/pallets/pallet-dip-provider/src/traits.rs index 76b742e129..52785c8ad0 100644 --- a/pallets/pallet-dip-provider/src/traits.rs +++ b/pallets/pallet-dip-provider/src/traits.rs @@ -17,12 +17,15 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org use did::DidRawOrigin; +use frame_support::sp_runtime::AccountId32; + +use crate::{Config, IdentityCommitmentVersion}; pub use identity_generation::*; pub mod identity_generation { - use sp_std::marker::PhantomData; + use super::*; - use crate::IdentityCommitmentVersion; + use sp_std::marker::PhantomData; pub trait IdentityCommitmentGenerator { type Error; @@ -102,7 +105,7 @@ pub trait SubmitterInfo { fn submitter(&self) -> Self::Submitter; } -impl SubmitterInfo for frame_support::sp_runtime::AccountId32 { +impl SubmitterInfo for AccountId32 { type Submitter = Self; fn submitter(&self) -> Self::Submitter { @@ -120,3 +123,51 @@ where self.submitter.clone() } } + +pub trait ProviderHooks +where + Runtime: Config, +{ + type Error: Into; + + fn on_identity_committed( + identifier: &Runtime::Identifier, + submitter: &Runtime::AccountId, + commitment: &Runtime::IdentityCommitment, + version: IdentityCommitmentVersion, + ) -> Result<(), Self::Error>; + + fn on_commitment_removed( + identifier: &Runtime::Identifier, + submitter: &Runtime::AccountId, + commitment: &Runtime::IdentityCommitment, + version: IdentityCommitmentVersion, + ) -> Result<(), Self::Error>; +} + +pub struct NoopHooks; + +impl ProviderHooks for NoopHooks +where + Runtime: Config, +{ + type Error = u16; + + fn on_commitment_removed( + _identifier: &Runtime::Identifier, + _submitter: &Runtime::AccountId, + _commitment: &Runtime::IdentityCommitment, + _version: IdentityCommitmentVersion, + ) -> Result<(), Self::Error> { + Ok(()) + } + + fn on_identity_committed( + _identifier: &Runtime::Identifier, + _submitter: &Runtime::AccountId, + _commitment: &Runtime::IdentityCommitment, + _version: IdentityCommitmentVersion, + ) -> Result<(), Self::Error> { + Ok(()) + } +}