Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions integration-tests/ahm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,7 @@ sp-runtime = { workspace = true, default-features = true }
sp-storage = { workspace = true, default-features = true }
sp-tracing = { workspace = true, default-features = true }
tokio = { features = ["full", "macros"], workspace = true }
xcm-builder = { workspace = true, default-features = true }
xcm-emulator = { workspace = true, default-features = true }
polkadot-runtime-common = { workspace = true, default-features = true }
codec = { workspace = true, default-features = true }
93 changes: 89 additions & 4 deletions integration-tests/ahm/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@
//! SNAP_RC="../../polkadot.snap" SNAP_AH="../../ah-polkadot.snap" RUST_LOG="info" ct polkadot-integration-tests-ahm -r on_initialize_works -- --nocapture
//! ```

use cumulus_primitives_core::{AggregateMessageOrigin, ParaId};
use asset_hub_polkadot_runtime::Runtime as AssetHub;
use cumulus_primitives_core::{AggregateMessageOrigin, Junction, Location, ParaId};
use frame_support::traits::*;
use pallet_rc_migrator::{types::PalletMigrationChecks, MigrationStage, RcMigrationStage};
use std::str::FromStr;

use asset_hub_polkadot_runtime::Runtime as AssetHub;
use polkadot_runtime::Runtime as Polkadot;
use polkadot_runtime_common::paras_registrar;
use sp_runtime::AccountId32;
use std::{collections::BTreeMap, str::FromStr};
use xcm_emulator::ConvertLocation;

use super::mock::*;

Expand Down Expand Up @@ -113,3 +115,86 @@ async fn account_migration_works() {
// some overweight ones.
});
}

#[test]
fn sovereign_account_translation() {
let good_cases = [
(
// para 2094 account https://polkadot.subscan.io/account/13YMK2dzLWfnGZXSLuAxgZbBiNMHLfnPZ8itzwXryJ9FcWsE
"13YMK2dzLWfnGZXSLuAxgZbBiNMHLfnPZ8itzwXryJ9FcWsE",
// on ah (different account id) https://assethub-polkadot.subscan.io/account/13cKp88oRErgQAFatu83oCvzxr2b45qVcnNLFu4Mr2ApU6ZC
"13cKp88oRErgQAFatu83oCvzxr2b45qVcnNLFu4Mr2ApU6ZC",
),
(
"13YMK2dsXbyC866w2tFM4vH52nRs3uTwac32jh1FNXZBXv18",
"13cKp88gcLA6Fgq5atCSBZctHG7AmKX3eFgTzeXkFFakPWuo",
),
];

for (rc_acc, ah_acc) in good_cases {
let rc_acc = AccountId32::from_str(rc_acc).unwrap();
let ah_acc = AccountId32::from_str(ah_acc).unwrap();

let (translated, _para_id) = pallet_rc_migrator::accounts::AccountsMigrator::<Polkadot>::try_translate_rc_sovereign_to_ah(rc_acc).unwrap().unwrap();
assert_eq!(translated, ah_acc);
}

let bad_cases = [
"13yJaZUmhMDG91AftfdNeJm6hMVSL9Jq2gqiyFdhiJgXf6AY", // wrong prefix
"13ddruDZgGbfVmbobzfNLV4momSgjkFnMXkfogizb4uEbHtQ", // "
"13cF4T4kfi8VYw2nTZfkYkn9BjGpmRDsivYxFqGYUWkU8L2d", // "
"13cKp88gcLA6Fgq5atCSBZctHG7AmKX3eFgTzeXkFFakPo6e", // last byte not 0
"13cF4T4kfiJ39NqGh4DAZSMo6NuWT1fYfZzCo9f5HH8dUFBJ", // 7 byte not zero
"13cKp88gcLA6Fgq5atCSBZctHGenFzUo3qmmReNVKzpnGvFg", // some center byte not zero
];

for rc_acc in bad_cases {
let rc_acc = AccountId32::from_str(rc_acc).unwrap();

let translated = pallet_rc_migrator::accounts::AccountsMigrator::<Polkadot>::try_translate_rc_sovereign_to_ah(rc_acc).unwrap();
assert!(translated.is_none());
}
}

/// For human consumption.
#[tokio::test]
async fn print_sovereign_account_translation() {
let (mut rc, mut ah) = load_externalities().await.unwrap();

let mut rc_to_ah = BTreeMap::new();

rc.execute_with(|| {
for para_id in paras_registrar::Paras::<Polkadot>::iter_keys().collect::<Vec<_>>() {
let rc_acc = xcm_builder::ChildParachainConvertsVia::<ParaId, AccountId32>::convert_location(&Location::new(0, Junction::Parachain(para_id.into()))).unwrap();

let (ah_acc, para_id) = pallet_rc_migrator::accounts::AccountsMigrator::<Polkadot>::try_translate_rc_sovereign_to_ah(rc_acc.clone()).unwrap().unwrap();
rc_to_ah.insert(rc_acc, (ah_acc, para_id));
}

for account in frame_system::Account::<Polkadot>::iter_keys() {
let translated = pallet_rc_migrator::accounts::AccountsMigrator::<Polkadot>::try_translate_rc_sovereign_to_ah(account.clone()).unwrap();

if let Some((ah_acc, para_id)) = translated {
if !rc_to_ah.contains_key(&account) {
println!("Account belongs to an unregistered para {}: {}", para_id, account);
rc_to_ah.insert(account, (ah_acc, para_id));
}
}
}
});

let mut csv: String = "para,rc,ah\n".into();

// Sanity check that they all exist. Note that they dont *have to*, but all do.
println!("Translating {} RC accounts to AH", rc_to_ah.len());
ah.execute_with(|| {
for (rc_acc, (ah_acc, para_id)) in rc_to_ah.iter() {
println!("[{}] {} -> {}", para_id, rc_acc, ah_acc);

csv.push_str(&format!("{},{},{}\n", para_id, rc_acc, ah_acc));
}
});

//std::fs::write("../../pallets/rc-migrator/src/sovereign_account_translation.csv",
// csv).unwrap();
}
32 changes: 32 additions & 0 deletions pallets/rc-migrator/src/accounts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Account Migration

Accounts are migrated with all their balance, locks and reserves at the beginning of the Asset Hub
migration.

## User Impact

Users need to be aware that all of their funds will be moved from the Relay chain to the Asset Hub.
The Account ID will stay the same. This ensures that normal user accounts will be to control their
funds on Asset Hub.

## Sovereign Account Translation

For parachain sovereign accounts, it is not possible to just use the same account ID. The sovereign
account address of a parachain is calculated differently, depending on whether it is the account on
the Relay or a parachain (like Asset Hub).

There are different kinds of sovereign accounts. In this context, we only focus on these parachain
sovereign accounts:
- On the Relay: derived from `"para" ++ para_id ++ 00..`
- On the Asset Hub and all other sibling parachains: derived from `"sibl" ++ para_id ++ 00..`

Our translation logic inverts the derivation and changes the prefix from `"para"` to `"sibl"` for
all accounts that match the pattern `"para" ++ para_id ++ 00..`. The full list of translated
accounts is in [this CSV file](./sovereign_account_translation.csv).

It is advised that parachains check that they can control their account on Asset Hub. They can also
forego this check if they do not need control thereof - for example when they are not holding any
funds on their relay sovereign account. However, please note that someone could still send funds to
that address before or after the migration.

Example for Bifrost: this is the [relay sovereign account](https://polkadot.subscan.io/account/13YMK2eeopZtUNpeHnJ1Ws2HqMQG6Ts9PGCZYGyFbSYoZfcm) and it gets translated to this [sibling sovereign account](https://assethub-polkadot.subscan.io/account/13cKp89TtYknbyYnqnF6dWN75q5ZosvFSuqzoEVkUAaNR47A).
32 changes: 32 additions & 0 deletions pallets/rc-migrator/src/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,11 @@ AH: mint_into(checking, checking_total - total_issuance) // publishes Balances::
*/

use crate::{types::*, *};
use codec::DecodeAll;
use frame_support::{traits::tokens::IdAmount, weights::WeightMeter};
use frame_system::Account as SystemAccount;
use pallet_balances::{AccountData, BalanceLock};
use sp_core::ByteArray;
use sp_runtime::traits::Zero;

/// Account type meant to transfer data between RC and AH.
Expand Down Expand Up @@ -542,4 +544,34 @@ impl<T: Config> AccountsMigrator<T> {
// TODO: define actual weight
Weight::from_all(1)
}

/// Try to translate a Parachain sovereign account to the Parachain AH sovereign account.
///
/// Returns:
/// - `Ok(None)` if the account is not a Parachain sovereign account
/// - `Ok(Some((ah_account, para_id)))` with the translated account and the para id
/// - `Err(())` otherwise
pub fn try_translate_rc_sovereign_to_ah(
acc: T::AccountId,
) -> Result<Option<(T::AccountId, u16)>, ()> {
let raw = acc.to_raw_vec();

// Must start with "para"
let Some(raw) = raw.strip_prefix(b"para") else {
return Ok(None);
};
// Must end with 26 zero bytes
let Some(raw) = raw.strip_suffix(&[0u8; 26]) else {
return Ok(None);
};
let para_id = u16::decode_all(&mut &raw[..]).map_err(|_| ())?;

// Translate to AH sibling account
let mut ah_raw = [0u8; 32];
ah_raw[0..4].copy_from_slice(b"sibl");
ah_raw[4..6].copy_from_slice(&para_id.encode());
let ah_acc = ah_raw.try_into().map_err(|_| ()).defensive()?;

Ok(Some((ah_acc, para_id)))
}
}
Loading
Loading