diff --git a/xcm/xcm-builder/src/location_conversion.rs b/xcm/xcm-builder/src/location_conversion.rs index 00aea3d2969d..4239dc17dbdc 100644 --- a/xcm/xcm-builder/src/location_conversion.rs +++ b/xcm/xcm-builder/src/location_conversion.rs @@ -22,6 +22,111 @@ use sp_std::{borrow::Borrow, marker::PhantomData}; use xcm::latest::prelude::*; use xcm_executor::traits::Convert; +/// Prefix for generating alias account for accounts coming +/// from chains that use 32 byte long representations. +pub const FOREIGN_CHAIN_PREFIX_PARA_32: [u8; 37] = *b"ForeignChainAliasAccountPrefix_Para32"; + +/// Prefix for generating alias account for accounts coming +/// from chains that use 20 byte long representations. +pub const FOREIGN_CHAIN_PREFIX_PARA_20: [u8; 37] = *b"ForeignChainAliasAccountPrefix_Para20"; + +/// Prefix for generating alias account for accounts coming +/// from the relay chain using 32 byte long representations. +pub const FOREIGN_CHAIN_PREFIX_RELAY: [u8; 36] = *b"ForeignChainAliasAccountPrefix_Relay"; + +/// This converter will for a given `AccountId32`/`AccountKey20` +/// always generate the same "remote" account for a specific +/// sending chain. +/// I.e. the user gets the same remote account +/// on every consuming para-chain and relay chain. +/// +/// Can be used as a converter in `SovereignSignedViaLocation` +/// +/// ## Example +/// Assuming the following network layout. +/// +/// ```notrust +/// R +/// / \ +/// / \ +/// P1 P2 +/// / \ / \ +/// / \ / \ +/// P1.1 P1.2 P2.1 P2.2 +/// ``` +/// Then a given account A will have the same alias accounts in the +/// same plane. So, it is important which chain account A acts from. +/// E.g. +/// * From P1.2 A will act as +/// * hash(ParaPrefix, A, 1, 1) on P1.2 +/// * hash(ParaPrefix, A, 1, 0) on P1 +/// * From P1 A will act as +/// * hash(RelayPrefix, A, 1) on P1.2 & P1.1 +/// * hash(ParaPrefix, A, 1, 1) on P2 +/// * hash(ParaPrefix, A, 1, 0) on R +/// +/// Note that the alias accounts have overlaps but never on the same +/// chain when the sender comes from different chains. +pub struct ForeignChainAliasAccount(PhantomData); +impl + Clone> Convert + for ForeignChainAliasAccount +{ + fn convert_ref(location: impl Borrow) -> Result { + let entropy = match location.borrow() { + // Used on the relay chain for sending paras that use 32 byte accounts + MultiLocation { + parents: 0, + interior: X2(Parachain(para_id), AccountId32 { id, .. }), + } => ForeignChainAliasAccount::::from_para_32(para_id, id, 0), + + // Used on the relay chain for sending paras that use 20 byte accounts + MultiLocation { + parents: 0, + interior: X2(Parachain(para_id), AccountKey20 { key, .. }), + } => ForeignChainAliasAccount::::from_para_20(para_id, key, 0), + + // Used on para-chain for sending paras that use 32 byte accounts + MultiLocation { + parents: 1, + interior: X2(Parachain(para_id), AccountId32 { id, .. }), + } => ForeignChainAliasAccount::::from_para_32(para_id, id, 1), + + // Used on para-chain for sending paras that use 20 byte accounts + MultiLocation { + parents: 1, + interior: X2(Parachain(para_id), AccountKey20 { key, .. }), + } => ForeignChainAliasAccount::::from_para_20(para_id, key, 1), + + // Used on para-chain for sending from the relay chain + MultiLocation { parents: 1, interior: X1(AccountId32 { id, .. }) } => + ForeignChainAliasAccount::::from_relay_32(id, 1), + + // No other conversions provided + _ => return Err(()), + }; + + Ok(entropy.into()) + } + + fn reverse_ref(_: impl Borrow) -> Result { + Err(()) + } +} + +impl ForeignChainAliasAccount { + fn from_para_32(para_id: &u32, id: &[u8; 32], parents: u8) -> [u8; 32] { + (FOREIGN_CHAIN_PREFIX_PARA_32, para_id, id, parents).using_encoded(blake2_256) + } + + fn from_para_20(para_id: &u32, id: &[u8; 20], parents: u8) -> [u8; 32] { + (FOREIGN_CHAIN_PREFIX_PARA_20, para_id, id, parents).using_encoded(blake2_256) + } + + fn from_relay_32(id: &[u8; 32], parents: u8) -> [u8; 32] { + (FOREIGN_CHAIN_PREFIX_RELAY, id, parents).using_encoded(blake2_256) + } +} + pub struct Account32Hash(PhantomData<(Network, AccountId)>); impl>, AccountId: From<[u8; 32]> + Into<[u8; 32]> + Clone> Convert for Account32Hash @@ -225,4 +330,215 @@ mod tests { let inverted = UniversalLocation::get().invert_target(&input); assert_eq!(inverted, Err(())); } + + #[test] + fn remote_account_convert_on_para_sending_para_32() { + let mul = MultiLocation { + parents: 1, + interior: X2(Parachain(1), AccountId32 { network: None, id: [0u8; 32] }), + }; + let rem_1 = ForeignChainAliasAccount::<[u8; 32]>::convert(mul).unwrap(); + + assert_eq!( + [ + 181, 186, 132, 152, 52, 210, 226, 199, 8, 235, 213, 242, 94, 70, 250, 170, 19, 163, + 196, 102, 245, 14, 172, 184, 2, 148, 108, 87, 230, 163, 204, 32 + ], + rem_1 + ); + + let mul = MultiLocation { + parents: 1, + interior: X2( + Parachain(1), + AccountId32 { network: Some(NetworkId::Polkadot), id: [0u8; 32] }, + ), + }; + + assert_eq!(ForeignChainAliasAccount::<[u8; 32]>::convert(mul).unwrap(), rem_1); + + let mul = MultiLocation { + parents: 1, + interior: X2(Parachain(2), AccountId32 { network: None, id: [0u8; 32] }), + }; + let rem_2 = ForeignChainAliasAccount::<[u8; 32]>::convert(mul).unwrap(); + + assert_eq!( + [ + 183, 188, 66, 169, 82, 250, 45, 30, 142, 119, 184, 55, 177, 64, 53, 114, 12, 147, + 128, 10, 60, 45, 41, 193, 87, 18, 86, 49, 127, 233, 243, 143 + ], + rem_2 + ); + + assert_ne!(rem_1, rem_2); + } + + #[test] + fn remote_account_convert_on_para_sending_para_20() { + let mul = MultiLocation { + parents: 1, + interior: X2(Parachain(1), AccountKey20 { network: None, key: [0u8; 20] }), + }; + let rem_1 = ForeignChainAliasAccount::<[u8; 32]>::convert(mul).unwrap(); + + assert_eq!( + [ + 210, 60, 37, 255, 116, 38, 221, 26, 85, 82, 252, 125, 220, 19, 41, 91, 185, 69, + 102, 83, 120, 63, 15, 212, 74, 141, 82, 203, 187, 212, 77, 120 + ], + rem_1 + ); + + let mul = MultiLocation { + parents: 1, + interior: X2( + Parachain(1), + AccountKey20 { network: Some(NetworkId::Polkadot), key: [0u8; 20] }, + ), + }; + + assert_eq!(ForeignChainAliasAccount::<[u8; 32]>::convert(mul).unwrap(), rem_1); + + let mul = MultiLocation { + parents: 1, + interior: X2(Parachain(2), AccountKey20 { network: None, key: [0u8; 20] }), + }; + let rem_2 = ForeignChainAliasAccount::<[u8; 32]>::convert(mul).unwrap(); + + assert_eq!( + [ + 197, 16, 31, 199, 234, 80, 166, 55, 178, 135, 95, 48, 19, 128, 9, 167, 51, 99, 215, + 147, 94, 171, 28, 157, 29, 107, 240, 22, 10, 104, 99, 186 + ], + rem_2 + ); + + assert_ne!(rem_1, rem_2); + } + + #[test] + fn remote_account_convert_on_para_sending_relay() { + let mul = MultiLocation { + parents: 1, + interior: X1(AccountId32 { network: None, id: [0u8; 32] }), + }; + let rem_1 = ForeignChainAliasAccount::<[u8; 32]>::convert(mul).unwrap(); + + assert_eq!( + [ + 227, 12, 152, 241, 220, 53, 26, 27, 1, 167, 167, 214, 61, 161, 255, 96, 56, 16, + 221, 59, 47, 45, 40, 193, 88, 92, 4, 167, 164, 27, 112, 99 + ], + rem_1 + ); + + let mul = MultiLocation { + parents: 1, + interior: X1(AccountId32 { network: Some(NetworkId::Polkadot), id: [0u8; 32] }), + }; + + assert_eq!(ForeignChainAliasAccount::<[u8; 32]>::convert(mul).unwrap(), rem_1); + + let mul = MultiLocation { + parents: 1, + interior: X1(AccountId32 { network: None, id: [1u8; 32] }), + }; + let rem_2 = ForeignChainAliasAccount::<[u8; 32]>::convert(mul).unwrap(); + + assert_eq!( + [ + 143, 195, 87, 73, 129, 2, 163, 211, 239, 51, 55, 235, 82, 173, 162, 206, 158, 237, + 166, 73, 254, 62, 131, 6, 170, 241, 209, 116, 105, 69, 29, 226 + ], + rem_2 + ); + + assert_ne!(rem_1, rem_2); + } + + #[test] + fn remote_account_convert_on_relay_sending_para_20() { + let mul = MultiLocation { + parents: 0, + interior: X2(Parachain(1), AccountKey20 { network: None, key: [0u8; 20] }), + }; + let rem_1 = ForeignChainAliasAccount::<[u8; 32]>::convert(mul).unwrap(); + + assert_eq!( + [ + 25, 251, 15, 92, 148, 141, 236, 238, 50, 108, 133, 56, 118, 11, 250, 122, 81, 160, + 104, 160, 97, 200, 210, 49, 208, 142, 64, 144, 24, 110, 246, 101 + ], + rem_1 + ); + + let mul = MultiLocation { + parents: 0, + interior: X2(Parachain(2), AccountKey20 { network: None, key: [0u8; 20] }), + }; + let rem_2 = ForeignChainAliasAccount::<[u8; 32]>::convert(mul).unwrap(); + + assert_eq!( + [ + 88, 157, 224, 235, 76, 88, 201, 143, 206, 227, 14, 192, 177, 245, 75, 62, 41, 10, + 107, 182, 61, 57, 239, 112, 43, 151, 58, 111, 150, 153, 234, 189 + ], + rem_2 + ); + + assert_ne!(rem_1, rem_2); + } + + #[test] + fn remote_account_convert_on_relay_sending_para_32() { + let mul = MultiLocation { + parents: 0, + interior: X2(Parachain(1), AccountId32 { network: None, id: [0u8; 32] }), + }; + let rem_1 = ForeignChainAliasAccount::<[u8; 32]>::convert(mul).unwrap(); + + assert_eq!( + [ + 45, 120, 232, 0, 226, 49, 106, 48, 65, 181, 184, 147, 224, 235, 198, 152, 183, 156, + 67, 57, 67, 67, 187, 104, 171, 23, 140, 21, 183, 152, 63, 20 + ], + rem_1 + ); + + let mul = MultiLocation { + parents: 0, + interior: X2( + Parachain(1), + AccountId32 { network: Some(NetworkId::Polkadot), id: [0u8; 32] }, + ), + }; + + assert_eq!(ForeignChainAliasAccount::<[u8; 32]>::convert(mul).unwrap(), rem_1); + + let mul = MultiLocation { + parents: 0, + interior: X2(Parachain(2), AccountId32 { network: None, id: [0u8; 32] }), + }; + let rem_2 = ForeignChainAliasAccount::<[u8; 32]>::convert(mul).unwrap(); + + assert_eq!( + [ + 97, 119, 110, 66, 239, 113, 96, 234, 127, 92, 66, 204, 53, 129, 33, 119, 213, 192, + 171, 100, 139, 51, 39, 62, 196, 163, 16, 213, 160, 44, 100, 228 + ], + rem_2 + ); + + assert_ne!(rem_1, rem_2); + } + + #[test] + fn remote_account_fails_with_bad_multilocation() { + let mul = MultiLocation { + parents: 1, + interior: X1(AccountKey20 { network: None, key: [0u8; 20] }), + }; + assert!(ForeignChainAliasAccount::<[u8; 32]>::convert(mul).is_err()); + } }