// 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::universal_exports::ensure_is_remote; use frame_support::traits::Get; use parity_scale_codec::{Decode, Encode}; use sp_io::hashing::blake2_256; use sp_runtime::traits::{AccountIdConversion, TrailingZeroInput}; 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 { fn convert_ref(location: impl Borrow) -> Result { Ok(("multiloc", location.borrow()).using_encoded(blake2_256).into()) } fn reverse_ref(_: impl Borrow) -> Result { Err(()) } } /// A [`MultiLocation`] consisting of a single `Parent` [`Junction`] will be converted to the /// parent `AccountId`. pub struct ParentIsPreset(PhantomData); impl Convert for ParentIsPreset { fn convert_ref(location: impl Borrow) -> Result { if location.borrow().contains_parents_only(1) { Ok(b"Parent" .using_encoded(|b| AccountId::decode(&mut TrailingZeroInput::new(b))) .expect("infinite length input; no invalid inputs for type; qed")) } else { Err(()) } } fn reverse_ref(who: impl Borrow) -> Result { let parent_account = b"Parent" .using_encoded(|b| AccountId::decode(&mut TrailingZeroInput::new(b))) .expect("infinite length input; no invalid inputs for type; qed"); if who.borrow() == &parent_account { Ok(Parent.into()) } else { Err(()) } } } pub struct ChildParachainConvertsVia(PhantomData<(ParaId, AccountId)>); impl + Into + AccountIdConversion, AccountId: Clone> Convert for ChildParachainConvertsVia { fn convert_ref(location: impl Borrow) -> Result { match location.borrow() { MultiLocation { parents: 0, interior: X1(Parachain(id)) } => Ok(ParaId::from(*id).into_account_truncating()), _ => Err(()), } } fn reverse_ref(who: impl Borrow) -> Result { if let Some(id) = ParaId::try_from_account(who.borrow()) { Ok(Parachain(id.into()).into()) } else { Err(()) } } } pub struct SiblingParachainConvertsVia(PhantomData<(ParaId, AccountId)>); impl + Into + AccountIdConversion, AccountId: Clone> Convert for SiblingParachainConvertsVia { fn convert_ref(location: impl Borrow) -> Result { match location.borrow() { MultiLocation { parents: 1, interior: X1(Parachain(id)) } => Ok(ParaId::from(*id).into_account_truncating()), _ => Err(()), } } fn reverse_ref(who: impl Borrow) -> Result { if let Some(id) = ParaId::try_from_account(who.borrow()) { Ok(MultiLocation::new(1, X1(Parachain(id.into())))) } else { Err(()) } } } /// Extracts the `AccountId32` from the passed `location` if the network matches. pub struct AccountId32Aliases(PhantomData<(Network, AccountId)>); impl>, AccountId: From<[u8; 32]> + Into<[u8; 32]> + Clone> Convert for AccountId32Aliases { fn convert(location: MultiLocation) -> Result { let id = match location { MultiLocation { parents: 0, interior: X1(AccountId32 { id, network: None }) } => id, MultiLocation { parents: 0, interior: X1(AccountId32 { id, network }) } if network == Network::get() => id, _ => return Err(location), }; Ok(id.into()) } fn reverse(who: AccountId) -> Result { Ok(AccountId32 { id: who.into(), network: Network::get() }.into()) } } /// Conversion implementation which converts from a `[u8; 32]`-based `AccountId` into a /// `MultiLocation` consisting solely of a `AccountId32` junction with a fixed value for its /// network (provided by `Network`) and the `AccountId`'s `[u8; 32]` datum for the `id`. pub struct AliasesIntoAccountId32(PhantomData<(Network, AccountId)>); impl<'a, Network: Get>, AccountId: Clone + Into<[u8; 32]> + Clone> sp_runtime::traits::Convert<&'a AccountId, MultiLocation> for AliasesIntoAccountId32 { fn convert(who: &AccountId) -> MultiLocation { AccountId32 { network: Network::get(), id: who.clone().into() }.into() } } pub struct AccountKey20Aliases(PhantomData<(Network, AccountId)>); impl>, AccountId: From<[u8; 20]> + Into<[u8; 20]> + Clone> Convert for AccountKey20Aliases { fn convert(location: MultiLocation) -> Result { let key = match location { MultiLocation { parents: 0, interior: X1(AccountKey20 { key, network: None }) } => key, MultiLocation { parents: 0, interior: X1(AccountKey20 { key, network }) } if network == Network::get() => key, _ => return Err(location), }; Ok(key.into()) } fn reverse(who: AccountId) -> Result { let j = AccountKey20 { key: who.into(), network: Network::get() }; Ok(j.into()) } } /// Converts a location which is a top-level parachain (i.e. a parachain held on a /// Relay-chain which provides its own consensus) into a 32-byte `AccountId`. /// /// This will always result in the *same account ID* being returned for the same /// parachain index under the same Relay-chain, regardless of the relative security of /// this Relay-chain compared to the local chain. /// /// Note: No distinction is made when the local chain happens to be the parachain in /// question or its Relay-chain. /// /// WARNING: This results in the same `AccountId` value being generated regardless /// of the relative security of the local chain and the Relay-chain of the input /// location. This may not have any immediate security risks, however since it creates /// commonalities between chains with different security characteristics, it could /// possibly form part of a more sophisticated attack scenario. pub struct GlobalConsensusParachainConvertsFor( PhantomData<(UniversalLocation, AccountId)>, ); impl, AccountId: From<[u8; 32]> + Clone> Convert for GlobalConsensusParachainConvertsFor { fn convert_ref(location: impl Borrow) -> Result { let universal_source = UniversalLocation::get(); log::trace!( target: "xcm::location_conversion", "GlobalConsensusParachainConvertsFor universal_source: {:?}, location: {:?}", universal_source, location.borrow(), ); let devolved = ensure_is_remote(universal_source, *location.borrow()).map_err(|_| ())?; let (remote_network, remote_location) = devolved; match remote_location { X1(Parachain(remote_network_para_id)) => Ok(AccountId::from(Self::from_params(&remote_network, &remote_network_para_id))), _ => Err(()), } } fn reverse_ref(_: impl Borrow) -> Result { // if this is ever be needed, we could implement some kind of guessing, if we have // configuration for supported networkId+paraId Err(()) } } impl GlobalConsensusParachainConvertsFor { fn from_params(network: &NetworkId, para_id: &u32) -> [u8; 32] { (b"glblcnsnss/prchn_", network, para_id).using_encoded(blake2_256) } } #[cfg(test)] mod tests { use super::*; use frame_support::parameter_types; use xcm::latest::Junction; fn account20() -> Junction { AccountKey20 { network: None, key: Default::default() } } fn account32() -> Junction { AccountId32 { network: None, id: Default::default() } } // Network Topology // v Source // Relay -> Para 1 -> SmartContract -> Account // -> Para 2 -> Account // ^ Target // // Inputs and outputs written as file paths: // // input location (source to target): ../../../para_2/account32_default // context (root to source): para_1/account20_default/account20_default // => // output (target to source): ../../para_1/account20_default/account20_default #[test] fn inverter_works_in_tree() { parameter_types! { pub UniversalLocation: InteriorMultiLocation = X3(Parachain(1), account20(), account20()); } let input = MultiLocation::new(3, X2(Parachain(2), account32())); let inverted = UniversalLocation::get().invert_target(&input).unwrap(); assert_eq!(inverted, MultiLocation::new(2, X3(Parachain(1), account20(), account20()))); } // Network Topology // v Source // Relay -> Para 1 -> SmartContract -> Account // ^ Target #[test] fn inverter_uses_context_as_inverted_location() { parameter_types! { pub UniversalLocation: InteriorMultiLocation = X2(account20(), account20()); } let input = MultiLocation::grandparent(); let inverted = UniversalLocation::get().invert_target(&input).unwrap(); assert_eq!(inverted, X2(account20(), account20()).into()); } // Network Topology // v Source // Relay -> Para 1 -> CollectivePallet -> Plurality // ^ Target #[test] fn inverter_uses_only_child_on_missing_context() { parameter_types! { pub UniversalLocation: InteriorMultiLocation = PalletInstance(5).into(); } let input = MultiLocation::grandparent(); let inverted = UniversalLocation::get().invert_target(&input).unwrap(); assert_eq!(inverted, (OnlyChild, PalletInstance(5)).into()); } #[test] fn inverter_errors_when_location_is_too_large() { parameter_types! { pub UniversalLocation: InteriorMultiLocation = Here; } let input = MultiLocation { parents: 99, interior: X1(Parachain(88)) }; let inverted = UniversalLocation::get().invert_target(&input); assert_eq!(inverted, Err(())); } #[test] fn global_consensus_parachain_converts_for_works() { parameter_types! { pub UniversalLocation: InteriorMultiLocation = X2(GlobalConsensus(ByGenesis([9; 32])), Parachain(1234)); } let test_data = vec![ (MultiLocation::parent(), false), (MultiLocation::new(0, X1(Parachain(1000))), false), (MultiLocation::new(1, X1(Parachain(1000))), false), ( MultiLocation::new( 2, X3( GlobalConsensus(ByGenesis([0; 32])), Parachain(1000), AccountId32 { network: None, id: [1; 32].into() }, ), ), false, ), (MultiLocation::new(2, X1(GlobalConsensus(ByGenesis([0; 32])))), false), ( MultiLocation::new(0, X2(GlobalConsensus(ByGenesis([0; 32])), Parachain(1000))), false, ), ( MultiLocation::new(1, X2(GlobalConsensus(ByGenesis([0; 32])), Parachain(1000))), false, ), (MultiLocation::new(2, X2(GlobalConsensus(ByGenesis([0; 32])), Parachain(1000))), true), ( MultiLocation::new(3, X2(GlobalConsensus(ByGenesis([0; 32])), Parachain(1000))), false, ), ( MultiLocation::new(9, X2(GlobalConsensus(ByGenesis([0; 32])), Parachain(1000))), false, ), ]; for (location, expected_result) in test_data { let result = GlobalConsensusParachainConvertsFor::::convert_ref( &location, ); match result { Ok(account) => { assert_eq!( true, expected_result, "expected_result: {}, but conversion passed: {:?}, location: {:?}", expected_result, account, location ); match &location { MultiLocation { interior: X2(GlobalConsensus(network), Parachain(para_id)), .. } => assert_eq!( account, GlobalConsensusParachainConvertsFor::::from_params(network, para_id), "expected_result: {}, but conversion passed: {:?}, location: {:?}", expected_result, account, location ), _ => assert_eq!( true, expected_result, "expected_result: {}, conversion passed: {:?}, but MultiLocation does not match expected pattern, location: {:?}", expected_result, account, location ) } }, Err(_) => { assert_eq!( false, expected_result, "expected_result: {} - but conversion failed, location: {:?}", expected_result, location ); }, } } // all success let res_gc_a_p1000 = GlobalConsensusParachainConvertsFor::::convert_ref( MultiLocation::new(2, X2(GlobalConsensus(ByGenesis([3; 32])), Parachain(1000))), ) .expect("conversion is ok"); let res_gc_a_p1001 = GlobalConsensusParachainConvertsFor::::convert_ref( MultiLocation::new(2, X2(GlobalConsensus(ByGenesis([3; 32])), Parachain(1001))), ) .expect("conversion is ok"); let res_gc_b_p1000 = GlobalConsensusParachainConvertsFor::::convert_ref( MultiLocation::new(2, X2(GlobalConsensus(ByGenesis([4; 32])), Parachain(1000))), ) .expect("conversion is ok"); let res_gc_b_p1001 = GlobalConsensusParachainConvertsFor::::convert_ref( MultiLocation::new(2, X2(GlobalConsensus(ByGenesis([4; 32])), Parachain(1001))), ) .expect("conversion is ok"); assert_ne!(res_gc_a_p1000, res_gc_a_p1001); assert_ne!(res_gc_a_p1000, res_gc_b_p1000); assert_ne!(res_gc_a_p1000, res_gc_b_p1001); assert_ne!(res_gc_b_p1000, res_gc_b_p1001); assert_ne!(res_gc_b_p1000, res_gc_a_p1001); assert_ne!(res_gc_b_p1001, res_gc_a_p1001); } #[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()); } }