|
| 1 | +// Copyright (C) Parity Technologies (UK) Ltd. |
| 2 | +// SPDX-License-Identifier: Apache-2.0 |
| 3 | + |
| 4 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | +// you may not use this file except in compliance with the License. |
| 6 | +// You may obtain a copy of the License at |
| 7 | +// |
| 8 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | +// |
| 10 | +// Unless required by applicable law or agreed to in writing, software |
| 11 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | +// See the License for the specific language governing permissions and |
| 14 | +// limitations under the License. |
| 15 | + |
| 16 | +//! This pallet is designed to go into a source chain and destination chain to migrate data. The |
| 17 | +//! design motivations are: |
| 18 | +//! |
| 19 | +//! - Call some function on the source chain that executes some migration (clearing state, |
| 20 | +//! forwarding an XCM program). |
| 21 | +//! - Call some function (probably from an XCM program) on the destination chain. |
| 22 | +//! - Avoid cluttering the source pallet with new dispatchables that are unrelated to its |
| 23 | +//! functionality and only used for migration. |
| 24 | +//! |
| 25 | +//! After the migration is complete, the pallet may be removed from both chains' runtimes as well as |
| 26 | +//! the `polkadot-runtime-common` crate. |
| 27 | +
|
| 28 | +use frame_support::{dispatch::DispatchResult, traits::Currency, weights::Weight}; |
| 29 | +pub use pallet::*; |
| 30 | +use pallet_identity; |
| 31 | +use sp_core::Get; |
| 32 | + |
| 33 | +#[cfg(feature = "runtime-benchmarks")] |
| 34 | +use frame_benchmarking::{account, impl_benchmark_test_suite, v2::*, BenchmarkError}; |
| 35 | + |
| 36 | +pub trait WeightInfo { |
| 37 | + fn reap_identity(r: u32, s: u32) -> Weight; |
| 38 | + fn poke_deposit() -> Weight; |
| 39 | +} |
| 40 | + |
| 41 | +impl WeightInfo for () { |
| 42 | + fn reap_identity(_r: u32, _s: u32) -> Weight { |
| 43 | + Weight::MAX |
| 44 | + } |
| 45 | + fn poke_deposit() -> Weight { |
| 46 | + Weight::MAX |
| 47 | + } |
| 48 | +} |
| 49 | + |
| 50 | +pub struct TestWeightInfo; |
| 51 | +impl WeightInfo for TestWeightInfo { |
| 52 | + fn reap_identity(_r: u32, _s: u32) -> Weight { |
| 53 | + Weight::zero() |
| 54 | + } |
| 55 | + fn poke_deposit() -> Weight { |
| 56 | + Weight::zero() |
| 57 | + } |
| 58 | +} |
| 59 | + |
| 60 | +// Must use the same `Balance` as `T`'s Identity pallet to handle deposits. |
| 61 | +type BalanceOf<T> = <<T as pallet_identity::Config>::Currency as Currency< |
| 62 | + <T as frame_system::Config>::AccountId, |
| 63 | +>>::Balance; |
| 64 | + |
| 65 | +#[frame_support::pallet] |
| 66 | +pub mod pallet { |
| 67 | + use super::*; |
| 68 | + use frame_support::{ |
| 69 | + dispatch::{DispatchResultWithPostInfo, PostDispatchInfo}, |
| 70 | + pallet_prelude::*, |
| 71 | + traits::EnsureOrigin, |
| 72 | + }; |
| 73 | + use frame_system::pallet_prelude::*; |
| 74 | + |
| 75 | + #[pallet::pallet] |
| 76 | + pub struct Pallet<T>(_); |
| 77 | + |
| 78 | + #[pallet::config] |
| 79 | + pub trait Config: frame_system::Config + pallet_identity::Config { |
| 80 | + /// Overarching event type. |
| 81 | + type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; |
| 82 | + |
| 83 | + /// The origin that can reap identities. Expected to be `EnsureSigned<AccountId>` on the |
| 84 | + /// source chain such that anyone can all this function. |
| 85 | + type Reaper: EnsureOrigin<Self::RuntimeOrigin>; |
| 86 | + |
| 87 | + /// A handler for what to do when an identity is reaped. |
| 88 | + type ReapIdentityHandler: OnReapIdentity<Self::AccountId>; |
| 89 | + |
| 90 | + /// Weight information for the extrinsics in the pallet. |
| 91 | + type WeightInfo: WeightInfo; |
| 92 | + } |
| 93 | + |
| 94 | + #[pallet::event] |
| 95 | + #[pallet::generate_deposit(pub(super) fn deposit_event)] |
| 96 | + pub enum Event<T: Config> { |
| 97 | + /// The identity and all sub accounts were reaped for `who`. |
| 98 | + IdentityReaped { who: T::AccountId }, |
| 99 | + /// The deposits held for `who` were updated. `identity` is the new deposit held for |
| 100 | + /// identity info, and `subs` is the new deposit held for the sub-accounts. |
| 101 | + DepositUpdated { who: T::AccountId, identity: BalanceOf<T>, subs: BalanceOf<T> }, |
| 102 | + } |
| 103 | + |
| 104 | + #[pallet::call] |
| 105 | + impl<T: Config> Pallet<T> { |
| 106 | + /// Reap the `IdentityInfo` of `who` from the Identity pallet of `T`, unreserving any |
| 107 | + /// deposits held and removing storage items associated with `who`. |
| 108 | + #[pallet::call_index(0)] |
| 109 | + #[pallet::weight(<T as pallet::Config>::WeightInfo::reap_identity( |
| 110 | + T::MaxRegistrars::get(), |
| 111 | + T::MaxSubAccounts::get() |
| 112 | + ))] |
| 113 | + pub fn reap_identity( |
| 114 | + origin: OriginFor<T>, |
| 115 | + who: T::AccountId, |
| 116 | + ) -> DispatchResultWithPostInfo { |
| 117 | + T::Reaper::ensure_origin(origin)?; |
| 118 | + // - number of registrars (required to calculate weight) |
| 119 | + // - byte size of `IdentityInfo` (required to calculate remote deposit) |
| 120 | + // - number of sub accounts (required to calculate both weight and remote deposit) |
| 121 | + let (registrars, bytes, subs) = pallet_identity::Pallet::<T>::reap_identity(&who)?; |
| 122 | + T::ReapIdentityHandler::on_reap_identity(&who, bytes, subs)?; |
| 123 | + Self::deposit_event(Event::IdentityReaped { who }); |
| 124 | + let post = PostDispatchInfo { |
| 125 | + actual_weight: Some(<T as pallet::Config>::WeightInfo::reap_identity( |
| 126 | + registrars, subs, |
| 127 | + )), |
| 128 | + pays_fee: Pays::No, |
| 129 | + }; |
| 130 | + Ok(post) |
| 131 | + } |
| 132 | + |
| 133 | + /// Update the deposit of `who`. Meant to be called by the system with an XCM `Transact` |
| 134 | + /// Instruction. |
| 135 | + #[pallet::call_index(1)] |
| 136 | + #[pallet::weight(<T as pallet::Config>::WeightInfo::poke_deposit())] |
| 137 | + pub fn poke_deposit(origin: OriginFor<T>, who: T::AccountId) -> DispatchResultWithPostInfo { |
| 138 | + ensure_root(origin)?; |
| 139 | + let (id_deposit, subs_deposit) = pallet_identity::Pallet::<T>::poke_deposit(&who)?; |
| 140 | + Self::deposit_event(Event::DepositUpdated { |
| 141 | + who, |
| 142 | + identity: id_deposit, |
| 143 | + subs: subs_deposit, |
| 144 | + }); |
| 145 | + Ok(Pays::No.into()) |
| 146 | + } |
| 147 | + } |
| 148 | +} |
| 149 | + |
| 150 | +/// Trait to handle reaping identity from state. |
| 151 | +pub trait OnReapIdentity<AccountId> { |
| 152 | + /// What to do when an identity is reaped. For example, the implementation could send an XCM |
| 153 | + /// program to another chain. Concretely, a type implementing this trait in the Polkadot |
| 154 | + /// runtime would teleport enough DOT to the People Chain to cover the Identity deposit there. |
| 155 | + /// |
| 156 | + /// This could also directly include `Transact { poke_deposit(..), ..}`. |
| 157 | + /// |
| 158 | + /// Inputs |
| 159 | + /// - `who`: Whose identity was reaped. |
| 160 | + /// - `bytes`: The byte size of `IdentityInfo`. |
| 161 | + /// - `subs`: The number of sub-accounts they had. |
| 162 | + fn on_reap_identity(who: &AccountId, bytes: u32, subs: u32) -> DispatchResult; |
| 163 | +} |
| 164 | + |
| 165 | +impl<AccountId> OnReapIdentity<AccountId> for () { |
| 166 | + fn on_reap_identity(_who: &AccountId, _bytes: u32, _subs: u32) -> DispatchResult { |
| 167 | + Ok(()) |
| 168 | + } |
| 169 | +} |
| 170 | + |
| 171 | +#[cfg(feature = "runtime-benchmarks")] |
| 172 | +#[benchmarks] |
| 173 | +mod benchmarks { |
| 174 | + use super::*; |
| 175 | + use frame_support::traits::EnsureOrigin; |
| 176 | + use frame_system::RawOrigin; |
| 177 | + use pallet_identity::{Data, IdentityInformationProvider, Judgement, Pallet as Identity}; |
| 178 | + use parity_scale_codec::Encode; |
| 179 | + use sp_runtime::{ |
| 180 | + traits::{Bounded, Hash, StaticLookup}, |
| 181 | + Saturating, |
| 182 | + }; |
| 183 | + use sp_std::{boxed::Box, vec::Vec, *}; |
| 184 | + |
| 185 | + const SEED: u32 = 0; |
| 186 | + |
| 187 | + fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) { |
| 188 | + let events = frame_system::Pallet::<T>::events(); |
| 189 | + let system_event: <T as frame_system::Config>::RuntimeEvent = generic_event.into(); |
| 190 | + let frame_system::EventRecord { event, .. } = &events[events.len() - 1]; |
| 191 | + assert_eq!(event, &system_event); |
| 192 | + } |
| 193 | + |
| 194 | + #[benchmark] |
| 195 | + fn reap_identity( |
| 196 | + r: Linear<0, { T::MaxRegistrars::get() }>, |
| 197 | + s: Linear<0, { T::MaxSubAccounts::get() }>, |
| 198 | + ) -> Result<(), BenchmarkError> { |
| 199 | + // set up target |
| 200 | + let target: T::AccountId = account("target", 0, SEED); |
| 201 | + let target_origin = |
| 202 | + <T as frame_system::Config>::RuntimeOrigin::from(RawOrigin::Signed(target.clone())); |
| 203 | + let target_lookup = T::Lookup::unlookup(target.clone()); |
| 204 | + let _ = T::Currency::make_free_balance_be(&target, BalanceOf::<T>::max_value()); |
| 205 | + |
| 206 | + // set identity |
| 207 | + let info = <T as pallet_identity::Config>::IdentityInformation::create_identity_info(); |
| 208 | + Identity::<T>::set_identity( |
| 209 | + RawOrigin::Signed(target.clone()).into(), |
| 210 | + Box::new(info.clone()), |
| 211 | + )?; |
| 212 | + |
| 213 | + // create and set subs |
| 214 | + let mut subs = Vec::new(); |
| 215 | + let data = Data::Raw(vec![0; 32].try_into().unwrap()); |
| 216 | + for ii in 0..s { |
| 217 | + let sub_account = account("sub", ii, SEED); |
| 218 | + subs.push((sub_account, data.clone())); |
| 219 | + } |
| 220 | + Identity::<T>::set_subs(target_origin.clone(), subs.clone())?; |
| 221 | + |
| 222 | + // add registrars and provide judgements |
| 223 | + let registrar_origin = T::RegistrarOrigin::try_successful_origin() |
| 224 | + .expect("RegistrarOrigin has no successful origin required for the benchmark"); |
| 225 | + for ii in 0..r { |
| 226 | + // registrar account |
| 227 | + let registrar: T::AccountId = account("registrar", ii, SEED); |
| 228 | + let registrar_lookup = T::Lookup::unlookup(registrar.clone()); |
| 229 | + let _ = <T as pallet_identity::Config>::Currency::make_free_balance_be( |
| 230 | + ®istrar, |
| 231 | + <T as pallet_identity::Config>::Currency::minimum_balance(), |
| 232 | + ); |
| 233 | + |
| 234 | + // add registrar |
| 235 | + Identity::<T>::add_registrar(registrar_origin.clone(), registrar_lookup)?; |
| 236 | + Identity::<T>::set_fee(RawOrigin::Signed(registrar.clone()).into(), ii, 10u32.into())?; |
| 237 | + let fields = <T as pallet_identity::Config>::IdentityInformation::all_fields(); |
| 238 | + Identity::<T>::set_fields(RawOrigin::Signed(registrar.clone()).into(), ii, fields)?; |
| 239 | + |
| 240 | + // request and provide judgement |
| 241 | + Identity::<T>::request_judgement(target_origin.clone(), ii, 10u32.into())?; |
| 242 | + Identity::<T>::provide_judgement( |
| 243 | + RawOrigin::Signed(registrar).into(), |
| 244 | + ii, |
| 245 | + target_lookup.clone(), |
| 246 | + Judgement::Reasonable, |
| 247 | + <T as frame_system::Config>::Hashing::hash_of(&info), |
| 248 | + )?; |
| 249 | + } |
| 250 | + |
| 251 | + let origin = T::Reaper::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; |
| 252 | + |
| 253 | + #[extrinsic_call] |
| 254 | + _(origin as T::RuntimeOrigin, target.clone()); |
| 255 | + |
| 256 | + assert_last_event::<T>(Event::<T>::IdentityReaped { who: target.clone() }.into()); |
| 257 | + |
| 258 | + let fields = <T as pallet_identity::Config>::IdentityInformation::all_fields(); |
| 259 | + assert!(!Identity::<T>::has_identity(&target, fields)); |
| 260 | + assert_eq!(Identity::<T>::subs(&target).len(), 0); |
| 261 | + |
| 262 | + Ok(()) |
| 263 | + } |
| 264 | + |
| 265 | + #[benchmark] |
| 266 | + fn poke_deposit() -> Result<(), BenchmarkError> { |
| 267 | + let target: T::AccountId = account("target", 0, SEED); |
| 268 | + let _ = T::Currency::make_free_balance_be(&target, BalanceOf::<T>::max_value()); |
| 269 | + let info = <T as pallet_identity::Config>::IdentityInformation::create_identity_info(); |
| 270 | + |
| 271 | + let _ = Identity::<T>::set_identity_no_deposit(&target, info.clone()); |
| 272 | + |
| 273 | + let sub_account: T::AccountId = account("sub", 0, SEED); |
| 274 | + let _ = Identity::<T>::set_sub_no_deposit(&target, sub_account.clone()); |
| 275 | + |
| 276 | + // expected deposits |
| 277 | + let expected_id_deposit = <T as pallet_identity::Config>::BasicDeposit::get() |
| 278 | + .saturating_add( |
| 279 | + <T as pallet_identity::Config>::ByteDeposit::get() |
| 280 | + .saturating_mul(<BalanceOf<T>>::from(info.encoded_size() as u32)), |
| 281 | + ); |
| 282 | + // only 1 sub |
| 283 | + let expected_sub_deposit = <T as pallet_identity::Config>::SubAccountDeposit::get(); |
| 284 | + |
| 285 | + #[extrinsic_call] |
| 286 | + _(RawOrigin::Root, target.clone()); |
| 287 | + |
| 288 | + assert_last_event::<T>( |
| 289 | + Event::<T>::DepositUpdated { |
| 290 | + who: target, |
| 291 | + identity: expected_id_deposit, |
| 292 | + subs: expected_sub_deposit, |
| 293 | + } |
| 294 | + .into(), |
| 295 | + ); |
| 296 | + |
| 297 | + Ok(()) |
| 298 | + } |
| 299 | + |
| 300 | + impl_benchmark_test_suite!( |
| 301 | + Pallet, |
| 302 | + crate::integration_tests::new_test_ext(), |
| 303 | + crate::integration_tests::Test, |
| 304 | + ); |
| 305 | +} |
0 commit comments