diff --git a/xcm/xcm-builder/src/lib.rs b/xcm/xcm-builder/src/lib.rs index 29e05d42cdf8..4bdcd33290ba 100644 --- a/xcm/xcm-builder/src/lib.rs +++ b/xcm/xcm-builder/src/lib.rs @@ -84,7 +84,7 @@ pub use filter_asset_location::{Case, NativeAsset}; mod universal_exports; pub use universal_exports::{ - BridgeBlobDispatcher, BridgeMessage, DispatchBlob, DispatchBlobError, ExporterFor, HaulBlob, - HaulBlobError, HaulBlobExporter, NetworkExportTable, SovereignPaidRemoteExporter, - UnpaidLocalExporter, UnpaidRemoteExporter, + ensure_is_remote, BridgeBlobDispatcher, BridgeMessage, DispatchBlob, DispatchBlobError, + ExporterFor, HaulBlob, HaulBlobError, HaulBlobExporter, NetworkExportTable, + SovereignPaidRemoteExporter, UnpaidLocalExporter, UnpaidRemoteExporter, }; diff --git a/xcm/xcm-builder/src/location_conversion.rs b/xcm/xcm-builder/src/location_conversion.rs index 4239dc17dbdc..66c9fc6dd31d 100644 --- a/xcm/xcm-builder/src/location_conversion.rs +++ b/xcm/xcm-builder/src/location_conversion.rs @@ -14,6 +14,7 @@ // 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; @@ -22,15 +23,15 @@ use sp_std::{borrow::Borrow, marker::PhantomData}; use xcm::latest::prelude::*; use xcm_executor::traits::Convert; -/// Prefix for generating alias account for accounts coming +/// 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 +/// 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 +/// 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"; @@ -48,7 +49,7 @@ pub const FOREIGN_CHAIN_PREFIX_RELAY: [u8; 36] = *b"ForeignChainAliasAccountPref /// ```notrust /// R /// / \ -/// / \ +/// / \ /// P1 P2 /// / \ / \ /// / \ / \ @@ -252,6 +253,58 @@ impl>, AccountId: From<[u8; 20]> + Into<[u8; 20]> } } +/// 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 will 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::*; @@ -331,6 +384,112 @@ mod tests { 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 { diff --git a/xcm/xcm-builder/src/universal_exports.rs b/xcm/xcm-builder/src/universal_exports.rs index a9868021c011..2d1c9f6d4c35 100644 --- a/xcm/xcm-builder/src/universal_exports.rs +++ b/xcm/xcm-builder/src/universal_exports.rs @@ -23,7 +23,11 @@ use xcm::prelude::*; use xcm_executor::traits::{validate_export, ExportXcm}; use SendError::*; -fn ensure_is_remote( +/// Returns the network ID and consensus location within that network of the remote +/// location `dest` which is itself specified as a location relative to the local +/// chain, itself situated at `universal_local` within the consensus universe. If +/// `dest` is not a location in remote consensus, then an error is returned. +pub fn ensure_is_remote( universal_local: impl Into, dest: impl Into, ) -> Result<(NetworkId, InteriorMultiLocation), MultiLocation> {