mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-26 22:47:56 +00:00
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:
@@ -79,8 +79,10 @@ mod tests;
|
||||
mod types;
|
||||
pub mod weights;
|
||||
|
||||
use codec::Encode;
|
||||
use frame_support::{
|
||||
pallet_prelude::DispatchResult,
|
||||
ensure,
|
||||
pallet_prelude::{DispatchError, DispatchResult},
|
||||
traits::{BalanceStatus, Currency, Get, OnUnbalanced, ReservableCurrency},
|
||||
};
|
||||
use sp_runtime::traits::{AppendZerosInput, Hash, Saturating, StaticLookup, Zero};
|
||||
@@ -395,8 +397,7 @@ pub mod pallet {
|
||||
);
|
||||
|
||||
let (old_deposit, old_ids) = <SubsOf<T>>::get(&sender);
|
||||
let new_deposit =
|
||||
T::SubAccountDeposit::get().saturating_mul(<BalanceOf<T>>::from(subs.len() as u32));
|
||||
let new_deposit = Self::subs_deposit(subs.len() as u32);
|
||||
|
||||
let not_other_sub =
|
||||
subs.iter().filter_map(|i| SuperOf::<T>::get(&i.0)).all(|i| i.0 == sender);
|
||||
@@ -898,6 +899,26 @@ impl<T: Config> Pallet<T> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Calculate the deposit required for a number of `sub` accounts.
|
||||
fn subs_deposit(subs: u32) -> BalanceOf<T> {
|
||||
T::SubAccountDeposit::get().saturating_mul(<BalanceOf<T>>::from(subs))
|
||||
}
|
||||
|
||||
/// Take the `current` deposit that `who` is holding, and update it to a `new` one.
|
||||
fn rejig_deposit(
|
||||
who: &T::AccountId,
|
||||
current: BalanceOf<T>,
|
||||
new: BalanceOf<T>,
|
||||
) -> DispatchResult {
|
||||
if new > current {
|
||||
T::Currency::reserve(who, new - current)?;
|
||||
} else if new < current {
|
||||
let err_amount = T::Currency::unreserve(who, current - new);
|
||||
debug_assert!(err_amount.is_zero());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if the account has corresponding identity information by the identity field.
|
||||
pub fn has_identity(
|
||||
who: &T::AccountId,
|
||||
@@ -906,4 +927,110 @@ impl<T: Config> Pallet<T> {
|
||||
IdentityOf::<T>::get(who)
|
||||
.map_or(false, |registration| (registration.info.has_identity(fields)))
|
||||
}
|
||||
|
||||
/// Reap an identity, clearing associated storage items and refunding any deposits. This
|
||||
/// function is very similar to (a) `clear_identity`, but called on a `target` account instead
|
||||
/// of self; and (b) `kill_identity`, but without imposing a slash.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `target`: The account for which to reap identity state.
|
||||
///
|
||||
/// Return type is a tuple of the number of registrars, `IdentityInfo` bytes, and sub accounts,
|
||||
/// respectively.
|
||||
///
|
||||
/// NOTE: This function is here temporarily for migration of Identity info from the Polkadot
|
||||
/// Relay Chain into a system parachain. It will be removed after the migration.
|
||||
pub fn reap_identity(who: &T::AccountId) -> Result<(u32, u32, u32), DispatchError> {
|
||||
// `take` any storage items keyed by `target`
|
||||
// identity
|
||||
let id = <IdentityOf<T>>::take(&who).ok_or(Error::<T>::NotNamed)?;
|
||||
let registrars = id.judgements.len() as u32;
|
||||
let encoded_byte_size = id.info.encoded_size() as u32;
|
||||
|
||||
// subs
|
||||
let (subs_deposit, sub_ids) = <SubsOf<T>>::take(&who);
|
||||
let actual_subs = sub_ids.len() as u32;
|
||||
for sub in sub_ids.iter() {
|
||||
<SuperOf<T>>::remove(sub);
|
||||
}
|
||||
|
||||
// unreserve any deposits
|
||||
let deposit = id.total_deposit().saturating_add(subs_deposit);
|
||||
let err_amount = T::Currency::unreserve(&who, deposit);
|
||||
debug_assert!(err_amount.is_zero());
|
||||
Ok((registrars, encoded_byte_size, actual_subs))
|
||||
}
|
||||
|
||||
/// Update the deposits held by `target` for its identity info.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `target`: The account for which to update deposits.
|
||||
///
|
||||
/// Return type is a tuple of the new Identity and Subs deposits, respectively.
|
||||
///
|
||||
/// NOTE: This function is here temporarily for migration of Identity info from the Polkadot
|
||||
/// Relay Chain into a system parachain. It will be removed after the migration.
|
||||
pub fn poke_deposit(
|
||||
target: &T::AccountId,
|
||||
) -> Result<(BalanceOf<T>, BalanceOf<T>), DispatchError> {
|
||||
// Identity Deposit
|
||||
let new_id_deposit = IdentityOf::<T>::try_mutate(
|
||||
&target,
|
||||
|registration| -> Result<BalanceOf<T>, DispatchError> {
|
||||
let reg = registration.as_mut().ok_or(Error::<T>::NoIdentity)?;
|
||||
// Calculate what deposit should be
|
||||
let encoded_byte_size = reg.info.encoded_size() as u32;
|
||||
let byte_deposit =
|
||||
T::ByteDeposit::get().saturating_mul(<BalanceOf<T>>::from(encoded_byte_size));
|
||||
let new_id_deposit = T::BasicDeposit::get().saturating_add(byte_deposit);
|
||||
|
||||
// Update account
|
||||
Self::rejig_deposit(&target, reg.deposit, new_id_deposit)?;
|
||||
|
||||
reg.deposit = new_id_deposit;
|
||||
Ok(new_id_deposit)
|
||||
},
|
||||
)?;
|
||||
|
||||
// Subs Deposit
|
||||
let new_subs_deposit = SubsOf::<T>::try_mutate(
|
||||
&target,
|
||||
|(current_subs_deposit, subs_of)| -> Result<BalanceOf<T>, DispatchError> {
|
||||
let new_subs_deposit = Self::subs_deposit(subs_of.len() as u32);
|
||||
Self::rejig_deposit(&target, *current_subs_deposit, new_subs_deposit)?;
|
||||
*current_subs_deposit = new_subs_deposit;
|
||||
Ok(new_subs_deposit)
|
||||
},
|
||||
)?;
|
||||
Ok((new_id_deposit, new_subs_deposit))
|
||||
}
|
||||
|
||||
/// Set an identity with zero deposit. Only used for benchmarking that involves `rejig_deposit`.
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub fn set_identity_no_deposit(
|
||||
who: &T::AccountId,
|
||||
info: T::IdentityInformation,
|
||||
) -> DispatchResult {
|
||||
IdentityOf::<T>::insert(
|
||||
&who,
|
||||
Registration {
|
||||
judgements: Default::default(),
|
||||
deposit: Zero::zero(),
|
||||
info: info.clone(),
|
||||
},
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set subs with zero deposit. Only used for benchmarking that involves `rejig_deposit`.
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub fn set_sub_no_deposit(who: &T::AccountId, sub: T::AccountId) -> DispatchResult {
|
||||
use frame_support::BoundedVec;
|
||||
let subs = BoundedVec::<_, T::MaxSubAccounts>::try_from(vec![sub]).unwrap();
|
||||
SubsOf::<T>::insert::<
|
||||
&T::AccountId,
|
||||
(BalanceOf<T>, BoundedVec<T::AccountId, T::MaxSubAccounts>),
|
||||
>(&who, (Zero::zero(), subs));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -712,3 +712,70 @@ fn test_has_identity() {
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reap_identity_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let ten_info = ten();
|
||||
assert_ok!(Identity::set_identity(RuntimeOrigin::signed(10), Box::new(ten_info.clone())));
|
||||
assert_ok!(Identity::set_subs(
|
||||
RuntimeOrigin::signed(10),
|
||||
vec![(20, Data::Raw(vec![40; 1].try_into().unwrap()))]
|
||||
));
|
||||
// deposit is correct
|
||||
let id_deposit = id_deposit(&ten_info);
|
||||
let subs_deposit: u64 = <<Test as Config>::SubAccountDeposit as Get<u64>>::get();
|
||||
assert_eq!(Balances::free_balance(10), 1000 - id_deposit - subs_deposit);
|
||||
// reap
|
||||
assert_ok!(Identity::reap_identity(&10));
|
||||
// no identity or subs
|
||||
assert!(Identity::identity(10).is_none());
|
||||
assert!(Identity::super_of(20).is_none());
|
||||
// balance is unreserved
|
||||
assert_eq!(Balances::free_balance(10), 1000);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn poke_deposit_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let ten_info = ten();
|
||||
// Set a custom registration with 0 deposit
|
||||
IdentityOf::<Test>::insert(
|
||||
&10,
|
||||
Registration {
|
||||
judgements: BoundedVec::default(),
|
||||
deposit: Zero::zero(),
|
||||
info: ten_info.clone(),
|
||||
},
|
||||
);
|
||||
assert!(Identity::identity(10).is_some());
|
||||
// Set a sub with zero deposit
|
||||
SubsOf::<Test>::insert::<&u64, (u64, BoundedVec<u64, ConstU32<2>>)>(
|
||||
&10,
|
||||
(0, vec![20].try_into().unwrap()),
|
||||
);
|
||||
SuperOf::<Test>::insert(&20, (&10, Data::Raw(vec![1; 1].try_into().unwrap())));
|
||||
// Balance is free
|
||||
assert_eq!(Balances::free_balance(10), 1000);
|
||||
|
||||
// poke
|
||||
assert_ok!(Identity::poke_deposit(&10));
|
||||
|
||||
// free balance reduced correctly
|
||||
let id_deposit = id_deposit(&ten_info);
|
||||
let subs_deposit: u64 = <<Test as Config>::SubAccountDeposit as Get<u64>>::get();
|
||||
assert_eq!(Balances::free_balance(10), 1000 - id_deposit - subs_deposit);
|
||||
// new registration deposit is 10
|
||||
assert_eq!(
|
||||
Identity::identity(&10),
|
||||
Some(Registration {
|
||||
judgements: BoundedVec::default(),
|
||||
deposit: id_deposit,
|
||||
info: ten()
|
||||
})
|
||||
);
|
||||
// new subs deposit is 10 vvvvvvvvvvvv
|
||||
assert_eq!(Identity::subs_of(10), (subs_deposit, vec![20].try_into().unwrap()));
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user