// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! This pezpallet is designed to go into a source chain and destination chain to migrate data. The //! design motivations are: //! //! - Call some function on the source chain that executes some migration (clearing state, //! forwarding an XCM program). //! - Call some function (probably from an XCM program) on the destination chain. //! - Avoid cluttering the source pezpallet with new dispatchables that are unrelated to its //! functionality and only used for migration. //! //! After the migration is complete, the pezpallet may be removed from both chains' runtimes as well //! as the `pezkuwi-runtime-common` crate. use pezframe_support::{dispatch::DispatchResult, traits::Currency, weights::Weight}; pub use pezpallet::*; use pezpallet_identity; use pezsp_core::Get; #[cfg(feature = "runtime-benchmarks")] use pezframe_benchmarking::{account, v2::*, BenchmarkError}; pub trait WeightInfo { fn reap_identity(r: u32, s: u32) -> Weight; fn poke_deposit() -> Weight; } impl WeightInfo for () { fn reap_identity(_r: u32, _s: u32) -> Weight { Weight::MAX } fn poke_deposit() -> Weight { Weight::MAX } } pub struct TestWeightInfo; impl WeightInfo for TestWeightInfo { fn reap_identity(_r: u32, _s: u32) -> Weight { Weight::zero() } fn poke_deposit() -> Weight { Weight::zero() } } // Must use the same `Balance` as `T`'s Identity pezpallet to handle deposits. type BalanceOf = <::Currency as Currency< ::AccountId, >>::Balance; #[pezframe_support::pezpallet] pub mod pezpallet { use super::*; use pezframe_support::{ dispatch::{DispatchResultWithPostInfo, PostDispatchInfo}, pezpallet_prelude::*, traits::EnsureOrigin, }; use pezframe_system::pezpallet_prelude::*; #[pezpallet::pezpallet] pub struct Pezpallet(_); #[pezpallet::config] pub trait Config: pezframe_system::Config + pezpallet_identity::Config { /// Overarching event type. #[allow(deprecated)] type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// The origin that can reap identities. Expected to be `EnsureSigned` on the /// source chain such that anyone can all this function. type Reaper: EnsureOrigin; /// A handler for what to do when an identity is reaped. type ReapIdentityHandler: OnReapIdentity; /// Weight information for the extrinsics in the pezpallet. type WeightInfo: WeightInfo; } #[pezpallet::event] #[pezpallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// The identity and all sub accounts were reaped for `who`. IdentityReaped { who: T::AccountId }, /// The deposits held for `who` were updated. `identity` is the new deposit held for /// identity info, and `subs` is the new deposit held for the sub-accounts. DepositUpdated { who: T::AccountId, identity: BalanceOf, subs: BalanceOf }, } #[pezpallet::call] impl Pezpallet { /// Reap the `IdentityInfo` of `who` from the Identity pezpallet of `T`, unreserving any /// deposits held and removing storage items associated with `who`. #[pezpallet::call_index(0)] #[pezpallet::weight(::WeightInfo::reap_identity( T::MaxRegistrars::get(), T::MaxSubAccounts::get() ))] pub fn reap_identity( origin: OriginFor, who: T::AccountId, ) -> DispatchResultWithPostInfo { T::Reaper::ensure_origin(origin)?; // - number of registrars (required to calculate weight) // - byte size of `IdentityInfo` (required to calculate remote deposit) // - number of sub accounts (required to calculate both weight and remote deposit) let (registrars, bytes, subs) = pezpallet_identity::Pezpallet::::reap_identity(&who)?; T::ReapIdentityHandler::on_reap_identity(&who, bytes, subs)?; Self::deposit_event(Event::IdentityReaped { who }); let post = PostDispatchInfo { actual_weight: Some(::WeightInfo::reap_identity( registrars, subs, )), pays_fee: Pays::No, }; Ok(post) } /// Update the deposit of `who`. Meant to be called by the system with an XCM `Transact` /// Instruction. #[pezpallet::call_index(1)] #[pezpallet::weight(::WeightInfo::poke_deposit())] pub fn poke_deposit(origin: OriginFor, who: T::AccountId) -> DispatchResultWithPostInfo { ensure_root(origin)?; let (id_deposit, subs_deposit) = pezpallet_identity::Pezpallet::::poke_deposit(&who)?; Self::deposit_event(Event::DepositUpdated { who, identity: id_deposit, subs: subs_deposit, }); Ok(Pays::No.into()) } } } /// Trait to handle reaping identity from state. pub trait OnReapIdentity { /// What to do when an identity is reaped. For example, the implementation could send an XCM /// program to another chain. Concretely, a type implementing this trait in the Pezkuwi /// runtime would teleport enough HEZ to the People Chain to cover the Identity deposit there. /// /// This could also directly include `Transact { poke_deposit(..), ..}`. /// /// Inputs /// - `who`: Whose identity was reaped. /// - `bytes`: The byte size of `IdentityInfo`. /// - `subs`: The number of sub-accounts they had. fn on_reap_identity(who: &AccountId, bytes: u32, subs: u32) -> DispatchResult; /// Ensure that identity reaping will be succesful in benchmarking. /// /// Should setup the state in a way that the same call ot `[Self::on_reap_identity]` will be /// successful. #[cfg(feature = "runtime-benchmarks")] fn ensure_successful_identity_reaping(who: &AccountId, bytes: u32, subs: u32); } impl OnReapIdentity for () { fn on_reap_identity(_who: &AccountId, _bytes: u32, _subs: u32) -> DispatchResult { Ok(()) } #[cfg(feature = "runtime-benchmarks")] fn ensure_successful_identity_reaping(_: &AccountId, _: u32, _: u32) {} } #[cfg(feature = "runtime-benchmarks")] #[benchmarks] mod benchmarks { use super::*; use alloc::{boxed::Box, vec, vec::Vec}; use codec::Encode; use pezframe_support::traits::EnsureOrigin; use pezframe_system::RawOrigin; use pezpallet_identity::{Data, IdentityInformationProvider, Judgement, Pezpallet as Identity}; use pezsp_runtime::{ traits::{Bounded, Hash, StaticLookup}, Saturating, }; const SEED: u32 = 0; fn assert_last_event(generic_event: ::RuntimeEvent) { let events = pezframe_system::Pezpallet::::events(); let system_event: ::RuntimeEvent = generic_event.into(); let pezframe_system::EventRecord { event, .. } = &events[events.len() - 1]; assert_eq!(event, &system_event); } #[benchmark] fn reap_identity( r: Linear<0, { T::MaxRegistrars::get() }>, s: Linear<0, { T::MaxSubAccounts::get() }>, ) -> Result<(), BenchmarkError> { // set up target let target: T::AccountId = account("target", 0, SEED); let target_origin = ::RuntimeOrigin::from(RawOrigin::Signed(target.clone())); let target_lookup = T::Lookup::unlookup(target.clone()); let _ = T::Currency::make_free_balance_be(&target, BalanceOf::::max_value()); // set identity let info = ::IdentityInformation::create_identity_info(); Identity::::set_identity( RawOrigin::Signed(target.clone()).into(), Box::new(info.clone()), )?; // create and set subs let mut subs = Vec::new(); let data = Data::Raw(vec![0; 32].try_into().unwrap()); for ii in 0..s { let sub_account = account("sub", ii, SEED); subs.push((sub_account, data.clone())); } Identity::::set_subs(target_origin.clone(), subs.clone())?; T::ReapIdentityHandler::ensure_successful_identity_reaping( &target, info.encoded_size() as u32, subs.len() as u32, ); // add registrars and provide judgements let registrar_origin = T::RegistrarOrigin::try_successful_origin() .expect("RegistrarOrigin has no successful origin required for the benchmark"); for ii in 0..r { // registrar account let registrar: T::AccountId = account("registrar", ii, SEED); let registrar_lookup = T::Lookup::unlookup(registrar.clone()); let _ = ::Currency::make_free_balance_be( ®istrar, ::Currency::minimum_balance(), ); // add registrar Identity::::add_registrar(registrar_origin.clone(), registrar_lookup)?; Identity::::set_fee(RawOrigin::Signed(registrar.clone()).into(), ii, 10u32.into())?; let fields = ::IdentityInformation::all_fields(); Identity::::set_fields(RawOrigin::Signed(registrar.clone()).into(), ii, fields)?; // request and provide judgement Identity::::request_judgement(target_origin.clone(), ii, 10u32.into())?; Identity::::provide_judgement( RawOrigin::Signed(registrar).into(), ii, target_lookup.clone(), Judgement::Reasonable, ::Hashing::hash_of(&info), )?; } let origin = T::Reaper::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; #[extrinsic_call] _(origin as T::RuntimeOrigin, target.clone()); assert_last_event::(Event::::IdentityReaped { who: target.clone() }.into()); let fields = ::IdentityInformation::all_fields(); assert!(!Identity::::has_identity(&target, fields)); assert_eq!(Identity::::subs(&target).len(), 0); Ok(()) } #[benchmark] fn poke_deposit() -> Result<(), BenchmarkError> { let target: T::AccountId = account("target", 0, SEED); let _ = T::Currency::make_free_balance_be(&target, BalanceOf::::max_value()); let info = ::IdentityInformation::create_identity_info(); let _ = Identity::::set_identity_no_deposit(&target, info.clone()); let sub_account: T::AccountId = account("sub", 0, SEED); let name = Data::Raw(b"benchsub".to_vec().try_into().unwrap()); let _ = Identity::::set_subs_no_deposit(&target, vec![(sub_account.clone(), name)]); // expected deposits let expected_id_deposit = ::BasicDeposit::get() .saturating_add( ::ByteDeposit::get() .saturating_mul(>::from(info.encoded_size() as u32)), ); // only 1 sub let expected_sub_deposit = ::SubAccountDeposit::get(); #[extrinsic_call] _(RawOrigin::Root, target.clone()); assert_last_event::( Event::::DepositUpdated { who: target, identity: expected_id_deposit, subs: expected_sub_deposit, } .into(), ); Ok(()) } impl_benchmark_test_suite!( Pezpallet, crate::integration_tests::new_test_ext(), crate::integration_tests::Test, ); }