// 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 .
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::*, VersionedLocation, 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 {
#[codec(index = 248)]
IdentityMigrator(IdentityMigratorCalls),
}
/// Call encoding for the calls needed from the Identity Migrator pallet.
#[derive(Encode, Decode)]
enum IdentityMigratorCalls {
#[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(PhantomData<(Runtime, AccountId)>);
impl ToParachainIdentityReaper {
/// 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))
}
}
// Note / Warning: This implementation should only be used in a transactional context. If not, then
// an error could result in assets being burned.
impl OnReapIdentity for ToParachainIdentityReaper
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 = Asset { id: AssetId(Here.into_location()), fun: Fungible(total_to_send) };
// People Chain: ParaId 1004
let destination: Location = Location::new(0, Parachain(1004));
// Do `check_out` accounting since the XCM Executor's `InitiateTeleport` doesn't support
// unpaid teleports.
// withdraw the asset from `who`
let who_origin =
Junction::AccountId32 { network: None, id: who.clone().into() }.into_location();
let _withdrawn = xcm_config::LocalAssetTransactor::withdraw_asset(&roc, &who_origin, None)
.map_err(|err| {
log::error!(
target: "runtime::on_reap_identity",
"withdraw_asset(what: {:?}, who_origin: {:?}) error: {:?}",
roc, who_origin, err
);
pallet_xcm::Error::::LowBalance
})?;
// 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(|err| {
log::error!(
target: "runtime::on_reap_identity",
"can_check_out(destination: {:?}, asset: {:?}, _) error: {:?}",
destination, roc, err
);
pallet_xcm::Error::::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: Assets =
vec![Asset { id: AssetId(Location::new(1, Here)), fun: Fungible(total_to_send) }]
.into();
let poke = PeopleRuntimePallets::::IdentityMigrator(PokeDeposit(who.clone()));
let remote_weight_limit = MigratorWeights::::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(),
},
]);
let encoded_versioned_xcm =
VersionedXcm::V4(program).encode().try_into().map_err(|error| {
log::error!(target: "runtime::on_reap_identity", "XCM too large, error: {:?}", error);
pallet_xcm::Error::::XcmTooLarge
})?;
// send
let _ = >::send_blob(
RawOrigin::Root.into(),
Box::new(VersionedLocation::V4(destination)),
encoded_versioned_xcm,
)?;
Ok(())
}
}