Identity Deposits Relay to Parachain Migration (#1814)

The goal of this PR is to migrate Identity deposits from the Relay Chain
to a system parachain.

The problem I want to solve is that `IdentityOf` and `SubsOf` both store
an amount that's held in reserve as a storage deposit. When migrating to
a parachain, we can take a snapshot of the actual `IdentityInfo` and
sub-account mappings, but should migrate (off chain) the `deposit`s to
zero, since the chain (and by extension, accounts) won't have any funds
at genesis.

The good news is that we expect parachain deposits to be significantly
lower (possibly 100x) on the parachain. That is, a deposit of 21 DOT on
the Relay Chain would need 0.21 DOT on a parachain. This PR proposes to
migrate the deposits in the following way:

1. Introduces a new pallet with two extrinsics: 
- `reap_identity`: Has a configurable `ReapOrigin`, which would be set
to `EnsureSigned` on the Relay Chain (i.e. callable by anyone) and
`EnsureRoot` on the parachain (we don't want identities reaped from
there).
- `poke_deposit`: Checks what deposit the pallet holds (at genesis,
zero) and attempts to update the amount based on the calculated deposit
for storage data.
2. `reap_identity` clears all storage data for a `target` account and
unreserves their deposit.
3. A `ReapIdentityHandler` teleports the necessary DOT to the parachain
and calls `poke_deposit`. Since the parachain deposit is much lower, and
was just unreserved, we know we have enough.

One awkwardness I ran into was that the XCMv3 instruction set does not
provide a way for the system to teleport assets without a fee being
deducted on reception. Users shouldn't have to pay a fee for the system
to migrate their info to a more efficient location. So I wrote my own
program and did the `InitiateTeleport` accounting on my own to send a
program with `UnpaidExecution`. Have discussed an
`InitiateUnpaidTeleport` instruction with @franciscoaguirre . Obviously
any chain executing this would have to pass a `Barrier` for free
execution.

TODO:

- [x] Confirm People Chain ParaId
- [x] Confirm People Chain deposit rates (determined in
https://github.com/paritytech/polkadot-sdk/pull/2281)
- [x] Add pallet to Westend

---------

Co-authored-by: Bastian Köcher <git@kchr.de>
This commit is contained in:
joe petrowski
2023-11-15 15:22:28 +01:00
committed by GitHub
parent 5b0622bc4d
commit c79b234b3b
15 changed files with 1111 additions and 13 deletions
+158
View File
@@ -0,0 +1,158 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot 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.
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
use crate::xcm_config;
use frame_support::pallet_prelude::DispatchResult;
use frame_system::RawOrigin;
use parity_scale_codec::{Decode, Encode};
use primitives::Balance;
use rococo_runtime_constants::currency::*;
use runtime_common::identity_migrator::{OnReapIdentity, WeightInfo};
use sp_std::{marker::PhantomData, prelude::*};
use xcm::{latest::prelude::*, VersionedMultiLocation, VersionedXcm};
use xcm_executor::traits::TransactAsset;
/// A type containing the encoding of the People Chain pallets in its runtime. Used to construct any
/// remote calls. The codec index must correspond to the index of `IdentityMigrator` in the
/// `construct_runtime` of the remote chain.
#[derive(Encode, Decode)]
enum PeopleRuntimePallets<AccountId: Encode> {
#[codec(index = 248)]
IdentityMigrator(IdentityMigratorCalls<AccountId>),
}
/// Call encoding for the calls needed from the Identity Migrator pallet.
#[derive(Encode, Decode)]
enum IdentityMigratorCalls<AccountId: Encode> {
#[codec(index = 1)]
PokeDeposit(AccountId),
}
/// Type that implements `OnReapIdentity` that will send the deposit needed to store the same
/// information on a parachain, sends the deposit there, and then updates it.
pub struct ToParachainIdentityReaper<Runtime, AccountId>(PhantomData<(Runtime, AccountId)>);
impl<Runtime, AccountId> ToParachainIdentityReaper<Runtime, AccountId> {
/// Calculate the balance needed on the remote chain based on the `IdentityInfo` and `Subs` on
/// this chain. The total includes:
///
/// - Identity basic deposit
/// - `IdentityInfo` byte deposit
/// - Sub accounts deposit
/// - 2x existential deposit (1 for account existence, 1 such that the user can transact)
fn calculate_remote_deposit(bytes: u32, subs: u32) -> Balance {
// Remote deposit constants. Parachain uses `deposit / 100`
// Source:
// https://github.com/paritytech/polkadot-sdk/blob/a146918/cumulus/parachains/common/src/rococo.rs#L29
//
// Parachain Deposit Configuration:
//
// pub const BasicDeposit: Balance = deposit(1, 17);
// pub const ByteDeposit: Balance = deposit(0, 1);
// pub const SubAccountDeposit: Balance = deposit(1, 53);
// pub const EXISTENTIAL_DEPOSIT: Balance = constants::currency::EXISTENTIAL_DEPOSIT / 10;
let para_basic_deposit = deposit(1, 17) / 100;
let para_byte_deposit = deposit(0, 1) / 100;
let para_sub_account_deposit = deposit(1, 53) / 100;
let para_existential_deposit = EXISTENTIAL_DEPOSIT / 10;
// pallet deposits
let id_deposit =
para_basic_deposit.saturating_add(para_byte_deposit.saturating_mul(bytes as Balance));
let subs_deposit = para_sub_account_deposit.saturating_mul(subs as Balance);
id_deposit
.saturating_add(subs_deposit)
.saturating_add(para_existential_deposit.saturating_mul(2))
}
}
impl<Runtime, AccountId> OnReapIdentity<AccountId> for ToParachainIdentityReaper<Runtime, AccountId>
where
Runtime: frame_system::Config + pallet_xcm::Config,
AccountId: Into<[u8; 32]> + Clone + Encode,
{
fn on_reap_identity(who: &AccountId, fields: u32, subs: u32) -> DispatchResult {
use crate::{
impls::IdentityMigratorCalls::PokeDeposit,
weights::runtime_common_identity_migrator::WeightInfo as MigratorWeights,
};
let total_to_send = Self::calculate_remote_deposit(fields, subs);
// define asset / destination from relay perspective
let roc = MultiAsset { id: Concrete(Here.into_location()), fun: Fungible(total_to_send) };
// People Chain: ParaId 1004
let destination: MultiLocation = MultiLocation::new(0, Parachain(1004));
// Do `check_out` accounting since the XCM Executor's `InitiateTeleport` doesn't support
// unpaid teleports.
// check out
xcm_config::LocalAssetTransactor::can_check_out(
&destination,
&roc,
// not used in AssetTransactor
&XcmContext { origin: None, message_id: [0; 32], topic: None },
)
.map_err(|_| pallet_xcm::Error::<Runtime>::CannotCheckOutTeleport)?;
xcm_config::LocalAssetTransactor::check_out(
&destination,
&roc,
// not used in AssetTransactor
&XcmContext { origin: None, message_id: [0; 32], topic: None },
);
// reanchor
let roc_reanchored: MultiAssets = vec![MultiAsset {
id: Concrete(MultiLocation::new(1, Here)),
fun: Fungible(total_to_send),
}]
.into();
let poke = PeopleRuntimePallets::<AccountId>::IdentityMigrator(PokeDeposit(who.clone()));
let remote_weight_limit = MigratorWeights::<Runtime>::poke_deposit().saturating_mul(2);
// Actual program to execute on People Chain.
let program: Xcm<()> = Xcm(vec![
// Unpaid as this is constructed by the system, once per user. The user shouldn't have
// their balance reduced by teleport fees for the favor of migrating.
UnpaidExecution { weight_limit: Unlimited, check_origin: None },
// Receive the asset into holding.
ReceiveTeleportedAsset(roc_reanchored),
// Deposit into the user's account.
DepositAsset {
assets: Wild(AllCounted(1)),
beneficiary: Junction::AccountId32 { network: None, id: who.clone().into() }
.into_location()
.into(),
},
// Poke the deposit to reserve the appropriate amount on the parachain.
Transact {
origin_kind: OriginKind::Superuser,
require_weight_at_most: remote_weight_limit,
call: poke.encode().into(),
},
]);
// send
let _ = <pallet_xcm::Pallet<Runtime>>::send(
RawOrigin::Root.into(),
Box::new(VersionedMultiLocation::V3(destination)),
Box::new(VersionedXcm::V3(program)),
)?;
Ok(())
}
}
+33 -6
View File
@@ -31,7 +31,7 @@ use primitives::{
ValidatorIndex, PARACHAIN_KEY_TYPE_ID,
};
use runtime_common::{
assigned_slots, auctions, claims, crowdloan, impl_runtime_weights,
assigned_slots, auctions, claims, crowdloan, identity_migrator, impl_runtime_weights,
impls::{
LocatableAssetConverter, ToAuthor, VersionedLocatableAsset, VersionedMultiLocationConverter,
},
@@ -68,9 +68,9 @@ use frame_support::{
genesis_builder_helper::{build_config, create_default_config},
parameter_types,
traits::{
fungible::HoldConsideration, EitherOf, EitherOfDiverse, Everything, InstanceFilter,
KeyOwnerProofSystem, LinearStoragePrice, PrivilegeCmp, ProcessMessage, ProcessMessageError,
StorageMapShim, WithdrawReasons,
fungible::HoldConsideration, Contains, EitherOf, EitherOfDiverse, EverythingBut,
InstanceFilter, KeyOwnerProofSystem, LinearStoragePrice, PrivilegeCmp, ProcessMessage,
ProcessMessageError, StorageMapShim, WithdrawReasons,
},
weights::{ConstantMultiplier, WeightMeter},
PalletId,
@@ -114,6 +114,10 @@ mod weights;
// XCM configurations.
pub mod xcm_config;
// Implemented types.
mod impls;
use impls::ToParachainIdentityReaper;
// Governance and configurations.
pub mod governance;
use governance::{
@@ -166,13 +170,24 @@ pub fn native_version() -> NativeVersion {
NativeVersion { runtime_version: VERSION, can_author_with: Default::default() }
}
/// A type to identify calls to the Identity pallet. These will be filtered to prevent invocation,
/// locking the state of the pallet and preventing further updates to identities and sub-identities.
/// The locked state will be the genesis state of a new system chain and then removed from the Relay
/// Chain.
pub struct IsIdentityCall;
impl Contains<RuntimeCall> for IsIdentityCall {
fn contains(c: &RuntimeCall) -> bool {
matches!(c, RuntimeCall::Identity(_))
}
}
parameter_types! {
pub const Version: RuntimeVersion = VERSION;
pub const SS58Prefix: u8 = 42;
}
impl frame_system::Config for Runtime {
type BaseCallFilter = Everything;
type BaseCallFilter = EverythingBut<IsIdentityCall>;
type BlockWeights = BlockWeights;
type BlockLength = BlockLength;
type DbWeight = RocksDbWeight;
@@ -1079,6 +1094,14 @@ impl auctions::Config for Runtime {
type WeightInfo = weights::runtime_common_auctions::WeightInfo<Runtime>;
}
impl identity_migrator::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
// To be changed to `EnsureSigned` once there is a People Chain to migrate to.
type Reaper = EnsureRoot<AccountId>;
type ReapIdentityHandler = ToParachainIdentityReaper<Runtime, Self::AccountId>;
type WeightInfo = weights::runtime_common_identity_migrator::WeightInfo<Runtime>;
}
type NisCounterpartInstance = pallet_balances::Instance2;
impl pallet_balances::Config<NisCounterpartInstance> for Runtime {
type Balance = Balance;
@@ -1340,7 +1363,7 @@ construct_runtime! {
// NIS pallet.
Nis: pallet_nis::{Pallet, Call, Storage, Event<T>, HoldReason} = 38,
// pub type NisCounterpartInstance = pallet_balances::Instance2;
// pub type NisCounterpartInstance = pallet_balances::Instance2;
NisCounterpartBalances: pallet_balances::<Instance2> = 45,
// Parachains pallets. Start indices at 50 to leave room.
@@ -1371,6 +1394,9 @@ construct_runtime! {
// Pallet for sending XCM.
XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event<T>, Origin, Config<T>} = 99,
// Pallet for migrating Identity to a parachain. To be removed post-migration.
IdentityMigrator: identity_migrator::{Pallet, Call, Event<T>} = 248,
ParasSudoWrapper: paras_sudo_wrapper::{Pallet, Call} = 250,
AssignedSlots: assigned_slots::{Pallet, Call, Storage, Event<T>, Config<T>} = 251,
@@ -1551,6 +1577,7 @@ mod benches {
[runtime_common::auctions, Auctions]
[runtime_common::crowdloan, Crowdloan]
[runtime_common::claims, Claims]
[runtime_common::identity_migrator, IdentityMigrator]
[runtime_common::slots, Slots]
[runtime_common::paras_registrar, Registrar]
[runtime_parachains::configuration, Configuration]
@@ -46,6 +46,7 @@ pub mod runtime_common_assigned_slots;
pub mod runtime_common_auctions;
pub mod runtime_common_claims;
pub mod runtime_common_crowdloan;
pub mod runtime_common_identity_migrator;
pub mod runtime_common_paras_registrar;
pub mod runtime_common_slots;
pub mod runtime_parachains_assigner_on_demand;
@@ -0,0 +1,97 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// 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.
//! Autogenerated weights for `runtime_common::identity_migrator`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2023-11-07, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `sbtb`, CPU: `13th Gen Intel(R) Core(TM) i7-1365U`
//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("rococo-dev")`, DB CACHE: 1024
// Executed Command:
// ./target/release/polkadot
// benchmark
// pallet
// --chain=rococo-dev
// --steps=2
// --repeat=1
// --pallet=runtime_common::identity_migrator
// --extrinsic=*
// --output=./migrator-release.rs
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(missing_docs)]
use frame_support::{traits::Get, weights::Weight};
use core::marker::PhantomData;
/// Weight functions for `runtime_common::identity_migrator`.
pub struct WeightInfo<T>(PhantomData<T>);
impl<T: frame_system::Config> runtime_common::identity_migrator::WeightInfo for WeightInfo<T> {
/// Storage: `Identity::IdentityOf` (r:1 w:1)
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7538), added: 10013, mode: `MaxEncodedLen`)
/// Storage: `Identity::SubsOf` (r:1 w:1)
/// Proof: `Identity::SubsOf` (`max_values`: None, `max_size`: Some(3258), added: 5733, mode: `MaxEncodedLen`)
/// Storage: `System::Account` (r:2 w:2)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
/// Storage: `Dmp::DeliveryFeeFactor` (r:1 w:0)
/// Proof: `Dmp::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `XcmPallet::SupportedVersion` (r:1 w:0)
/// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `Dmp::DownwardMessageQueues` (r:1 w:1)
/// Proof: `Dmp::DownwardMessageQueues` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1)
/// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `Identity::SuperOf` (r:0 w:100)
/// Proof: `Identity::SuperOf` (`max_values`: None, `max_size`: Some(114), added: 2589, mode: `MaxEncodedLen`)
/// The range of component `r` is `[0, 20]`.
/// The range of component `s` is `[0, 100]`.
fn reap_identity(r: u32, s: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `7292 + r * (8 ±0) + s * (32 ±0)`
// Estimated: `11003 + r * (8 ±0) + s * (33 ±0)`
// Minimum execution time: 163_756_000 picoseconds.
Weight::from_parts(158_982_500, 0)
.saturating_add(Weight::from_parts(0, 11003))
// Standard Error: 1_143_629
.saturating_add(Weight::from_parts(238_675, 0).saturating_mul(r.into()))
// Standard Error: 228_725
.saturating_add(Weight::from_parts(1_529_645, 0).saturating_mul(s.into()))
.saturating_add(T::DbWeight::get().reads(8))
.saturating_add(T::DbWeight::get().writes(5))
.saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into())))
.saturating_add(Weight::from_parts(0, 8).saturating_mul(r.into()))
.saturating_add(Weight::from_parts(0, 33).saturating_mul(s.into()))
}
/// Storage: `Identity::IdentityOf` (r:1 w:1)
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7538), added: 10013, mode: `MaxEncodedLen`)
/// Storage: `System::Account` (r:1 w:1)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
/// Storage: `Identity::SubsOf` (r:1 w:1)
/// Proof: `Identity::SubsOf` (`max_values`: None, `max_size`: Some(3258), added: 5733, mode: `MaxEncodedLen`)
fn poke_deposit() -> Weight {
// Proof Size summary in bytes:
// Measured: `7229`
// Estimated: `11003`
// Minimum execution time: 137_570_000 picoseconds.
Weight::from_parts(137_570_000, 0)
.saturating_add(Weight::from_parts(0, 11003))
.saturating_add(T::DbWeight::get().reads(3))
.saturating_add(T::DbWeight::get().writes(3))
}
}