diff --git a/.github/workflows/auto-add-parachain-issues.yml b/.github/workflows/auto-add-parachain-issues.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6b5222b6ff74147b063d913ec0dcdec299a6fcea
--- /dev/null
+++ b/.github/workflows/auto-add-parachain-issues.yml
@@ -0,0 +1,30 @@
+# If there are new issues related to the async backing feature,
+# add it to the parachain team's board and set a custom "meta" field.
+
+name: Add selected issues to Parachain team board
+on:
+  issues:
+    types:
+      - labeled
+
+jobs:
+  add-parachain-issues:
+    if: github.event.label.name == 'T16-async_backing'
+    runs-on: ubuntu-latest
+    steps:
+      - name: Generate token
+        id: generate_token
+        uses: tibdex/github-app-token@v2.1.0
+        with:
+          app_id: ${{ secrets.PROJECT_APP_ID }}
+          private_key: ${{ secrets.PROJECT_APP_KEY }}
+      - name: Sync issues
+        uses: paritytech/github-issue-sync@v0.3.2
+        with:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          PROJECT_TOKEN: ${{ steps.generate_token.outputs.token }}
+          project: 119 # Parachain team board
+          project_field: 'meta'
+          project_value: 'async backing'
+          labels: |
+            T16-async_backing
diff --git a/.github/workflows/sync-templates.yml b/.github/workflows/sync-templates.yml
new file mode 100644
index 0000000000000000000000000000000000000000..511c9d0e8cd06f7b4b7b16126d6565cae9047a00
--- /dev/null
+++ b/.github/workflows/sync-templates.yml
@@ -0,0 +1,159 @@
+name: Synchronize templates
+
+
+# This job is used to keep the repository templates up-to-date.
+# The code of the templates exist inside the monorepo, and upon releases we synchronize the repositories:
+# - https://github.com/paritytech/polkadot-sdk-minimal-template
+# - https://github.com/paritytech/polkadot-sdk-parachain-template
+# - https://github.com/paritytech/polkadot-sdk-solochain-template
+#
+# The job moves the template code out of the monorepo,
+# replaces any references to the monorepo workspace using psvm and toml-cli,
+# checks that it builds successfully,
+# and commits and pushes the result to each respective repository.
+# If the build fails, a PR is created instead for manual inspection.
+
+
+on:
+  # A manual dispatch for now - automatic on releases later.
+  workflow_dispatch:
+    inputs:
+      crate_release_version:
+        description: 'A release version to use, e.g. 1.9.0'
+        required: true
+
+
+jobs:
+  sync-templates:
+    runs-on: ubuntu-latest
+    environment: master
+    strategy:
+      fail-fast: false
+      matrix:
+        template: ["minimal", "solochain", "parachain"]
+    env:
+      template-path: "polkadot-sdk-${{ matrix.template }}-template"
+    steps:
+
+      # 1. Prerequisites.
+
+      - name: Configure git identity
+        run: |
+          git config --global user.name "Template Bot"
+          git config --global user.email "163342540+paritytech-polkadotsdk-templatebot[bot]@users.noreply.github.com"
+      - uses: actions/checkout@v3
+        with:
+          path: polkadot-sdk
+          ref: "release-crates-io-v${{ github.event.inputs.crate_release_version }}"
+      - name: Generate a token for the template repository
+        id: app_token
+        uses: actions/create-github-app-token@v1.9.3
+        with:
+          owner: "paritytech"
+          repositories: "polkadot-sdk-${{ matrix.template }}-template"
+          app-id: ${{ secrets.TEMPLATE_APP_ID }}
+          private-key: ${{ secrets.TEMPLATE_APP_KEY }}
+      - uses: actions/checkout@v3
+        with:
+          repository: "paritytech/polkadot-sdk-${{ matrix.template }}-template"
+          path: "${{ env.template-path }}"
+          token: ${{ steps.app_token.outputs.token }}
+      - name: Install toml-cli
+        run: cargo install --git https://github.com/gnprice/toml-cli --rev ea69e9d2ca4f0f858110dc7a5ae28bcb918c07fb # v0.2.3
+      - name: Install Polkadot SDK Version Manager
+        run: cargo install --git https://github.com/paritytech/psvm --rev c41261ffb52ab0c115adbbdb17e2cb7900d2bdfd psvm # master
+      - name: Rust compilation prerequisites
+        run: |
+          sudo apt update
+          sudo apt install -y \
+            protobuf-compiler
+          rustup target add wasm32-unknown-unknown
+          rustup component add rustfmt clippy rust-src
+      
+      # 2. Yanking the template out of the monorepo workspace.
+
+      - name: Use psvm to replace git references with released creates.
+        run: find . -type f -name 'Cargo.toml' -exec psvm -o -v ${{ github.event.inputs.crate_release_version }} -p {} \;
+        working-directory: polkadot-sdk/templates/${{ matrix.template }}/
+      - name: Create a new workspace Cargo.toml
+        run: |
+          cat << EOF > Cargo.toml
+          [workspace.package]
+          license = "MIT-0"
+          authors = ["Parity Technologies <admin@parity.io>"]
+          homepage = "https://substrate.io"
+
+          [workspace]
+          members = [
+              "node",
+              "pallets/template",
+              "runtime",
+          ]
+          resolver = "2"
+          EOF
+        shell: bash
+        working-directory: polkadot-sdk/templates/${{ matrix.template }}/
+      - name: Update workspace configuration
+        run: |
+          set -euo pipefail
+          # toml-cli has no overwrite functionality yet, so we use temporary files.
+          # We cannot pipe the output straight to the same file while the CLI still reads and processes it.
+
+          toml set templates/${{ matrix.template }}/Cargo.toml 'workspace.package.repository' "https://github.com/paritytech/polkadot-sdk-${{ matrix.template }}-template.git" > Cargo.temp
+          mv Cargo.temp ./templates/${{ matrix.template }}/Cargo.toml
+
+          toml set templates/${{ matrix.template }}/Cargo.toml 'workspace.package.edition' "$(toml get --raw Cargo.toml 'workspace.package.edition')" > Cargo.temp
+          mv Cargo.temp ./templates/${{ matrix.template }}/Cargo.toml
+
+          toml get Cargo.toml 'workspace.lints' --output-toml >> ./templates/${{ matrix.template }}/Cargo.toml
+
+          toml get Cargo.toml 'workspace.dependencies' --output-toml >> ./templates/${{ matrix.template }}/Cargo.toml
+        working-directory: polkadot-sdk
+      - name: Print the result Cargo.tomls for debugging
+        if: runner.debug == '1'
+        run: find . -type f -name 'Cargo.toml' -exec cat {} \;
+        working-directory: polkadot-sdk/templates/${{ matrix.template }}/
+
+      - name: Clean the destination repository
+        run: rm -rf ./*
+        working-directory: "${{ env.template-path }}"
+      - name: Copy over the new changes
+        run: |
+          cp -r polkadot-sdk/templates/${{ matrix.template }}/* "${{ env.template-path }}/"
+
+      # 3. Verify the build. Push the changes or create a PR.
+
+      # We've run into out-of-disk error when compiling in the next step, so we free up some space this way.
+      - name: Free Disk Space (Ubuntu)
+        uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # 1.3.1
+        with:
+          android: true # This alone is a 12 GB save.
+          # We disable the rest because it caused some problems. (they're enabled by default)
+          # The Android removal is enough.
+          dotnet: false
+          haskell: false
+          large-packages: false
+          swap-storage: false
+
+      - name: Check if it compiles
+        id: check-compilation
+        run: cargo check && cargo test
+        working-directory: "${{ env.template-path }}"
+        timeout-minutes: 90
+      - name: Create PR on failure
+        if: failure() && steps.check-compilation.outcome == 'failure'
+        uses: peter-evans/create-pull-request@5b4a9f6a9e2af26e5f02351490b90d01eb8ec1e5 # v5
+        with:
+          path: "${{ env.template-path }}"
+          token: ${{ steps.app_token.outputs.token }}
+          add-paths: |
+            ./*
+          title: "[Don't merge] Update the ${{ matrix.template }} template"
+          body: "The template has NOT been successfully built and needs to be inspected."
+          branch: "update-template/${{ github.event_name }}"
+      - name: Push changes
+        run: |
+          git add -A .
+          git commit --allow-empty -m "Update template triggered by ${{ github.event_name }}"
+          git push
+        working-directory: "${{ env.template-path }}"
diff --git a/.gitlab/pipeline/zombienet/polkadot.yml b/.gitlab/pipeline/zombienet/polkadot.yml
index ba05c709a27b163386e36f680812e0cf24f10277..6b72075c513b73c075d1dc10c90d0461bf0e1a82 100644
--- a/.gitlab/pipeline/zombienet/polkadot.yml
+++ b/.gitlab/pipeline/zombienet/polkadot.yml
@@ -161,6 +161,8 @@ zombienet-polkadot-functional-0011-async-backing-6-seconds-rate:
 zombienet-polkadot-elastic-scaling-0001-basic-3cores-6s-blocks:
   extends:
     - .zombienet-polkadot-common
+  variables:
+    FORCED_INFRA_INSTANCE: "spot-iops"
   script:
     - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh
       --local-dir="${LOCAL_DIR}/elastic_scaling"
diff --git a/Cargo.lock b/Cargo.lock
index 8718e62aacb5a8fbae0f214e1c69f8b342c384cf..e9022ed1d8d9994eb60b224cdf1b81a17d82fad9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -809,6 +809,7 @@ dependencies = [
  "parachains-common",
  "rococo-emulated-chain",
  "sp-core",
+ "staging-xcm",
  "testnet-parachains-constants",
 ]
 
@@ -928,6 +929,7 @@ dependencies = [
  "frame-support",
  "parachains-common",
  "sp-core",
+ "staging-xcm",
  "testnet-parachains-constants",
  "westend-emulated-chain",
 ]
@@ -15793,6 +15795,7 @@ dependencies = [
  "pallet-multisig",
  "pallet-nis",
  "pallet-offences",
+ "pallet-parameters",
  "pallet-preimage",
  "pallet-proxy",
  "pallet-ranked-collective",
diff --git a/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs b/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4b0c052df8008410cb531c21d173ead2c4fdd450
--- /dev/null
+++ b/bridges/bin/runtime-common/src/extensions/check_obsolete_extension.rs
@@ -0,0 +1,205 @@
+// Copyright (C) Parity Technologies (UK) Ltd.
+// This file is part of Parity Bridges Common.
+
+// Parity Bridges Common 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.
+
+// Parity Bridges Common 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 Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
+
+//! Transaction extension that rejects bridge-related transactions, that include
+//! obsolete (duplicated) data or do not pass some additional pallet-specific
+//! checks.
+
+use crate::messages_call_ext::MessagesCallSubType;
+use pallet_bridge_grandpa::CallSubType as GrandpaCallSubType;
+use pallet_bridge_parachains::CallSubType as ParachainsCallSubtype;
+use sp_runtime::transaction_validity::TransactionValidity;
+
+/// A duplication of the `FilterCall` trait.
+///
+/// We need this trait in order to be able to implement it for the messages pallet,
+/// since the implementation is done outside of the pallet crate.
+pub trait BridgeRuntimeFilterCall<Call> {
+	/// Checks if a runtime call is valid.
+	fn validate(call: &Call) -> TransactionValidity;
+}
+
+impl<T, I: 'static> BridgeRuntimeFilterCall<T::RuntimeCall> for pallet_bridge_grandpa::Pallet<T, I>
+where
+	T: pallet_bridge_grandpa::Config<I>,
+	T::RuntimeCall: GrandpaCallSubType<T, I>,
+{
+	fn validate(call: &T::RuntimeCall) -> TransactionValidity {
+		GrandpaCallSubType::<T, I>::check_obsolete_submit_finality_proof(call)
+	}
+}
+
+impl<T, I: 'static> BridgeRuntimeFilterCall<T::RuntimeCall>
+	for pallet_bridge_parachains::Pallet<T, I>
+where
+	T: pallet_bridge_parachains::Config<I>,
+	T::RuntimeCall: ParachainsCallSubtype<T, I>,
+{
+	fn validate(call: &T::RuntimeCall) -> TransactionValidity {
+		ParachainsCallSubtype::<T, I>::check_obsolete_submit_parachain_heads(call)
+	}
+}
+
+impl<T: pallet_bridge_messages::Config<I>, I: 'static> BridgeRuntimeFilterCall<T::RuntimeCall>
+	for pallet_bridge_messages::Pallet<T, I>
+where
+	T::RuntimeCall: MessagesCallSubType<T, I>,
+{
+	/// Validate messages in order to avoid "mining" messages delivery and delivery confirmation
+	/// transactions, that are delivering outdated messages/confirmations. Without this validation,
+	/// even honest relayers may lose their funds if there are multiple relays running and
+	/// submitting the same messages/confirmations.
+	fn validate(call: &T::RuntimeCall) -> TransactionValidity {
+		call.check_obsolete_call()
+	}
+}
+
+/// Declares a runtime-specific `BridgeRejectObsoleteHeadersAndMessages` signed extension.
+///
+/// ## Example
+///
+/// ```nocompile
+/// generate_bridge_reject_obsolete_headers_and_messages!{
+///     Call, AccountId
+///     BridgeRococoGrandpa, BridgeRococoMessages,
+///     BridgeRococoParachains
+/// }
+/// ```
+///
+/// The goal of this extension is to avoid "mining" transactions that provide outdated bridged
+/// headers and messages. Without that extension, even honest relayers may lose their funds if
+/// there are multiple relays running and submitting the same information.
+#[macro_export]
+macro_rules! generate_bridge_reject_obsolete_headers_and_messages {
+	($call:ty, $account_id:ty, $($filter_call:ty),*) => {
+		#[derive(Clone, codec::Decode, Default, codec::Encode, Eq, PartialEq, sp_runtime::RuntimeDebug, scale_info::TypeInfo)]
+		pub struct BridgeRejectObsoleteHeadersAndMessages;
+		impl sp_runtime::traits::SignedExtension for BridgeRejectObsoleteHeadersAndMessages {
+			const IDENTIFIER: &'static str = "BridgeRejectObsoleteHeadersAndMessages";
+			type AccountId = $account_id;
+			type Call = $call;
+			type AdditionalSigned = ();
+			type Pre = ();
+
+			fn additional_signed(&self) -> sp_std::result::Result<
+				(),
+				sp_runtime::transaction_validity::TransactionValidityError,
+			> {
+				Ok(())
+			}
+
+			fn validate(
+				&self,
+				_who: &Self::AccountId,
+				call: &Self::Call,
+				_info: &sp_runtime::traits::DispatchInfoOf<Self::Call>,
+				_len: usize,
+			) -> sp_runtime::transaction_validity::TransactionValidity {
+				let valid = sp_runtime::transaction_validity::ValidTransaction::default();
+				$(
+					let valid = valid
+						.combine_with(<$filter_call as $crate::extensions::check_obsolete_extension::BridgeRuntimeFilterCall<$call>>::validate(call)?);
+				)*
+				Ok(valid)
+			}
+
+			fn pre_dispatch(
+				self,
+				who: &Self::AccountId,
+				call: &Self::Call,
+				info: &sp_runtime::traits::DispatchInfoOf<Self::Call>,
+				len: usize,
+			) -> Result<Self::Pre, sp_runtime::transaction_validity::TransactionValidityError> {
+				self.validate(who, call, info, len).map(drop)
+			}
+		}
+	};
+}
+
+#[cfg(test)]
+mod tests {
+	use super::*;
+	use frame_support::{assert_err, assert_ok};
+	use sp_runtime::{
+		traits::SignedExtension,
+		transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction},
+	};
+
+	pub struct MockCall {
+		data: u32,
+	}
+
+	impl sp_runtime::traits::Dispatchable for MockCall {
+		type RuntimeOrigin = ();
+		type Config = ();
+		type Info = ();
+		type PostInfo = ();
+
+		fn dispatch(
+			self,
+			_origin: Self::RuntimeOrigin,
+		) -> sp_runtime::DispatchResultWithInfo<Self::PostInfo> {
+			unimplemented!()
+		}
+	}
+
+	struct FirstFilterCall;
+	impl BridgeRuntimeFilterCall<MockCall> for FirstFilterCall {
+		fn validate(call: &MockCall) -> TransactionValidity {
+			if call.data <= 1 {
+				return InvalidTransaction::Custom(1).into()
+			}
+
+			Ok(ValidTransaction { priority: 1, ..Default::default() })
+		}
+	}
+
+	struct SecondFilterCall;
+	impl BridgeRuntimeFilterCall<MockCall> for SecondFilterCall {
+		fn validate(call: &MockCall) -> TransactionValidity {
+			if call.data <= 2 {
+				return InvalidTransaction::Custom(2).into()
+			}
+
+			Ok(ValidTransaction { priority: 2, ..Default::default() })
+		}
+	}
+
+	#[test]
+	fn test() {
+		generate_bridge_reject_obsolete_headers_and_messages!(
+			MockCall,
+			(),
+			FirstFilterCall,
+			SecondFilterCall
+		);
+
+		assert_err!(
+			BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 1 }, &(), 0),
+			InvalidTransaction::Custom(1)
+		);
+
+		assert_err!(
+			BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 2 }, &(), 0),
+			InvalidTransaction::Custom(2)
+		);
+
+		assert_ok!(
+			BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 3 }, &(), 0),
+			ValidTransaction { priority: 3, ..Default::default() }
+		)
+	}
+}
diff --git a/bridges/bin/runtime-common/src/extensions/mod.rs b/bridges/bin/runtime-common/src/extensions/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..3f1b506aaae3ef66fe6f44379258356a2074464c
--- /dev/null
+++ b/bridges/bin/runtime-common/src/extensions/mod.rs
@@ -0,0 +1,21 @@
+// Copyright (C) Parity Technologies (UK) Ltd.
+// This file is part of Parity Bridges Common.
+
+// Parity Bridges Common 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.
+
+// Parity Bridges Common 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 Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
+
+//! Bridge-specific transaction extensions.
+
+pub mod check_obsolete_extension;
+pub mod priority_calculator;
+pub mod refund_relayer_extension;
diff --git a/bridges/bin/runtime-common/src/priority_calculator.rs b/bridges/bin/runtime-common/src/extensions/priority_calculator.rs
similarity index 100%
rename from bridges/bin/runtime-common/src/priority_calculator.rs
rename to bridges/bin/runtime-common/src/extensions/priority_calculator.rs
diff --git a/bridges/bin/runtime-common/src/refund_relayer_extension.rs b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs
similarity index 99%
rename from bridges/bin/runtime-common/src/refund_relayer_extension.rs
rename to bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs
index 455392a0a277f3520cd7f58150f12e7420d36014..f97b23ecaaa99fdaed0a54d00b2b89c14847750a 100644
--- a/bridges/bin/runtime-common/src/refund_relayer_extension.rs
+++ b/bridges/bin/runtime-common/src/extensions/refund_relayer_extension.rs
@@ -520,8 +520,9 @@ where
 		}
 
 		// compute priority boost
-		let priority_boost =
-			crate::priority_calculator::compute_priority_boost::<T::Priority>(bundled_messages);
+		let priority_boost = crate::extensions::priority_calculator::compute_priority_boost::<
+			T::Priority,
+		>(bundled_messages);
 		let valid_transaction = ValidTransactionBuilder::default().priority(priority_boost);
 
 		log::trace!(
diff --git a/bridges/bin/runtime-common/src/lib.rs b/bridges/bin/runtime-common/src/lib.rs
index 2722f6f1c6d14f09ab215f8f020f2c449eda4d4b..5679acd6006ccb8540f940f0f90363f902d643f7 100644
--- a/bridges/bin/runtime-common/src/lib.rs
+++ b/bridges/bin/runtime-common/src/lib.rs
@@ -19,11 +19,7 @@
 #![warn(missing_docs)]
 #![cfg_attr(not(feature = "std"), no_std)]
 
-use crate::messages_call_ext::MessagesCallSubType;
-use pallet_bridge_grandpa::CallSubType as GrandpaCallSubType;
-use pallet_bridge_parachains::CallSubType as ParachainsCallSubtype;
-use sp_runtime::transaction_validity::TransactionValidity;
-
+pub mod extensions;
 pub mod messages;
 pub mod messages_api;
 pub mod messages_benchmarking;
@@ -31,8 +27,6 @@ pub mod messages_call_ext;
 pub mod messages_generation;
 pub mod messages_xcm_extension;
 pub mod parachains_benchmarking;
-pub mod priority_calculator;
-pub mod refund_relayer_extension;
 
 mod mock;
 
@@ -40,184 +34,3 @@ mod mock;
 pub mod integrity;
 
 const LOG_TARGET_BRIDGE_DISPATCH: &str = "runtime::bridge-dispatch";
-
-/// A duplication of the `FilterCall` trait.
-///
-/// We need this trait in order to be able to implement it for the messages pallet,
-/// since the implementation is done outside of the pallet crate.
-pub trait BridgeRuntimeFilterCall<Call> {
-	/// Checks if a runtime call is valid.
-	fn validate(call: &Call) -> TransactionValidity;
-}
-
-impl<T, I: 'static> BridgeRuntimeFilterCall<T::RuntimeCall> for pallet_bridge_grandpa::Pallet<T, I>
-where
-	T: pallet_bridge_grandpa::Config<I>,
-	T::RuntimeCall: GrandpaCallSubType<T, I>,
-{
-	fn validate(call: &T::RuntimeCall) -> TransactionValidity {
-		GrandpaCallSubType::<T, I>::check_obsolete_submit_finality_proof(call)
-	}
-}
-
-impl<T, I: 'static> BridgeRuntimeFilterCall<T::RuntimeCall>
-	for pallet_bridge_parachains::Pallet<T, I>
-where
-	T: pallet_bridge_parachains::Config<I>,
-	T::RuntimeCall: ParachainsCallSubtype<T, I>,
-{
-	fn validate(call: &T::RuntimeCall) -> TransactionValidity {
-		ParachainsCallSubtype::<T, I>::check_obsolete_submit_parachain_heads(call)
-	}
-}
-
-impl<T: pallet_bridge_messages::Config<I>, I: 'static> BridgeRuntimeFilterCall<T::RuntimeCall>
-	for pallet_bridge_messages::Pallet<T, I>
-where
-	T::RuntimeCall: MessagesCallSubType<T, I>,
-{
-	/// Validate messages in order to avoid "mining" messages delivery and delivery confirmation
-	/// transactions, that are delivering outdated messages/confirmations. Without this validation,
-	/// even honest relayers may lose their funds if there are multiple relays running and
-	/// submitting the same messages/confirmations.
-	fn validate(call: &T::RuntimeCall) -> TransactionValidity {
-		call.check_obsolete_call()
-	}
-}
-
-/// Declares a runtime-specific `BridgeRejectObsoleteHeadersAndMessages` signed extension.
-///
-/// ## Example
-///
-/// ```nocompile
-/// generate_bridge_reject_obsolete_headers_and_messages!{
-///     Call, AccountId
-///     BridgeRococoGrandpa, BridgeRococoMessages,
-///     BridgeRococoParachains
-/// }
-/// ```
-///
-/// The goal of this extension is to avoid "mining" transactions that provide outdated bridged
-/// headers and messages. Without that extension, even honest relayers may lose their funds if
-/// there are multiple relays running and submitting the same information.
-#[macro_export]
-macro_rules! generate_bridge_reject_obsolete_headers_and_messages {
-	($call:ty, $account_id:ty, $($filter_call:ty),*) => {
-		#[derive(Clone, codec::Decode, Default, codec::Encode, Eq, PartialEq, sp_runtime::RuntimeDebug, scale_info::TypeInfo)]
-		pub struct BridgeRejectObsoleteHeadersAndMessages;
-		impl sp_runtime::traits::SignedExtension for BridgeRejectObsoleteHeadersAndMessages {
-			const IDENTIFIER: &'static str = "BridgeRejectObsoleteHeadersAndMessages";
-			type AccountId = $account_id;
-			type Call = $call;
-			type AdditionalSigned = ();
-			type Pre = ();
-
-			fn additional_signed(&self) -> sp_std::result::Result<
-				(),
-				sp_runtime::transaction_validity::TransactionValidityError,
-			> {
-				Ok(())
-			}
-
-			fn validate(
-				&self,
-				_who: &Self::AccountId,
-				call: &Self::Call,
-				_info: &sp_runtime::traits::DispatchInfoOf<Self::Call>,
-				_len: usize,
-			) -> sp_runtime::transaction_validity::TransactionValidity {
-				let valid = sp_runtime::transaction_validity::ValidTransaction::default();
-				$(
-					let valid = valid
-						.combine_with(<$filter_call as $crate::BridgeRuntimeFilterCall<$call>>::validate(call)?);
-				)*
-				Ok(valid)
-			}
-
-			fn pre_dispatch(
-				self,
-				who: &Self::AccountId,
-				call: &Self::Call,
-				info: &sp_runtime::traits::DispatchInfoOf<Self::Call>,
-				len: usize,
-			) -> Result<Self::Pre, sp_runtime::transaction_validity::TransactionValidityError> {
-				self.validate(who, call, info, len).map(drop)
-			}
-		}
-	};
-}
-
-#[cfg(test)]
-mod tests {
-	use crate::BridgeRuntimeFilterCall;
-	use frame_support::{assert_err, assert_ok};
-	use sp_runtime::{
-		traits::SignedExtension,
-		transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction},
-	};
-
-	pub struct MockCall {
-		data: u32,
-	}
-
-	impl sp_runtime::traits::Dispatchable for MockCall {
-		type RuntimeOrigin = ();
-		type Config = ();
-		type Info = ();
-		type PostInfo = ();
-
-		fn dispatch(
-			self,
-			_origin: Self::RuntimeOrigin,
-		) -> sp_runtime::DispatchResultWithInfo<Self::PostInfo> {
-			unimplemented!()
-		}
-	}
-
-	struct FirstFilterCall;
-	impl BridgeRuntimeFilterCall<MockCall> for FirstFilterCall {
-		fn validate(call: &MockCall) -> TransactionValidity {
-			if call.data <= 1 {
-				return InvalidTransaction::Custom(1).into()
-			}
-
-			Ok(ValidTransaction { priority: 1, ..Default::default() })
-		}
-	}
-
-	struct SecondFilterCall;
-	impl BridgeRuntimeFilterCall<MockCall> for SecondFilterCall {
-		fn validate(call: &MockCall) -> TransactionValidity {
-			if call.data <= 2 {
-				return InvalidTransaction::Custom(2).into()
-			}
-
-			Ok(ValidTransaction { priority: 2, ..Default::default() })
-		}
-	}
-
-	#[test]
-	fn test() {
-		generate_bridge_reject_obsolete_headers_and_messages!(
-			MockCall,
-			(),
-			FirstFilterCall,
-			SecondFilterCall
-		);
-
-		assert_err!(
-			BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 1 }, &(), 0),
-			InvalidTransaction::Custom(1)
-		);
-
-		assert_err!(
-			BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 2 }, &(), 0),
-			InvalidTransaction::Custom(2)
-		);
-
-		assert_ok!(
-			BridgeRejectObsoleteHeadersAndMessages.validate(&(), &MockCall { data: 3 }, &(), 0),
-			ValidTransaction { priority: 3, ..Default::default() }
-		)
-	}
-}
diff --git a/bridges/modules/xcm-bridge-hub-router/src/lib.rs b/bridges/modules/xcm-bridge-hub-router/src/lib.rs
index 5d0be41b1b5588e3ddc8c6306c9bf83ec29d6056..c6008802ae9a50c02ec54d981e5bd503ff659816 100644
--- a/bridges/modules/xcm-bridge-hub-router/src/lib.rs
+++ b/bridges/modules/xcm-bridge-hub-router/src/lib.rs
@@ -191,6 +191,10 @@ pub mod pallet {
 	impl<T: Config<I>, I: 'static> Pallet<T, I> {
 		/// Called when new message is sent (queued to local outbound XCM queue) over the bridge.
 		pub(crate) fn on_message_sent_to_bridge(message_size: u32) {
+			log::trace!(
+				target: LOG_TARGET,
+				"on_message_sent_to_bridge - message_size: {message_size:?}",
+			);
 			let _ = Bridge::<T, I>::try_mutate(|bridge| {
 				let is_channel_with_bridge_hub_congested = T::WithBridgeHubChannel::is_congested();
 				let is_bridge_congested = bridge.is_congested;
@@ -238,14 +242,16 @@ impl<T: Config<I>, I: 'static> ExporterFor for Pallet<T, I> {
 		remote_location: &InteriorLocation,
 		message: &Xcm<()>,
 	) -> Option<(Location, Option<Asset>)> {
+		log::trace!(
+			target: LOG_TARGET,
+			"exporter_for - network: {network:?}, remote_location: {remote_location:?}, msg: {message:?}",
+		);
 		// ensure that the message is sent to the expected bridged network (if specified).
 		if let Some(bridged_network) = T::BridgedNetworkId::get() {
 			if *network != bridged_network {
 				log::trace!(
 					target: LOG_TARGET,
-					"Router with bridged_network_id {:?} does not support bridging to network {:?}!",
-					bridged_network,
-					network,
+					"Router with bridged_network_id {bridged_network:?} does not support bridging to network {network:?}!",
 				);
 				return None
 			}
@@ -300,7 +306,7 @@ impl<T: Config<I>, I: 'static> ExporterFor for Pallet<T, I> {
 
 		log::info!(
 			target: LOG_TARGET,
-			"Going to send message to {:?} ({} bytes) over bridge. Computed bridge fee {:?} using fee factor {}",
+			"Validate send message to {:?} ({} bytes) over bridge. Computed bridge fee {:?} using fee factor {}",
 			(network, remote_location),
 			message_size,
 			fee,
@@ -321,6 +327,7 @@ impl<T: Config<I>, I: 'static> SendXcm for Pallet<T, I> {
 		dest: &mut Option<Location>,
 		xcm: &mut Option<Xcm<()>>,
 	) -> SendResult<Self::Ticket> {
+		log::trace!(target: LOG_TARGET, "validate - msg: {xcm:?}, destination: {dest:?}");
 		// `dest` and `xcm` are required here
 		let dest_ref = dest.as_ref().ok_or(SendError::MissingArgument)?;
 		let xcm_ref = xcm.as_ref().ok_or(SendError::MissingArgument)?;
@@ -366,6 +373,7 @@ impl<T: Config<I>, I: 'static> SendXcm for Pallet<T, I> {
 		// increase delivery fee factor if required
 		Self::on_message_sent_to_bridge(message_size);
 
+		log::trace!(target: LOG_TARGET, "deliver - message sent, xcm_hash: {xcm_hash:?}");
 		Ok(xcm_hash)
 	}
 }
diff --git a/bridges/primitives/relayers/src/registration.rs b/bridges/primitives/relayers/src/registration.rs
index bc2d0d127aefec3c6982b17915202cc0f87984f2..38fa7c2d9075b7e84986eb4ff173cdb24db5610f 100644
--- a/bridges/primitives/relayers/src/registration.rs
+++ b/bridges/primitives/relayers/src/registration.rs
@@ -21,7 +21,7 @@
 //! required finality proofs). This extension boosts priority of message delivery
 //! transactions, based on the number of bundled messages. So transaction with more
 //! messages has larger priority than the transaction with less messages.
-//! See `bridge_runtime_common::priority_calculator` for details;
+//! See `bridge_runtime_common::extensions::priority_calculator` for details;
 //!
 //! This encourages relayers to include more messages to their delivery transactions.
 //! At the same time, we are not verifying storage proofs before boosting
diff --git a/bridges/relays/messages/src/message_race_delivery.rs b/bridges/relays/messages/src/message_race_delivery.rs
index 137deb5b74f757aa111d5652cbb251a94979e166..f18c43cc7f0e084100d8096432dccafbd61301be 100644
--- a/bridges/relays/messages/src/message_race_delivery.rs
+++ b/bridges/relays/messages/src/message_race_delivery.rs
@@ -446,7 +446,7 @@ where
 		))
 	}
 
-	/// Returns lastest confirmed message at source chain, given source block.
+	/// Returns latest confirmed message at source chain, given source block.
 	fn latest_confirmed_nonce_at_source(&self, at: &SourceHeaderIdOf<P>) -> Option<MessageNonce> {
 		self.latest_confirmed_nonces_at_source
 			.iter()
diff --git a/bridges/relays/messages/src/message_race_strategy.rs b/bridges/relays/messages/src/message_race_strategy.rs
index 93d178e55b04f64a9631f04b4e93b67594d67e54..3a532331d79dc83f680ca7b5e21e471e60335b84 100644
--- a/bridges/relays/messages/src/message_race_strategy.rs
+++ b/bridges/relays/messages/src/message_race_strategy.rs
@@ -567,7 +567,7 @@ mod tests {
 		let source_header_1 = header_id(1);
 		let target_header_1 = header_id(1);
 
-		// we start in perfec sync state - all headers are synced and finalized on both ends
+		// we start in perfect sync state - all headers are synced and finalized on both ends
 		let mut state = TestRaceStateImpl {
 			best_finalized_source_header_id_at_source: Some(source_header_1),
 			best_finalized_source_header_id_at_best_target: Some(source_header_1),
diff --git a/bridges/relays/parachains/README.md b/bridges/relays/parachains/README.md
index 9043b0b0a9cdd84c984d62b4e7c3adc6df44d6bc..f24e7a4c5d3040750361c31d7196e684f46edd13 100644
--- a/bridges/relays/parachains/README.md
+++ b/bridges/relays/parachains/README.md
@@ -23,7 +23,7 @@ to return the best known head of given parachain. When required, it must be able
 finality delivery transaction to the target node.
 
 The main entrypoint for the crate is the [`run` function](./src/parachains_loop.rs), which takes source and target
-clients and [`ParachainSyncParams`](./src/parachains_loop.rs) parameters. The most imporant parameter is the
+clients and [`ParachainSyncParams`](./src/parachains_loop.rs) parameters. The most important parameter is the
 `parachains` - it is the set of parachains, which relay tracks and updates. The other important parameter that
 may affect the relay operational costs is the `strategy`. If it is set to `Any`, then the finality delivery
 transaction is submitted if at least one of tracked parachain heads is updated. The other option is `All`. Then
diff --git a/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh b/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh
index 66c9ddc037b8efb005d2239b174eb5710dddaf53..41aa862be5764ea93fbe09fa706621486131d4c6 100755
--- a/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh
+++ b/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh
@@ -212,19 +212,19 @@ case "$1" in
           "ws://127.0.0.1:8943" \
           "//Alice" \
           "$ASSET_HUB_ROCOCO_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_ROCOCO" \
-          $((1000000000000 + 50000000000 * 20))
+          100000000000000
       # drip SA of lane dedicated to asset hub for paying rewards for delivery
       transfer_balance \
           "ws://127.0.0.1:8943" \
           "//Alice" \
           "$ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_ThisChain" \
-          $((1000000000000 + 2000000000000))
+          100000000000000
       # drip SA of lane dedicated to asset hub for paying rewards for delivery confirmation
       transfer_balance \
           "ws://127.0.0.1:8943" \
           "//Alice" \
           "$ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_BridgedChain" \
-          $((1000000000000 + 2000000000000))
+          100000000000000
       # set XCM version of remote BridgeHubWestend
       force_xcm_version \
           "ws://127.0.0.1:9942" \
@@ -270,19 +270,19 @@ case "$1" in
           "ws://127.0.0.1:8945" \
           "//Alice" \
           "$ASSET_HUB_WESTEND_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_WESTEND" \
-          $((1000000000000000 + 50000000000 * 20))
+          100000000000000
       # drip SA of lane dedicated to asset hub for paying rewards for delivery
       transfer_balance \
           "ws://127.0.0.1:8945" \
           "//Alice" \
           "$ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_ThisChain" \
-          $((1000000000000000 + 2000000000000))
+          100000000000000
       # drip SA of lane dedicated to asset hub for paying rewards for delivery confirmation
       transfer_balance \
           "ws://127.0.0.1:8945" \
           "//Alice" \
           "$ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_BridgedChain" \
-          $((1000000000000000 + 2000000000000))
+          100000000000000
       # set XCM version of remote BridgeHubRococo
       force_xcm_version \
           "ws://127.0.0.1:9945" \
diff --git a/bridges/testing/framework/utils/bridges.sh b/bridges/testing/framework/utils/bridges.sh
index 7c8399461584a85e4e8eedf5f347d9d74725f1c9..07d9e4cd50b1651961724ac2d4c2badca2030e71 100755
--- a/bridges/testing/framework/utils/bridges.sh
+++ b/bridges/testing/framework/utils/bridges.sh
@@ -53,7 +53,7 @@ function call_polkadot_js_api() {
     #           With it, it just submits it to the tx pool and exits.
     # --nonce -1: means to compute transaction nonce using `system_accountNextIndex` RPC, which includes all
     #             transaction that are in the tx pool.
-    polkadot-js-api --noWait --nonce -1 "$@"
+    polkadot-js-api --nonce -1 "$@" || true
 }
 
 function generate_hex_encoded_call_data() {
diff --git a/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/run.sh b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/run.sh
index 3a604b3876d96241903c1c5a110cc6392f26cb7e..32419dc84f59e14d779eef30b26939b297240e55 100755
--- a/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/run.sh
+++ b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/run.sh
@@ -24,12 +24,6 @@ echo -e "Sleeping 90s before starting relayer ...\n"
 sleep 90
 ${BASH_SOURCE%/*}/../../environments/rococo-westend/start_relayer.sh $rococo_dir $westend_dir relayer_pid
 
-# Sometimes the relayer syncs multiple parachain heads in the beginning leading to test failures.
-# See issue: https://github.com/paritytech/parity-bridges-common/issues/2838.
-# TODO: Remove this sleep after the issue is fixed.
-echo -e "Sleeping 180s before runing the tests ...\n"
-sleep 180
-
 run_zndsl ${BASH_SOURCE%/*}/rococo-to-westend.zndsl $westend_dir
 run_zndsl ${BASH_SOURCE%/*}/westend-to-rococo.zndsl $rococo_dir
 
diff --git a/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs b/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs
index aa5e67e453f69bd02920888a19d73b94e92addce..06f19941165a26374555064efcd797e1b9ebde34 100644
--- a/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs
+++ b/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs
@@ -451,6 +451,17 @@ impl RuntimeApiSubsystemClient for BlockChainRpcClient {
 	) -> Result<BTreeMap<CoreIndex, VecDeque<cumulus_primitives_core::ParaId>>, ApiError> {
 		Ok(self.rpc_client.parachain_host_claim_queue(at).await?)
 	}
+
+	async fn candidates_pending_availability(
+		&self,
+		at: Hash,
+		para_id: cumulus_primitives_core::ParaId,
+	) -> Result<Vec<polkadot_primitives::CommittedCandidateReceipt<Hash>>, sp_api::ApiError> {
+		Ok(self
+			.rpc_client
+			.parachain_host_candidates_pending_availability(at, para_id)
+			.await?)
+	}
 }
 
 #[async_trait::async_trait]
diff --git a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs
index 547803865c28a28a7534b3a5d7e0075a2332c71b..864ce6c57125ae5c8dcb5419561dd61a43c38579 100644
--- a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs
+++ b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs
@@ -655,6 +655,20 @@ impl RelayChainRpcClient {
 			.await
 	}
 
+	/// Get the receipt of all candidates pending availability.
+	pub async fn parachain_host_candidates_pending_availability(
+		&self,
+		at: RelayHash,
+		para_id: ParaId,
+	) -> Result<Vec<CommittedCandidateReceipt>, RelayChainError> {
+		self.call_remote_runtime_function(
+			"ParachainHost_candidates_pending_availability",
+			at,
+			Some(para_id),
+		)
+		.await
+	}
+
 	pub async fn validation_code_hash(
 		&self,
 		at: RelayHash,
diff --git a/cumulus/pallets/xcmp-queue/src/lib.rs b/cumulus/pallets/xcmp-queue/src/lib.rs
index b4cd925d540ead4ef17af337f59192c4cfec0042..deced13a9e814ff2a4f05a9a02fe68d169b5a6be 100644
--- a/cumulus/pallets/xcmp-queue/src/lib.rs
+++ b/cumulus/pallets/xcmp-queue/src/lib.rs
@@ -942,7 +942,10 @@ impl<T: Config> SendXcm for Pallet<T> {
 				Self::deposit_event(Event::XcmpMessageSent { message_hash: hash });
 				Ok(hash)
 			},
-			Err(e) => Err(SendError::Transport(e.into())),
+			Err(e) => {
+				log::error!(target: LOG_TARGET, "Deliver error: {e:?}");
+				Err(SendError::Transport(e.into()))
+			},
 		}
 	}
 }
diff --git a/cumulus/parachains/chain-specs/coretime-westend.json b/cumulus/parachains/chain-specs/coretime-westend.json
index 377870f9e2b3dd8f9303936cc54668a8ed830bf4..8f096fa6a9629dda3891efd00349467baff41bbf 100644
--- a/cumulus/parachains/chain-specs/coretime-westend.json
+++ b/cumulus/parachains/chain-specs/coretime-westend.json
@@ -7,7 +7,9 @@
     "/dns/westend-coretime-collator-node-1.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWMh2imeAzsZKGQgm2cv6Uoep3GBYtwGfujt1bs5YfVzkH",
     "/dns/boot.metaspan.io/tcp/33019/p2p/12D3KooWCa1uNnEZqiqJY9jkKNQxwSLGPeZ5MjWHhjQMGwga9JMM",
     "/dns/boot-node.helikon.io/tcp/9420/p2p/12D3KooWFBPartM873MNm1AmVK3etUz34cAE9A9rwPztPno2epQ3",
-    "/dns/boot-node.helikon.io/tcp/9422/wss/p2p/12D3KooWFBPartM873MNm1AmVK3etUz34cAE9A9rwPztPno2epQ3"
+    "/dns/boot-node.helikon.io/tcp/9422/wss/p2p/12D3KooWFBPartM873MNm1AmVK3etUz34cAE9A9rwPztPno2epQ3",
+    "/dns/coretime-westend-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWHewSFwJueRprNZNfkncdjud9DrGzvP1qfmgPd7VK66gw",
+    "/dns/coretime-westend-boot-ng.dwellir.com/tcp/30356/p2p/12D3KooWHewSFwJueRprNZNfkncdjud9DrGzvP1qfmgPd7VK66gw"
   ],
   "telemetryEndpoints": null,
   "protocolId": null,
diff --git a/cumulus/parachains/chain-specs/people-westend.json b/cumulus/parachains/chain-specs/people-westend.json
index d6f0e15e0248e967151767c64771c062d5a89741..6dd8579cf257049edc1159f6b06d5d670284f7a9 100644
--- a/cumulus/parachains/chain-specs/people-westend.json
+++ b/cumulus/parachains/chain-specs/people-westend.json
@@ -24,7 +24,7 @@
     "/dns/people-westend-bootnode.turboflakes.io/tcp/30650/p2p/12D3KooWQEhmZg3uMkuxVUx3jbsD84zEX4dUKtvHfmCoBWMhybKW",
     "/dns/people-westend-bootnode.turboflakes.io/tcp/30750/wss/p2p/12D3KooWQEhmZg3uMkuxVUx3jbsD84zEX4dUKtvHfmCoBWMhybKW",
     "/dns/people-westend-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWBdCpCabhgBpLn67LWcXE2JJCCTMhuJHrfDNiTiCCr3KX",
-    "/dns/people-westend-boot-ng.dwellir.com/tcp/30355/p2p/12D3KooWBdCpCabhgBpLn67LWcXE2JJCCTMhuJHrfDNiTiCCr3KX"    
+    "/dns/people-westend-boot-ng.dwellir.com/tcp/30355/p2p/12D3KooWBdCpCabhgBpLn67LWcXE2JJCCTMhuJHrfDNiTiCCr3KX"
   ],
   "telemetryEndpoints": null,
   "protocolId": null,
diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml
index 98762beb0cb23132c3880515287328bb09bde032..8100e681348836fb28c9236b9ba20d27f117d71b 100644
--- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml
+++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml
@@ -23,3 +23,6 @@ emulated-integration-tests-common = { path = "../../../../common", default-featu
 asset-hub-rococo-runtime = { path = "../../../../../../runtimes/assets/asset-hub-rococo" }
 rococo-emulated-chain = { path = "../../../relays/rococo" }
 testnet-parachains-constants = { path = "../../../../../../runtimes/constants", features = ["rococo"] }
+
+# Polkadot
+xcm = { package = "staging-xcm", path = "../../../../../../../../polkadot/xcm", default-features = false }
diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/lib.rs
index f1e972e869dc94465aa28356a7eaa1c4cd4503ef..202d02b250bb2e90261a01c13c6aab59c674b511 100644
--- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/lib.rs
+++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/lib.rs
@@ -22,7 +22,8 @@ use frame_support::traits::OnInitialize;
 use emulated_integration_tests_common::{
 	impl_accounts_helpers_for_parachain, impl_assert_events_helpers_for_parachain,
 	impl_assets_helpers_for_parachain, impl_assets_helpers_for_system_parachain,
-	impl_xcm_helpers_for_parachain, impls::Parachain, xcm_emulator::decl_test_parachains,
+	impl_foreign_assets_helpers_for_parachain, impl_xcm_helpers_for_parachain, impls::Parachain,
+	xcm_emulator::decl_test_parachains,
 };
 use rococo_emulated_chain::Rococo;
 
@@ -56,4 +57,5 @@ impl_accounts_helpers_for_parachain!(AssetHubRococo);
 impl_assert_events_helpers_for_parachain!(AssetHubRococo);
 impl_assets_helpers_for_system_parachain!(AssetHubRococo, Rococo);
 impl_assets_helpers_for_parachain!(AssetHubRococo);
+impl_foreign_assets_helpers_for_parachain!(AssetHubRococo, xcm::v3::Location);
 impl_xcm_helpers_for_parachain!(AssetHubRococo);
diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/Cargo.toml
index a42a9abf618d403852561d5d4b20e7fb6ad576e7..e0abaa66c5cabba445b91c19436f9a4ce3642386 100644
--- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/Cargo.toml
+++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/Cargo.toml
@@ -23,3 +23,6 @@ emulated-integration-tests-common = { path = "../../../../common", default-featu
 asset-hub-westend-runtime = { path = "../../../../../../runtimes/assets/asset-hub-westend" }
 westend-emulated-chain = { path = "../../../relays/westend" }
 testnet-parachains-constants = { path = "../../../../../../runtimes/constants", features = ["westend"] }
+
+# Polkadot
+xcm = { package = "staging-xcm", path = "../../../../../../../../polkadot/xcm", default-features = false }
diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/lib.rs
index 7f05eefb4c208dab2192bc111347ffa4f2760fc0..6043a6aeda48f1e1ec010ac42e98a50feaae3a30 100644
--- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/lib.rs
+++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/lib.rs
@@ -22,7 +22,8 @@ use frame_support::traits::OnInitialize;
 use emulated_integration_tests_common::{
 	impl_accounts_helpers_for_parachain, impl_assert_events_helpers_for_parachain,
 	impl_assets_helpers_for_parachain, impl_assets_helpers_for_system_parachain,
-	impl_xcm_helpers_for_parachain, impls::Parachain, xcm_emulator::decl_test_parachains,
+	impl_foreign_assets_helpers_for_parachain, impl_xcm_helpers_for_parachain, impls::Parachain,
+	xcm_emulator::decl_test_parachains,
 };
 use westend_emulated_chain::Westend;
 
@@ -56,4 +57,5 @@ impl_accounts_helpers_for_parachain!(AssetHubWestend);
 impl_assert_events_helpers_for_parachain!(AssetHubWestend);
 impl_assets_helpers_for_system_parachain!(AssetHubWestend, Westend);
 impl_assets_helpers_for_parachain!(AssetHubWestend);
+impl_foreign_assets_helpers_for_parachain!(AssetHubWestend, xcm::v3::Location);
 impl_xcm_helpers_for_parachain!(AssetHubWestend);
diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs
index d81ab8143ddba678617aaa67db122298ce29606c..450439f5ea3080b66c5c572dfdae972c23c52a4b 100644
--- a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs
+++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs
@@ -17,8 +17,6 @@
 use frame_support::parameter_types;
 use sp_core::{sr25519, storage::Storage};
 
-// Polkadot
-use xcm::v3::Location;
 // Cumulus
 use emulated_integration_tests_common::{
 	accounts, build_genesis_storage, collators, get_account_id_from_seed, SAFE_XCM_VERSION,
@@ -79,20 +77,9 @@ pub fn genesis(para_id: u32) -> Storage {
 		foreign_assets: penpal_runtime::ForeignAssetsConfig {
 			assets: vec![
 				// Relay Native asset representation
-				(
-					Location::try_from(RelayLocation::get()).expect("conversion works"),
-					PenpalAssetOwner::get(),
-					true,
-					ED,
-				),
+				(RelayLocation::get(), PenpalAssetOwner::get(), true, ED),
 				// Sufficient AssetHub asset representation
-				(
-					Location::try_from(LocalReservableFromAssetHub::get())
-						.expect("conversion works"),
-					PenpalAssetOwner::get(),
-					true,
-					ED,
-				),
+				(LocalReservableFromAssetHub::get(), PenpalAssetOwner::get(), true, ED),
 			],
 			..Default::default()
 		},
diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/lib.rs
index 0b49c7a3e091a615632728b6ba74ca4dffefae66..c268b014bfa34e1b8c0a450ae2e446bb6f636c9d 100644
--- a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/lib.rs
+++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/lib.rs
@@ -16,16 +16,20 @@
 mod genesis;
 pub use genesis::{genesis, PenpalAssetOwner, PenpalSudoAccount, ED, PARA_ID_A, PARA_ID_B};
 pub use penpal_runtime::xcm_config::{
-	CustomizableAssetFromSystemAssetHub, LocalTeleportableToAssetHub, XcmConfig,
+	CustomizableAssetFromSystemAssetHub, RelayNetworkId as PenpalRelayNetworkId,
 };
 
 // Substrate
 use frame_support::traits::OnInitialize;
+use sp_core::Encode;
 
 // Cumulus
 use emulated_integration_tests_common::{
 	impl_accounts_helpers_for_parachain, impl_assert_events_helpers_for_parachain,
-	impl_assets_helpers_for_parachain, impls::Parachain, xcm_emulator::decl_test_parachains,
+	impl_assets_helpers_for_parachain, impl_foreign_assets_helpers_for_parachain,
+	impl_xcm_helpers_for_parachain,
+	impls::{NetworkId, Parachain},
+	xcm_emulator::decl_test_parachains,
 };
 
 // Penpal Parachain declaration
@@ -34,6 +38,10 @@ decl_test_parachains! {
 		genesis = genesis(PARA_ID_A),
 		on_init = {
 			penpal_runtime::AuraExt::on_initialize(1);
+			frame_support::assert_ok!(penpal_runtime::System::set_storage(
+				penpal_runtime::RuntimeOrigin::root(),
+				vec![(PenpalRelayNetworkId::key().to_vec(), NetworkId::Rococo.encode())],
+			));
 		},
 		runtime = penpal_runtime,
 		core = {
@@ -53,6 +61,10 @@ decl_test_parachains! {
 		genesis = genesis(PARA_ID_B),
 		on_init = {
 			penpal_runtime::AuraExt::on_initialize(1);
+			frame_support::assert_ok!(penpal_runtime::System::set_storage(
+				penpal_runtime::RuntimeOrigin::root(),
+				vec![(PenpalRelayNetworkId::key().to_vec(), NetworkId::Westend.encode())],
+			));
 		},
 		runtime = penpal_runtime,
 		core = {
@@ -76,4 +88,8 @@ impl_accounts_helpers_for_parachain!(PenpalB);
 impl_assert_events_helpers_for_parachain!(PenpalA);
 impl_assert_events_helpers_for_parachain!(PenpalB);
 impl_assets_helpers_for_parachain!(PenpalA);
+impl_foreign_assets_helpers_for_parachain!(PenpalA, xcm::latest::Location);
 impl_assets_helpers_for_parachain!(PenpalB);
+impl_foreign_assets_helpers_for_parachain!(PenpalB, xcm::latest::Location);
+impl_xcm_helpers_for_parachain!(PenpalA);
+impl_xcm_helpers_for_parachain!(PenpalB);
diff --git a/cumulus/parachains/integration-tests/emulated/common/src/impls.rs b/cumulus/parachains/integration-tests/emulated/common/src/impls.rs
index 618c3addc5d0c67c3954610425345d3ec8b2f36b..c8a2f097abe95bb0f6003957c7ef5cc90c4f09c3 100644
--- a/cumulus/parachains/integration-tests/emulated/common/src/impls.rs
+++ b/cumulus/parachains/integration-tests/emulated/common/src/impls.rs
@@ -38,9 +38,7 @@ pub use polkadot_runtime_parachains::{
 	inclusion::{AggregateMessageOrigin, UmpQueueId},
 };
 pub use xcm::{
-	prelude::{Location, OriginKind, Outcome, VersionedXcm, XcmVersion},
-	v3,
-	v4::Error as XcmError,
+	prelude::{Location, OriginKind, Outcome, VersionedXcm, XcmError, XcmVersion},
 	DoubleEncoded,
 };
 
@@ -696,12 +694,12 @@ macro_rules! impl_assets_helpers_for_system_parachain {
 
 #[macro_export]
 macro_rules! impl_assets_helpers_for_parachain {
-	( $chain:ident) => {
+	($chain:ident) => {
 		$crate::impls::paste::paste! {
 			impl<N: $crate::impls::Network> $chain<N> {
-				/// Create foreign assets using sudo `ForeignAssets::force_create()`
-				pub fn force_create_foreign_asset(
-					id: $crate::impls::v3::Location,
+				/// Create assets using sudo `Assets::force_create()`
+				pub fn force_create_asset(
+					id: u32,
 					owner: $crate::impls::AccountId,
 					is_sufficient: bool,
 					min_balance: u128,
@@ -711,20 +709,20 @@ macro_rules! impl_assets_helpers_for_parachain {
 					let sudo_origin = <$chain<N> as $crate::impls::Chain>::RuntimeOrigin::root();
 					<Self as $crate::impls::TestExt>::execute_with(|| {
 						$crate::impls::assert_ok!(
-							<Self as [<$chain ParaPallet>]>::ForeignAssets::force_create(
+							<Self as [<$chain ParaPallet>]>::Assets::force_create(
 								sudo_origin,
-								id.clone(),
+								id.clone().into(),
 								owner.clone().into(),
 								is_sufficient,
 								min_balance,
 							)
 						);
-						assert!(<Self as [<$chain ParaPallet>]>::ForeignAssets::asset_exists(id.clone()));
+						assert!(<Self as [<$chain ParaPallet>]>::Assets::asset_exists(id.clone()));
 						type RuntimeEvent<N> = <$chain<N> as $crate::impls::Chain>::RuntimeEvent;
 						$crate::impls::assert_expected_events!(
 							Self,
 							vec![
-								RuntimeEvent::<N>::ForeignAssets(
+								RuntimeEvent::<N>::Assets(
 									$crate::impls::pallet_assets::Event::ForceCreated {
 										asset_id,
 										..
@@ -736,19 +734,19 @@ macro_rules! impl_assets_helpers_for_parachain {
 					for (beneficiary, amount) in prefund_accounts.into_iter() {
 						let signed_origin =
 							<$chain<N> as $crate::impls::Chain>::RuntimeOrigin::signed(owner.clone());
-						Self::mint_foreign_asset(signed_origin, id.clone(), beneficiary, amount);
+						Self::mint_asset(signed_origin, id.clone(), beneficiary, amount);
 					}
 				}
 
-				/// Mint assets making use of the ForeignAssets pallet-assets instance
-				pub fn mint_foreign_asset(
+				/// Mint assets making use of the assets pallet
+				pub fn mint_asset(
 					signed_origin: <Self as $crate::impls::Chain>::RuntimeOrigin,
-					id: $crate::impls::v3::Location,
+					id: u32,
 					beneficiary: $crate::impls::AccountId,
 					amount_to_mint: u128,
 				) {
 					<Self as $crate::impls::TestExt>::execute_with(|| {
-						$crate::impls::assert_ok!(<Self as [<$chain ParaPallet>]>::ForeignAssets::mint(
+						$crate::impls::assert_ok!(<Self as [<$chain ParaPallet>]>::Assets::mint(
 							signed_origin,
 							id.clone().into(),
 							beneficiary.clone().into(),
@@ -760,7 +758,7 @@ macro_rules! impl_assets_helpers_for_parachain {
 						$crate::impls::assert_expected_events!(
 							Self,
 							vec![
-								RuntimeEvent::<N>::ForeignAssets(
+								RuntimeEvent::<N>::Assets(
 									$crate::impls::pallet_assets::Event::Issued { asset_id, owner, amount }
 								) => {
 									asset_id: *asset_id == id,
@@ -771,9 +769,39 @@ macro_rules! impl_assets_helpers_for_parachain {
 						);
 					});
 				}
-				/// Create assets using sudo `Assets::force_create()`
-				pub fn force_create_asset(
-					id: u32,
+
+				/// Returns the encoded call for `create` from the assets pallet
+				pub fn create_asset_call(
+					asset_id: u32,
+					min_balance: $crate::impls::Balance,
+					admin: $crate::impls::AccountId,
+				) -> $crate::impls::DoubleEncoded<()> {
+					use $crate::impls::{Chain, Encode};
+
+					<Self as Chain>::RuntimeCall::Assets($crate::impls::pallet_assets::Call::<
+						<Self as Chain>::Runtime,
+						$crate::impls::pallet_assets::Instance1,
+					>::create {
+						id: asset_id.into(),
+						min_balance,
+						admin: admin.into(),
+					})
+					.encode()
+					.into()
+				}
+			}
+		}
+	};
+}
+
+#[macro_export]
+macro_rules! impl_foreign_assets_helpers_for_parachain {
+	($chain:ident, $asset_id_type:ty) => {
+		$crate::impls::paste::paste! {
+			impl<N: $crate::impls::Network> $chain<N> {
+				/// Create foreign assets using sudo `ForeignAssets::force_create()`
+				pub fn force_create_foreign_asset(
+					id: $asset_id_type,
 					owner: $crate::impls::AccountId,
 					is_sufficient: bool,
 					min_balance: u128,
@@ -783,20 +811,20 @@ macro_rules! impl_assets_helpers_for_parachain {
 					let sudo_origin = <$chain<N> as $crate::impls::Chain>::RuntimeOrigin::root();
 					<Self as $crate::impls::TestExt>::execute_with(|| {
 						$crate::impls::assert_ok!(
-							<Self as [<$chain ParaPallet>]>::Assets::force_create(
+							<Self as [<$chain ParaPallet>]>::ForeignAssets::force_create(
 								sudo_origin,
-								id.clone().into(),
+								id.clone(),
 								owner.clone().into(),
 								is_sufficient,
 								min_balance,
 							)
 						);
-						assert!(<Self as [<$chain ParaPallet>]>::Assets::asset_exists(id.clone()));
+						assert!(<Self as [<$chain ParaPallet>]>::ForeignAssets::asset_exists(id.clone()));
 						type RuntimeEvent<N> = <$chain<N> as $crate::impls::Chain>::RuntimeEvent;
 						$crate::impls::assert_expected_events!(
 							Self,
 							vec![
-								RuntimeEvent::<N>::Assets(
+								RuntimeEvent::<N>::ForeignAssets(
 									$crate::impls::pallet_assets::Event::ForceCreated {
 										asset_id,
 										..
@@ -808,19 +836,19 @@ macro_rules! impl_assets_helpers_for_parachain {
 					for (beneficiary, amount) in prefund_accounts.into_iter() {
 						let signed_origin =
 							<$chain<N> as $crate::impls::Chain>::RuntimeOrigin::signed(owner.clone());
-						Self::mint_asset(signed_origin, id.clone(), beneficiary, amount);
+						Self::mint_foreign_asset(signed_origin, id.clone(), beneficiary, amount);
 					}
 				}
 
-				/// Mint assets making use of the assets pallet
-				pub fn mint_asset(
+				/// Mint assets making use of the ForeignAssets pallet-assets instance
+				pub fn mint_foreign_asset(
 					signed_origin: <Self as $crate::impls::Chain>::RuntimeOrigin,
-					id: u32,
+					id: $asset_id_type,
 					beneficiary: $crate::impls::AccountId,
 					amount_to_mint: u128,
 				) {
 					<Self as $crate::impls::TestExt>::execute_with(|| {
-						$crate::impls::assert_ok!(<Self as [<$chain ParaPallet>]>::Assets::mint(
+						$crate::impls::assert_ok!(<Self as [<$chain ParaPallet>]>::ForeignAssets::mint(
 							signed_origin,
 							id.clone().into(),
 							beneficiary.clone().into(),
@@ -832,7 +860,7 @@ macro_rules! impl_assets_helpers_for_parachain {
 						$crate::impls::assert_expected_events!(
 							Self,
 							vec![
-								RuntimeEvent::<N>::Assets(
+								RuntimeEvent::<N>::ForeignAssets(
 									$crate::impls::pallet_assets::Event::Issued { asset_id, owner, amount }
 								) => {
 									asset_id: *asset_id == id,
@@ -844,29 +872,9 @@ macro_rules! impl_assets_helpers_for_parachain {
 					});
 				}
 
-				/// Returns the encoded call for `create` from the assets pallet
-				pub fn create_asset_call(
-					asset_id: u32,
-					min_balance: $crate::impls::Balance,
-					admin: $crate::impls::AccountId,
-				) -> $crate::impls::DoubleEncoded<()> {
-					use $crate::impls::{Chain, Encode};
-
-					<Self as Chain>::RuntimeCall::Assets($crate::impls::pallet_assets::Call::<
-						<Self as Chain>::Runtime,
-						$crate::impls::pallet_assets::Instance1,
-					>::create {
-						id: asset_id.into(),
-						min_balance,
-						admin: admin.into(),
-					})
-					.encode()
-					.into()
-				}
-
 				/// Returns the encoded call for `create` from the foreign assets pallet
 				pub fn create_foreign_asset_call(
-					asset_id: $crate::impls::v3::Location,
+					asset_id: $asset_id_type,
 					min_balance: $crate::impls::Balance,
 					admin: $crate::impls::AccountId,
 				) -> $crate::impls::DoubleEncoded<()> {
diff --git a/cumulus/parachains/integration-tests/emulated/networks/rococo-westend-system/src/lib.rs b/cumulus/parachains/integration-tests/emulated/networks/rococo-westend-system/src/lib.rs
index ee8b038a364d73301732f278786b30b18d534643..d87bc5aa9633468a0f379da1c417a1caaba2505f 100644
--- a/cumulus/parachains/integration-tests/emulated/networks/rococo-westend-system/src/lib.rs
+++ b/cumulus/parachains/integration-tests/emulated/networks/rococo-westend-system/src/lib.rs
@@ -25,7 +25,7 @@ use asset_hub_rococo_emulated_chain::AssetHubRococo;
 use asset_hub_westend_emulated_chain::AssetHubWestend;
 use bridge_hub_rococo_emulated_chain::BridgeHubRococo;
 use bridge_hub_westend_emulated_chain::BridgeHubWestend;
-use penpal_emulated_chain::PenpalA;
+use penpal_emulated_chain::{PenpalA, PenpalB};
 use rococo_emulated_chain::Rococo;
 use westend_emulated_chain::Westend;
 
@@ -48,13 +48,13 @@ decl_test_networks! {
 			PenpalA,
 		],
 		bridge = RococoWestendMockBridge
-
 	},
 	pub struct WestendMockNet {
 		relay_chain = Westend,
 		parachains = vec![
 			AssetHubWestend,
 			BridgeHubWestend,
+			PenpalB,
 		],
 		bridge = WestendRococoMockBridge
 	},
@@ -96,5 +96,6 @@ decl_test_sender_receiver_accounts_parameter_types! {
 	WestendRelay { sender: ALICE, receiver: BOB },
 	AssetHubWestendPara { sender: ALICE, receiver: BOB },
 	BridgeHubWestendPara { sender: ALICE, receiver: BOB },
-	PenpalAPara { sender: ALICE, receiver: BOB }
+	PenpalAPara { sender: ALICE, receiver: BOB },
+	PenpalBPara { sender: ALICE, receiver: BOB }
 }
diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs
index a5a4914e21d826ea6c70af4ae31a0d4dee43ef64..322c6cf1f2282670e474b0c5737fabd09afbe94d 100644
--- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs
+++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs
@@ -30,6 +30,7 @@ mod imports {
 		prelude::{AccountId32 as AccountId32Junction, *},
 		v3,
 	};
+	pub use xcm_executor::traits::TransferType;
 
 	// Cumulus
 	pub use asset_test_utils::xcm_helpers;
@@ -81,6 +82,7 @@ mod imports {
 	pub type SystemParaToParaTest = Test<AssetHubRococo, PenpalA>;
 	pub type ParaToSystemParaTest = Test<PenpalA, AssetHubRococo>;
 	pub type ParaToParaThroughRelayTest = Test<PenpalA, PenpalB, Rococo>;
+	pub type ParaToParaThroughAHTest = Test<PenpalA, PenpalB, AssetHubRococo>;
 }
 
 #[cfg(test)]
diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/foreign_assets_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/foreign_assets_transfers.rs
new file mode 100644
index 0000000000000000000000000000000000000000..6bdf89e6f277edb7d0e6e85383223d52b24c89ea
--- /dev/null
+++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/foreign_assets_transfers.rs
@@ -0,0 +1,628 @@
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use super::reserve_transfer::*;
+use crate::{
+	imports::*,
+	tests::teleport::do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt,
+};
+
+fn para_to_para_assethub_hop_assertions(t: ParaToParaThroughAHTest) {
+	type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent;
+	let sov_penpal_a_on_ah = AssetHubRococo::sovereign_account_id_of(
+		AssetHubRococo::sibling_location_of(PenpalA::para_id()),
+	);
+	let sov_penpal_b_on_ah = AssetHubRococo::sovereign_account_id_of(
+		AssetHubRococo::sibling_location_of(PenpalB::para_id()),
+	);
+
+	assert_expected_events!(
+		AssetHubRococo,
+		vec![
+			// Withdrawn from sender parachain SA
+			RuntimeEvent::Balances(
+				pallet_balances::Event::Burned { who, amount }
+			) => {
+				who: *who == sov_penpal_a_on_ah,
+				amount: *amount == t.args.amount,
+			},
+			// Deposited to receiver parachain SA
+			RuntimeEvent::Balances(
+				pallet_balances::Event::Minted { who, .. }
+			) => {
+				who: *who == sov_penpal_b_on_ah,
+			},
+			RuntimeEvent::MessageQueue(
+				pallet_message_queue::Event::Processed { success: true, .. }
+			) => {},
+		]
+	);
+}
+
+fn ah_to_para_transfer_assets(t: SystemParaToParaTest) -> DispatchResult {
+	let fee_idx = t.args.fee_asset_item as usize;
+	let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
+	<AssetHubRococo as AssetHubRococoPallet>::PolkadotXcm::transfer_assets_using_type(
+		t.signed_origin,
+		bx!(t.args.dest.into()),
+		bx!(t.args.beneficiary.into()),
+		bx!(t.args.assets.into()),
+		bx!(TransferType::LocalReserve),
+		bx!(fee.id.into()),
+		bx!(TransferType::LocalReserve),
+		t.args.weight_limit,
+	)
+}
+
+fn para_to_ah_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult {
+	let fee_idx = t.args.fee_asset_item as usize;
+	let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
+	<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type(
+		t.signed_origin,
+		bx!(t.args.dest.into()),
+		bx!(t.args.beneficiary.into()),
+		bx!(t.args.assets.into()),
+		bx!(TransferType::DestinationReserve),
+		bx!(fee.id.into()),
+		bx!(TransferType::DestinationReserve),
+		t.args.weight_limit,
+	)
+}
+
+fn para_to_para_transfer_assets_through_ah(t: ParaToParaThroughAHTest) -> DispatchResult {
+	let fee_idx = t.args.fee_asset_item as usize;
+	let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
+	let asset_hub_location: Location = PenpalA::sibling_location_of(AssetHubRococo::para_id());
+	<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type(
+		t.signed_origin,
+		bx!(t.args.dest.into()),
+		bx!(t.args.beneficiary.into()),
+		bx!(t.args.assets.into()),
+		bx!(TransferType::RemoteReserve(asset_hub_location.clone().into())),
+		bx!(fee.id.into()),
+		bx!(TransferType::RemoteReserve(asset_hub_location.into())),
+		t.args.weight_limit,
+	)
+}
+
+fn para_to_asset_hub_teleport_foreign_assets(t: ParaToSystemParaTest) -> DispatchResult {
+	let fee_idx = t.args.fee_asset_item as usize;
+	let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
+	<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type(
+		t.signed_origin,
+		bx!(t.args.dest.into()),
+		bx!(t.args.beneficiary.into()),
+		bx!(t.args.assets.into()),
+		bx!(TransferType::Teleport),
+		bx!(fee.id.into()),
+		bx!(TransferType::DestinationReserve),
+		t.args.weight_limit,
+	)
+}
+
+fn asset_hub_to_para_teleport_foreign_assets(t: SystemParaToParaTest) -> DispatchResult {
+	let fee_idx = t.args.fee_asset_item as usize;
+	let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
+	<AssetHubRococo as AssetHubRococoPallet>::PolkadotXcm::transfer_assets_using_type(
+		t.signed_origin,
+		bx!(t.args.dest.into()),
+		bx!(t.args.beneficiary.into()),
+		bx!(t.args.assets.into()),
+		bx!(TransferType::Teleport),
+		bx!(fee.id.into()),
+		bx!(TransferType::LocalReserve),
+		t.args.weight_limit,
+	)
+}
+
+// ===========================================================================
+// ======= Transfer - Native + Bridged Assets - AssetHub->Parachain ==========
+// ===========================================================================
+/// Transfers of native asset plus bridged asset from AssetHub to some Parachain
+/// while paying fees using native asset.
+#[test]
+fn transfer_foreign_assets_from_asset_hub_to_para() {
+	let destination = AssetHubRococo::sibling_location_of(PenpalA::para_id());
+	let sender = AssetHubRococoSender::get();
+	let native_amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 10000;
+	let native_asset_location = RelayLocation::get();
+	let receiver = PenpalAReceiver::get();
+	let assets_owner = PenpalAssetOwner::get();
+	// Foreign asset used: bridged WND
+	let foreign_amount_to_send = ASSET_HUB_ROCOCO_ED * 10_000_000;
+	let wnd_at_rococo_parachains =
+		Location::new(2, [Junction::GlobalConsensus(NetworkId::Westend)]);
+
+	// Configure destination chain to trust AH as reserve of WND
+	PenpalA::execute_with(|| {
+		assert_ok!(<PenpalA as Chain>::System::set_storage(
+			<PenpalA as Chain>::RuntimeOrigin::root(),
+			vec![(
+				penpal_runtime::xcm_config::CustomizableAssetFromSystemAssetHub::key().to_vec(),
+				Location::new(2, [GlobalConsensus(Westend)]).encode(),
+			)],
+		));
+	});
+	PenpalA::force_create_foreign_asset(
+		wnd_at_rococo_parachains.clone(),
+		assets_owner.clone(),
+		false,
+		ASSET_MIN_BALANCE,
+		vec![],
+	);
+	AssetHubRococo::force_create_foreign_asset(
+		wnd_at_rococo_parachains.clone().try_into().unwrap(),
+		assets_owner.clone(),
+		false,
+		ASSET_MIN_BALANCE,
+		vec![],
+	);
+	AssetHubRococo::mint_foreign_asset(
+		<AssetHubRococo as Chain>::RuntimeOrigin::signed(assets_owner),
+		wnd_at_rococo_parachains.clone().try_into().unwrap(),
+		sender.clone(),
+		foreign_amount_to_send * 2,
+	);
+
+	// Assets to send
+	let assets: Vec<Asset> = vec![
+		(Parent, native_amount_to_send).into(),
+		(wnd_at_rococo_parachains.clone(), foreign_amount_to_send).into(),
+	];
+	let fee_asset_id = AssetId(Parent.into());
+	let fee_asset_item = assets.iter().position(|a| a.id == fee_asset_id).unwrap() as u32;
+
+	// Init Test
+	let test_args = TestContext {
+		sender: sender.clone(),
+		receiver: receiver.clone(),
+		args: TestArgs::new_para(
+			destination.clone(),
+			receiver.clone(),
+			native_amount_to_send,
+			assets.into(),
+			None,
+			fee_asset_item,
+		),
+	};
+	let mut test = SystemParaToParaTest::new(test_args);
+
+	// Query initial balances
+	let sender_balance_before = test.sender.balance;
+	let sender_wnds_before = AssetHubRococo::execute_with(|| {
+		type ForeignAssets = <AssetHubRococo as AssetHubRococoPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(
+			wnd_at_rococo_parachains.clone().try_into().unwrap(),
+			&sender,
+		)
+	});
+	let receiver_assets_before = PenpalA::execute_with(|| {
+		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(native_asset_location.clone(), &receiver)
+	});
+	let receiver_wnds_before = PenpalA::execute_with(|| {
+		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(wnd_at_rococo_parachains.clone(), &receiver)
+	});
+
+	// Set assertions and dispatchables
+	test.set_assertion::<AssetHubRococo>(system_para_to_para_sender_assertions);
+	test.set_assertion::<PenpalA>(system_para_to_para_receiver_assertions);
+	test.set_dispatchable::<AssetHubRococo>(ah_to_para_transfer_assets);
+	test.assert();
+
+	// Query final balances
+	let sender_balance_after = test.sender.balance;
+	let sender_wnds_after = AssetHubRococo::execute_with(|| {
+		type ForeignAssets = <AssetHubRococo as AssetHubRococoPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(
+			wnd_at_rococo_parachains.clone().try_into().unwrap(),
+			&sender,
+		)
+	});
+	let receiver_assets_after = PenpalA::execute_with(|| {
+		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(native_asset_location, &receiver)
+	});
+	let receiver_wnds_after = PenpalA::execute_with(|| {
+		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(wnd_at_rococo_parachains, &receiver)
+	});
+
+	// Sender's balance is reduced by amount sent plus delivery fees
+	assert!(sender_balance_after < sender_balance_before - native_amount_to_send);
+	// Sender's balance is reduced by foreign amount sent
+	assert_eq!(sender_wnds_after, sender_wnds_before - foreign_amount_to_send);
+	// Receiver's assets is increased
+	assert!(receiver_assets_after > receiver_assets_before);
+	// Receiver's assets increased by `amount_to_send - delivery_fees - bought_execution`;
+	// `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but
+	// should be non-zero
+	assert!(receiver_assets_after < receiver_assets_before + native_amount_to_send);
+	// Receiver's balance is increased by foreign amount sent
+	assert_eq!(receiver_wnds_after, receiver_wnds_before + foreign_amount_to_send);
+}
+
+/// Reserve Transfers of native asset from Parachain to System Parachain should work
+// ===========================================================================
+// ======= Transfer - Native + Bridged Assets - Parachain->AssetHub ==========
+// ===========================================================================
+/// Transfers of native asset plus bridged asset from some Parachain to AssetHub
+/// while paying fees using native asset.
+#[test]
+fn transfer_foreign_assets_from_para_to_asset_hub() {
+	// Init values for Parachain
+	let destination = PenpalA::sibling_location_of(AssetHubRococo::para_id());
+	let sender = PenpalASender::get();
+	let native_amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 10000;
+	let native_asset_location = RelayLocation::get();
+	let assets_owner = PenpalAssetOwner::get();
+
+	// Foreign asset used: bridged WND
+	let foreign_amount_to_send = ASSET_HUB_ROCOCO_ED * 10_000_000;
+	let wnd_at_rococo_parachains =
+		Location::new(2, [Junction::GlobalConsensus(NetworkId::Westend)]);
+
+	// Configure destination chain to trust AH as reserve of WND
+	PenpalA::execute_with(|| {
+		assert_ok!(<PenpalA as Chain>::System::set_storage(
+			<PenpalA as Chain>::RuntimeOrigin::root(),
+			vec![(
+				penpal_runtime::xcm_config::CustomizableAssetFromSystemAssetHub::key().to_vec(),
+				Location::new(2, [GlobalConsensus(Westend)]).encode(),
+			)],
+		));
+	});
+	PenpalA::force_create_foreign_asset(
+		wnd_at_rococo_parachains.clone(),
+		assets_owner.clone(),
+		false,
+		ASSET_MIN_BALANCE,
+		vec![],
+	);
+	AssetHubRococo::force_create_foreign_asset(
+		wnd_at_rococo_parachains.clone().try_into().unwrap(),
+		assets_owner.clone(),
+		false,
+		ASSET_MIN_BALANCE,
+		vec![],
+	);
+
+	// fund Parachain's sender account
+	PenpalA::mint_foreign_asset(
+		<PenpalA as Chain>::RuntimeOrigin::signed(assets_owner.clone()),
+		native_asset_location.clone(),
+		sender.clone(),
+		native_amount_to_send * 2,
+	);
+	PenpalA::mint_foreign_asset(
+		<PenpalA as Chain>::RuntimeOrigin::signed(assets_owner.clone()),
+		wnd_at_rococo_parachains.clone(),
+		sender.clone(),
+		foreign_amount_to_send * 2,
+	);
+
+	// Init values for System Parachain
+	let receiver = AssetHubRococoReceiver::get();
+	let penpal_location_as_seen_by_ahr = AssetHubRococo::sibling_location_of(PenpalA::para_id());
+	let sov_penpal_on_ahr = AssetHubRococo::sovereign_account_id_of(penpal_location_as_seen_by_ahr);
+
+	// fund Parachain's SA on AssetHub with the assets held in reserve
+	AssetHubRococo::fund_accounts(vec![(
+		sov_penpal_on_ahr.clone().into(),
+		native_amount_to_send * 2,
+	)]);
+	AssetHubRococo::mint_foreign_asset(
+		<AssetHubRococo as Chain>::RuntimeOrigin::signed(assets_owner),
+		wnd_at_rococo_parachains.clone().try_into().unwrap(),
+		sov_penpal_on_ahr,
+		foreign_amount_to_send * 2,
+	);
+
+	// Assets to send
+	let assets: Vec<Asset> = vec![
+		(Parent, native_amount_to_send).into(),
+		(wnd_at_rococo_parachains.clone(), foreign_amount_to_send).into(),
+	];
+	let fee_asset_id = AssetId(Parent.into());
+	let fee_asset_item = assets.iter().position(|a| a.id == fee_asset_id).unwrap() as u32;
+
+	// Init Test
+	let test_args = TestContext {
+		sender: sender.clone(),
+		receiver: receiver.clone(),
+		args: TestArgs::new_para(
+			destination.clone(),
+			receiver.clone(),
+			native_amount_to_send,
+			assets.into(),
+			None,
+			fee_asset_item,
+		),
+	};
+	let mut test = ParaToSystemParaTest::new(test_args);
+
+	// Query initial balances
+	let sender_native_before = PenpalA::execute_with(|| {
+		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(native_asset_location.clone(), &sender)
+	});
+	let sender_wnds_before = PenpalA::execute_with(|| {
+		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(wnd_at_rococo_parachains.clone(), &sender)
+	});
+	let receiver_native_before = test.receiver.balance;
+	let receiver_wnds_before = AssetHubRococo::execute_with(|| {
+		type ForeignAssets = <AssetHubRococo as AssetHubRococoPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(
+			wnd_at_rococo_parachains.clone().try_into().unwrap(),
+			&receiver,
+		)
+	});
+
+	// Set assertions and dispatchables
+	test.set_assertion::<PenpalA>(para_to_system_para_sender_assertions);
+	test.set_assertion::<AssetHubRococo>(para_to_system_para_receiver_assertions);
+	test.set_dispatchable::<PenpalA>(para_to_ah_transfer_assets);
+	test.assert();
+
+	// Query final balances
+	let sender_native_after = PenpalA::execute_with(|| {
+		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(native_asset_location, &sender)
+	});
+	let sender_wnds_after = PenpalA::execute_with(|| {
+		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(wnd_at_rococo_parachains.clone(), &sender)
+	});
+	let receiver_native_after = test.receiver.balance;
+	let receiver_wnds_after = AssetHubRococo::execute_with(|| {
+		type ForeignAssets = <AssetHubRococo as AssetHubRococoPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(
+			wnd_at_rococo_parachains.try_into().unwrap(),
+			&receiver,
+		)
+	});
+
+	// Sender's balance is reduced by amount sent plus delivery fees
+	assert!(sender_native_after < sender_native_before - native_amount_to_send);
+	// Sender's balance is reduced by foreign amount sent
+	assert_eq!(sender_wnds_after, sender_wnds_before - foreign_amount_to_send);
+	// Receiver's balance is increased
+	assert!(receiver_native_after > receiver_native_before);
+	// Receiver's balance increased by `amount_to_send - delivery_fees - bought_execution`;
+	// `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but
+	// should be non-zero
+	assert!(receiver_native_after < receiver_native_before + native_amount_to_send);
+	// Receiver's balance is increased by foreign amount sent
+	assert_eq!(receiver_wnds_after, receiver_wnds_before + foreign_amount_to_send);
+}
+
+// ==============================================================================
+// ===== Transfer - Native + Bridged Assets - Parachain->AssetHub->Parachain ====
+// ==============================================================================
+/// Transfers of native asset plus bridged asset from Parachain to Parachain
+/// (through AssetHub reserve) with fees paid using native asset.
+#[test]
+fn transfer_foreign_assets_from_para_to_para_through_asset_hub() {
+	// Init values for Parachain Origin
+	let destination = PenpalA::sibling_location_of(PenpalB::para_id());
+	let sender = PenpalASender::get();
+	let roc_to_send: Balance = ROCOCO_ED * 10000;
+	let assets_owner = PenpalAssetOwner::get();
+	let roc_location = RelayLocation::get();
+	let sender_as_seen_by_ah = AssetHubRococo::sibling_location_of(PenpalA::para_id());
+	let sov_of_sender_on_ah = AssetHubRococo::sovereign_account_id_of(sender_as_seen_by_ah);
+	let receiver_as_seen_by_ah = AssetHubRococo::sibling_location_of(PenpalB::para_id());
+	let sov_of_receiver_on_ah = AssetHubRococo::sovereign_account_id_of(receiver_as_seen_by_ah);
+	let wnd_to_send = ASSET_HUB_ROCOCO_ED * 10_000_000;
+
+	// Configure destination chain to trust AH as reserve of WND
+	PenpalB::execute_with(|| {
+		assert_ok!(<PenpalB as Chain>::System::set_storage(
+			<PenpalB as Chain>::RuntimeOrigin::root(),
+			vec![(
+				penpal_runtime::xcm_config::CustomizableAssetFromSystemAssetHub::key().to_vec(),
+				Location::new(2, [GlobalConsensus(Westend)]).encode(),
+			)],
+		));
+	});
+
+	// Register WND as foreign asset and transfer it around the Rococo ecosystem
+	let wnd_at_rococo_parachains =
+		Location::new(2, [Junction::GlobalConsensus(NetworkId::Westend)]);
+	AssetHubRococo::force_create_foreign_asset(
+		wnd_at_rococo_parachains.clone().try_into().unwrap(),
+		assets_owner.clone(),
+		false,
+		ASSET_MIN_BALANCE,
+		vec![],
+	);
+	PenpalA::force_create_foreign_asset(
+		wnd_at_rococo_parachains.clone(),
+		assets_owner.clone(),
+		false,
+		ASSET_MIN_BALANCE,
+		vec![],
+	);
+	PenpalB::force_create_foreign_asset(
+		wnd_at_rococo_parachains.clone(),
+		assets_owner.clone(),
+		false,
+		ASSET_MIN_BALANCE,
+		vec![],
+	);
+
+	// fund Parachain's sender account
+	PenpalA::mint_foreign_asset(
+		<PenpalA as Chain>::RuntimeOrigin::signed(assets_owner.clone()),
+		roc_location.clone(),
+		sender.clone(),
+		roc_to_send * 2,
+	);
+	PenpalA::mint_foreign_asset(
+		<PenpalA as Chain>::RuntimeOrigin::signed(assets_owner.clone()),
+		wnd_at_rococo_parachains.clone(),
+		sender.clone(),
+		wnd_to_send * 2,
+	);
+	// fund the Parachain Origin's SA on Asset Hub with the assets held in reserve
+	AssetHubRococo::fund_accounts(vec![(sov_of_sender_on_ah.clone().into(), roc_to_send * 2)]);
+	AssetHubRococo::mint_foreign_asset(
+		<AssetHubRococo as Chain>::RuntimeOrigin::signed(assets_owner),
+		wnd_at_rococo_parachains.clone().try_into().unwrap(),
+		sov_of_sender_on_ah.clone(),
+		wnd_to_send * 2,
+	);
+
+	// Init values for Parachain Destination
+	let receiver = PenpalBReceiver::get();
+
+	// Assets to send
+	let assets: Vec<Asset> = vec![
+		(roc_location.clone(), roc_to_send).into(),
+		(wnd_at_rococo_parachains.clone(), wnd_to_send).into(),
+	];
+	let fee_asset_id: AssetId = roc_location.clone().into();
+	let fee_asset_item = assets.iter().position(|a| a.id == fee_asset_id).unwrap() as u32;
+
+	// Init Test
+	let test_args = TestContext {
+		sender: sender.clone(),
+		receiver: receiver.clone(),
+		args: TestArgs::new_para(
+			destination,
+			receiver.clone(),
+			roc_to_send,
+			assets.into(),
+			None,
+			fee_asset_item,
+		),
+	};
+	let mut test = ParaToParaThroughAHTest::new(test_args);
+
+	// Query initial balances
+	let sender_rocs_before = PenpalA::execute_with(|| {
+		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(roc_location.clone(), &sender)
+	});
+	let sender_wnds_before = PenpalA::execute_with(|| {
+		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(wnd_at_rococo_parachains.clone(), &sender)
+	});
+	let rocs_in_sender_reserve_on_ahr_before =
+		<AssetHubRococo as Chain>::account_data_of(sov_of_sender_on_ah.clone()).free;
+	let wnds_in_sender_reserve_on_ahr_before = AssetHubRococo::execute_with(|| {
+		type Assets = <AssetHubRococo as AssetHubRococoPallet>::ForeignAssets;
+		<Assets as Inspect<_>>::balance(
+			wnd_at_rococo_parachains.clone().try_into().unwrap(),
+			&sov_of_sender_on_ah,
+		)
+	});
+	let rocs_in_receiver_reserve_on_ahr_before =
+		<AssetHubRococo as Chain>::account_data_of(sov_of_receiver_on_ah.clone()).free;
+	let wnds_in_receiver_reserve_on_ahr_before = AssetHubRococo::execute_with(|| {
+		type Assets = <AssetHubRococo as AssetHubRococoPallet>::ForeignAssets;
+		<Assets as Inspect<_>>::balance(
+			wnd_at_rococo_parachains.clone().try_into().unwrap(),
+			&sov_of_receiver_on_ah,
+		)
+	});
+	let receiver_rocs_before = PenpalB::execute_with(|| {
+		type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(roc_location.clone(), &receiver)
+	});
+	let receiver_wnds_before = PenpalB::execute_with(|| {
+		type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(wnd_at_rococo_parachains.clone(), &receiver)
+	});
+
+	// Set assertions and dispatchables
+	test.set_assertion::<PenpalA>(para_to_para_through_hop_sender_assertions);
+	test.set_assertion::<AssetHubRococo>(para_to_para_assethub_hop_assertions);
+	test.set_assertion::<PenpalB>(para_to_para_through_hop_receiver_assertions);
+	test.set_dispatchable::<PenpalA>(para_to_para_transfer_assets_through_ah);
+	test.assert();
+
+	// Query final balances
+	let sender_rocs_after = PenpalA::execute_with(|| {
+		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(roc_location.clone(), &sender)
+	});
+	let sender_wnds_after = PenpalA::execute_with(|| {
+		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(wnd_at_rococo_parachains.clone(), &sender)
+	});
+	let wnds_in_sender_reserve_on_ahr_after = AssetHubRococo::execute_with(|| {
+		type Assets = <AssetHubRococo as AssetHubRococoPallet>::ForeignAssets;
+		<Assets as Inspect<_>>::balance(
+			wnd_at_rococo_parachains.clone().try_into().unwrap(),
+			&sov_of_sender_on_ah,
+		)
+	});
+	let rocs_in_sender_reserve_on_ahr_after =
+		<AssetHubRococo as Chain>::account_data_of(sov_of_sender_on_ah).free;
+	let wnds_in_receiver_reserve_on_ahr_after = AssetHubRococo::execute_with(|| {
+		type Assets = <AssetHubRococo as AssetHubRococoPallet>::ForeignAssets;
+		<Assets as Inspect<_>>::balance(
+			wnd_at_rococo_parachains.clone().try_into().unwrap(),
+			&sov_of_receiver_on_ah,
+		)
+	});
+	let rocs_in_receiver_reserve_on_ahr_after =
+		<AssetHubRococo as Chain>::account_data_of(sov_of_receiver_on_ah).free;
+	let receiver_rocs_after = PenpalB::execute_with(|| {
+		type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(roc_location, &receiver)
+	});
+	let receiver_wnds_after = PenpalB::execute_with(|| {
+		type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(wnd_at_rococo_parachains, &receiver)
+	});
+
+	// Sender's balance is reduced by amount sent plus delivery fees
+	assert!(sender_rocs_after < sender_rocs_before - roc_to_send);
+	assert_eq!(sender_wnds_after, sender_wnds_before - wnd_to_send);
+	// Sovereign accounts on reserve are changed accordingly
+	assert_eq!(
+		rocs_in_sender_reserve_on_ahr_after,
+		rocs_in_sender_reserve_on_ahr_before - roc_to_send
+	);
+	assert_eq!(
+		wnds_in_sender_reserve_on_ahr_after,
+		wnds_in_sender_reserve_on_ahr_before - wnd_to_send
+	);
+	assert!(rocs_in_receiver_reserve_on_ahr_after > rocs_in_receiver_reserve_on_ahr_before);
+	assert_eq!(
+		wnds_in_receiver_reserve_on_ahr_after,
+		wnds_in_receiver_reserve_on_ahr_before + wnd_to_send
+	);
+	// Receiver's balance is increased
+	assert!(receiver_rocs_after > receiver_rocs_before);
+	assert_eq!(receiver_wnds_after, receiver_wnds_before + wnd_to_send);
+}
+
+// ==============================================================================================
+// ==== Bidirectional Transfer - Native + Teleportable Foreign Assets - Parachain<->AssetHub ====
+// ==============================================================================================
+/// Transfers of native asset plus teleportable foreign asset from Parachain to AssetHub and back
+/// with fees paid using native asset.
+#[test]
+fn bidirectional_teleport_foreign_asset_between_para_and_asset_hub_using_explicit_transfer_types() {
+	do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt(
+		para_to_asset_hub_teleport_foreign_assets,
+		asset_hub_to_para_teleport_foreign_assets,
+	);
+}
diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs
index b3841af0e6c38372b8fb621fac468b25bdec63a1..2402989225af2a6b3d03c7f353c8b0e7266b9fb1 100644
--- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs
+++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs
@@ -13,6 +13,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+mod foreign_assets_transfers;
 mod reserve_transfer;
 mod send;
 mod set_xcm_versions;
diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs
index a0738839087a51e87df3187ac6f06d2889cce64e..5aef70f5cbfc08522a5693d38bb2c774209a2469 100644
--- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs
+++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs
@@ -47,7 +47,7 @@ fn para_to_relay_sender_assertions(t: ParaToRelayTest) {
 			RuntimeEvent::ForeignAssets(
 				pallet_assets::Event::Burned { asset_id, owner, balance, .. }
 			) => {
-				asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"),
+				asset_id: *asset_id == RelayLocation::get(),
 				owner: *owner == t.sender.account_id,
 				balance: *balance == t.args.amount,
 			},
@@ -55,70 +55,92 @@ fn para_to_relay_sender_assertions(t: ParaToRelayTest) {
 	);
 }
 
-fn system_para_to_para_sender_assertions(t: SystemParaToParaTest) {
+pub fn system_para_to_para_sender_assertions(t: SystemParaToParaTest) {
 	type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent;
-
-	AssetHubRococo::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(
-		864_610_000,
-		8_799,
-	)));
-
+	AssetHubRococo::assert_xcm_pallet_attempted_complete(None);
+
+	let sov_acc_of_dest = AssetHubRococo::sovereign_account_id_of(t.args.dest.clone());
+	for (idx, asset) in t.args.assets.into_inner().into_iter().enumerate() {
+		let expected_id = asset.id.0.clone().try_into().unwrap();
+		let asset_amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap();
+		if idx == t.args.fee_asset_item as usize {
+			assert_expected_events!(
+				AssetHubRococo,
+				vec![
+					// Amount of native asset is transferred to Parachain's Sovereign account
+					RuntimeEvent::Balances(
+						pallet_balances::Event::Transfer { from, to, amount }
+					) => {
+						from: *from == t.sender.account_id,
+						to: *to == sov_acc_of_dest,
+						amount: *amount == asset_amount,
+					},
+				]
+			);
+		} else {
+			assert_expected_events!(
+				AssetHubRococo,
+				vec![
+					// Amount of foreign asset is transferred to Parachain's Sovereign account
+					RuntimeEvent::ForeignAssets(
+						pallet_assets::Event::Transferred { asset_id, from, to, amount },
+					) => {
+						asset_id: *asset_id == expected_id,
+						from: *from == t.sender.account_id,
+						to: *to == sov_acc_of_dest,
+						amount: *amount == asset_amount,
+					},
+				]
+			);
+		}
+	}
 	assert_expected_events!(
 		AssetHubRococo,
 		vec![
-			// Amount to reserve transfer is transferred to Parachain's Sovereign account
-			RuntimeEvent::Balances(
-				pallet_balances::Event::Transfer { from, to, amount }
-			) => {
-				from: *from == t.sender.account_id,
-				to: *to == AssetHubRococo::sovereign_account_id_of(
-					t.args.dest.clone()
-				),
-				amount: *amount == t.args.amount,
-			},
 			// Transport fees are paid
-			RuntimeEvent::PolkadotXcm(
-				pallet_xcm::Event::FeesPaid { .. }
-			) => {},
+			RuntimeEvent::PolkadotXcm(pallet_xcm::Event::FeesPaid { .. }) => {},
 		]
 	);
 	AssetHubRococo::assert_xcm_pallet_sent();
 }
 
-fn system_para_to_para_receiver_assertions(t: SystemParaToParaTest) {
+pub fn system_para_to_para_receiver_assertions(t: SystemParaToParaTest) {
 	type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
-	let system_para_native_asset_location =
-		v3::Location::try_from(RelayLocation::get()).expect("conversion works");
 
 	PenpalA::assert_xcmp_queue_success(None);
-
-	assert_expected_events!(
-		PenpalA,
-		vec![
-			RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => {
-				asset_id: *asset_id == system_para_native_asset_location,
-				owner: *owner == t.receiver.account_id,
-			},
-		]
-	);
+	for asset in t.args.assets.into_inner().into_iter() {
+		let expected_id = asset.id.0.try_into().unwrap();
+		assert_expected_events!(
+			PenpalA,
+			vec![
+				RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => {
+					asset_id: *asset_id == expected_id,
+					owner: *owner == t.receiver.account_id,
+				},
+			]
+		);
+	}
 }
 
-fn para_to_system_para_sender_assertions(t: ParaToSystemParaTest) {
+pub fn para_to_system_para_sender_assertions(t: ParaToSystemParaTest) {
 	type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
-	PenpalA::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(864_610_000, 8_799)));
-	assert_expected_events!(
-		PenpalA,
-		vec![
-			// Amount to reserve transfer is transferred to Parachain's Sovereign account
-			RuntimeEvent::ForeignAssets(
-				pallet_assets::Event::Burned { asset_id, owner, balance, .. }
-			) => {
-				asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"),
-				owner: *owner == t.sender.account_id,
-				balance: *balance == t.args.amount,
-			},
-		]
-	);
+	PenpalA::assert_xcm_pallet_attempted_complete(None);
+	for asset in t.args.assets.into_inner().into_iter() {
+		let expected_id = asset.id.0;
+		let asset_amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap();
+		assert_expected_events!(
+			PenpalA,
+			vec![
+				RuntimeEvent::ForeignAssets(
+					pallet_assets::Event::Burned { asset_id, owner, balance }
+				) => {
+					asset_id: *asset_id == expected_id,
+					owner: *owner == t.sender.account_id,
+					balance: *balance == asset_amount,
+				},
+			]
+		);
+	}
 }
 
 fn para_to_relay_receiver_assertions(t: ParaToRelayTest) {
@@ -150,25 +172,57 @@ fn para_to_relay_receiver_assertions(t: ParaToRelayTest) {
 	);
 }
 
-fn para_to_system_para_receiver_assertions(t: ParaToSystemParaTest) {
+pub fn para_to_system_para_receiver_assertions(t: ParaToSystemParaTest) {
 	type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent;
-	let sov_penpal_on_ahr = AssetHubRococo::sovereign_account_id_of(
-		AssetHubRococo::sibling_location_of(PenpalA::para_id()),
-	);
-
 	AssetHubRococo::assert_xcmp_queue_success(None);
 
+	let sov_acc_of_penpal = AssetHubRococo::sovereign_account_id_of(t.args.dest.clone());
+	for (idx, asset) in t.args.assets.into_inner().into_iter().enumerate() {
+		let expected_id = asset.id.0.clone().try_into().unwrap();
+		let asset_amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap();
+		if idx == t.args.fee_asset_item as usize {
+			assert_expected_events!(
+				AssetHubRococo,
+				vec![
+					// Amount of native is withdrawn from Parachain's Sovereign account
+					RuntimeEvent::Balances(
+						pallet_balances::Event::Burned { who, amount }
+					) => {
+						who: *who == sov_acc_of_penpal.clone().into(),
+						amount: *amount == asset_amount,
+					},
+					RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => {
+						who: *who == t.receiver.account_id,
+					},
+				]
+			);
+		} else {
+			assert_expected_events!(
+				AssetHubRococo,
+				vec![
+					// Amount of foreign asset is transferred from Parachain's Sovereign account
+					// to Receiver's account
+					RuntimeEvent::ForeignAssets(
+						pallet_assets::Event::Burned { asset_id, owner, balance },
+					) => {
+						asset_id: *asset_id == expected_id,
+						owner: *owner == sov_acc_of_penpal,
+						balance: *balance == asset_amount,
+					},
+					RuntimeEvent::ForeignAssets(
+						pallet_assets::Event::Issued { asset_id, owner, amount },
+					) => {
+						asset_id: *asset_id == expected_id,
+						owner: *owner == t.receiver.account_id,
+						amount: *amount == asset_amount,
+					},
+				]
+			);
+		}
+	}
 	assert_expected_events!(
 		AssetHubRococo,
 		vec![
-			// Amount to reserve transfer is withdrawn from Parachain's Sovereign account
-			RuntimeEvent::Balances(
-				pallet_balances::Event::Burned { who, amount }
-			) => {
-				who: *who == sov_penpal_on_ahr.clone().into(),
-				amount: *amount == t.args.amount,
-			},
-			RuntimeEvent::Balances(pallet_balances::Event::Minted { .. }) => {},
 			RuntimeEvent::MessageQueue(
 				pallet_message_queue::Event::Processed { success: true, .. }
 			) => {},
@@ -212,10 +266,8 @@ fn system_para_to_para_assets_sender_assertions(t: SystemParaToParaTest) {
 
 fn para_to_system_para_assets_sender_assertions(t: ParaToSystemParaTest) {
 	type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
-	let system_para_native_asset_location =
-		v3::Location::try_from(RelayLocation::get()).expect("conversion works");
-	let reservable_asset_location =
-		v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works");
+	let system_para_native_asset_location = RelayLocation::get();
+	let reservable_asset_location = PenpalLocalReservableFromAssetHub::get();
 	PenpalA::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(864_610_000, 8799)));
 	assert_expected_events!(
 		PenpalA,
@@ -245,14 +297,13 @@ fn para_to_system_para_assets_sender_assertions(t: ParaToSystemParaTest) {
 
 fn system_para_to_para_assets_receiver_assertions(t: SystemParaToParaTest) {
 	type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
-	let system_para_asset_location =
-		v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works");
+	let system_para_asset_location = PenpalLocalReservableFromAssetHub::get();
 	PenpalA::assert_xcmp_queue_success(None);
 	assert_expected_events!(
 		PenpalA,
 		vec![
 			RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => {
-				asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"),
+				asset_id: *asset_id == RelayLocation::get(),
 				owner: *owner == t.receiver.account_id,
 			},
 			RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, amount }) => {
@@ -304,7 +355,7 @@ fn relay_to_para_assets_receiver_assertions(t: RelayToParaTest) {
 		PenpalA,
 		vec![
 			RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => {
-				asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"),
+				asset_id: *asset_id == RelayLocation::get(),
 				owner: *owner == t.receiver.account_id,
 			},
 			RuntimeEvent::MessageQueue(
@@ -314,29 +365,27 @@ fn relay_to_para_assets_receiver_assertions(t: RelayToParaTest) {
 	);
 }
 
-fn para_to_para_through_relay_sender_assertions(t: ParaToParaThroughRelayTest) {
+pub fn para_to_para_through_hop_sender_assertions<Hop: Clone>(t: Test<PenpalA, PenpalB, Hop>) {
 	type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
 
-	let relay_asset_location =
-		v3::Location::try_from(RelayLocation::get()).expect("conversion works");
-
 	PenpalA::assert_xcm_pallet_attempted_complete(None);
-	// XCM sent to relay reserve
-	PenpalA::assert_parachain_system_ump_sent();
-
-	assert_expected_events!(
-		PenpalA,
-		vec![
-			// Amount to reserve transfer is transferred to Parachain's Sovereign account
-			RuntimeEvent::ForeignAssets(
-				pallet_assets::Event::Burned { asset_id, owner, balance },
-			) => {
-				asset_id: *asset_id == relay_asset_location,
-				owner: *owner == t.sender.account_id,
-				balance: *balance == t.args.amount,
-			},
-		]
-	);
+	for asset in t.args.assets.into_inner() {
+		let expected_id = asset.id.0.clone().try_into().unwrap();
+		let amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap();
+		assert_expected_events!(
+			PenpalA,
+			vec![
+				// Amount to reserve transfer is transferred to Parachain's Sovereign account
+				RuntimeEvent::ForeignAssets(
+					pallet_assets::Event::Burned { asset_id, owner, balance },
+				) => {
+					asset_id: *asset_id == expected_id,
+					owner: *owner == t.sender.account_id,
+					balance: *balance == amount,
+				},
+			]
+		);
+	}
 }
 
 fn para_to_para_relay_hop_assertions(t: ParaToParaThroughRelayTest) {
@@ -369,22 +418,22 @@ fn para_to_para_relay_hop_assertions(t: ParaToParaThroughRelayTest) {
 	);
 }
 
-fn para_to_para_through_relay_receiver_assertions(t: ParaToParaThroughRelayTest) {
+pub fn para_to_para_through_hop_receiver_assertions<Hop: Clone>(t: Test<PenpalA, PenpalB, Hop>) {
 	type RuntimeEvent = <PenpalB as Chain>::RuntimeEvent;
-	let relay_asset_location =
-		v3::Location::try_from(RelayLocation::get()).expect("conversion works");
 
 	PenpalB::assert_xcmp_queue_success(None);
-
-	assert_expected_events!(
-		PenpalB,
-		vec![
-			RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => {
-				asset_id: *asset_id == relay_asset_location,
-				owner: *owner == t.receiver.account_id,
-			},
-		]
-	);
+	for asset in t.args.assets.into_inner().into_iter() {
+		let expected_id = asset.id.0.try_into().unwrap();
+		assert_expected_events!(
+			PenpalB,
+			vec![
+				RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => {
+					asset_id: *asset_id == expected_id,
+					owner: *owner == t.receiver.account_id,
+				},
+			]
+		);
+	}
 }
 
 fn relay_to_para_reserve_transfer_assets(t: RelayToParaTest) -> DispatchResult {
@@ -526,8 +575,7 @@ fn reserve_transfer_native_asset_from_relay_to_para() {
 	let amount_to_send: Balance = ROCOCO_ED * 1000;
 
 	// Init values fot Parachain
-	let relay_native_asset_location =
-		v3::Location::try_from(RelayLocation::get()).expect("conversion works");
+	let relay_native_asset_location = RelayLocation::get();
 	let receiver = PenpalAReceiver::get();
 
 	// Init Test
@@ -542,7 +590,7 @@ fn reserve_transfer_native_asset_from_relay_to_para() {
 	let sender_balance_before = test.sender.balance;
 	let receiver_assets_before = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
-		<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.into(), &receiver)
+		<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.clone(), &receiver)
 	});
 
 	// Set assertions and dispatchables
@@ -555,7 +603,7 @@ fn reserve_transfer_native_asset_from_relay_to_para() {
 	let sender_balance_after = test.sender.balance;
 	let receiver_assets_after = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
-		<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.into(), &receiver)
+		<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location, &receiver)
 	});
 
 	// Sender's balance is reduced by amount sent plus delivery fees
@@ -577,13 +625,12 @@ fn reserve_transfer_native_asset_from_para_to_relay() {
 	let amount_to_send: Balance = ROCOCO_ED * 1000;
 	let assets: Assets = (Parent, amount_to_send).into();
 	let asset_owner = PenpalAssetOwner::get();
-	let relay_native_asset_location =
-		v3::Location::try_from(RelayLocation::get()).expect("conversion works");
+	let relay_native_asset_location = RelayLocation::get();
 
 	// fund Parachain's sender account
 	PenpalA::mint_foreign_asset(
 		<PenpalA as Chain>::RuntimeOrigin::signed(asset_owner),
-		relay_native_asset_location,
+		relay_native_asset_location.clone(),
 		sender.clone(),
 		amount_to_send * 2,
 	);
@@ -614,7 +661,7 @@ fn reserve_transfer_native_asset_from_para_to_relay() {
 	// Query initial balances
 	let sender_assets_before = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
-		<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.into(), &sender)
+		<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.clone(), &sender)
 	});
 	let receiver_balance_before = test.receiver.balance;
 
@@ -627,7 +674,7 @@ fn reserve_transfer_native_asset_from_para_to_relay() {
 	// Query final balances
 	let sender_assets_after = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
-		<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.into(), &sender)
+		<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location, &sender)
 	});
 	let receiver_balance_after = test.receiver.balance;
 
@@ -654,8 +701,7 @@ fn reserve_transfer_native_asset_from_system_para_to_para() {
 	let assets: Assets = (Parent, amount_to_send).into();
 
 	// Init values for Parachain
-	let system_para_native_asset_location =
-		v3::Location::try_from(RelayLocation::get()).expect("conversion works");
+	let system_para_native_asset_location = RelayLocation::get();
 	let receiver = PenpalAReceiver::get();
 
 	// Init Test
@@ -677,7 +723,7 @@ fn reserve_transfer_native_asset_from_system_para_to_para() {
 	let sender_balance_before = test.sender.balance;
 	let receiver_assets_before = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
-		<ForeignAssets as Inspect<_>>::balance(system_para_native_asset_location.into(), &receiver)
+		<ForeignAssets as Inspect<_>>::balance(system_para_native_asset_location.clone(), &receiver)
 	});
 
 	// Set assertions and dispatchables
@@ -711,14 +757,13 @@ fn reserve_transfer_native_asset_from_para_to_system_para() {
 	let sender = PenpalASender::get();
 	let amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 10000;
 	let assets: Assets = (Parent, amount_to_send).into();
-	let system_para_native_asset_location =
-		v3::Location::try_from(RelayLocation::get()).expect("conversion works");
+	let system_para_native_asset_location = RelayLocation::get();
 	let asset_owner = PenpalAssetOwner::get();
 
 	// fund Parachain's sender account
 	PenpalA::mint_foreign_asset(
 		<PenpalA as Chain>::RuntimeOrigin::signed(asset_owner),
-		system_para_native_asset_location,
+		system_para_native_asset_location.clone(),
 		sender.clone(),
 		amount_to_send * 2,
 	);
@@ -749,7 +794,7 @@ fn reserve_transfer_native_asset_from_para_to_system_para() {
 	// Query initial balances
 	let sender_assets_before = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
-		<ForeignAssets as Inspect<_>>::balance(system_para_native_asset_location, &sender)
+		<ForeignAssets as Inspect<_>>::balance(system_para_native_asset_location.clone(), &sender)
 	});
 	let receiver_balance_before = test.receiver.balance;
 
@@ -776,9 +821,9 @@ fn reserve_transfer_native_asset_from_para_to_system_para() {
 	assert!(receiver_balance_after < receiver_balance_before + amount_to_send);
 }
 
-// =========================================================================
-// ======= Reserve Transfers - Non-system Asset - AssetHub<>Parachain ======
-// =========================================================================
+// ==================================================================================
+// ======= Reserve Transfers - Native + Non-system Asset - AssetHub<>Parachain ======
+// ==================================================================================
 /// Reserve Transfers of a local asset and native asset from System Parachain to Parachain should
 /// work
 #[test]
@@ -817,10 +862,8 @@ fn reserve_transfer_assets_from_system_para_to_para() {
 
 	// Init values for Parachain
 	let receiver = PenpalAReceiver::get();
-	let system_para_native_asset_location =
-		v3::Location::try_from(RelayLocation::get()).expect("conversion works");
-	let system_para_foreign_asset_location =
-		v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works");
+	let system_para_native_asset_location = RelayLocation::get();
+	let system_para_foreign_asset_location = PenpalLocalReservableFromAssetHub::get();
 
 	// Init Test
 	let para_test_args = TestContext {
@@ -845,11 +888,14 @@ fn reserve_transfer_assets_from_system_para_to_para() {
 	});
 	let receiver_system_native_assets_before = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
-		<ForeignAssets as Inspect<_>>::balance(system_para_native_asset_location, &receiver)
+		<ForeignAssets as Inspect<_>>::balance(system_para_native_asset_location.clone(), &receiver)
 	});
 	let receiver_foreign_assets_before = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
-		<ForeignAssets as Inspect<_>>::balance(system_para_foreign_asset_location, &receiver)
+		<ForeignAssets as Inspect<_>>::balance(
+			system_para_foreign_asset_location.clone(),
+			&receiver,
+		)
 	});
 
 	// Set assertions and dispatchables
@@ -866,7 +912,7 @@ fn reserve_transfer_assets_from_system_para_to_para() {
 	});
 	let receiver_system_native_assets_after = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
-		<ForeignAssets as Inspect<_>>::balance(system_para_native_asset_location, &receiver)
+		<ForeignAssets as Inspect<_>>::balance(system_para_native_asset_location.clone(), &receiver)
 	});
 	let receiver_foreign_assets_after = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
@@ -904,14 +950,11 @@ fn reserve_transfer_assets_from_para_to_system_para() {
 	let asset_amount_to_send = ASSET_HUB_ROCOCO_ED * 10000;
 	let penpal_asset_owner = PenpalAssetOwner::get();
 	let penpal_asset_owner_signer = <PenpalA as Chain>::RuntimeOrigin::signed(penpal_asset_owner);
-	let asset_location_on_penpal =
-		v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works");
-	let asset_location_on_penpal_latest: Location = asset_location_on_penpal.try_into().unwrap();
-	let system_asset_location_on_penpal =
-		v3::Location::try_from(RelayLocation::get()).expect("conversion works");
+	let asset_location_on_penpal = PenpalLocalReservableFromAssetHub::get();
+	let system_asset_location_on_penpal = RelayLocation::get();
 	let assets: Assets = vec![
 		(Parent, fee_amount_to_send).into(),
-		(asset_location_on_penpal_latest, asset_amount_to_send).into(),
+		(asset_location_on_penpal.clone(), asset_amount_to_send).into(),
 	]
 	.into();
 	let fee_asset_index = assets
@@ -938,10 +981,8 @@ fn reserve_transfer_assets_from_para_to_system_para() {
 	let receiver = AssetHubRococoReceiver::get();
 	let penpal_location_as_seen_by_ahr = AssetHubRococo::sibling_location_of(PenpalA::para_id());
 	let sov_penpal_on_ahr = AssetHubRococo::sovereign_account_id_of(penpal_location_as_seen_by_ahr);
-	let system_para_native_asset_location =
-		v3::Location::try_from(RelayLocation::get()).expect("conversion works");
-	let system_para_foreign_asset_location =
-		v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works");
+	let system_para_native_asset_location = RelayLocation::get();
+	let system_para_foreign_asset_location = PenpalLocalReservableFromAssetHub::get();
 	let ah_asset_owner = AssetHubRococoAssetOwner::get();
 	let ah_asset_owner_signer = <AssetHubRococo as Chain>::RuntimeOrigin::signed(ah_asset_owner);
 
@@ -976,11 +1017,11 @@ fn reserve_transfer_assets_from_para_to_system_para() {
 	// Query initial balances
 	let sender_system_assets_before = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
-		<ForeignAssets as Inspect<_>>::balance(system_para_native_asset_location, &sender)
+		<ForeignAssets as Inspect<_>>::balance(system_para_native_asset_location.clone(), &sender)
 	});
 	let sender_foreign_assets_before = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
-		<ForeignAssets as Inspect<_>>::balance(system_para_foreign_asset_location, &sender)
+		<ForeignAssets as Inspect<_>>::balance(system_para_foreign_asset_location.clone(), &sender)
 	});
 	let receiver_balance_before = test.receiver.balance;
 	let receiver_assets_before = AssetHubRococo::execute_with(|| {
@@ -997,7 +1038,7 @@ fn reserve_transfer_assets_from_para_to_system_para() {
 	// Query final balances
 	let sender_system_assets_after = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
-		<ForeignAssets as Inspect<_>>::balance(system_para_native_asset_location, &sender)
+		<ForeignAssets as Inspect<_>>::balance(system_para_native_asset_location.clone(), &sender)
 	});
 	let sender_foreign_assets_after = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
@@ -1029,22 +1070,21 @@ fn reserve_transfer_assets_from_para_to_system_para() {
 /// Reserve Transfers of native asset from Parachain to Parachain (through Relay reserve) should
 /// work
 #[test]
-fn reserve_transfer_native_asset_from_para_to_para_trough_relay() {
+fn reserve_transfer_native_asset_from_para_to_para_through_relay() {
 	// Init values for Parachain Origin
 	let destination = PenpalA::sibling_location_of(PenpalB::para_id());
 	let sender = PenpalASender::get();
 	let amount_to_send: Balance = ROCOCO_ED * 10000;
 	let asset_owner = PenpalAssetOwner::get();
 	let assets = (Parent, amount_to_send).into();
-	let relay_native_asset_location =
-		v3::Location::try_from(RelayLocation::get()).expect("conversion works");
+	let relay_native_asset_location = RelayLocation::get();
 	let sender_as_seen_by_relay = Rococo::child_location_of(PenpalA::para_id());
 	let sov_of_sender_on_relay = Rococo::sovereign_account_id_of(sender_as_seen_by_relay);
 
 	// fund Parachain's sender account
 	PenpalA::mint_foreign_asset(
 		<PenpalA as Chain>::RuntimeOrigin::signed(asset_owner),
-		relay_native_asset_location,
+		relay_native_asset_location.clone(),
 		sender.clone(),
 		amount_to_send * 2,
 	);
@@ -1066,24 +1106,24 @@ fn reserve_transfer_native_asset_from_para_to_para_trough_relay() {
 	// Query initial balances
 	let sender_assets_before = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
-		<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location, &sender)
+		<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.clone(), &sender)
 	});
 	let receiver_assets_before = PenpalB::execute_with(|| {
 		type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
-		<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location, &receiver)
+		<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.clone(), &receiver)
 	});
 
 	// Set assertions and dispatchables
-	test.set_assertion::<PenpalA>(para_to_para_through_relay_sender_assertions);
+	test.set_assertion::<PenpalA>(para_to_para_through_hop_sender_assertions);
 	test.set_assertion::<Rococo>(para_to_para_relay_hop_assertions);
-	test.set_assertion::<PenpalB>(para_to_para_through_relay_receiver_assertions);
+	test.set_assertion::<PenpalB>(para_to_para_through_hop_receiver_assertions);
 	test.set_dispatchable::<PenpalA>(para_to_para_through_relay_limited_reserve_transfer_assets);
 	test.assert();
 
 	// Query final balances
 	let sender_assets_after = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
-		<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location, &sender)
+		<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.clone(), &sender)
 	});
 	let receiver_assets_after = PenpalB::execute_with(|| {
 		type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/swap.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/swap.rs
index e13300b7c11426416f543c2ee026702277b695b1..919e0080ba62d90a78a3738c4c8f141c01979feb 100644
--- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/swap.rs
+++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/swap.rs
@@ -17,7 +17,10 @@ use crate::imports::*;
 
 #[test]
 fn swap_locally_on_chain_using_local_assets() {
-	let asset_native = Box::new(asset_hub_rococo_runtime::xcm_config::TokenLocationV3::get());
+	let asset_native = Box::new(
+		v3::Location::try_from(asset_hub_rococo_runtime::xcm_config::TokenLocation::get())
+			.expect("conversion works"),
+	);
 	let asset_one = Box::new(v3::Location::new(
 		0,
 		[
@@ -112,10 +115,9 @@ fn swap_locally_on_chain_using_local_assets() {
 
 #[test]
 fn swap_locally_on_chain_using_foreign_assets() {
-	let asset_native =
-		Box::new(v3::Location::try_from(RelayLocation::get()).expect("conversion works"));
+	let asset_native = Box::new(v3::Location::try_from(RelayLocation::get()).unwrap());
 	let asset_location_on_penpal =
-		v3::Location::try_from(PenpalLocalTeleportableToAssetHub::get()).expect("conversion works");
+		v3::Location::try_from(PenpalLocalTeleportableToAssetHub::get()).unwrap();
 	let foreign_asset_at_asset_hub_rococo =
 		v3::Location::new(1, [v3::Junction::Parachain(PenpalA::para_id().into())])
 			.appended_with(asset_location_on_penpal)
@@ -228,11 +230,9 @@ fn swap_locally_on_chain_using_foreign_assets() {
 
 #[test]
 fn cannot_create_pool_from_pool_assets() {
-	let asset_native = Box::new(asset_hub_rococo_runtime::xcm_config::TokenLocationV3::get());
-	let mut asset_one = asset_hub_rococo_runtime::xcm_config::PoolAssetsPalletLocationV3::get();
-	asset_one
-		.append_with(v3::Junction::GeneralIndex(ASSET_ID.into()))
-		.expect("pool assets");
+	let asset_native = asset_hub_rococo_runtime::xcm_config::TokenLocation::get();
+	let mut asset_one = asset_hub_rococo_runtime::xcm_config::PoolAssetsPalletLocation::get();
+	asset_one.append_with(GeneralIndex(ASSET_ID.into())).expect("pool assets");
 
 	AssetHubRococo::execute_with(|| {
 		let pool_owner_account_id = asset_hub_rococo_runtime::AssetConversionOrigin::get();
@@ -255,8 +255,8 @@ fn cannot_create_pool_from_pool_assets() {
 		assert_matches::assert_matches!(
 			<AssetHubRococo as AssetHubRococoPallet>::AssetConversion::create_pool(
 				<AssetHubRococo as Chain>::RuntimeOrigin::signed(AssetHubRococoSender::get()),
-				asset_native,
-				Box::new(asset_one),
+				Box::new(v3::Location::try_from(asset_native).expect("conversion works")),
+				Box::new(v3::Location::try_from(asset_one).expect("conversion works")),
 			),
 			Err(DispatchError::Module(ModuleError{index: _, error: _, message})) => assert_eq!(message, Some("Unknown"))
 		);
@@ -265,7 +265,9 @@ fn cannot_create_pool_from_pool_assets() {
 
 #[test]
 fn pay_xcm_fee_with_some_asset_swapped_for_native() {
-	let asset_native = asset_hub_rococo_runtime::xcm_config::TokenLocationV3::get();
+	let asset_native =
+		v3::Location::try_from(asset_hub_rococo_runtime::xcm_config::TokenLocation::get())
+			.expect("conversion works");
 	let asset_one = xcm::v3::Location {
 		parents: 0,
 		interior: [
diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs
index 1cbb7fb8c193accc65ef160a3a09514bd51debf5..f74378d7631a610a57c61153e62aedfcb588a611 100644
--- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs
+++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs
@@ -110,8 +110,7 @@ fn para_dest_assertions(t: RelayToSystemParaTest) {
 
 fn penpal_to_ah_foreign_assets_sender_assertions(t: ParaToSystemParaTest) {
 	type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
-	let system_para_native_asset_location =
-		v3::Location::try_from(RelayLocation::get()).expect("conversion works");
+	let system_para_native_asset_location = RelayLocation::get();
 	let expected_asset_id = t.args.asset_id.unwrap();
 	let (_, expected_asset_amount) =
 		non_fee_asset(&t.args.assets, t.args.fee_asset_item as usize).unwrap();
@@ -204,8 +203,7 @@ fn ah_to_penpal_foreign_assets_receiver_assertions(t: SystemParaToParaTest) {
 	let (_, expected_asset_amount) =
 		non_fee_asset(&t.args.assets, t.args.fee_asset_item as usize).unwrap();
 	let checking_account = <PenpalA as PenpalAPallet>::PolkadotXcm::check_account();
-	let system_para_native_asset_location =
-		v3::Location::try_from(RelayLocation::get()).expect("conversion works");
+	let system_para_native_asset_location = RelayLocation::get();
 
 	PenpalA::assert_xcmp_queue_success(None);
 
@@ -414,29 +412,28 @@ fn teleport_to_other_system_parachains_works() {
 	);
 }
 
-/// Bidirectional teleports of local Penpal assets to Asset Hub as foreign assets should work
-/// (using native reserve-based transfer for fees)
-#[test]
-fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
+/// Bidirectional teleports of local Penpal assets to Asset Hub as foreign assets while paying
+/// fees using (reserve transferred) native asset.
+pub fn do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt(
+	para_to_ah_dispatchable: fn(ParaToSystemParaTest) -> DispatchResult,
+	ah_to_para_dispatchable: fn(SystemParaToParaTest) -> DispatchResult,
+) {
 	// Init values for Parachain
 	let fee_amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 10000;
-	let asset_location_on_penpal =
-		v3::Location::try_from(PenpalLocalTeleportableToAssetHub::get()).expect("conversion works");
+	let asset_location_on_penpal = PenpalLocalTeleportableToAssetHub::get();
 	let asset_id_on_penpal = match asset_location_on_penpal.last() {
-		Some(v3::Junction::GeneralIndex(id)) => *id as u32,
+		Some(Junction::GeneralIndex(id)) => *id as u32,
 		_ => unreachable!(),
 	};
 	let asset_amount_to_send = ASSET_HUB_ROCOCO_ED * 1000;
 	let asset_owner = PenpalAssetOwner::get();
-	let system_para_native_asset_location =
-		v3::Location::try_from(RelayLocation::get()).expect("conversion works");
+	let system_para_native_asset_location = RelayLocation::get();
 	let sender = PenpalASender::get();
 	let penpal_check_account = <PenpalA as PenpalAPallet>::PolkadotXcm::check_account();
 	let ah_as_seen_by_penpal = PenpalA::sibling_location_of(AssetHubRococo::para_id());
-	let asset_location_on_penpal_latest: Location = asset_location_on_penpal.try_into().unwrap();
 	let penpal_assets: Assets = vec![
 		(Parent, fee_amount_to_send).into(),
-		(asset_location_on_penpal_latest, asset_amount_to_send).into(),
+		(asset_location_on_penpal.clone(), asset_amount_to_send).into(),
 	]
 	.into();
 	let fee_asset_index = penpal_assets
@@ -448,7 +445,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
 	// fund Parachain's sender account
 	PenpalA::mint_foreign_asset(
 		<PenpalA as Chain>::RuntimeOrigin::signed(asset_owner.clone()),
-		system_para_native_asset_location,
+		system_para_native_asset_location.clone(),
 		sender.clone(),
 		fee_amount_to_send * 2,
 	);
@@ -472,7 +469,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
 
 	// Init values for System Parachain
 	let foreign_asset_at_asset_hub_rococo =
-		v3::Location::new(1, [v3::Junction::Parachain(PenpalA::para_id().into())])
+		Location::new(1, [Junction::Parachain(PenpalA::para_id().into())])
 			.appended_with(asset_location_on_penpal)
 			.unwrap();
 	let penpal_to_ah_beneficiary_id = AssetHubRococoReceiver::get();
@@ -494,7 +491,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
 	let penpal_sender_balance_before = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
 		<ForeignAssets as Inspect<_>>::balance(
-			system_para_native_asset_location,
+			system_para_native_asset_location.clone(),
 			&PenpalASender::get(),
 		)
 	});
@@ -508,20 +505,20 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
 	let ah_receiver_assets_before = AssetHubRococo::execute_with(|| {
 		type Assets = <AssetHubRococo as AssetHubRococoPallet>::ForeignAssets;
 		<Assets as Inspect<_>>::balance(
-			foreign_asset_at_asset_hub_rococo,
+			foreign_asset_at_asset_hub_rococo.clone().try_into().unwrap(),
 			&AssetHubRococoReceiver::get(),
 		)
 	});
 
 	penpal_to_ah.set_assertion::<PenpalA>(penpal_to_ah_foreign_assets_sender_assertions);
 	penpal_to_ah.set_assertion::<AssetHubRococo>(penpal_to_ah_foreign_assets_receiver_assertions);
-	penpal_to_ah.set_dispatchable::<PenpalA>(para_to_system_para_transfer_assets);
+	penpal_to_ah.set_dispatchable::<PenpalA>(para_to_ah_dispatchable);
 	penpal_to_ah.assert();
 
 	let penpal_sender_balance_after = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
 		<ForeignAssets as Inspect<_>>::balance(
-			system_para_native_asset_location,
+			system_para_native_asset_location.clone(),
 			&PenpalASender::get(),
 		)
 	});
@@ -535,7 +532,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
 	let ah_receiver_assets_after = AssetHubRococo::execute_with(|| {
 		type Assets = <AssetHubRococo as AssetHubRococoPallet>::ForeignAssets;
 		<Assets as Inspect<_>>::balance(
-			foreign_asset_at_asset_hub_rococo,
+			foreign_asset_at_asset_hub_rococo.clone().try_into().unwrap(),
 			&AssetHubRococoReceiver::get(),
 		)
 	});
@@ -563,19 +560,17 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
 		type ForeignAssets = <AssetHubRococo as AssetHubRococoPallet>::ForeignAssets;
 		assert_ok!(ForeignAssets::transfer(
 			<AssetHubRococo as Chain>::RuntimeOrigin::signed(AssetHubRococoReceiver::get()),
-			foreign_asset_at_asset_hub_rococo,
+			foreign_asset_at_asset_hub_rococo.clone().try_into().unwrap(),
 			AssetHubRococoSender::get().into(),
 			asset_amount_to_send,
 		));
 	});
 
-	let foreign_asset_at_asset_hub_rococo_latest: Location =
-		foreign_asset_at_asset_hub_rococo.try_into().unwrap();
 	let ah_to_penpal_beneficiary_id = PenpalAReceiver::get();
 	let penpal_as_seen_by_ah = AssetHubRococo::sibling_location_of(PenpalA::para_id());
 	let ah_assets: Assets = vec![
 		(Parent, fee_amount_to_send).into(),
-		(foreign_asset_at_asset_hub_rococo_latest, asset_amount_to_send).into(),
+		(foreign_asset_at_asset_hub_rococo.clone(), asset_amount_to_send).into(),
 	]
 	.into();
 	let fee_asset_index = ah_assets
@@ -603,7 +598,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
 	let penpal_receiver_balance_before = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
 		<ForeignAssets as Inspect<_>>::balance(
-			system_para_native_asset_location,
+			system_para_native_asset_location.clone(),
 			&PenpalAReceiver::get(),
 		)
 	});
@@ -611,7 +606,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
 	let ah_sender_assets_before = AssetHubRococo::execute_with(|| {
 		type ForeignAssets = <AssetHubRococo as AssetHubRococoPallet>::ForeignAssets;
 		<ForeignAssets as Inspect<_>>::balance(
-			foreign_asset_at_asset_hub_rococo,
+			foreign_asset_at_asset_hub_rococo.clone().try_into().unwrap(),
 			&AssetHubRococoSender::get(),
 		)
 	});
@@ -622,7 +617,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
 
 	ah_to_penpal.set_assertion::<AssetHubRococo>(ah_to_penpal_foreign_assets_sender_assertions);
 	ah_to_penpal.set_assertion::<PenpalA>(ah_to_penpal_foreign_assets_receiver_assertions);
-	ah_to_penpal.set_dispatchable::<AssetHubRococo>(system_para_to_para_transfer_assets);
+	ah_to_penpal.set_dispatchable::<AssetHubRococo>(ah_to_para_dispatchable);
 	ah_to_penpal.assert();
 
 	let ah_sender_balance_after = ah_to_penpal.sender.balance;
@@ -637,7 +632,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
 	let ah_sender_assets_after = AssetHubRococo::execute_with(|| {
 		type ForeignAssets = <AssetHubRococo as AssetHubRococoPallet>::ForeignAssets;
 		<ForeignAssets as Inspect<_>>::balance(
-			foreign_asset_at_asset_hub_rococo,
+			foreign_asset_at_asset_hub_rococo.try_into().unwrap(),
 			&AssetHubRococoSender::get(),
 		)
 	});
@@ -660,3 +655,13 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
 	// Receiver's balance is increased by exact amount
 	assert_eq!(penpal_receiver_assets_after, penpal_receiver_assets_before + asset_amount_to_send);
 }
+
+/// Bidirectional teleports of local Penpal assets to Asset Hub as foreign assets should work
+/// (using native reserve-based transfer for fees)
+#[test]
+fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
+	do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt(
+		para_to_system_para_transfer_assets,
+		system_para_to_para_transfer_assets,
+	);
+}
diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs
index c9f5fe0647e12ba0121261505e27ff56c3f82f96..e687251c14f9e2059787e817f6f480c738881e7b 100644
--- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs
+++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs
@@ -30,6 +30,7 @@ mod imports {
 		prelude::{AccountId32 as AccountId32Junction, *},
 		v3,
 	};
+	pub use xcm_executor::traits::TransferType;
 
 	// Cumulus
 	pub use asset_test_utils::xcm_helpers;
@@ -85,6 +86,7 @@ mod imports {
 	pub type SystemParaToParaTest = Test<AssetHubWestend, PenpalA>;
 	pub type ParaToSystemParaTest = Test<PenpalA, AssetHubWestend>;
 	pub type ParaToParaThroughRelayTest = Test<PenpalA, PenpalB, Westend>;
+	pub type ParaToParaThroughAHTest = Test<PenpalA, PenpalB, AssetHubWestend>;
 }
 
 #[cfg(test)]
diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/foreign_assets_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/foreign_assets_transfers.rs
new file mode 100644
index 0000000000000000000000000000000000000000..8cfda37c84c9495acce070bad7a42ad8ef058277
--- /dev/null
+++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/foreign_assets_transfers.rs
@@ -0,0 +1,629 @@
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use super::reserve_transfer::*;
+use crate::{
+	imports::*,
+	tests::teleport::do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt,
+};
+
+fn para_to_para_assethub_hop_assertions(t: ParaToParaThroughAHTest) {
+	type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
+	let sov_penpal_a_on_ah = AssetHubWestend::sovereign_account_id_of(
+		AssetHubWestend::sibling_location_of(PenpalA::para_id()),
+	);
+	let sov_penpal_b_on_ah = AssetHubWestend::sovereign_account_id_of(
+		AssetHubWestend::sibling_location_of(PenpalB::para_id()),
+	);
+
+	assert_expected_events!(
+		AssetHubWestend,
+		vec![
+			// Withdrawn from sender parachain SA
+			RuntimeEvent::Balances(
+				pallet_balances::Event::Burned { who, amount }
+			) => {
+				who: *who == sov_penpal_a_on_ah,
+				amount: *amount == t.args.amount,
+			},
+			// Deposited to receiver parachain SA
+			RuntimeEvent::Balances(
+				pallet_balances::Event::Minted { who, .. }
+			) => {
+				who: *who == sov_penpal_b_on_ah,
+			},
+			RuntimeEvent::MessageQueue(
+				pallet_message_queue::Event::Processed { success: true, .. }
+			) => {},
+		]
+	);
+}
+
+fn ah_to_para_transfer_assets(t: SystemParaToParaTest) -> DispatchResult {
+	let fee_idx = t.args.fee_asset_item as usize;
+	let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
+	<AssetHubWestend as AssetHubWestendPallet>::PolkadotXcm::transfer_assets_using_type(
+		t.signed_origin,
+		bx!(t.args.dest.into()),
+		bx!(t.args.beneficiary.into()),
+		bx!(t.args.assets.into()),
+		bx!(TransferType::LocalReserve),
+		bx!(fee.id.into()),
+		bx!(TransferType::LocalReserve),
+		t.args.weight_limit,
+	)
+}
+
+fn para_to_ah_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult {
+	let fee_idx = t.args.fee_asset_item as usize;
+	let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
+	<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type(
+		t.signed_origin,
+		bx!(t.args.dest.into()),
+		bx!(t.args.beneficiary.into()),
+		bx!(t.args.assets.into()),
+		bx!(TransferType::DestinationReserve),
+		bx!(fee.id.into()),
+		bx!(TransferType::DestinationReserve),
+		t.args.weight_limit,
+	)
+}
+
+fn para_to_para_transfer_assets_through_ah(t: ParaToParaThroughAHTest) -> DispatchResult {
+	let fee_idx = t.args.fee_asset_item as usize;
+	let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
+	let asset_hub_location: Location = PenpalA::sibling_location_of(AssetHubWestend::para_id());
+	<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type(
+		t.signed_origin,
+		bx!(t.args.dest.into()),
+		bx!(t.args.beneficiary.into()),
+		bx!(t.args.assets.into()),
+		bx!(TransferType::RemoteReserve(asset_hub_location.clone().into())),
+		bx!(fee.id.into()),
+		bx!(TransferType::RemoteReserve(asset_hub_location.into())),
+		t.args.weight_limit,
+	)
+}
+
+fn para_to_asset_hub_teleport_foreign_assets(t: ParaToSystemParaTest) -> DispatchResult {
+	let fee_idx = t.args.fee_asset_item as usize;
+	let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
+	<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type(
+		t.signed_origin,
+		bx!(t.args.dest.into()),
+		bx!(t.args.beneficiary.into()),
+		bx!(t.args.assets.into()),
+		bx!(TransferType::Teleport),
+		bx!(fee.id.into()),
+		bx!(TransferType::DestinationReserve),
+		t.args.weight_limit,
+	)
+}
+
+fn asset_hub_to_para_teleport_foreign_assets(t: SystemParaToParaTest) -> DispatchResult {
+	let fee_idx = t.args.fee_asset_item as usize;
+	let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
+	<AssetHubWestend as AssetHubWestendPallet>::PolkadotXcm::transfer_assets_using_type(
+		t.signed_origin,
+		bx!(t.args.dest.into()),
+		bx!(t.args.beneficiary.into()),
+		bx!(t.args.assets.into()),
+		bx!(TransferType::Teleport),
+		bx!(fee.id.into()),
+		bx!(TransferType::LocalReserve),
+		t.args.weight_limit,
+	)
+}
+
+// ===========================================================================
+// ======= Transfer - Native + Bridged Assets - AssetHub->Parachain ==========
+// ===========================================================================
+/// Transfers of native asset plus bridged asset from AssetHub to some Parachain
+/// while paying fees using native asset.
+#[test]
+fn transfer_foreign_assets_from_asset_hub_to_para() {
+	let destination = AssetHubWestend::sibling_location_of(PenpalA::para_id());
+	let sender = AssetHubWestendSender::get();
+	let native_amount_to_send: Balance = ASSET_HUB_WESTEND_ED * 1000;
+	let native_asset_location = RelayLocation::get();
+	let receiver = PenpalAReceiver::get();
+	let assets_owner = PenpalAssetOwner::get();
+	// Foreign asset used: bridged ROC
+	let foreign_amount_to_send = ASSET_HUB_WESTEND_ED * 10_000_000;
+	let roc_at_westend_parachains =
+		Location::new(2, [Junction::GlobalConsensus(NetworkId::Rococo)]);
+
+	// Configure destination chain to trust AH as reserve of ROC
+	PenpalA::execute_with(|| {
+		assert_ok!(<PenpalA as Chain>::System::set_storage(
+			<PenpalA as Chain>::RuntimeOrigin::root(),
+			vec![(
+				penpal_runtime::xcm_config::CustomizableAssetFromSystemAssetHub::key().to_vec(),
+				Location::new(2, [GlobalConsensus(Rococo)]).encode(),
+			)],
+		));
+	});
+	PenpalA::force_create_foreign_asset(
+		roc_at_westend_parachains.clone(),
+		assets_owner.clone(),
+		false,
+		ASSET_MIN_BALANCE,
+		vec![],
+	);
+	AssetHubWestend::force_create_foreign_asset(
+		roc_at_westend_parachains.clone().try_into().unwrap(),
+		assets_owner.clone(),
+		false,
+		ASSET_MIN_BALANCE,
+		vec![],
+	);
+	AssetHubWestend::mint_foreign_asset(
+		<AssetHubWestend as Chain>::RuntimeOrigin::signed(assets_owner),
+		roc_at_westend_parachains.clone().try_into().unwrap(),
+		sender.clone(),
+		foreign_amount_to_send * 2,
+	);
+
+	// Assets to send
+	let assets: Vec<Asset> = vec![
+		(Parent, native_amount_to_send).into(),
+		(roc_at_westend_parachains.clone(), foreign_amount_to_send).into(),
+	];
+	let fee_asset_id = AssetId(Parent.into());
+	let fee_asset_item = assets.iter().position(|a| a.id == fee_asset_id).unwrap() as u32;
+
+	// Init Test
+	let test_args = TestContext {
+		sender: sender.clone(),
+		receiver: receiver.clone(),
+		args: TestArgs::new_para(
+			destination.clone(),
+			receiver.clone(),
+			native_amount_to_send,
+			assets.into(),
+			None,
+			fee_asset_item,
+		),
+	};
+	let mut test = SystemParaToParaTest::new(test_args);
+
+	// Query initial balances
+	let sender_balance_before = test.sender.balance;
+	let sender_rocs_before = AssetHubWestend::execute_with(|| {
+		type ForeignAssets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(
+			roc_at_westend_parachains.clone().try_into().unwrap(),
+			&sender,
+		)
+	});
+	let receiver_assets_before = PenpalA::execute_with(|| {
+		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(native_asset_location.clone(), &receiver)
+	});
+	let receiver_rocs_before = PenpalA::execute_with(|| {
+		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(roc_at_westend_parachains.clone(), &receiver)
+	});
+
+	// Set assertions and dispatchables
+	test.set_assertion::<AssetHubWestend>(system_para_to_para_sender_assertions);
+	test.set_assertion::<PenpalA>(system_para_to_para_receiver_assertions);
+	test.set_dispatchable::<AssetHubWestend>(ah_to_para_transfer_assets);
+	test.assert();
+
+	// Query final balances
+	let sender_balance_after = test.sender.balance;
+	let sender_rocs_after = AssetHubWestend::execute_with(|| {
+		type ForeignAssets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(
+			roc_at_westend_parachains.clone().try_into().unwrap(),
+			&sender,
+		)
+	});
+	let receiver_assets_after = PenpalA::execute_with(|| {
+		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(native_asset_location, &receiver)
+	});
+	let receiver_rocs_after = PenpalA::execute_with(|| {
+		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(roc_at_westend_parachains, &receiver)
+	});
+
+	// Sender's balance is reduced by amount sent plus delivery fees
+	assert!(sender_balance_after < sender_balance_before - native_amount_to_send);
+	// Sender's balance is reduced by foreign amount sent
+	assert_eq!(sender_rocs_after, sender_rocs_before - foreign_amount_to_send);
+	// Receiver's assets is increased
+	assert!(receiver_assets_after > receiver_assets_before);
+	// Receiver's assets increased by `amount_to_send - delivery_fees - bought_execution`;
+	// `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but
+	// should be non-zero
+	assert!(receiver_assets_after < receiver_assets_before + native_amount_to_send);
+	// Receiver's balance is increased by foreign amount sent
+	assert_eq!(receiver_rocs_after, receiver_rocs_before + foreign_amount_to_send);
+}
+
+/// Reserve Transfers of native asset from Parachain to System Parachain should work
+// ===========================================================================
+// ======= Transfer - Native + Bridged Assets - Parachain->AssetHub ==========
+// ===========================================================================
+/// Transfers of native asset plus bridged asset from some Parachain to AssetHub
+/// while paying fees using native asset.
+#[test]
+fn transfer_foreign_assets_from_para_to_asset_hub() {
+	// Init values for Parachain
+	let destination = PenpalA::sibling_location_of(AssetHubWestend::para_id());
+	let sender = PenpalASender::get();
+	let native_amount_to_send: Balance = ASSET_HUB_WESTEND_ED * 10000;
+	let native_asset_location = RelayLocation::get();
+	let assets_owner = PenpalAssetOwner::get();
+
+	// Foreign asset used: bridged ROC
+	let foreign_amount_to_send = ASSET_HUB_WESTEND_ED * 10_000_000;
+	let roc_at_westend_parachains =
+		Location::new(2, [Junction::GlobalConsensus(NetworkId::Rococo)]);
+
+	// Configure destination chain to trust AH as reserve of ROC
+	PenpalA::execute_with(|| {
+		assert_ok!(<PenpalA as Chain>::System::set_storage(
+			<PenpalA as Chain>::RuntimeOrigin::root(),
+			vec![(
+				penpal_runtime::xcm_config::CustomizableAssetFromSystemAssetHub::key().to_vec(),
+				Location::new(2, [GlobalConsensus(Rococo)]).encode(),
+			)],
+		));
+	});
+	PenpalA::force_create_foreign_asset(
+		roc_at_westend_parachains.clone(),
+		assets_owner.clone(),
+		false,
+		ASSET_MIN_BALANCE,
+		vec![],
+	);
+	AssetHubWestend::force_create_foreign_asset(
+		roc_at_westend_parachains.clone().try_into().unwrap(),
+		assets_owner.clone(),
+		false,
+		ASSET_MIN_BALANCE,
+		vec![],
+	);
+
+	// fund Parachain's sender account
+	PenpalA::mint_foreign_asset(
+		<PenpalA as Chain>::RuntimeOrigin::signed(assets_owner.clone()),
+		native_asset_location.clone(),
+		sender.clone(),
+		native_amount_to_send * 2,
+	);
+	PenpalA::mint_foreign_asset(
+		<PenpalA as Chain>::RuntimeOrigin::signed(assets_owner.clone()),
+		roc_at_westend_parachains.clone(),
+		sender.clone(),
+		foreign_amount_to_send * 2,
+	);
+
+	// Init values for System Parachain
+	let receiver = AssetHubWestendReceiver::get();
+	let penpal_location_as_seen_by_ahr = AssetHubWestend::sibling_location_of(PenpalA::para_id());
+	let sov_penpal_on_ahr =
+		AssetHubWestend::sovereign_account_id_of(penpal_location_as_seen_by_ahr);
+
+	// fund Parachain's SA on AssetHub with the assets held in reserve
+	AssetHubWestend::fund_accounts(vec![(
+		sov_penpal_on_ahr.clone().into(),
+		native_amount_to_send * 2,
+	)]);
+	AssetHubWestend::mint_foreign_asset(
+		<AssetHubWestend as Chain>::RuntimeOrigin::signed(assets_owner),
+		roc_at_westend_parachains.clone().try_into().unwrap(),
+		sov_penpal_on_ahr,
+		foreign_amount_to_send * 2,
+	);
+
+	// Assets to send
+	let assets: Vec<Asset> = vec![
+		(Parent, native_amount_to_send).into(),
+		(roc_at_westend_parachains.clone(), foreign_amount_to_send).into(),
+	];
+	let fee_asset_id = AssetId(Parent.into());
+	let fee_asset_item = assets.iter().position(|a| a.id == fee_asset_id).unwrap() as u32;
+
+	// Init Test
+	let test_args = TestContext {
+		sender: sender.clone(),
+		receiver: receiver.clone(),
+		args: TestArgs::new_para(
+			destination.clone(),
+			receiver.clone(),
+			native_amount_to_send,
+			assets.into(),
+			None,
+			fee_asset_item,
+		),
+	};
+	let mut test = ParaToSystemParaTest::new(test_args);
+
+	// Query initial balances
+	let sender_native_before = PenpalA::execute_with(|| {
+		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(native_asset_location.clone(), &sender)
+	});
+	let sender_rocs_before = PenpalA::execute_with(|| {
+		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(roc_at_westend_parachains.clone(), &sender)
+	});
+	let receiver_native_before = test.receiver.balance;
+	let receiver_rocs_before = AssetHubWestend::execute_with(|| {
+		type ForeignAssets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(
+			roc_at_westend_parachains.clone().try_into().unwrap(),
+			&receiver,
+		)
+	});
+
+	// Set assertions and dispatchables
+	test.set_assertion::<PenpalA>(para_to_system_para_sender_assertions);
+	test.set_assertion::<AssetHubWestend>(para_to_system_para_receiver_assertions);
+	test.set_dispatchable::<PenpalA>(para_to_ah_transfer_assets);
+	test.assert();
+
+	// Query final balances
+	let sender_native_after = PenpalA::execute_with(|| {
+		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(native_asset_location, &sender)
+	});
+	let sender_rocs_after = PenpalA::execute_with(|| {
+		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(roc_at_westend_parachains.clone(), &sender)
+	});
+	let receiver_native_after = test.receiver.balance;
+	let receiver_rocs_after = AssetHubWestend::execute_with(|| {
+		type ForeignAssets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(
+			roc_at_westend_parachains.try_into().unwrap(),
+			&receiver,
+		)
+	});
+
+	// Sender's balance is reduced by amount sent plus delivery fees
+	assert!(sender_native_after < sender_native_before - native_amount_to_send);
+	// Sender's balance is reduced by foreign amount sent
+	assert_eq!(sender_rocs_after, sender_rocs_before - foreign_amount_to_send);
+	// Receiver's balance is increased
+	assert!(receiver_native_after > receiver_native_before);
+	// Receiver's balance increased by `amount_to_send - delivery_fees - bought_execution`;
+	// `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but
+	// should be non-zero
+	assert!(receiver_native_after < receiver_native_before + native_amount_to_send);
+	// Receiver's balance is increased by foreign amount sent
+	assert_eq!(receiver_rocs_after, receiver_rocs_before + foreign_amount_to_send);
+}
+
+// ==============================================================================
+// ===== Transfer - Native + Bridged Assets - Parachain->AssetHub->Parachain ====
+// ==============================================================================
+/// Transfers of native asset plus bridged asset from Parachain to Parachain
+/// (through AssetHub reserve) with fees paid using native asset.
+#[test]
+fn transfer_foreign_assets_from_para_to_para_through_asset_hub() {
+	// Init values for Parachain Origin
+	let destination = PenpalA::sibling_location_of(PenpalB::para_id());
+	let sender = PenpalASender::get();
+	let wnd_to_send: Balance = WESTEND_ED * 10000;
+	let assets_owner = PenpalAssetOwner::get();
+	let wnd_location = RelayLocation::get();
+	let sender_as_seen_by_ah = AssetHubWestend::sibling_location_of(PenpalA::para_id());
+	let sov_of_sender_on_ah = AssetHubWestend::sovereign_account_id_of(sender_as_seen_by_ah);
+	let receiver_as_seen_by_ah = AssetHubWestend::sibling_location_of(PenpalB::para_id());
+	let sov_of_receiver_on_ah = AssetHubWestend::sovereign_account_id_of(receiver_as_seen_by_ah);
+	let roc_to_send = ASSET_HUB_WESTEND_ED * 10_000_000;
+
+	// Configure destination chain to trust AH as reserve of ROC
+	PenpalB::execute_with(|| {
+		assert_ok!(<PenpalB as Chain>::System::set_storage(
+			<PenpalB as Chain>::RuntimeOrigin::root(),
+			vec![(
+				penpal_runtime::xcm_config::CustomizableAssetFromSystemAssetHub::key().to_vec(),
+				Location::new(2, [GlobalConsensus(Rococo)]).encode(),
+			)],
+		));
+	});
+
+	// Register ROC as foreign asset and transfer it around the Westend ecosystem
+	let roc_at_westend_parachains =
+		Location::new(2, [Junction::GlobalConsensus(NetworkId::Rococo)]);
+	AssetHubWestend::force_create_foreign_asset(
+		roc_at_westend_parachains.clone().try_into().unwrap(),
+		assets_owner.clone(),
+		false,
+		ASSET_MIN_BALANCE,
+		vec![],
+	);
+	PenpalA::force_create_foreign_asset(
+		roc_at_westend_parachains.clone(),
+		assets_owner.clone(),
+		false,
+		ASSET_MIN_BALANCE,
+		vec![],
+	);
+	PenpalB::force_create_foreign_asset(
+		roc_at_westend_parachains.clone(),
+		assets_owner.clone(),
+		false,
+		ASSET_MIN_BALANCE,
+		vec![],
+	);
+
+	// fund Parachain's sender account
+	PenpalA::mint_foreign_asset(
+		<PenpalA as Chain>::RuntimeOrigin::signed(assets_owner.clone()),
+		wnd_location.clone(),
+		sender.clone(),
+		wnd_to_send * 2,
+	);
+	PenpalA::mint_foreign_asset(
+		<PenpalA as Chain>::RuntimeOrigin::signed(assets_owner.clone()),
+		roc_at_westend_parachains.clone(),
+		sender.clone(),
+		roc_to_send * 2,
+	);
+	// fund the Parachain Origin's SA on Asset Hub with the assets held in reserve
+	AssetHubWestend::fund_accounts(vec![(sov_of_sender_on_ah.clone().into(), wnd_to_send * 2)]);
+	AssetHubWestend::mint_foreign_asset(
+		<AssetHubWestend as Chain>::RuntimeOrigin::signed(assets_owner),
+		roc_at_westend_parachains.clone().try_into().unwrap(),
+		sov_of_sender_on_ah.clone(),
+		roc_to_send * 2,
+	);
+
+	// Init values for Parachain Destination
+	let receiver = PenpalBReceiver::get();
+
+	// Assets to send
+	let assets: Vec<Asset> = vec![
+		(wnd_location.clone(), wnd_to_send).into(),
+		(roc_at_westend_parachains.clone(), roc_to_send).into(),
+	];
+	let fee_asset_id: AssetId = wnd_location.clone().into();
+	let fee_asset_item = assets.iter().position(|a| a.id == fee_asset_id).unwrap() as u32;
+
+	// Init Test
+	let test_args = TestContext {
+		sender: sender.clone(),
+		receiver: receiver.clone(),
+		args: TestArgs::new_para(
+			destination,
+			receiver.clone(),
+			wnd_to_send,
+			assets.into(),
+			None,
+			fee_asset_item,
+		),
+	};
+	let mut test = ParaToParaThroughAHTest::new(test_args);
+
+	// Query initial balances
+	let sender_wnds_before = PenpalA::execute_with(|| {
+		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(wnd_location.clone(), &sender)
+	});
+	let sender_rocs_before = PenpalA::execute_with(|| {
+		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(roc_at_westend_parachains.clone(), &sender)
+	});
+	let wnds_in_sender_reserve_on_ah_before =
+		<AssetHubWestend as Chain>::account_data_of(sov_of_sender_on_ah.clone()).free;
+	let rocs_in_sender_reserve_on_ah_before = AssetHubWestend::execute_with(|| {
+		type Assets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets;
+		<Assets as Inspect<_>>::balance(
+			roc_at_westend_parachains.clone().try_into().unwrap(),
+			&sov_of_sender_on_ah,
+		)
+	});
+	let wnds_in_receiver_reserve_on_ah_before =
+		<AssetHubWestend as Chain>::account_data_of(sov_of_receiver_on_ah.clone()).free;
+	let rocs_in_receiver_reserve_on_ah_before = AssetHubWestend::execute_with(|| {
+		type Assets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets;
+		<Assets as Inspect<_>>::balance(
+			roc_at_westend_parachains.clone().try_into().unwrap(),
+			&sov_of_receiver_on_ah,
+		)
+	});
+	let receiver_wnds_before = PenpalB::execute_with(|| {
+		type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(wnd_location.clone(), &receiver)
+	});
+	let receiver_rocs_before = PenpalB::execute_with(|| {
+		type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(roc_at_westend_parachains.clone(), &receiver)
+	});
+
+	// Set assertions and dispatchables
+	test.set_assertion::<PenpalA>(para_to_para_through_hop_sender_assertions);
+	test.set_assertion::<AssetHubWestend>(para_to_para_assethub_hop_assertions);
+	test.set_assertion::<PenpalB>(para_to_para_through_hop_receiver_assertions);
+	test.set_dispatchable::<PenpalA>(para_to_para_transfer_assets_through_ah);
+	test.assert();
+
+	// Query final balances
+	let sender_wnds_after = PenpalA::execute_with(|| {
+		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(wnd_location.clone(), &sender)
+	});
+	let sender_rocs_after = PenpalA::execute_with(|| {
+		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(roc_at_westend_parachains.clone(), &sender)
+	});
+	let rocs_in_sender_reserve_on_ah_after = AssetHubWestend::execute_with(|| {
+		type Assets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets;
+		<Assets as Inspect<_>>::balance(
+			roc_at_westend_parachains.clone().try_into().unwrap(),
+			&sov_of_sender_on_ah,
+		)
+	});
+	let wnds_in_sender_reserve_on_ah_after =
+		<AssetHubWestend as Chain>::account_data_of(sov_of_sender_on_ah).free;
+	let rocs_in_receiver_reserve_on_ah_after = AssetHubWestend::execute_with(|| {
+		type Assets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets;
+		<Assets as Inspect<_>>::balance(
+			roc_at_westend_parachains.clone().try_into().unwrap(),
+			&sov_of_receiver_on_ah,
+		)
+	});
+	let wnds_in_receiver_reserve_on_ah_after =
+		<AssetHubWestend as Chain>::account_data_of(sov_of_receiver_on_ah).free;
+	let receiver_wnds_after = PenpalB::execute_with(|| {
+		type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(wnd_location, &receiver)
+	});
+	let receiver_rocs_after = PenpalB::execute_with(|| {
+		type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(roc_at_westend_parachains, &receiver)
+	});
+
+	// Sender's balance is reduced by amount sent plus delivery fees
+	assert!(sender_wnds_after < sender_wnds_before - wnd_to_send);
+	assert_eq!(sender_rocs_after, sender_rocs_before - roc_to_send);
+	// Sovereign accounts on reserve are changed accordingly
+	assert_eq!(
+		wnds_in_sender_reserve_on_ah_after,
+		wnds_in_sender_reserve_on_ah_before - wnd_to_send
+	);
+	assert_eq!(
+		rocs_in_sender_reserve_on_ah_after,
+		rocs_in_sender_reserve_on_ah_before - roc_to_send
+	);
+	assert!(wnds_in_receiver_reserve_on_ah_after > wnds_in_receiver_reserve_on_ah_before);
+	assert_eq!(
+		rocs_in_receiver_reserve_on_ah_after,
+		rocs_in_receiver_reserve_on_ah_before + roc_to_send
+	);
+	// Receiver's balance is increased
+	assert!(receiver_wnds_after > receiver_wnds_before);
+	assert_eq!(receiver_rocs_after, receiver_rocs_before + roc_to_send);
+}
+
+// ==============================================================================================
+// ==== Bidirectional Transfer - Native + Teleportable Foreign Assets - Parachain<->AssetHub ====
+// ==============================================================================================
+/// Transfers of native asset plus teleportable foreign asset from Parachain to AssetHub and back
+/// with fees paid using native asset.
+#[test]
+fn bidirectional_teleport_foreign_asset_between_para_and_asset_hub_using_explicit_transfer_types() {
+	do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt(
+		para_to_asset_hub_teleport_foreign_assets,
+		asset_hub_to_para_teleport_foreign_assets,
+	);
+}
diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs
index 3cd7c9c46d69edd738f067724485824ef51d3259..e463e21e9e5295a7c33efc30006299ee2a801902 100644
--- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs
+++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs
@@ -14,6 +14,7 @@
 // limitations under the License.
 
 mod fellowship_treasury;
+mod foreign_assets_transfers;
 mod reserve_transfer;
 mod send;
 mod set_xcm_versions;
diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs
index a26dfef8e8e702ee3f22870116adc03ee8ed1ca2..df01eb0d48ad929194e81808e36cf77528b54a21 100644
--- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs
+++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs
@@ -47,7 +47,7 @@ fn para_to_relay_sender_assertions(t: ParaToRelayTest) {
 			RuntimeEvent::ForeignAssets(
 				pallet_assets::Event::Burned { asset_id, owner, balance, .. }
 			) => {
-				asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"),
+				asset_id: *asset_id == RelayLocation::get(),
 				owner: *owner == t.sender.account_id,
 				balance: *balance == t.args.amount,
 			},
@@ -55,70 +55,92 @@ fn para_to_relay_sender_assertions(t: ParaToRelayTest) {
 	);
 }
 
-fn system_para_to_para_sender_assertions(t: SystemParaToParaTest) {
+pub fn system_para_to_para_sender_assertions(t: SystemParaToParaTest) {
 	type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
-
-	AssetHubWestend::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(
-		864_610_000,
-		8_799,
-	)));
-
+	AssetHubWestend::assert_xcm_pallet_attempted_complete(None);
+
+	let sov_acc_of_dest = AssetHubWestend::sovereign_account_id_of(t.args.dest.clone());
+	for (idx, asset) in t.args.assets.into_inner().into_iter().enumerate() {
+		let expected_id = asset.id.0.clone().try_into().unwrap();
+		let asset_amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap();
+		if idx == t.args.fee_asset_item as usize {
+			assert_expected_events!(
+				AssetHubWestend,
+				vec![
+					// Amount of native asset is transferred to Parachain's Sovereign account
+					RuntimeEvent::Balances(
+						pallet_balances::Event::Transfer { from, to, amount }
+					) => {
+						from: *from == t.sender.account_id,
+						to: *to == sov_acc_of_dest,
+						amount: *amount == asset_amount,
+					},
+				]
+			);
+		} else {
+			assert_expected_events!(
+				AssetHubWestend,
+				vec![
+					// Amount of foreign asset is transferred to Parachain's Sovereign account
+					RuntimeEvent::ForeignAssets(
+						pallet_assets::Event::Transferred { asset_id, from, to, amount },
+					) => {
+						asset_id: *asset_id == expected_id,
+						from: *from == t.sender.account_id,
+						to: *to == sov_acc_of_dest,
+						amount: *amount == asset_amount,
+					},
+				]
+			);
+		}
+	}
 	assert_expected_events!(
 		AssetHubWestend,
 		vec![
-			// Amount to reserve transfer is transferred to Parachain's Sovereign account
-			RuntimeEvent::Balances(
-				pallet_balances::Event::Transfer { from, to, amount }
-			) => {
-				from: *from == t.sender.account_id,
-				to: *to == AssetHubWestend::sovereign_account_id_of(
-					t.args.dest.clone()
-				),
-				amount: *amount == t.args.amount,
-			},
 			// Transport fees are paid
-			RuntimeEvent::PolkadotXcm(
-				pallet_xcm::Event::FeesPaid { .. }
-			) => {},
+			RuntimeEvent::PolkadotXcm(pallet_xcm::Event::FeesPaid { .. }) => {},
 		]
 	);
 	AssetHubWestend::assert_xcm_pallet_sent();
 }
 
-fn system_para_to_para_receiver_assertions(t: SystemParaToParaTest) {
+pub fn system_para_to_para_receiver_assertions(t: SystemParaToParaTest) {
 	type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
-	let system_para_native_asset_location =
-		v3::Location::try_from(RelayLocation::get()).expect("conversion works");
 
 	PenpalA::assert_xcmp_queue_success(None);
-
-	assert_expected_events!(
-		PenpalA,
-		vec![
-			RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => {
-				asset_id: *asset_id == system_para_native_asset_location,
-				owner: *owner == t.receiver.account_id,
-			},
-		]
-	);
+	for asset in t.args.assets.into_inner().into_iter() {
+		let expected_id = asset.id.0.try_into().unwrap();
+		assert_expected_events!(
+			PenpalA,
+			vec![
+				RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => {
+					asset_id: *asset_id == expected_id,
+					owner: *owner == t.receiver.account_id,
+				},
+			]
+		);
+	}
 }
 
-fn para_to_system_para_sender_assertions(t: ParaToSystemParaTest) {
+pub fn para_to_system_para_sender_assertions(t: ParaToSystemParaTest) {
 	type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
-	PenpalA::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(864_610_000, 8_799)));
-	assert_expected_events!(
-		PenpalA,
-		vec![
-			// Amount to reserve transfer is transferred to Parachain's Sovereign account
-			RuntimeEvent::ForeignAssets(
-				pallet_assets::Event::Burned { asset_id, owner, balance, .. }
-			) => {
-				asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"),
-				owner: *owner == t.sender.account_id,
-				balance: *balance == t.args.amount,
-			},
-		]
-	);
+	PenpalA::assert_xcm_pallet_attempted_complete(None);
+	for asset in t.args.assets.into_inner().into_iter() {
+		let expected_id = asset.id.0;
+		let asset_amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap();
+		assert_expected_events!(
+			PenpalA,
+			vec![
+				RuntimeEvent::ForeignAssets(
+					pallet_assets::Event::Burned { asset_id, owner, balance }
+				) => {
+					asset_id: *asset_id == expected_id,
+					owner: *owner == t.sender.account_id,
+					balance: *balance == asset_amount,
+				},
+			]
+		);
+	}
 }
 
 fn para_to_relay_receiver_assertions(t: ParaToRelayTest) {
@@ -150,25 +172,57 @@ fn para_to_relay_receiver_assertions(t: ParaToRelayTest) {
 	);
 }
 
-fn para_to_system_para_receiver_assertions(t: ParaToSystemParaTest) {
+pub fn para_to_system_para_receiver_assertions(t: ParaToSystemParaTest) {
 	type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
-	let sov_penpal_on_ahr = AssetHubWestend::sovereign_account_id_of(
-		AssetHubWestend::sibling_location_of(PenpalA::para_id()),
-	);
-
 	AssetHubWestend::assert_xcmp_queue_success(None);
 
+	let sov_acc_of_penpal = AssetHubWestend::sovereign_account_id_of(t.args.dest.clone());
+	for (idx, asset) in t.args.assets.into_inner().into_iter().enumerate() {
+		let expected_id = asset.id.0.clone().try_into().unwrap();
+		let asset_amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap();
+		if idx == t.args.fee_asset_item as usize {
+			assert_expected_events!(
+				AssetHubWestend,
+				vec![
+					// Amount of native is withdrawn from Parachain's Sovereign account
+					RuntimeEvent::Balances(
+						pallet_balances::Event::Burned { who, amount }
+					) => {
+						who: *who == sov_acc_of_penpal.clone().into(),
+						amount: *amount == asset_amount,
+					},
+					RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => {
+						who: *who == t.receiver.account_id,
+					},
+				]
+			);
+		} else {
+			assert_expected_events!(
+				AssetHubWestend,
+				vec![
+					// Amount of foreign asset is transferred from Parachain's Sovereign account
+					// to Receiver's account
+					RuntimeEvent::ForeignAssets(
+						pallet_assets::Event::Burned { asset_id, owner, balance },
+					) => {
+						asset_id: *asset_id == expected_id,
+						owner: *owner == sov_acc_of_penpal,
+						balance: *balance == asset_amount,
+					},
+					RuntimeEvent::ForeignAssets(
+						pallet_assets::Event::Issued { asset_id, owner, amount },
+					) => {
+						asset_id: *asset_id == expected_id,
+						owner: *owner == t.receiver.account_id,
+						amount: *amount == asset_amount,
+					},
+				]
+			);
+		}
+	}
 	assert_expected_events!(
 		AssetHubWestend,
 		vec![
-			// Amount to reserve transfer is withdrawn from Parachain's Sovereign account
-			RuntimeEvent::Balances(
-				pallet_balances::Event::Burned { who, amount }
-			) => {
-				who: *who == sov_penpal_on_ahr.clone().into(),
-				amount: *amount == t.args.amount,
-			},
-			RuntimeEvent::Balances(pallet_balances::Event::Minted { .. }) => {},
 			RuntimeEvent::MessageQueue(
 				pallet_message_queue::Event::Processed { success: true, .. }
 			) => {},
@@ -212,10 +266,8 @@ fn system_para_to_para_assets_sender_assertions(t: SystemParaToParaTest) {
 
 fn para_to_system_para_assets_sender_assertions(t: ParaToSystemParaTest) {
 	type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
-	let system_para_native_asset_location =
-		v3::Location::try_from(RelayLocation::get()).expect("conversion works");
-	let reservable_asset_location =
-		v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works");
+	let system_para_native_asset_location = RelayLocation::get();
+	let reservable_asset_location = PenpalLocalReservableFromAssetHub::get();
 	PenpalA::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(864_610_000, 8799)));
 	assert_expected_events!(
 		PenpalA,
@@ -245,14 +297,13 @@ fn para_to_system_para_assets_sender_assertions(t: ParaToSystemParaTest) {
 
 fn system_para_to_para_assets_receiver_assertions(t: SystemParaToParaTest) {
 	type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
-	let system_para_asset_location =
-		v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works");
+	let system_para_asset_location = PenpalLocalReservableFromAssetHub::get();
 	PenpalA::assert_xcmp_queue_success(None);
 	assert_expected_events!(
 		PenpalA,
 		vec![
 			RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => {
-				asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"),
+				asset_id: *asset_id == RelayLocation::get(),
 				owner: *owner == t.receiver.account_id,
 			},
 			RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, amount }) => {
@@ -304,7 +355,7 @@ fn relay_to_para_assets_receiver_assertions(t: RelayToParaTest) {
 		PenpalA,
 		vec![
 			RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => {
-				asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"),
+				asset_id: *asset_id == RelayLocation::get(),
 				owner: *owner == t.receiver.account_id,
 			},
 			RuntimeEvent::MessageQueue(
@@ -314,29 +365,27 @@ fn relay_to_para_assets_receiver_assertions(t: RelayToParaTest) {
 	);
 }
 
-fn para_to_para_through_relay_sender_assertions(t: ParaToParaThroughRelayTest) {
+pub fn para_to_para_through_hop_sender_assertions<Hop: Clone>(t: Test<PenpalA, PenpalB, Hop>) {
 	type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
-
-	let relay_asset_location =
-		v3::Location::try_from(RelayLocation::get()).expect("conversion works");
-
 	PenpalA::assert_xcm_pallet_attempted_complete(None);
-	// XCM sent to relay reserve
-	PenpalA::assert_parachain_system_ump_sent();
 
-	assert_expected_events!(
-		PenpalA,
-		vec![
-			// Amount to reserve transfer is transferred to Parachain's Sovereign account
-			RuntimeEvent::ForeignAssets(
-				pallet_assets::Event::Burned { asset_id, owner, balance },
-			) => {
-				asset_id: *asset_id == relay_asset_location,
-				owner: *owner == t.sender.account_id,
-				balance: *balance == t.args.amount,
-			},
-		]
-	);
+	for asset in t.args.assets.into_inner() {
+		let expected_id = asset.id.0.clone().try_into().unwrap();
+		let amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap();
+		assert_expected_events!(
+			PenpalA,
+			vec![
+				// Amount to reserve transfer is transferred to Parachain's Sovereign account
+				RuntimeEvent::ForeignAssets(
+					pallet_assets::Event::Burned { asset_id, owner, balance },
+				) => {
+					asset_id: *asset_id == expected_id,
+					owner: *owner == t.sender.account_id,
+					balance: *balance == amount,
+				},
+			]
+		);
+	}
 }
 
 fn para_to_para_relay_hop_assertions(t: ParaToParaThroughRelayTest) {
@@ -369,22 +418,22 @@ fn para_to_para_relay_hop_assertions(t: ParaToParaThroughRelayTest) {
 	);
 }
 
-fn para_to_para_through_relay_receiver_assertions(t: ParaToParaThroughRelayTest) {
+pub fn para_to_para_through_hop_receiver_assertions<Hop: Clone>(t: Test<PenpalA, PenpalB, Hop>) {
 	type RuntimeEvent = <PenpalB as Chain>::RuntimeEvent;
-	let relay_asset_location =
-		v3::Location::try_from(RelayLocation::get()).expect("conversion works");
 
 	PenpalB::assert_xcmp_queue_success(None);
-
-	assert_expected_events!(
-		PenpalB,
-		vec![
-			RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => {
-				asset_id: *asset_id == relay_asset_location,
-				owner: *owner == t.receiver.account_id,
-			},
-		]
-	);
+	for asset in t.args.assets.into_inner().into_iter() {
+		let expected_id = asset.id.0.try_into().unwrap();
+		assert_expected_events!(
+			PenpalB,
+			vec![
+				RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => {
+					asset_id: *asset_id == expected_id,
+					owner: *owner == t.receiver.account_id,
+				},
+			]
+		);
+	}
 }
 
 fn relay_to_para_reserve_transfer_assets(t: RelayToParaTest) -> DispatchResult {
@@ -526,8 +575,7 @@ fn reserve_transfer_native_asset_from_relay_to_para() {
 	let amount_to_send: Balance = WESTEND_ED * 1000;
 
 	// Init values fot Parachain
-	let relay_native_asset_location =
-		v3::Location::try_from(RelayLocation::get()).expect("conversion works");
+	let relay_native_asset_location = RelayLocation::get();
 	let receiver = PenpalAReceiver::get();
 
 	// Init Test
@@ -542,7 +590,7 @@ fn reserve_transfer_native_asset_from_relay_to_para() {
 	let sender_balance_before = test.sender.balance;
 	let receiver_assets_before = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
-		<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.into(), &receiver)
+		<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.clone(), &receiver)
 	});
 
 	// Set assertions and dispatchables
@@ -555,7 +603,7 @@ fn reserve_transfer_native_asset_from_relay_to_para() {
 	let sender_balance_after = test.sender.balance;
 	let receiver_assets_after = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
-		<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.into(), &receiver)
+		<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location, &receiver)
 	});
 
 	// Sender's balance is reduced by amount sent plus delivery fees
@@ -577,13 +625,12 @@ fn reserve_transfer_native_asset_from_para_to_relay() {
 	let amount_to_send: Balance = WESTEND_ED * 1000;
 	let assets: Assets = (Parent, amount_to_send).into();
 	let asset_owner = PenpalAssetOwner::get();
-	let relay_native_asset_location =
-		v3::Location::try_from(RelayLocation::get()).expect("conversion works");
+	let relay_native_asset_location = RelayLocation::get();
 
 	// fund Parachain's sender account
 	PenpalA::mint_foreign_asset(
 		<PenpalA as Chain>::RuntimeOrigin::signed(asset_owner),
-		relay_native_asset_location,
+		relay_native_asset_location.clone(),
 		sender.clone(),
 		amount_to_send * 2,
 	);
@@ -614,7 +661,7 @@ fn reserve_transfer_native_asset_from_para_to_relay() {
 	// Query initial balances
 	let sender_assets_before = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
-		<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.into(), &sender)
+		<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.clone(), &sender)
 	});
 	let receiver_balance_before = test.receiver.balance;
 
@@ -627,7 +674,7 @@ fn reserve_transfer_native_asset_from_para_to_relay() {
 	// Query final balances
 	let sender_assets_after = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
-		<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.into(), &sender)
+		<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location, &sender)
 	});
 	let receiver_balance_after = test.receiver.balance;
 
@@ -654,8 +701,7 @@ fn reserve_transfer_native_asset_from_system_para_to_para() {
 	let assets: Assets = (Parent, amount_to_send).into();
 
 	// Init values for Parachain
-	let system_para_native_asset_location =
-		v3::Location::try_from(RelayLocation::get()).expect("conversion works");
+	let system_para_native_asset_location = RelayLocation::get();
 	let receiver = PenpalAReceiver::get();
 
 	// Init Test
@@ -677,7 +723,7 @@ fn reserve_transfer_native_asset_from_system_para_to_para() {
 	let sender_balance_before = test.sender.balance;
 	let receiver_assets_before = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
-		<ForeignAssets as Inspect<_>>::balance(system_para_native_asset_location.into(), &receiver)
+		<ForeignAssets as Inspect<_>>::balance(system_para_native_asset_location.clone(), &receiver)
 	});
 
 	// Set assertions and dispatchables
@@ -711,14 +757,13 @@ fn reserve_transfer_native_asset_from_para_to_system_para() {
 	let sender = PenpalASender::get();
 	let amount_to_send: Balance = ASSET_HUB_WESTEND_ED * 1000;
 	let assets: Assets = (Parent, amount_to_send).into();
-	let system_para_native_asset_location =
-		v3::Location::try_from(RelayLocation::get()).expect("conversion works");
+	let system_para_native_asset_location = RelayLocation::get();
 	let asset_owner = PenpalAssetOwner::get();
 
 	// fund Parachain's sender account
 	PenpalA::mint_foreign_asset(
 		<PenpalA as Chain>::RuntimeOrigin::signed(asset_owner),
-		system_para_native_asset_location,
+		system_para_native_asset_location.clone(),
 		sender.clone(),
 		amount_to_send * 2,
 	);
@@ -750,7 +795,7 @@ fn reserve_transfer_native_asset_from_para_to_system_para() {
 	// Query initial balances
 	let sender_assets_before = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
-		<ForeignAssets as Inspect<_>>::balance(system_para_native_asset_location, &sender)
+		<ForeignAssets as Inspect<_>>::balance(system_para_native_asset_location.clone(), &sender)
 	});
 	let receiver_balance_before = test.receiver.balance;
 
@@ -818,10 +863,8 @@ fn reserve_transfer_assets_from_system_para_to_para() {
 
 	// Init values for Parachain
 	let receiver = PenpalAReceiver::get();
-	let system_para_native_asset_location =
-		v3::Location::try_from(RelayLocation::get()).expect("conversion works");
-	let system_para_foreign_asset_location =
-		v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works");
+	let system_para_native_asset_location = RelayLocation::get();
+	let system_para_foreign_asset_location = PenpalLocalReservableFromAssetHub::get();
 
 	// Init Test
 	let para_test_args = TestContext {
@@ -846,11 +889,14 @@ fn reserve_transfer_assets_from_system_para_to_para() {
 	});
 	let receiver_system_native_assets_before = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
-		<ForeignAssets as Inspect<_>>::balance(system_para_native_asset_location, &receiver)
+		<ForeignAssets as Inspect<_>>::balance(system_para_native_asset_location.clone(), &receiver)
 	});
 	let receiver_foreign_assets_before = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
-		<ForeignAssets as Inspect<_>>::balance(system_para_foreign_asset_location, &receiver)
+		<ForeignAssets as Inspect<_>>::balance(
+			system_para_foreign_asset_location.clone(),
+			&receiver,
+		)
 	});
 
 	// Set assertions and dispatchables
@@ -905,14 +951,11 @@ fn reserve_transfer_assets_from_para_to_system_para() {
 	let asset_amount_to_send = ASSET_HUB_WESTEND_ED * 100;
 	let penpal_asset_owner = PenpalAssetOwner::get();
 	let penpal_asset_owner_signer = <PenpalA as Chain>::RuntimeOrigin::signed(penpal_asset_owner);
-	let asset_location_on_penpal =
-		v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works");
-	let asset_location_on_penpal_latest: Location = asset_location_on_penpal.try_into().unwrap();
-	let system_asset_location_on_penpal =
-		v3::Location::try_from(RelayLocation::get()).expect("conversion works");
+	let asset_location_on_penpal = PenpalLocalReservableFromAssetHub::get();
+	let system_asset_location_on_penpal = RelayLocation::get();
 	let assets: Assets = vec![
 		(Parent, fee_amount_to_send).into(),
-		(asset_location_on_penpal_latest, asset_amount_to_send).into(),
+		(asset_location_on_penpal.clone(), asset_amount_to_send).into(),
 	]
 	.into();
 	let fee_asset_index = assets
@@ -940,10 +983,8 @@ fn reserve_transfer_assets_from_para_to_system_para() {
 	let penpal_location_as_seen_by_ahr = AssetHubWestend::sibling_location_of(PenpalA::para_id());
 	let sov_penpal_on_ahr =
 		AssetHubWestend::sovereign_account_id_of(penpal_location_as_seen_by_ahr);
-	let system_para_native_asset_location =
-		v3::Location::try_from(RelayLocation::get()).expect("conversion works");
-	let system_para_foreign_asset_location =
-		v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works");
+	let system_para_native_asset_location = RelayLocation::get();
+	let system_para_foreign_asset_location = PenpalLocalReservableFromAssetHub::get();
 	let ah_asset_owner = AssetHubWestendAssetOwner::get();
 	let ah_asset_owner_signer = <AssetHubWestend as Chain>::RuntimeOrigin::signed(ah_asset_owner);
 
@@ -978,11 +1019,11 @@ fn reserve_transfer_assets_from_para_to_system_para() {
 	// Query initial balances
 	let sender_system_assets_before = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
-		<ForeignAssets as Inspect<_>>::balance(system_para_native_asset_location, &sender)
+		<ForeignAssets as Inspect<_>>::balance(system_para_native_asset_location.clone(), &sender)
 	});
 	let sender_foreign_assets_before = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
-		<ForeignAssets as Inspect<_>>::balance(system_para_foreign_asset_location, &sender)
+		<ForeignAssets as Inspect<_>>::balance(system_para_foreign_asset_location.clone(), &sender)
 	});
 	let receiver_balance_before = test.receiver.balance;
 	let receiver_assets_before = AssetHubWestend::execute_with(|| {
@@ -1031,22 +1072,21 @@ fn reserve_transfer_assets_from_para_to_system_para() {
 /// Reserve Transfers of native asset from Parachain to Parachain (through Relay reserve) should
 /// work
 #[test]
-fn reserve_transfer_native_asset_from_para_to_para_trough_relay() {
+fn reserve_transfer_native_asset_from_para_to_para_through_relay() {
 	// Init values for Parachain Origin
 	let destination = PenpalA::sibling_location_of(PenpalB::para_id());
 	let sender = PenpalASender::get();
 	let amount_to_send: Balance = WESTEND_ED * 10000;
 	let asset_owner = PenpalAssetOwner::get();
 	let assets = (Parent, amount_to_send).into();
-	let relay_native_asset_location =
-		v3::Location::try_from(RelayLocation::get()).expect("conversion works");
+	let relay_native_asset_location = RelayLocation::get();
 	let sender_as_seen_by_relay = Westend::child_location_of(PenpalA::para_id());
 	let sov_of_sender_on_relay = Westend::sovereign_account_id_of(sender_as_seen_by_relay);
 
 	// fund Parachain's sender account
 	PenpalA::mint_foreign_asset(
 		<PenpalA as Chain>::RuntimeOrigin::signed(asset_owner),
-		relay_native_asset_location,
+		relay_native_asset_location.clone(),
 		sender.clone(),
 		amount_to_send * 2,
 	);
@@ -1068,24 +1108,24 @@ fn reserve_transfer_native_asset_from_para_to_para_trough_relay() {
 	// Query initial balances
 	let sender_assets_before = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
-		<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location, &sender)
+		<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.clone(), &sender)
 	});
 	let receiver_assets_before = PenpalB::execute_with(|| {
 		type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
-		<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location, &receiver)
+		<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.clone(), &receiver)
 	});
 
 	// Set assertions and dispatchables
-	test.set_assertion::<PenpalA>(para_to_para_through_relay_sender_assertions);
+	test.set_assertion::<PenpalA>(para_to_para_through_hop_sender_assertions);
 	test.set_assertion::<Westend>(para_to_para_relay_hop_assertions);
-	test.set_assertion::<PenpalB>(para_to_para_through_relay_receiver_assertions);
+	test.set_assertion::<PenpalB>(para_to_para_through_hop_receiver_assertions);
 	test.set_dispatchable::<PenpalA>(para_to_para_through_relay_limited_reserve_transfer_assets);
 	test.assert();
 
 	// Query final balances
 	let sender_assets_after = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
-		<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location, &sender)
+		<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.clone(), &sender)
 	});
 	let receiver_assets_after = PenpalB::execute_with(|| {
 		type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/swap.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/swap.rs
index aa673c03483af13cae2ac146049399644b265b6b..31f763be637079292d3b1aa49bbbfe5668d86653 100644
--- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/swap.rs
+++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/swap.rs
@@ -17,7 +17,10 @@ use crate::imports::*;
 
 #[test]
 fn swap_locally_on_chain_using_local_assets() {
-	let asset_native = Box::new(asset_hub_westend_runtime::xcm_config::WestendLocationV3::get());
+	let asset_native = Box::new(
+		v3::Location::try_from(asset_hub_westend_runtime::xcm_config::WestendLocation::get())
+			.expect("conversion works"),
+	);
 	let asset_one = Box::new(v3::Location {
 		parents: 0,
 		interior: [
@@ -111,8 +114,7 @@ fn swap_locally_on_chain_using_local_assets() {
 
 #[test]
 fn swap_locally_on_chain_using_foreign_assets() {
-	let asset_native =
-		Box::new(v3::Location::try_from(RelayLocation::get()).expect("conversion works"));
+	let asset_native = Box::new(v3::Location::try_from(RelayLocation::get()).unwrap());
 	let asset_location_on_penpal =
 		v3::Location::try_from(PenpalLocalTeleportableToAssetHub::get()).expect("conversion_works");
 	let foreign_asset_at_asset_hub_westend =
@@ -227,11 +229,9 @@ fn swap_locally_on_chain_using_foreign_assets() {
 
 #[test]
 fn cannot_create_pool_from_pool_assets() {
-	let asset_native = Box::new(asset_hub_westend_runtime::xcm_config::WestendLocationV3::get());
-	let mut asset_one = asset_hub_westend_runtime::xcm_config::PoolAssetsPalletLocationV3::get();
-	asset_one
-		.append_with(v3::Junction::GeneralIndex(ASSET_ID.into()))
-		.expect("pool assets");
+	let asset_native = asset_hub_westend_runtime::xcm_config::WestendLocation::get();
+	let mut asset_one = asset_hub_westend_runtime::xcm_config::PoolAssetsPalletLocation::get();
+	asset_one.append_with(GeneralIndex(ASSET_ID.into())).expect("pool assets");
 
 	AssetHubWestend::execute_with(|| {
 		let pool_owner_account_id = asset_hub_westend_runtime::AssetConversionOrigin::get();
@@ -254,8 +254,8 @@ fn cannot_create_pool_from_pool_assets() {
 		assert_matches::assert_matches!(
 			<AssetHubWestend as AssetHubWestendPallet>::AssetConversion::create_pool(
 				<AssetHubWestend as Chain>::RuntimeOrigin::signed(AssetHubWestendSender::get()),
-				asset_native,
-				Box::new(asset_one),
+				Box::new(v3::Location::try_from(asset_native).expect("conversion works")),
+				Box::new(v3::Location::try_from(asset_one).expect("conversion works")),
 			),
 			Err(DispatchError::Module(ModuleError{index: _, error: _, message})) => assert_eq!(message, Some("Unknown"))
 		);
@@ -264,7 +264,9 @@ fn cannot_create_pool_from_pool_assets() {
 
 #[test]
 fn pay_xcm_fee_with_some_asset_swapped_for_native() {
-	let asset_native = asset_hub_westend_runtime::xcm_config::WestendLocationV3::get();
+	let asset_native =
+		v3::Location::try_from(asset_hub_westend_runtime::xcm_config::WestendLocation::get())
+			.expect("conversion works");
 	let asset_one = xcm::v3::Location {
 		parents: 0,
 		interior: [
diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs
index ac518d2ed4a445836364a23df313b319b8193e78..a524b87b2daf3a1352af1ea33b64282c2f4a8137 100644
--- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs
+++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs
@@ -110,8 +110,7 @@ fn para_dest_assertions(t: RelayToSystemParaTest) {
 
 fn penpal_to_ah_foreign_assets_sender_assertions(t: ParaToSystemParaTest) {
 	type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
-	let system_para_native_asset_location =
-		v3::Location::try_from(RelayLocation::get()).expect("conversion works");
+	let system_para_native_asset_location = RelayLocation::get();
 	let expected_asset_id = t.args.asset_id.unwrap();
 	let (_, expected_asset_amount) =
 		non_fee_asset(&t.args.assets, t.args.fee_asset_item as usize).unwrap();
@@ -204,8 +203,7 @@ fn ah_to_penpal_foreign_assets_receiver_assertions(t: SystemParaToParaTest) {
 	let (_, expected_asset_amount) =
 		non_fee_asset(&t.args.assets, t.args.fee_asset_item as usize).unwrap();
 	let checking_account = <PenpalA as PenpalAPallet>::PolkadotXcm::check_account();
-	let system_para_native_asset_location =
-		v3::Location::try_from(RelayLocation::get()).expect("conversion works");
+	let system_para_native_asset_location = RelayLocation::get();
 
 	PenpalA::assert_xcmp_queue_success(None);
 
@@ -414,29 +412,28 @@ fn teleport_to_other_system_parachains_works() {
 	);
 }
 
-/// Bidirectional teleports of local Penpal assets to Asset Hub as foreign assets should work
-/// (using native reserve-based transfer for fees)
-#[test]
-fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
+/// Bidirectional teleports of local Penpal assets to Asset Hub as foreign assets while paying
+/// fees using (reserve transferred) native asset.
+pub fn do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt(
+	para_to_ah_dispatchable: fn(ParaToSystemParaTest) -> DispatchResult,
+	ah_to_para_dispatchable: fn(SystemParaToParaTest) -> DispatchResult,
+) {
 	// Init values for Parachain
 	let fee_amount_to_send: Balance = ASSET_HUB_WESTEND_ED * 100;
-	let asset_location_on_penpal =
-		v3::Location::try_from(PenpalLocalTeleportableToAssetHub::get()).expect("conversion works");
+	let asset_location_on_penpal = PenpalLocalTeleportableToAssetHub::get();
 	let asset_id_on_penpal = match asset_location_on_penpal.last() {
-		Some(v3::Junction::GeneralIndex(id)) => *id as u32,
+		Some(Junction::GeneralIndex(id)) => *id as u32,
 		_ => unreachable!(),
 	};
 	let asset_amount_to_send = ASSET_HUB_WESTEND_ED * 100;
 	let asset_owner = PenpalAssetOwner::get();
-	let system_para_native_asset_location =
-		v3::Location::try_from(RelayLocation::get()).expect("conversion works");
+	let system_para_native_asset_location = RelayLocation::get();
 	let sender = PenpalASender::get();
 	let penpal_check_account = <PenpalA as PenpalAPallet>::PolkadotXcm::check_account();
 	let ah_as_seen_by_penpal = PenpalA::sibling_location_of(AssetHubWestend::para_id());
-	let asset_location_on_penpal_latest: Location = asset_location_on_penpal.try_into().unwrap();
 	let penpal_assets: Assets = vec![
 		(Parent, fee_amount_to_send).into(),
-		(asset_location_on_penpal_latest, asset_amount_to_send).into(),
+		(asset_location_on_penpal.clone(), asset_amount_to_send).into(),
 	]
 	.into();
 	let fee_asset_index = penpal_assets
@@ -448,7 +445,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
 	// fund Parachain's sender account
 	PenpalA::mint_foreign_asset(
 		<PenpalA as Chain>::RuntimeOrigin::signed(asset_owner.clone()),
-		system_para_native_asset_location,
+		system_para_native_asset_location.clone(),
 		sender.clone(),
 		fee_amount_to_send * 2,
 	);
@@ -475,7 +472,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
 
 	// Init values for System Parachain
 	let foreign_asset_at_asset_hub_westend =
-		v3::Location::new(1, [v3::Junction::Parachain(PenpalA::para_id().into())])
+		Location::new(1, [Junction::Parachain(PenpalA::para_id().into())])
 			.appended_with(asset_location_on_penpal)
 			.unwrap();
 	let penpal_to_ah_beneficiary_id = AssetHubWestendReceiver::get();
@@ -497,7 +494,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
 	let penpal_sender_balance_before = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
 		<ForeignAssets as Inspect<_>>::balance(
-			system_para_native_asset_location,
+			system_para_native_asset_location.clone(),
 			&PenpalASender::get(),
 		)
 	});
@@ -511,20 +508,20 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
 	let ah_receiver_assets_before = AssetHubWestend::execute_with(|| {
 		type Assets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets;
 		<Assets as Inspect<_>>::balance(
-			foreign_asset_at_asset_hub_westend,
+			foreign_asset_at_asset_hub_westend.clone().try_into().unwrap(),
 			&AssetHubWestendReceiver::get(),
 		)
 	});
 
 	penpal_to_ah.set_assertion::<PenpalA>(penpal_to_ah_foreign_assets_sender_assertions);
 	penpal_to_ah.set_assertion::<AssetHubWestend>(penpal_to_ah_foreign_assets_receiver_assertions);
-	penpal_to_ah.set_dispatchable::<PenpalA>(para_to_system_para_transfer_assets);
+	penpal_to_ah.set_dispatchable::<PenpalA>(para_to_ah_dispatchable);
 	penpal_to_ah.assert();
 
 	let penpal_sender_balance_after = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
 		<ForeignAssets as Inspect<_>>::balance(
-			system_para_native_asset_location,
+			system_para_native_asset_location.clone(),
 			&PenpalASender::get(),
 		)
 	});
@@ -538,7 +535,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
 	let ah_receiver_assets_after = AssetHubWestend::execute_with(|| {
 		type Assets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets;
 		<Assets as Inspect<_>>::balance(
-			foreign_asset_at_asset_hub_westend,
+			foreign_asset_at_asset_hub_westend.clone().try_into().unwrap(),
 			&AssetHubWestendReceiver::get(),
 		)
 	});
@@ -566,19 +563,17 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
 		type ForeignAssets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets;
 		assert_ok!(ForeignAssets::transfer(
 			<AssetHubWestend as Chain>::RuntimeOrigin::signed(AssetHubWestendReceiver::get()),
-			foreign_asset_at_asset_hub_westend,
+			foreign_asset_at_asset_hub_westend.clone().try_into().unwrap(),
 			AssetHubWestendSender::get().into(),
 			asset_amount_to_send,
 		));
 	});
 
-	let foreign_asset_at_asset_hub_westend_latest: Location =
-		foreign_asset_at_asset_hub_westend.try_into().unwrap();
 	let ah_to_penpal_beneficiary_id = PenpalAReceiver::get();
 	let penpal_as_seen_by_ah = AssetHubWestend::sibling_location_of(PenpalA::para_id());
 	let ah_assets: Assets = vec![
 		(Parent, fee_amount_to_send).into(),
-		(foreign_asset_at_asset_hub_westend_latest, asset_amount_to_send).into(),
+		(foreign_asset_at_asset_hub_westend.clone(), asset_amount_to_send).into(),
 	]
 	.into();
 	let fee_asset_index = ah_assets
@@ -606,7 +601,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
 	let penpal_receiver_balance_before = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
 		<ForeignAssets as Inspect<_>>::balance(
-			system_para_native_asset_location,
+			system_para_native_asset_location.clone(),
 			&PenpalAReceiver::get(),
 		)
 	});
@@ -614,7 +609,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
 	let ah_sender_assets_before = AssetHubWestend::execute_with(|| {
 		type ForeignAssets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets;
 		<ForeignAssets as Inspect<_>>::balance(
-			foreign_asset_at_asset_hub_westend,
+			foreign_asset_at_asset_hub_westend.clone().try_into().unwrap(),
 			&AssetHubWestendSender::get(),
 		)
 	});
@@ -625,7 +620,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
 
 	ah_to_penpal.set_assertion::<AssetHubWestend>(ah_to_penpal_foreign_assets_sender_assertions);
 	ah_to_penpal.set_assertion::<PenpalA>(ah_to_penpal_foreign_assets_receiver_assertions);
-	ah_to_penpal.set_dispatchable::<AssetHubWestend>(system_para_to_para_transfer_assets);
+	ah_to_penpal.set_dispatchable::<AssetHubWestend>(ah_to_para_dispatchable);
 	ah_to_penpal.assert();
 
 	let ah_sender_balance_after = ah_to_penpal.sender.balance;
@@ -640,7 +635,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
 	let ah_sender_assets_after = AssetHubWestend::execute_with(|| {
 		type ForeignAssets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets;
 		<ForeignAssets as Inspect<_>>::balance(
-			foreign_asset_at_asset_hub_westend,
+			foreign_asset_at_asset_hub_westend.clone().try_into().unwrap(),
 			&AssetHubWestendSender::get(),
 		)
 	});
@@ -663,3 +658,13 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
 	// Receiver's balance is increased by exact amount
 	assert_eq!(penpal_receiver_assets_after, penpal_receiver_assets_before + asset_amount_to_send);
 }
+
+/// Bidirectional teleports of local Penpal assets to Asset Hub as foreign assets should work
+/// (using native reserve-based transfer for fees)
+#[test]
+fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
+	do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt(
+		para_to_system_para_transfer_assets,
+		system_para_to_para_transfer_assets,
+	);
+}
diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs
index b5e19cf3fa3a252abe28b31229c876d104b6a8e9..0415af580ef8add90c92620e93052e356abe2de9 100644
--- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs
+++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs
@@ -25,6 +25,7 @@ mod imports {
 		prelude::{AccountId32 as AccountId32Junction, *},
 		v3::{self, NetworkId::Westend as WestendId},
 	};
+	pub use xcm_executor::traits::TransferType;
 
 	// Cumulus
 	pub use emulated_integration_tests_common::{
@@ -46,7 +47,7 @@ mod imports {
 		bridge_hub_rococo_emulated_chain::{
 			genesis::ED as BRIDGE_HUB_ROCOCO_ED, BridgeHubRococoParaPallet as BridgeHubRococoPallet,
 		},
-		penpal_emulated_chain::PenpalAParaPallet as PenpalAPallet,
+		penpal_emulated_chain::{PenpalAParaPallet as PenpalAPallet, PenpalAssetOwner},
 		rococo_emulated_chain::{genesis::ED as ROCOCO_ED, RococoRelayPallet as RococoPallet},
 		AssetHubRococoPara as AssetHubRococo, AssetHubRococoParaReceiver as AssetHubRococoReceiver,
 		AssetHubRococoParaSender as AssetHubRococoSender, AssetHubWestendPara as AssetHubWestend,
diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs
index 787a82ed32f7376f1c94c584711098c15d4da198..69d625be280454c4368d223fb5092be8df9de39d 100644
--- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs
+++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs
@@ -31,6 +31,73 @@ fn send_asset_from_asset_hub_rococo_to_asset_hub_westend(id: Location, amount: u
 	assert_bridge_hub_westend_message_received();
 }
 
+fn send_asset_from_penpal_rococo_through_local_asset_hub_to_westend_asset_hub(
+	id: Location,
+	transfer_amount: u128,
+) {
+	let destination = asset_hub_westend_location();
+	let local_asset_hub: Location = PenpalA::sibling_location_of(AssetHubRococo::para_id());
+	let sov_penpal_on_ahr = AssetHubRococo::sovereign_account_id_of(
+		AssetHubRococo::sibling_location_of(PenpalA::para_id()),
+	);
+	let sov_ahw_on_ahr = AssetHubRococo::sovereign_account_of_parachain_on_other_global_consensus(
+		Westend,
+		AssetHubWestend::para_id(),
+	);
+
+	// fund the AHR's SA on BHR for paying bridge transport fees
+	BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id(), 10_000_000_000_000u128);
+
+	// set XCM versions
+	PenpalA::force_xcm_version(local_asset_hub.clone(), XCM_VERSION);
+	AssetHubRococo::force_xcm_version(destination.clone(), XCM_VERSION);
+	BridgeHubRococo::force_xcm_version(bridge_hub_westend_location(), XCM_VERSION);
+
+	// send message over bridge
+	assert_ok!(PenpalA::execute_with(|| {
+		let signed_origin = <PenpalA as Chain>::RuntimeOrigin::signed(PenpalASender::get());
+		let beneficiary: Location =
+			AccountId32Junction { network: None, id: AssetHubWestendReceiver::get().into() }.into();
+		let assets: Assets = (id.clone(), transfer_amount).into();
+		let fees_id: AssetId = id.into();
+
+		<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type(
+			signed_origin,
+			bx!(destination.into()),
+			bx!(beneficiary.into()),
+			bx!(assets.clone().into()),
+			bx!(TransferType::RemoteReserve(local_asset_hub.clone().into())),
+			bx!(fees_id.into()),
+			bx!(TransferType::RemoteReserve(local_asset_hub.into())),
+			WeightLimit::Unlimited,
+		)
+	}));
+	AssetHubRococo::execute_with(|| {
+		type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent;
+		assert_expected_events!(
+			AssetHubRococo,
+			vec![
+				// Amount to reserve transfer is withdrawn from Penpal's sovereign account
+				RuntimeEvent::Balances(
+					pallet_balances::Event::Burned { who, amount }
+				) => {
+					who: *who == sov_penpal_on_ahr.clone().into(),
+					amount: *amount == transfer_amount,
+				},
+				// Amount deposited in AHW's sovereign account
+				RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => {
+					who: *who == sov_ahw_on_ahr.clone().into(),
+				},
+				RuntimeEvent::XcmpQueue(
+					cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }
+				) => {},
+			]
+		);
+	});
+	assert_bridge_hub_rococo_message_accepted(true);
+	assert_bridge_hub_westend_message_received();
+}
+
 #[test]
 fn send_rocs_from_asset_hub_rococo_to_asset_hub_westend() {
 	let roc_at_asset_hub_rococo: v3::Location = v3::Parent.into();
@@ -45,7 +112,7 @@ fn send_rocs_from_asset_hub_rococo_to_asset_hub_westend() {
 		vec![],
 	);
 	let sov_ahw_on_ahr = AssetHubRococo::sovereign_account_of_parachain_on_other_global_consensus(
-		NetworkId::Westend,
+		Westend,
 		AssetHubWestend::para_id(),
 	);
 
@@ -101,9 +168,11 @@ fn send_rocs_from_asset_hub_rococo_to_asset_hub_westend() {
 		<Assets as Inspect<_>>::balance(roc_at_asset_hub_westend, &AssetHubWestendReceiver::get())
 	});
 
-	let roc_at_asset_hub_rococo_latest: Location = roc_at_asset_hub_rococo.try_into().unwrap();
 	let amount = ASSET_HUB_ROCOCO_ED * 1_000_000;
-	send_asset_from_asset_hub_rococo_to_asset_hub_westend(roc_at_asset_hub_rococo_latest, amount);
+	send_asset_from_asset_hub_rococo_to_asset_hub_westend(
+		roc_at_asset_hub_rococo.try_into().unwrap(),
+		amount,
+	);
 	AssetHubWestend::execute_with(|| {
 		type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
 		assert_expected_events!(
@@ -135,7 +204,7 @@ fn send_rocs_from_asset_hub_rococo_to_asset_hub_westend() {
 	assert!(sender_rocs_before > sender_rocs_after);
 	// Receiver's balance is increased
 	assert!(receiver_rocs_after > receiver_rocs_before);
-	// Reserve balance is reduced by sent amount
+	// Reserve balance is increased by sent amount
 	assert_eq!(rocs_in_reserve_on_ahr_after, rocs_in_reserve_on_ahr_before + amount);
 }
 
@@ -144,7 +213,7 @@ fn send_wnds_from_asset_hub_rococo_to_asset_hub_westend() {
 	let prefund_amount = 10_000_000_000_000u128;
 	let wnd_at_asset_hub_rococo =
 		v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Westend)]);
-	let owner: AccountId = AssetHubWestend::account_id_of(ALICE);
+	let owner: AccountId = AssetHubRococo::account_id_of(ALICE);
 	AssetHubRococo::force_create_foreign_asset(
 		wnd_at_asset_hub_rococo,
 		owner,
@@ -155,7 +224,7 @@ fn send_wnds_from_asset_hub_rococo_to_asset_hub_westend() {
 
 	// fund the AHR's SA on AHW with the WND tokens held in reserve
 	let sov_ahr_on_ahw = AssetHubWestend::sovereign_account_of_parachain_on_other_global_consensus(
-		NetworkId::Rococo,
+		Rococo,
 		AssetHubRococo::para_id(),
 	);
 	AssetHubWestend::fund_accounts(vec![(sov_ahr_on_ahw.clone(), prefund_amount)]);
@@ -171,10 +240,9 @@ fn send_wnds_from_asset_hub_rococo_to_asset_hub_westend() {
 	let receiver_wnds_before =
 		<AssetHubWestend as Chain>::account_data_of(AssetHubWestendReceiver::get()).free;
 
-	let wnd_at_asset_hub_rococo_latest: Location = wnd_at_asset_hub_rococo.try_into().unwrap();
 	let amount_to_send = ASSET_HUB_WESTEND_ED * 1_000;
 	send_asset_from_asset_hub_rococo_to_asset_hub_westend(
-		wnd_at_asset_hub_rococo_latest.clone(),
+		Location::try_from(wnd_at_asset_hub_rococo).unwrap(),
 		amount_to_send,
 	);
 	AssetHubWestend::execute_with(|| {
@@ -217,3 +285,95 @@ fn send_wnds_from_asset_hub_rococo_to_asset_hub_westend() {
 	// Reserve balance is reduced by sent amount
 	assert_eq!(wnds_in_reserve_on_ahw_after, wnds_in_reserve_on_ahw_before - amount_to_send);
 }
+
+#[test]
+fn send_rocs_from_penpal_rococo_through_asset_hub_rococo_to_asset_hub_westend() {
+	let roc_at_rococo_parachains: Location = Parent.into();
+	let roc_at_asset_hub_westend = Location::new(2, [Junction::GlobalConsensus(NetworkId::Rococo)]);
+	let owner: AccountId = AssetHubWestend::account_id_of(ALICE);
+	AssetHubWestend::force_create_foreign_asset(
+		roc_at_asset_hub_westend.clone().try_into().unwrap(),
+		owner,
+		true,
+		ASSET_MIN_BALANCE,
+		vec![],
+	);
+	let sov_ahw_on_ahr = AssetHubRococo::sovereign_account_of_parachain_on_other_global_consensus(
+		Westend,
+		AssetHubWestend::para_id(),
+	);
+
+	let amount = ASSET_HUB_ROCOCO_ED * 10_000_000;
+	let penpal_location = AssetHubRococo::sibling_location_of(PenpalA::para_id());
+	let sov_penpal_on_ahr = AssetHubRococo::sovereign_account_id_of(penpal_location);
+	// fund Penpal's sovereign account on AssetHub
+	AssetHubRococo::fund_accounts(vec![(sov_penpal_on_ahr.into(), amount * 2)]);
+	// fund Penpal's sender account
+	PenpalA::mint_foreign_asset(
+		<PenpalA as Chain>::RuntimeOrigin::signed(PenpalAssetOwner::get()),
+		roc_at_rococo_parachains.clone(),
+		PenpalASender::get(),
+		amount * 2,
+	);
+
+	let rocs_in_reserve_on_ahr_before =
+		<AssetHubRococo as Chain>::account_data_of(sov_ahw_on_ahr.clone()).free;
+	let sender_rocs_before = PenpalA::execute_with(|| {
+		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(
+			roc_at_rococo_parachains.clone(),
+			&PenpalASender::get(),
+		)
+	});
+	let receiver_rocs_before = AssetHubWestend::execute_with(|| {
+		type Assets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets;
+		<Assets as Inspect<_>>::balance(
+			roc_at_asset_hub_westend.clone().try_into().unwrap(),
+			&AssetHubWestendReceiver::get(),
+		)
+	});
+	send_asset_from_penpal_rococo_through_local_asset_hub_to_westend_asset_hub(
+		roc_at_rococo_parachains.clone(),
+		amount,
+	);
+
+	AssetHubWestend::execute_with(|| {
+		type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
+		assert_expected_events!(
+			AssetHubWestend,
+			vec![
+				// issue ROCs on AHW
+				RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => {
+					asset_id: *asset_id == roc_at_rococo_parachains.clone().try_into().unwrap(),
+					owner: *owner == AssetHubWestendReceiver::get(),
+				},
+				// message processed successfully
+				RuntimeEvent::MessageQueue(
+					pallet_message_queue::Event::Processed { success: true, .. }
+				) => {},
+			]
+		);
+	});
+
+	let sender_rocs_after = PenpalA::execute_with(|| {
+		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(roc_at_rococo_parachains, &PenpalASender::get())
+	});
+	let receiver_rocs_after = AssetHubWestend::execute_with(|| {
+		type Assets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets;
+		<Assets as Inspect<_>>::balance(
+			roc_at_asset_hub_westend.try_into().unwrap(),
+			&AssetHubWestendReceiver::get(),
+		)
+	});
+	let rocs_in_reserve_on_ahr_after =
+		<AssetHubRococo as Chain>::account_data_of(sov_ahw_on_ahr.clone()).free;
+
+	// Sender's balance is reduced
+	assert!(sender_rocs_after < sender_rocs_before);
+	// Receiver's balance is increased
+	assert!(receiver_rocs_after > receiver_rocs_before);
+	// Reserve balance is increased by sent amount (less fess)
+	assert!(rocs_in_reserve_on_ahr_after > rocs_in_reserve_on_ahr_before);
+	assert!(rocs_in_reserve_on_ahr_after <= rocs_in_reserve_on_ahr_before + amount);
+}
diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs
index 780ba57f78a18c87c327db2acc5cd27442a0221b..e332eb5bfda7c0a05f618c66e6b65cbf10e6bffc 100644
--- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs
+++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs
@@ -306,8 +306,6 @@ fn send_token_from_ethereum_to_penpal() {
 	// The Weth asset location, identified by the contract address on Ethereum
 	let weth_asset_location: Location =
 		(Parent, Parent, EthereumNetwork::get(), AccountKey20 { network: None, key: WETH }).into();
-	// Converts the Weth asset location into an asset ID
-	let weth_asset_id: v3::Location = weth_asset_location.try_into().unwrap();
 
 	let origin_location = (Parent, Parent, EthereumNetwork::get()).into();
 
@@ -321,12 +319,12 @@ fn send_token_from_ethereum_to_penpal() {
 	PenpalA::execute_with(|| {
 		assert_ok!(<PenpalA as PenpalAPallet>::ForeignAssets::create(
 			<PenpalA as Chain>::RuntimeOrigin::signed(PenpalASender::get()),
-			weth_asset_id,
+			weth_asset_location.clone(),
 			asset_hub_sovereign.into(),
 			1000,
 		));
 
-		assert!(<PenpalA as PenpalAPallet>::ForeignAssets::asset_exists(weth_asset_id));
+		assert!(<PenpalA as PenpalAPallet>::ForeignAssets::asset_exists(weth_asset_location));
 	});
 
 	BridgeHubRococo::execute_with(|| {
@@ -381,10 +379,8 @@ fn send_token_from_ethereum_to_penpal() {
 #[test]
 fn send_weth_asset_from_asset_hub_to_ethereum() {
 	use asset_hub_rococo_runtime::xcm_config::bridging::to_ethereum::DefaultBridgeHubEthereumBaseFee;
-	let assethub_sovereign = BridgeHubRococo::sovereign_account_id_of(Location::new(
-		1,
-		[Parachain(AssetHubRococo::para_id().into())],
-	));
+	let assethub_location = BridgeHubRococo::sibling_location_of(AssetHubRococo::para_id());
+	let assethub_sovereign = BridgeHubRococo::sovereign_account_id_of(assethub_location);
 
 	AssetHubRococo::force_default_xcm_version(Some(XCM_VERSION));
 	BridgeHubRococo::force_default_xcm_version(Some(XCM_VERSION));
diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs
index 60c31ce5a4aefeb5852f87ddd6804ca267857801..36b846e103131882e36b899bdb323d9b969cddde 100644
--- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs
+++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs
@@ -26,6 +26,7 @@ mod imports {
 		v3,
 		v4::NetworkId::Rococo as RococoId,
 	};
+	pub use xcm_executor::traits::TransferType;
 
 	// Cumulus
 	pub use emulated_integration_tests_common::{
@@ -48,13 +49,15 @@ mod imports {
 			genesis::ED as BRIDGE_HUB_WESTEND_ED,
 			BridgeHubWestendParaPallet as BridgeHubWestendPallet,
 		},
+		penpal_emulated_chain::{PenpalAssetOwner, PenpalBParaPallet as PenpalBPallet},
 		westend_emulated_chain::WestendRelayPallet as WestendPallet,
 		AssetHubRococoPara as AssetHubRococo, AssetHubRococoParaReceiver as AssetHubRococoReceiver,
 		AssetHubRococoParaSender as AssetHubRococoSender, AssetHubWestendPara as AssetHubWestend,
 		AssetHubWestendParaReceiver as AssetHubWestendReceiver,
 		AssetHubWestendParaSender as AssetHubWestendSender, BridgeHubRococoPara as BridgeHubRococo,
 		BridgeHubWestendPara as BridgeHubWestend,
-		BridgeHubWestendParaSender as BridgeHubWestendSender, WestendRelay as Westend,
+		BridgeHubWestendParaSender as BridgeHubWestendSender, PenpalBPara as PenpalB,
+		PenpalBParaSender as PenpalBSender, WestendRelay as Westend,
 	};
 
 	pub const ASSET_MIN_BALANCE: u128 = 1000;
diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs
index 5b0990973d2103f7fa606c4abcccd41a893067d2..3a8ce7d43f3e6da98fb2160a62c43c3964f0fe77 100644
--- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs
+++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs
@@ -30,6 +30,73 @@ fn send_asset_from_asset_hub_westend_to_asset_hub_rococo(id: Location, amount: u
 	assert_bridge_hub_rococo_message_received();
 }
 
+fn send_asset_from_penpal_westend_through_local_asset_hub_to_rococo_asset_hub(
+	id: Location,
+	transfer_amount: u128,
+) {
+	let destination = asset_hub_rococo_location();
+	let local_asset_hub: Location = PenpalB::sibling_location_of(AssetHubWestend::para_id());
+	let sov_penpal_on_ahw = AssetHubWestend::sovereign_account_id_of(
+		AssetHubWestend::sibling_location_of(PenpalB::para_id()),
+	);
+	let sov_ahr_on_ahw = AssetHubWestend::sovereign_account_of_parachain_on_other_global_consensus(
+		Rococo,
+		AssetHubRococo::para_id(),
+	);
+
+	// fund the AHW's SA on BHW for paying bridge transport fees
+	BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id(), 10_000_000_000_000u128);
+
+	// set XCM versions
+	PenpalB::force_xcm_version(local_asset_hub.clone(), XCM_VERSION);
+	AssetHubWestend::force_xcm_version(destination.clone(), XCM_VERSION);
+	BridgeHubWestend::force_xcm_version(bridge_hub_rococo_location(), XCM_VERSION);
+
+	// send message over bridge
+	assert_ok!(PenpalB::execute_with(|| {
+		let signed_origin = <PenpalB as Chain>::RuntimeOrigin::signed(PenpalBSender::get());
+		let beneficiary: Location =
+			AccountId32Junction { network: None, id: AssetHubRococoReceiver::get().into() }.into();
+		let assets: Assets = (id.clone(), transfer_amount).into();
+		let fees_id: AssetId = id.into();
+
+		<PenpalB as PenpalBPallet>::PolkadotXcm::transfer_assets_using_type(
+			signed_origin,
+			bx!(destination.into()),
+			bx!(beneficiary.into()),
+			bx!(assets.into()),
+			bx!(TransferType::RemoteReserve(local_asset_hub.clone().into())),
+			bx!(fees_id.into()),
+			bx!(TransferType::RemoteReserve(local_asset_hub.into())),
+			WeightLimit::Unlimited,
+		)
+	}));
+	AssetHubWestend::execute_with(|| {
+		type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
+		assert_expected_events!(
+			AssetHubWestend,
+			vec![
+				// Amount to reserve transfer is withdrawn from Penpal's sovereign account
+				RuntimeEvent::Balances(
+					pallet_balances::Event::Burned { who, amount }
+				) => {
+					who: *who == sov_penpal_on_ahw.clone().into(),
+					amount: *amount == transfer_amount,
+				},
+				// Amount deposited in AHR's sovereign account
+				RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => {
+					who: *who == sov_ahr_on_ahw.clone().into(),
+				},
+				RuntimeEvent::XcmpQueue(
+					cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }
+				) => {},
+			]
+		);
+	});
+	assert_bridge_hub_westend_message_accepted(true);
+	assert_bridge_hub_rococo_message_received();
+}
+
 #[test]
 fn send_wnds_from_asset_hub_westend_to_asset_hub_rococo() {
 	let wnd_at_asset_hub_westend: Location = Parent.into();
@@ -44,7 +111,7 @@ fn send_wnds_from_asset_hub_westend_to_asset_hub_rococo() {
 		vec![],
 	);
 	let sov_ahr_on_ahw = AssetHubWestend::sovereign_account_of_parachain_on_other_global_consensus(
-		NetworkId::Rococo,
+		Rococo,
 		AssetHubRococo::para_id(),
 	);
 
@@ -153,7 +220,7 @@ fn send_rocs_from_asset_hub_westend_to_asset_hub_rococo() {
 
 	// fund the AHW's SA on AHR with the ROC tokens held in reserve
 	let sov_ahw_on_ahr = AssetHubRococo::sovereign_account_of_parachain_on_other_global_consensus(
-		NetworkId::Westend,
+		Westend,
 		AssetHubWestend::para_id(),
 	);
 	AssetHubRococo::fund_accounts(vec![(sov_ahw_on_ahr.clone(), prefund_amount)]);
@@ -169,10 +236,9 @@ fn send_rocs_from_asset_hub_westend_to_asset_hub_rococo() {
 	let receiver_rocs_before =
 		<AssetHubRococo as Chain>::account_data_of(AssetHubRococoReceiver::get()).free;
 
-	let roc_at_asset_hub_westend_latest: Location = roc_at_asset_hub_westend.try_into().unwrap();
 	let amount_to_send = ASSET_HUB_ROCOCO_ED * 1_000;
 	send_asset_from_asset_hub_westend_to_asset_hub_rococo(
-		roc_at_asset_hub_westend_latest.clone(),
+		roc_at_asset_hub_westend.try_into().unwrap(),
 		amount_to_send,
 	);
 	AssetHubRococo::execute_with(|| {
@@ -215,3 +281,95 @@ fn send_rocs_from_asset_hub_westend_to_asset_hub_rococo() {
 	// Reserve balance is reduced by sent amount
 	assert_eq!(rocs_in_reserve_on_ahr_after, rocs_in_reserve_on_ahr_before - amount_to_send);
 }
+
+#[test]
+fn send_wnds_from_penpal_westend_through_asset_hub_westend_to_asset_hub_rococo() {
+	let wnd_at_westend_parachains: Location = Parent.into();
+	let wnd_at_asset_hub_rococo = Location::new(2, [Junction::GlobalConsensus(NetworkId::Westend)]);
+	let owner: AccountId = AssetHubRococo::account_id_of(ALICE);
+	AssetHubRococo::force_create_foreign_asset(
+		wnd_at_asset_hub_rococo.clone().try_into().unwrap(),
+		owner,
+		true,
+		ASSET_MIN_BALANCE,
+		vec![],
+	);
+	let sov_ahr_on_ahw = AssetHubWestend::sovereign_account_of_parachain_on_other_global_consensus(
+		Rococo,
+		AssetHubRococo::para_id(),
+	);
+
+	let amount = ASSET_HUB_WESTEND_ED * 10_000_000;
+	let penpal_location = AssetHubWestend::sibling_location_of(PenpalB::para_id());
+	let sov_penpal_on_ahw = AssetHubWestend::sovereign_account_id_of(penpal_location);
+	// fund Penpal's sovereign account on AssetHub
+	AssetHubWestend::fund_accounts(vec![(sov_penpal_on_ahw.into(), amount * 2)]);
+	// fund Penpal's sender account
+	PenpalB::mint_foreign_asset(
+		<PenpalB as Chain>::RuntimeOrigin::signed(PenpalAssetOwner::get()),
+		wnd_at_westend_parachains.clone(),
+		PenpalBSender::get(),
+		amount * 2,
+	);
+
+	let wnds_in_reserve_on_ahw_before =
+		<AssetHubWestend as Chain>::account_data_of(sov_ahr_on_ahw.clone()).free;
+	let sender_wnds_before = PenpalB::execute_with(|| {
+		type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(
+			wnd_at_westend_parachains.clone(),
+			&PenpalBSender::get(),
+		)
+	});
+	let receiver_wnds_before = AssetHubRococo::execute_with(|| {
+		type Assets = <AssetHubRococo as AssetHubRococoPallet>::ForeignAssets;
+		<Assets as Inspect<_>>::balance(
+			wnd_at_asset_hub_rococo.clone().try_into().unwrap(),
+			&AssetHubRococoReceiver::get(),
+		)
+	});
+	send_asset_from_penpal_westend_through_local_asset_hub_to_rococo_asset_hub(
+		wnd_at_westend_parachains.clone(),
+		amount,
+	);
+
+	AssetHubRococo::execute_with(|| {
+		type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent;
+		assert_expected_events!(
+			AssetHubRococo,
+			vec![
+				// issue WNDs on AHR
+				RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => {
+					asset_id: *asset_id == wnd_at_westend_parachains.clone().try_into().unwrap(),
+					owner: *owner == AssetHubRococoReceiver::get(),
+				},
+				// message processed successfully
+				RuntimeEvent::MessageQueue(
+					pallet_message_queue::Event::Processed { success: true, .. }
+				) => {},
+			]
+		);
+	});
+
+	let sender_wnds_after = PenpalB::execute_with(|| {
+		type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
+		<ForeignAssets as Inspect<_>>::balance(wnd_at_westend_parachains, &PenpalBSender::get())
+	});
+	let receiver_wnds_after = AssetHubRococo::execute_with(|| {
+		type Assets = <AssetHubRococo as AssetHubRococoPallet>::ForeignAssets;
+		<Assets as Inspect<_>>::balance(
+			wnd_at_asset_hub_rococo.try_into().unwrap(),
+			&AssetHubRococoReceiver::get(),
+		)
+	});
+	let wnds_in_reserve_on_ahw_after =
+		<AssetHubWestend as Chain>::account_data_of(sov_ahr_on_ahw.clone()).free;
+
+	// Sender's balance is reduced
+	assert!(sender_wnds_after < sender_wnds_before);
+	// Receiver's balance is increased
+	assert!(receiver_wnds_after > receiver_wnds_before);
+	// Reserve balance is increased by sent amount (less fess)
+	assert!(wnds_in_reserve_on_ahw_after > wnds_in_reserve_on_ahw_before);
+	assert!(wnds_in_reserve_on_ahw_after <= wnds_in_reserve_on_ahw_before + amount);
+}
diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs
index 6a46bc79b68ddf9de16eed71c9863c8a24c5464f..942f8cf8639c8680f9c65601eafb668508cf6f04 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs
+++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs
@@ -322,7 +322,7 @@ pub type LocalAndForeignAssets = fungibles::UnionOf<
 	Assets,
 	ForeignAssets,
 	LocalFromLeft<
-		AssetIdForTrustBackedAssetsConvert<TrustBackedAssetsPalletLocationV3>,
+		AssetIdForTrustBackedAssetsConvert<TrustBackedAssetsPalletLocationV3, xcm::v3::Location>,
 		AssetIdForTrustBackedAssets,
 		xcm::v3::Location,
 	>,
diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs
index 598c058cb28385bf92a15ddfb5224118391a429d..c464fec4edd6f9195693fdb399555dba78b3ca16 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs
+++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs
@@ -82,8 +82,6 @@ parameter_types! {
 		PalletInstance(<PoolAssets as PalletInfoAccess>::index() as u8).into();
 	pub UniquesPalletLocation: Location =
 		PalletInstance(<Uniques as PalletInfoAccess>::index() as u8).into();
-	pub PoolAssetsPalletLocationV3: xcm::v3::Location =
-		xcm::v3::Junction::PalletInstance(<PoolAssets as PalletInfoAccess>::index() as u8).into();
 	pub CheckingAccount: AccountId = PolkadotXcm::check_account();
 	pub const GovernanceLocation: Location = Location::parent();
 	pub StakingPot: AccountId = CollatorSelection::account_id();
@@ -179,6 +177,7 @@ pub type ForeignAssetsConvertedConcreteId = assets_common::ForeignAssetsConverte
 		StartsWithExplicitGlobalConsensus<UniversalLocationNetworkId>,
 	),
 	Balance,
+	xcm::v3::Location,
 >;
 
 /// Means for transacting foreign assets from different global consensus.
@@ -581,7 +580,11 @@ impl xcm_executor::Config for XcmConfig {
 			WeightToFee,
 			crate::NativeAndNonPoolAssets,
 			(
-				TrustBackedAssetsAsLocation<TrustBackedAssetsPalletLocation, Balance>,
+				TrustBackedAssetsAsLocation<
+					TrustBackedAssetsPalletLocation,
+					Balance,
+					xcm::v3::Location,
+				>,
 				ForeignAssetsConvertedConcreteId,
 			),
 			ResolveAssetTo<StakingPot, crate::NativeAndNonPoolAssets>,
diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs
index 5fa7455ad2a0b5620a6f6934b87acfe3b85d4f57..f670c5f424efeac0e00ddf472f1948e06d93bd68 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs
+++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs
@@ -22,8 +22,7 @@ use asset_hub_rococo_runtime::{
 	xcm_config::{
 		bridging, AssetFeeAsExistentialDepositMultiplierFeeCharger, CheckingAccount,
 		ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger, ForeignCreatorsSovereignAccountOf,
-		LocationToAccountId, StakingPot, TokenLocation, TokenLocationV3,
-		TrustBackedAssetsPalletLocation, TrustBackedAssetsPalletLocationV3, XcmConfig,
+		LocationToAccountId, StakingPot, TokenLocation, TrustBackedAssetsPalletLocation, XcmConfig,
 	},
 	AllPalletsWithoutSystem, AssetConversion, AssetDeposit, Assets, Balances, CollatorSelection,
 	ExistentialDeposit, ForeignAssets, ForeignAssetsInstance, MetadataDepositBase,
@@ -53,17 +52,14 @@ use sp_std::ops::Mul;
 use std::convert::Into;
 use testnet_parachains_constants::rococo::{consensus::*, currency::UNITS, fee::WeightToFee};
 use xcm::latest::prelude::{Assets as XcmAssets, *};
-use xcm_builder::V4V3LocationConverter;
+use xcm_builder::WithLatestLocationConverter;
 use xcm_executor::traits::{JustTry, WeightTrader};
 
 const ALICE: [u8; 32] = [1u8; 32];
 const SOME_ASSET_ADMIN: [u8; 32] = [5u8; 32];
 
 type AssetIdForTrustBackedAssetsConvert =
-	assets_common::AssetIdForTrustBackedAssetsConvert<TrustBackedAssetsPalletLocationV3>;
-
-type AssetIdForTrustBackedAssetsConvertLatest =
-	assets_common::AssetIdForTrustBackedAssetsConvertLatest<TrustBackedAssetsPalletLocation>;
+	assets_common::AssetIdForTrustBackedAssetsConvert<TrustBackedAssetsPalletLocation>;
 
 type RuntimeHelper = asset_test_utils::RuntimeHelper<Runtime, AllPalletsWithoutSystem>;
 
@@ -204,7 +200,7 @@ fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() {
 			let bob: AccountId = SOME_ASSET_ADMIN.into();
 			let staking_pot = CollatorSelection::account_id();
 			let asset_1: u32 = 1;
-			let native_location = TokenLocationV3::get();
+			let native_location = TokenLocation::get();
 			let asset_1_location =
 				AssetIdForTrustBackedAssetsConvert::convert_back(&asset_1).unwrap();
 			// bob's initial balance for native and `asset1` assets.
@@ -221,14 +217,24 @@ fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() {
 
 			assert_ok!(AssetConversion::create_pool(
 				RuntimeHelper::origin_of(bob.clone()),
-				Box::new(native_location),
-				Box::new(asset_1_location)
+				Box::new(
+					xcm::v3::Location::try_from(native_location.clone()).expect("conversion works")
+				),
+				Box::new(
+					xcm::v3::Location::try_from(asset_1_location.clone())
+						.expect("conversion works")
+				)
 			));
 
 			assert_ok!(AssetConversion::add_liquidity(
 				RuntimeHelper::origin_of(bob.clone()),
-				Box::new(native_location),
-				Box::new(asset_1_location),
+				Box::new(
+					xcm::v3::Location::try_from(native_location.clone()).expect("conversion works")
+				),
+				Box::new(
+					xcm::v3::Location::try_from(asset_1_location.clone())
+						.expect("conversion works")
+				),
 				pool_liquidity,
 				pool_liquidity,
 				1,
@@ -240,8 +246,6 @@ fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() {
 			let asset_total_issuance = Assets::total_issuance(asset_1);
 			let native_total_issuance = Balances::total_issuance();
 
-			let asset_1_location_latest: Location = asset_1_location.try_into().unwrap();
-
 			// prepare input to buy weight.
 			let weight = Weight::from_parts(4_000_000_000, 0);
 			let fee = WeightToFee::weight_to_fee(&weight);
@@ -249,7 +253,7 @@ fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() {
 				AssetConversion::get_amount_in(&fee, &pool_liquidity, &pool_liquidity).unwrap();
 			let extra_amount = 100;
 			let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
-			let payment: Asset = (asset_1_location_latest.clone(), asset_fee + extra_amount).into();
+			let payment: Asset = (asset_1_location.clone(), asset_fee + extra_amount).into();
 
 			// init trader and buy weight.
 			let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
@@ -257,24 +261,25 @@ fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() {
 				trader.buy_weight(weight, payment.into(), &ctx).expect("Expected Ok");
 
 			// assert.
-			let unused_amount = unused_asset
-				.fungible
-				.get(&asset_1_location_latest.clone().into())
-				.map_or(0, |a| *a);
+			let unused_amount =
+				unused_asset.fungible.get(&asset_1_location.clone().into()).map_or(0, |a| *a);
 			assert_eq!(unused_amount, extra_amount);
 			assert_eq!(Assets::total_issuance(asset_1), asset_total_issuance + asset_fee);
 
 			// prepare input to refund weight.
 			let refund_weight = Weight::from_parts(1_000_000_000, 0);
 			let refund = WeightToFee::weight_to_fee(&refund_weight);
-			let (reserve1, reserve2) =
-				AssetConversion::get_reserves(native_location, asset_1_location).unwrap();
+			let (reserve1, reserve2) = AssetConversion::get_reserves(
+				xcm::v3::Location::try_from(native_location).expect("conversion works"),
+				xcm::v3::Location::try_from(asset_1_location.clone()).expect("conversion works"),
+			)
+			.unwrap();
 			let asset_refund =
 				AssetConversion::get_amount_out(&refund, &reserve1, &reserve2).unwrap();
 
 			// refund.
 			let actual_refund = trader.refund_weight(refund_weight, &ctx).unwrap();
-			assert_eq!(actual_refund, (asset_1_location_latest, asset_refund).into());
+			assert_eq!(actual_refund, (asset_1_location, asset_refund).into());
 
 			// assert.
 			assert_eq!(Balances::balance(&staking_pot), initial_balance);
@@ -303,7 +308,8 @@ fn test_buy_and_refund_weight_with_swap_foreign_asset_xcm_trader() {
 		.execute_with(|| {
 			let bob: AccountId = SOME_ASSET_ADMIN.into();
 			let staking_pot = CollatorSelection::account_id();
-			let native_location = TokenLocationV3::get();
+			let native_location =
+				xcm::v3::Location::try_from(TokenLocation::get()).expect("conversion works");
 			let foreign_location = xcm::v3::Location {
 				parents: 1,
 				interior: (
@@ -435,7 +441,7 @@ fn test_asset_xcm_take_first_trader() {
 
 			// get asset id as location
 			let asset_location =
-				AssetIdForTrustBackedAssetsConvertLatest::convert_back(&local_asset_id).unwrap();
+				AssetIdForTrustBackedAssetsConvert::convert_back(&local_asset_id).unwrap();
 
 			// Set Alice as block author, who will receive fees
 			RuntimeHelper::run_to_block(2, AccountId::from(ALICE));
@@ -603,9 +609,7 @@ fn test_asset_xcm_take_first_trader_with_refund() {
 
 			// We are going to buy 4e9 weight
 			let bought = Weight::from_parts(4_000_000_000u64, 0);
-
-			let asset_location =
-				AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap();
+			let asset_location = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap();
 
 			// lets calculate amount needed
 			let amount_bought = WeightToFee::weight_to_fee(&bought);
@@ -623,7 +627,7 @@ fn test_asset_xcm_take_first_trader_with_refund() {
 			// We actually use half of the weight
 			let weight_used = bought / 2;
 
-			// Make sure refurnd works.
+			// Make sure refund works.
 			let amount_refunded = WeightToFee::weight_to_fee(&(bought - weight_used));
 
 			assert_eq!(
@@ -677,8 +681,7 @@ fn test_asset_xcm_take_first_trader_refund_not_possible_since_amount_less_than_e
 			// We are going to buy small amount
 			let bought = Weight::from_parts(500_000_000u64, 0);
 
-			let asset_location =
-				AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap();
+			let asset_location = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap();
 
 			let amount_bought = WeightToFee::weight_to_fee(&bought);
 
@@ -730,8 +733,7 @@ fn test_that_buying_ed_refund_does_not_refund_for_take_first_trader() {
 			// We are gonna buy ED
 			let bought = Weight::from_parts(ExistentialDeposit::get().try_into().unwrap(), 0);
 
-			let asset_location =
-				AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap();
+			let asset_location = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap();
 
 			let amount_bought = WeightToFee::weight_to_fee(&bought);
 
@@ -807,8 +809,7 @@ fn test_asset_xcm_trader_not_possible_for_non_sufficient_assets() {
 			// lets calculate amount needed
 			let asset_amount_needed = WeightToFee::weight_to_fee(&bought);
 
-			let asset_location =
-				AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap();
+			let asset_location = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap();
 
 			let asset: Asset = (asset_location, asset_amount_needed).into();
 
@@ -925,13 +926,16 @@ fn test_assets_balances_api_works() {
 			)));
 			// check trusted asset
 			assert!(result.inner().iter().any(|asset| asset.eq(&(
-				AssetIdForTrustBackedAssetsConvertLatest::convert_back(&local_asset_id).unwrap(),
+				AssetIdForTrustBackedAssetsConvert::convert_back(&local_asset_id).unwrap(),
 				minimum_asset_balance
 			)
 				.into())));
 			// check foreign asset
 			assert!(result.inner().iter().any(|asset| asset.eq(&(
-				V4V3LocationConverter::convert_back(&foreign_asset_id_location).unwrap(),
+				WithLatestLocationConverter::<xcm::v3::Location>::convert_back(
+					&foreign_asset_id_location
+				)
+				.unwrap(),
 				6 * foreign_asset_minimum_asset_balance
 			)
 				.into())));
@@ -1004,7 +1008,7 @@ asset_test_utils::include_asset_transactor_transfer_with_pallet_assets_instance_
 	XcmConfig,
 	TrustBackedAssetsInstance,
 	AssetIdForTrustBackedAssets,
-	AssetIdForTrustBackedAssetsConvertLatest,
+	AssetIdForTrustBackedAssetsConvert,
 	collator_session_keys(),
 	ExistentialDeposit::get(),
 	12345,
@@ -1044,7 +1048,7 @@ asset_test_utils::include_create_and_manage_foreign_assets_for_local_consensus_p
 	ForeignCreatorsSovereignAccountOf,
 	ForeignAssetsInstance,
 	xcm::v3::Location,
-	V4V3LocationConverter,
+	WithLatestLocationConverter<xcm::v3::Location>,
 	collator_session_keys(),
 	ExistentialDeposit::get(),
 	AssetDeposit::get(),
diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs
index f967ceaa616853ce7cc6954f6e3959b5eef93841..35b73d89dcde120cc1937230de7fdc8cab0e4034 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs
+++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs
@@ -302,7 +302,7 @@ pub type LocalAndForeignAssets = fungibles::UnionOf<
 	Assets,
 	ForeignAssets,
 	LocalFromLeft<
-		AssetIdForTrustBackedAssetsConvert<TrustBackedAssetsPalletLocationV3>,
+		AssetIdForTrustBackedAssetsConvert<TrustBackedAssetsPalletLocationV3, xcm::v3::Location>,
 		AssetIdForTrustBackedAssets,
 		xcm::v3::Location,
 	>,
diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs
index b92419899395e93d05536d4785739bab4e528894..9ba07ccdc0388f26206526a100fc6e4a72ec370e 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs
+++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs
@@ -79,8 +79,6 @@ parameter_types! {
 		PalletInstance(<PoolAssets as PalletInfoAccess>::index() as u8).into();
 	pub UniquesPalletLocation: Location =
 		PalletInstance(<Uniques as PalletInfoAccess>::index() as u8).into();
-	pub PoolAssetsPalletLocationV3: xcm::v3::Location =
-		xcm::v3::Junction::PalletInstance(<PoolAssets as PalletInfoAccess>::index() as u8).into();
 	pub CheckingAccount: AccountId = PolkadotXcm::check_account();
 	pub StakingPot: AccountId = CollatorSelection::account_id();
 	pub TreasuryAccount: AccountId = TREASURY_PALLET_ID.into_account_truncating();
@@ -173,6 +171,7 @@ pub type ForeignAssetsConvertedConcreteId = assets_common::ForeignAssetsConverte
 		StartsWithExplicitGlobalConsensus<UniversalLocationNetworkId>,
 	),
 	Balance,
+	xcm::v3::Location,
 >;
 
 /// Means for transacting foreign assets from different global consensus.
@@ -604,7 +603,11 @@ impl xcm_executor::Config for XcmConfig {
 			WeightToFee,
 			crate::NativeAndNonPoolAssets,
 			(
-				TrustBackedAssetsAsLocation<TrustBackedAssetsPalletLocation, Balance>,
+				TrustBackedAssetsAsLocation<
+					TrustBackedAssetsPalletLocation,
+					Balance,
+					xcm::v3::Location,
+				>,
 				ForeignAssetsConvertedConcreteId,
 			),
 			ResolveAssetTo<StakingPot, crate::NativeAndNonPoolAssets>,
diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs
index 6696cb2322391c2dbad5f6c9a0afc4d5537de68c..b5957dd5df92ff1180909535cb6e604deadd8829 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs
+++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs
@@ -22,8 +22,8 @@ use asset_hub_westend_runtime::{
 	xcm_config::{
 		bridging, AssetFeeAsExistentialDepositMultiplierFeeCharger, CheckingAccount,
 		ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger, ForeignCreatorsSovereignAccountOf,
-		LocationToAccountId, StakingPot, TrustBackedAssetsPalletLocation,
-		TrustBackedAssetsPalletLocationV3, WestendLocation, WestendLocationV3, XcmConfig,
+		LocationToAccountId, StakingPot, TrustBackedAssetsPalletLocation, WestendLocation,
+		XcmConfig,
 	},
 	AllPalletsWithoutSystem, Assets, Balances, ExistentialDeposit, ForeignAssets,
 	ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, ParachainSystem,
@@ -53,17 +53,14 @@ use sp_runtime::traits::MaybeEquivalence;
 use std::{convert::Into, ops::Mul};
 use testnet_parachains_constants::westend::{consensus::*, currency::UNITS, fee::WeightToFee};
 use xcm::latest::prelude::{Assets as XcmAssets, *};
-use xcm_builder::V4V3LocationConverter;
+use xcm_builder::WithLatestLocationConverter;
 use xcm_executor::traits::{ConvertLocation, JustTry, WeightTrader};
 
 const ALICE: [u8; 32] = [1u8; 32];
 const SOME_ASSET_ADMIN: [u8; 32] = [5u8; 32];
 
 type AssetIdForTrustBackedAssetsConvert =
-	assets_common::AssetIdForTrustBackedAssetsConvert<TrustBackedAssetsPalletLocationV3>;
-
-type AssetIdForTrustBackedAssetsConvertLatest =
-	assets_common::AssetIdForTrustBackedAssetsConvertLatest<TrustBackedAssetsPalletLocation>;
+	assets_common::AssetIdForTrustBackedAssetsConvert<TrustBackedAssetsPalletLocation>;
 
 type RuntimeHelper = asset_test_utils::RuntimeHelper<Runtime, AllPalletsWithoutSystem>;
 
@@ -204,7 +201,7 @@ fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() {
 			let bob: AccountId = SOME_ASSET_ADMIN.into();
 			let staking_pot = CollatorSelection::account_id();
 			let asset_1: u32 = 1;
-			let native_location = WestendLocationV3::get();
+			let native_location = WestendLocation::get();
 			let asset_1_location =
 				AssetIdForTrustBackedAssetsConvert::convert_back(&asset_1).unwrap();
 			// bob's initial balance for native and `asset1` assets.
@@ -221,14 +218,24 @@ fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() {
 
 			assert_ok!(AssetConversion::create_pool(
 				RuntimeHelper::origin_of(bob.clone()),
-				Box::new(native_location),
-				Box::new(asset_1_location)
+				Box::new(
+					xcm::v3::Location::try_from(native_location.clone()).expect("conversion works")
+				),
+				Box::new(
+					xcm::v3::Location::try_from(asset_1_location.clone())
+						.expect("conversion works")
+				)
 			));
 
 			assert_ok!(AssetConversion::add_liquidity(
 				RuntimeHelper::origin_of(bob.clone()),
-				Box::new(native_location),
-				Box::new(asset_1_location),
+				Box::new(
+					xcm::v3::Location::try_from(native_location.clone()).expect("conversion works")
+				),
+				Box::new(
+					xcm::v3::Location::try_from(asset_1_location.clone())
+						.expect("conversion works")
+				),
 				pool_liquidity,
 				pool_liquidity,
 				1,
@@ -240,8 +247,6 @@ fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() {
 			let asset_total_issuance = Assets::total_issuance(asset_1);
 			let native_total_issuance = Balances::total_issuance();
 
-			let asset_1_location_latest: Location = asset_1_location.try_into().unwrap();
-
 			// prepare input to buy weight.
 			let weight = Weight::from_parts(4_000_000_000, 0);
 			let fee = WeightToFee::weight_to_fee(&weight);
@@ -249,7 +254,7 @@ fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() {
 				AssetConversion::get_amount_in(&fee, &pool_liquidity, &pool_liquidity).unwrap();
 			let extra_amount = 100;
 			let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
-			let payment: Asset = (asset_1_location_latest.clone(), asset_fee + extra_amount).into();
+			let payment: Asset = (asset_1_location.clone(), asset_fee + extra_amount).into();
 
 			// init trader and buy weight.
 			let mut trader = <XcmConfig as xcm_executor::Config>::Trader::new();
@@ -257,24 +262,25 @@ fn test_buy_and_refund_weight_with_swap_local_asset_xcm_trader() {
 				trader.buy_weight(weight, payment.into(), &ctx).expect("Expected Ok");
 
 			// assert.
-			let unused_amount = unused_asset
-				.fungible
-				.get(&asset_1_location_latest.clone().into())
-				.map_or(0, |a| *a);
+			let unused_amount =
+				unused_asset.fungible.get(&asset_1_location.clone().into()).map_or(0, |a| *a);
 			assert_eq!(unused_amount, extra_amount);
 			assert_eq!(Assets::total_issuance(asset_1), asset_total_issuance + asset_fee);
 
 			// prepare input to refund weight.
 			let refund_weight = Weight::from_parts(1_000_000_000, 0);
 			let refund = WeightToFee::weight_to_fee(&refund_weight);
-			let (reserve1, reserve2) =
-				AssetConversion::get_reserves(native_location, asset_1_location).unwrap();
+			let (reserve1, reserve2) = AssetConversion::get_reserves(
+				xcm::v3::Location::try_from(native_location).expect("conversion works"),
+				xcm::v3::Location::try_from(asset_1_location.clone()).expect("conversion works"),
+			)
+			.unwrap();
 			let asset_refund =
 				AssetConversion::get_amount_out(&refund, &reserve1, &reserve2).unwrap();
 
 			// refund.
 			let actual_refund = trader.refund_weight(refund_weight, &ctx).unwrap();
-			assert_eq!(actual_refund, (asset_1_location_latest, asset_refund).into());
+			assert_eq!(actual_refund, (asset_1_location, asset_refund).into());
 
 			// assert.
 			assert_eq!(Balances::balance(&staking_pot), initial_balance);
@@ -303,7 +309,8 @@ fn test_buy_and_refund_weight_with_swap_foreign_asset_xcm_trader() {
 		.execute_with(|| {
 			let bob: AccountId = SOME_ASSET_ADMIN.into();
 			let staking_pot = CollatorSelection::account_id();
-			let native_location = WestendLocationV3::get();
+			let native_location =
+				xcm::v3::Location::try_from(WestendLocation::get()).expect("conversion works");
 			let foreign_location = xcm::v3::Location {
 				parents: 1,
 				interior: (
@@ -435,7 +442,7 @@ fn test_asset_xcm_take_first_trader() {
 
 			// get asset id as location
 			let asset_location =
-				AssetIdForTrustBackedAssetsConvertLatest::convert_back(&local_asset_id).unwrap();
+				AssetIdForTrustBackedAssetsConvert::convert_back(&local_asset_id).unwrap();
 
 			// Set Alice as block author, who will receive fees
 			RuntimeHelper::run_to_block(2, AccountId::from(ALICE));
@@ -599,8 +606,7 @@ fn test_asset_xcm_take_first_trader_with_refund() {
 
 			// We are going to buy 4e9 weight
 			let bought = Weight::from_parts(4_000_000_000u64, 0);
-			let asset_location =
-				AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap();
+			let asset_location = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap();
 
 			// lets calculate amount needed
 			let amount_bought = WeightToFee::weight_to_fee(&bought);
@@ -672,8 +678,7 @@ fn test_asset_xcm_take_first_trader_refund_not_possible_since_amount_less_than_e
 			// We are going to buy small amount
 			let bought = Weight::from_parts(500_000_000u64, 0);
 
-			let asset_location =
-				AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap();
+			let asset_location = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap();
 
 			let amount_bought = WeightToFee::weight_to_fee(&bought);
 
@@ -724,8 +729,7 @@ fn test_that_buying_ed_refund_does_not_refund_for_take_first_trader() {
 
 			let bought = Weight::from_parts(500_000_000u64, 0);
 
-			let asset_location =
-				AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap();
+			let asset_location = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap();
 
 			let amount_bought = WeightToFee::weight_to_fee(&bought);
 
@@ -801,8 +805,7 @@ fn test_asset_xcm_take_first_trader_not_possible_for_non_sufficient_assets() {
 			// lets calculate amount needed
 			let asset_amount_needed = WeightToFee::weight_to_fee(&bought);
 
-			let asset_location =
-				AssetIdForTrustBackedAssetsConvertLatest::convert_back(&1).unwrap();
+			let asset_location = AssetIdForTrustBackedAssetsConvert::convert_back(&1).unwrap();
 
 			let asset: Asset = (asset_location, asset_amount_needed).into();
 
@@ -923,13 +926,16 @@ fn test_assets_balances_api_works() {
 			)));
 			// check trusted asset
 			assert!(result.inner().iter().any(|asset| asset.eq(&(
-				AssetIdForTrustBackedAssetsConvertLatest::convert_back(&local_asset_id).unwrap(),
+				AssetIdForTrustBackedAssetsConvert::convert_back(&local_asset_id).unwrap(),
 				minimum_asset_balance
 			)
 				.into())));
 			// check foreign asset
 			assert!(result.inner().iter().any(|asset| asset.eq(&(
-				V4V3LocationConverter::convert_back(&foreign_asset_id_location).unwrap(),
+				WithLatestLocationConverter::<xcm::v3::Location>::convert_back(
+					&foreign_asset_id_location
+				)
+				.unwrap(),
 				6 * foreign_asset_minimum_asset_balance
 			)
 				.into())));
@@ -1002,7 +1008,7 @@ asset_test_utils::include_asset_transactor_transfer_with_pallet_assets_instance_
 	XcmConfig,
 	TrustBackedAssetsInstance,
 	AssetIdForTrustBackedAssets,
-	AssetIdForTrustBackedAssetsConvertLatest,
+	AssetIdForTrustBackedAssetsConvert,
 	collator_session_keys(),
 	ExistentialDeposit::get(),
 	12345,
@@ -1043,7 +1049,7 @@ asset_test_utils::include_create_and_manage_foreign_assets_for_local_consensus_p
 	ForeignCreatorsSovereignAccountOf,
 	ForeignAssetsInstance,
 	xcm::v3::Location,
-	V4V3LocationConverter,
+	WithLatestLocationConverter<xcm::v3::Location>,
 	collator_session_keys(),
 	ExistentialDeposit::get(),
 	AssetDeposit::get(),
diff --git a/cumulus/parachains/runtimes/assets/common/src/lib.rs b/cumulus/parachains/runtimes/assets/common/src/lib.rs
index 85e8ab15bab54497658e5a75d996001ee278dde2..1b99549619ad2eba865cb87fba9170c57c3979e7 100644
--- a/cumulus/parachains/runtimes/assets/common/src/lib.rs
+++ b/cumulus/parachains/runtimes/assets/common/src/lib.rs
@@ -26,36 +26,37 @@ pub mod runtime_api;
 use crate::matching::{LocalLocationPattern, ParentLocation};
 use frame_support::traits::{Equals, EverythingBut};
 use parachains_common::{AssetIdForTrustBackedAssets, CollectionId, ItemId};
+use sp_runtime::traits::TryConvertInto;
+use xcm::latest::Location;
 use xcm_builder::{
-	AsPrefixedGeneralIndex, MatchedConvertedConcreteId, StartsWith, V4V3LocationConverter,
+	AsPrefixedGeneralIndex, MatchedConvertedConcreteId, StartsWith, WithLatestLocationConverter,
 };
-use xcm_executor::traits::JustTry;
 
 /// `Location` vs `AssetIdForTrustBackedAssets` converter for `TrustBackedAssets`
-pub type AssetIdForTrustBackedAssetsConvert<TrustBackedAssetsPalletLocation> =
+pub type AssetIdForTrustBackedAssetsConvert<TrustBackedAssetsPalletLocation, L = Location> =
 	AsPrefixedGeneralIndex<
 		TrustBackedAssetsPalletLocation,
 		AssetIdForTrustBackedAssets,
-		JustTry,
-		xcm::v3::Location,
+		TryConvertInto,
+		L,
 	>;
 
-pub type AssetIdForTrustBackedAssetsConvertLatest<TrustBackedAssetsPalletLocation> =
-	AsPrefixedGeneralIndex<TrustBackedAssetsPalletLocation, AssetIdForTrustBackedAssets, JustTry>;
-
 /// `Location` vs `CollectionId` converter for `Uniques`
 pub type CollectionIdForUniquesConvert<UniquesPalletLocation> =
-	AsPrefixedGeneralIndex<UniquesPalletLocation, CollectionId, JustTry>;
+	AsPrefixedGeneralIndex<UniquesPalletLocation, CollectionId, TryConvertInto>;
 
 /// [`MatchedConvertedConcreteId`] converter dedicated for `TrustBackedAssets`
-pub type TrustBackedAssetsConvertedConcreteId<TrustBackedAssetsPalletLocation, Balance> =
-	MatchedConvertedConcreteId<
-		AssetIdForTrustBackedAssets,
-		Balance,
-		StartsWith<TrustBackedAssetsPalletLocation>,
-		AssetIdForTrustBackedAssetsConvertLatest<TrustBackedAssetsPalletLocation>,
-		JustTry,
-	>;
+pub type TrustBackedAssetsConvertedConcreteId<
+	TrustBackedAssetsPalletLocation,
+	Balance,
+	L = Location,
+> = MatchedConvertedConcreteId<
+	AssetIdForTrustBackedAssets,
+	Balance,
+	StartsWith<TrustBackedAssetsPalletLocation>,
+	AssetIdForTrustBackedAssetsConvert<TrustBackedAssetsPalletLocation, L>,
+	TryConvertInto,
+>;
 
 /// [`MatchedConvertedConcreteId`] converter dedicated for `Uniques`
 pub type UniquesConvertedConcreteId<UniquesPalletLocation> = MatchedConvertedConcreteId<
@@ -65,28 +66,26 @@ pub type UniquesConvertedConcreteId<UniquesPalletLocation> = MatchedConvertedCon
 	// junction within the pallet itself.
 	StartsWith<UniquesPalletLocation>,
 	CollectionIdForUniquesConvert<UniquesPalletLocation>,
-	JustTry,
+	TryConvertInto,
 >;
 
-/// [`MatchedConvertedConcreteId`] converter dedicated for storing `AssetId` as `Location`.
-pub type LocationConvertedConcreteId<LocationFilter, Balance> = MatchedConvertedConcreteId<
-	xcm::v3::Location,
+/// [`MatchedConvertedConcreteId`] converter dedicated for `TrustBackedAssets`,
+/// it is a similar implementation to `TrustBackedAssetsConvertedConcreteId`,
+/// but it converts `AssetId` to `xcm::v*::Location` type instead of `AssetIdForTrustBackedAssets =
+/// u32`
+pub type TrustBackedAssetsAsLocation<
+	TrustBackedAssetsPalletLocation,
 	Balance,
-	LocationFilter,
-	V4V3LocationConverter,
-	JustTry,
+	L,
+	LocationConverter = WithLatestLocationConverter<L>,
+> = MatchedConvertedConcreteId<
+	L,
+	Balance,
+	StartsWith<TrustBackedAssetsPalletLocation>,
+	LocationConverter,
+	TryConvertInto,
 >;
 
-/// [`MatchedConvertedConcreteId`] converter dedicated for `TrustBackedAssets`
-pub type TrustBackedAssetsAsLocation<TrustBackedAssetsPalletLocation, Balance> =
-	MatchedConvertedConcreteId<
-		xcm::v3::Location,
-		Balance,
-		StartsWith<TrustBackedAssetsPalletLocation>,
-		V4V3LocationConverter,
-		JustTry,
-	>;
-
 /// [`MatchedConvertedConcreteId`] converter dedicated for storing `ForeignAssets` with `AssetId` as
 /// `Location`.
 ///
@@ -95,21 +94,29 @@ pub type TrustBackedAssetsAsLocation<TrustBackedAssetsPalletLocation, Balance> =
 /// - all local Locations
 ///
 /// `AdditionalLocationExclusionFilter` can customize additional excluded Locations
-pub type ForeignAssetsConvertedConcreteId<AdditionalLocationExclusionFilter, Balance> =
-	LocationConvertedConcreteId<
-		EverythingBut<(
-			// Excludes relay/parent chain currency
-			Equals<ParentLocation>,
-			// Here we rely on fact that something like this works:
-			// assert!(Location::new(1,
-			// [Parachain(100)]).starts_with(&Location::parent()));
-			// assert!([Parachain(100)].into().starts_with(&Here));
-			StartsWith<LocalLocationPattern>,
-			// Here we can exclude more stuff or leave it as `()`
-			AdditionalLocationExclusionFilter,
-		)>,
-		Balance,
-	>;
+pub type ForeignAssetsConvertedConcreteId<
+	AdditionalLocationExclusionFilter,
+	Balance,
+	AssetId,
+	LocationToAssetIdConverter = WithLatestLocationConverter<AssetId>,
+	BalanceConverter = TryConvertInto,
+> = MatchedConvertedConcreteId<
+	AssetId,
+	Balance,
+	EverythingBut<(
+		// Excludes relay/parent chain currency
+		Equals<ParentLocation>,
+		// Here we rely on fact that something like this works:
+		// assert!(Location::new(1,
+		// [Parachain(100)]).starts_with(&Location::parent()));
+		// assert!([Parachain(100)].into().starts_with(&Here));
+		StartsWith<LocalLocationPattern>,
+		// Here we can exclude more stuff or leave it as `()`
+		AdditionalLocationExclusionFilter,
+	)>,
+	LocationToAssetIdConverter,
+	BalanceConverter,
+>;
 
 pub type AssetIdForPoolAssets = u32;
 /// `Location` vs `AssetIdForPoolAssets` converter for `PoolAssets` with explicit `v3 Location`.
@@ -122,7 +129,7 @@ pub type AssetIdForPoolAssetsConvertV3Location<PoolAssetsPalletLocation> = AsPre
 
 /// `Location` vs `AssetIdForPoolAssets` converter for `PoolAssets`.
 pub type AssetIdForPoolAssetsConvert<PoolAssetsPalletLocation> =
-	AsPrefixedGeneralIndex<PoolAssetsPalletLocation, AssetIdForPoolAssets, JustTry>;
+	AsPrefixedGeneralIndex<PoolAssetsPalletLocation, AssetIdForPoolAssets, TryConvertInto>;
 /// [`MatchedConvertedConcreteId`] converter dedicated for `PoolAssets`
 pub type PoolAssetsConvertedConcreteId<PoolAssetsPalletLocation, Balance> =
 	MatchedConvertedConcreteId<
@@ -130,7 +137,7 @@ pub type PoolAssetsConvertedConcreteId<PoolAssetsPalletLocation, Balance> =
 		Balance,
 		StartsWith<PoolAssetsPalletLocation>,
 		AssetIdForPoolAssetsConvert<PoolAssetsPalletLocation>,
-		JustTry,
+		TryConvertInto,
 	>;
 
 #[cfg(test)]
@@ -138,7 +145,7 @@ mod tests {
 	use super::*;
 	use sp_runtime::traits::MaybeEquivalence;
 	use xcm::prelude::*;
-	use xcm_builder::StartsWithExplicitGlobalConsensus;
+	use xcm_builder::{StartsWithExplicitGlobalConsensus, WithLatestLocationConverter};
 	use xcm_executor::traits::{Error as MatchError, MatchesFungibles};
 
 	#[test]
@@ -151,14 +158,14 @@ mod tests {
 			Location::new(5, [PalletInstance(13), GeneralIndex(local_asset_id.into())]);
 
 		assert_eq!(
-			AssetIdForTrustBackedAssetsConvertLatest::<TrustBackedAssetsPalletLocation>::convert_back(
+			AssetIdForTrustBackedAssetsConvert::<TrustBackedAssetsPalletLocation>::convert_back(
 				&local_asset_id
 			)
 			.unwrap(),
 			expected_reverse_ref
 		);
 		assert_eq!(
-			AssetIdForTrustBackedAssetsConvertLatest::<TrustBackedAssetsPalletLocation>::convert(
+			AssetIdForTrustBackedAssetsConvert::<TrustBackedAssetsPalletLocation>::convert(
 				&expected_reverse_ref
 			)
 			.unwrap(),
@@ -171,7 +178,7 @@ mod tests {
 		frame_support::parameter_types! {
 			pub TrustBackedAssetsPalletLocation: Location = Location::new(0, [PalletInstance(13)]);
 		}
-		// setup convert
+		// set up a converter
 		type TrustBackedAssetsConvert =
 			TrustBackedAssetsConvertedConcreteId<TrustBackedAssetsPalletLocation, u128>;
 
@@ -254,19 +261,21 @@ mod tests {
 	}
 
 	#[test]
-	fn location_converted_concrete_id_converter_works() {
+	fn foreign_assets_converted_concrete_id_converter_works() {
 		frame_support::parameter_types! {
 			pub Parachain100Pattern: Location = Location::new(1, [Parachain(100)]);
 			pub UniversalLocationNetworkId: NetworkId = NetworkId::ByGenesis([9; 32]);
 		}
 
-		// setup convert
+		// set up a converter which uses `xcm::v3::Location` under the hood
 		type Convert = ForeignAssetsConvertedConcreteId<
 			(
 				StartsWith<Parachain100Pattern>,
 				StartsWithExplicitGlobalConsensus<UniversalLocationNetworkId>,
 			),
 			u128,
+			xcm::v3::Location,
+			WithLatestLocationConverter<xcm::v3::Location>,
 		>;
 
 		let test_data = vec![
diff --git a/cumulus/parachains/runtimes/assets/common/src/matching.rs b/cumulus/parachains/runtimes/assets/common/src/matching.rs
index 478bba4565dc1a6d8a45d47b1569b406596b6be7..3aad88e177caad1095a3dbe21dd3a3308b103680 100644
--- a/cumulus/parachains/runtimes/assets/common/src/matching.rs
+++ b/cumulus/parachains/runtimes/assets/common/src/matching.rs
@@ -113,17 +113,14 @@ impl<UniversalLocation: Get<InteriorLocation>, Reserves: ContainsPair<Asset, Loc
 		);
 
 		// check remote origin
-		let _ = match ensure_is_remote(universal_source.clone(), origin.clone()) {
-			Ok(devolved) => devolved,
-			Err(_) => {
-				log::trace!(
-					target: "xcm::contains",
-					"IsTrustedBridgedReserveLocationForConcreteAsset origin: {:?} is not remote to the universal_source: {:?}",
-					origin, universal_source
-				);
-				return false
-			},
-		};
+		if ensure_is_remote(universal_source.clone(), origin.clone()).is_err() {
+			log::trace!(
+				target: "xcm::contains",
+				"IsTrustedBridgedReserveLocationForConcreteAsset origin: {:?} is not remote to the universal_source: {:?}",
+				origin, universal_source
+			);
+			return false
+		}
 
 		// check asset according to the configured reserve locations
 		Reserves::contains(asset, origin)
diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs
index 6dbf96edc2ab0360385b8e04bf1dc52732abd9ca..8845f0538b5c828f459431f1a01b54fefc98e9dc 100644
--- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs
@@ -29,6 +29,10 @@ use crate::{
 use bp_messages::LaneId;
 use bp_runtime::Chain;
 use bridge_runtime_common::{
+	extensions::refund_relayer_extension::{
+		ActualFeeRefund, RefundBridgedGrandpaMessages, RefundSignedExtensionAdapter,
+		RefundableMessagesLane,
+	},
 	messages,
 	messages::{
 		source::{FromBridgedChainMessagesDeliveryProof, TargetHeaderChainAdapter},
@@ -39,10 +43,6 @@ use bridge_runtime_common::{
 		SenderAndLane, XcmAsPlainPayload, XcmBlobHauler, XcmBlobHaulerAdapter,
 		XcmBlobMessageDispatch, XcmVersionOfDestAndRemoteBridge,
 	},
-	refund_relayer_extension::{
-		ActualFeeRefund, RefundBridgedGrandpaMessages, RefundSignedExtensionAdapter,
-		RefundableMessagesLane,
-	},
 };
 
 use frame_support::{parameter_types, traits::PalletInfoAccess};
@@ -273,7 +273,7 @@ mod tests {
 		// Bulletin chain - it has the same (almost) runtime for Polkadot Bulletin and Rococo
 		// Bulletin, so we have to adhere Polkadot names here
 
-		bridge_runtime_common::priority_calculator::ensure_priority_boost_is_sane::<
+		bridge_runtime_common::extensions::priority_calculator::ensure_priority_boost_is_sane::<
 			Runtime,
 			WithRococoBulletinMessagesInstance,
 			PriorityBoostPerMessage,
diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs
index 5d55d7afbacfdb22f6939c88e87eaf64321945ff..e5a00073407f8f754f58ef88e825ca598b7bde94 100644
--- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs
@@ -28,6 +28,10 @@ use crate::{
 use bp_messages::LaneId;
 use bp_runtime::Chain;
 use bridge_runtime_common::{
+	extensions::refund_relayer_extension::{
+		ActualFeeRefund, RefundBridgedParachainMessages, RefundSignedExtensionAdapter,
+		RefundableMessagesLane, RefundableParachain,
+	},
 	messages,
 	messages::{
 		source::{FromBridgedChainMessagesDeliveryProof, TargetHeaderChainAdapter},
@@ -38,10 +42,6 @@ use bridge_runtime_common::{
 		SenderAndLane, XcmAsPlainPayload, XcmBlobHauler, XcmBlobHaulerAdapter,
 		XcmBlobMessageDispatch, XcmVersionOfDestAndRemoteBridge,
 	},
-	refund_relayer_extension::{
-		ActualFeeRefund, RefundBridgedParachainMessages, RefundSignedExtensionAdapter,
-		RefundableMessagesLane, RefundableParachain,
-	},
 };
 
 use codec::Encode;
@@ -318,7 +318,7 @@ mod tests {
 			},
 		});
 
-		bridge_runtime_common::priority_calculator::ensure_priority_boost_is_sane::<
+		bridge_runtime_common::extensions::priority_calculator::ensure_priority_boost_is_sane::<
 			Runtime,
 			WithBridgeHubWestendMessagesInstance,
 			PriorityBoostPerMessage,
diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs
index bce722aa5f87d006af0ec71429d6c84eeab4972d..d5da41cce2860c8b12db49f1defc5a6764bc6535 100644
--- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs
@@ -25,6 +25,10 @@ use bp_messages::LaneId;
 use bp_parachains::SingleParaStoredHeaderDataBuilder;
 use bp_runtime::Chain;
 use bridge_runtime_common::{
+	extensions::refund_relayer_extension::{
+		ActualFeeRefund, RefundBridgedParachainMessages, RefundSignedExtensionAdapter,
+		RefundableMessagesLane, RefundableParachain,
+	},
 	messages,
 	messages::{
 		source::{FromBridgedChainMessagesDeliveryProof, TargetHeaderChainAdapter},
@@ -35,10 +39,6 @@ use bridge_runtime_common::{
 		SenderAndLane, XcmAsPlainPayload, XcmBlobHauler, XcmBlobHaulerAdapter,
 		XcmBlobMessageDispatch, XcmVersionOfDestAndRemoteBridge,
 	},
-	refund_relayer_extension::{
-		ActualFeeRefund, RefundBridgedParachainMessages, RefundSignedExtensionAdapter,
-		RefundableMessagesLane, RefundableParachain,
-	},
 };
 use codec::Encode;
 use frame_support::{
@@ -352,7 +352,7 @@ mod tests {
 			},
 		});
 
-		bridge_runtime_common::priority_calculator::ensure_priority_boost_is_sane::<
+		bridge_runtime_common::extensions::priority_calculator::ensure_priority_boost_is_sane::<
 			Runtime,
 			WithBridgeHubRococoMessagesInstance,
 			PriorityBoostPerMessage,
diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/contracts.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/contracts.rs
index 171ac6a9528f134d9c22548500805ef36e9504f9..fcd786711bbe90096f2ef5b8d427cec23879027b 100644
--- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/contracts.rs
+++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/contracts.rs
@@ -71,7 +71,7 @@ impl Config for Runtime {
 	type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>;
 	type MaxDelegateDependencies = ConstU32<32>;
 	type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent;
-	type Migrations = ();
+	type Migrations = (pallet_contracts::migration::v16::Migration<Runtime>,);
 	type RuntimeHoldReason = RuntimeHoldReason;
 	type Debug = ();
 	type Environment = ();
diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs
index 3863ea5022f9142b230f5dba9a3840f858daa127..46fcbc6319c951efa91408ce310f3451002c8b77 100644
--- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs
+++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs
@@ -78,7 +78,7 @@ pub type LocationToAccountId = (
 );
 
 /// Means for transacting the native currency on this chain.
-pub type CurrencyTransactor = FungibleAdapter<
+pub type FungibleTransactor = FungibleAdapter<
 	// Use this currency:
 	Balances,
 	// Use this currency when it is a fungible asset matching the given location or name:
@@ -171,7 +171,7 @@ pub struct XcmConfig;
 impl xcm_executor::Config for XcmConfig {
 	type RuntimeCall = RuntimeCall;
 	type XcmSender = XcmRouter;
-	type AssetTransactor = CurrencyTransactor;
+	type AssetTransactor = FungibleTransactor;
 	type OriginConverter = XcmOriginToTransactDispatchOrigin;
 	type IsReserve = NativeAsset;
 	type IsTeleporter = TrustedTeleporter;
diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/xcm_config.rs
index b0b276128272b66f5b5866f3e40bec957d717e42..7580ab33b8d63ae7f4510e784b22e60bea3b78da 100644
--- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/xcm_config.rs
+++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/xcm_config.rs
@@ -77,7 +77,7 @@ pub type LocationToAccountId = (
 );
 
 /// Means for transacting the native currency on this chain.
-pub type CurrencyTransactor = FungibleAdapter<
+pub type FungibleTransactor = FungibleAdapter<
 	// Use this currency:
 	Balances,
 	// Use this currency when it is a fungible asset matching the given location or name:
@@ -106,7 +106,7 @@ pub type RegionTransactor = NonFungibleAdapter<
 >;
 
 /// Means for transacting assets on this chain.
-pub type AssetTransactors = (CurrencyTransactor, RegionTransactor);
+pub type AssetTransactors = (FungibleTransactor, RegionTransactor);
 
 /// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance,
 /// ready for dispatching a transaction with XCM's `Transact`. There is an `OriginKind` that can
diff --git a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs
index 919bfe83e7d7aff8d90be3af0358dd28b93581e1..89885d77378ba2c1df13116b5f2d41f547002b30 100644
--- a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs
+++ b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs
@@ -72,18 +72,14 @@ use sp_std::prelude::*;
 #[cfg(feature = "std")]
 use sp_version::NativeVersion;
 use sp_version::RuntimeVersion;
-use xcm_config::XcmOriginToTransactDispatchOrigin;
+use xcm_config::{ForeignAssetsAssetId, XcmOriginToTransactDispatchOrigin};
 
 #[cfg(any(feature = "std", test))]
 pub use sp_runtime::BuildStorage;
 
-// Polkadot imports
+use parachains_common::{AccountId, Signature};
 use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate};
-
 use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight};
-
-// XCM Imports
-use parachains_common::{AccountId, Signature};
 use xcm::latest::prelude::{AssetId as AssetLocationId, BodyId};
 
 /// Balance of an account.
@@ -474,8 +470,8 @@ pub type ForeignAssetsInstance = pallet_assets::Instance2;
 impl pallet_assets::Config<ForeignAssetsInstance> for Runtime {
 	type RuntimeEvent = RuntimeEvent;
 	type Balance = Balance;
-	type AssetId = xcm::v3::Location;
-	type AssetIdParameter = xcm::v3::Location;
+	type AssetId = ForeignAssetsAssetId;
+	type AssetIdParameter = ForeignAssetsAssetId;
 	type Currency = Balances;
 	type CreateOrigin = AsEnsureOriginWithArg<EnsureSigned<AccountId>>;
 	type ForceOrigin = EnsureRoot<AccountId>;
diff --git a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs
index c12372abbe90f273f46f25863ccf08353d64e125..6832e2f4f4409b833cce74d691cb0442ec6a724f 100644
--- a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs
+++ b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs
@@ -40,7 +40,7 @@ use pallet_xcm::XcmPassthrough;
 use parachains_common::{xcm_config::AssetFeeAsExistentialDepositMultiplier, TREASURY_PALLET_ID};
 use polkadot_parachain_primitives::primitives::Sibling;
 use polkadot_runtime_common::{impls::ToAuthor, xcm_sender::ExponentialPrice};
-use sp_runtime::traits::{AccountIdConversion, ConvertInto};
+use sp_runtime::traits::{AccountIdConversion, ConvertInto, Identity, TryConvertInto};
 use xcm::latest::prelude::*;
 use xcm_builder::{
 	AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom,
@@ -58,9 +58,16 @@ parameter_types! {
 	pub const RelayLocation: Location = Location::parent();
 	// Local native currency which is stored in `pallet_balances``
 	pub const PenpalNativeCurrency: Location = Location::here();
-	pub const RelayNetwork: Option<NetworkId> = None;
+	// The Penpal runtime is utilized for testing with various environment setups.
+	// This storage item allows us to customize the `NetworkId` where Penpal is deployed.
+	// By default, it is set to `NetworkId::Rococo` and can be changed using `System::set_storage`.
+	pub storage RelayNetworkId: NetworkId = NetworkId::Westend;
+	pub RelayNetwork: Option<NetworkId> = Some(RelayNetworkId::get());
 	pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into();
-	pub UniversalLocation: InteriorLocation = [Parachain(ParachainInfo::parachain_id().into())].into();
+	pub UniversalLocation: InteriorLocation = [
+		GlobalConsensus(RelayNetworkId::get()),
+		Parachain(ParachainInfo::parachain_id().into())
+	].into();
 	pub TreasuryAccount: AccountId = TREASURY_PALLET_ID.into_account_truncating();
 }
 
@@ -77,7 +84,7 @@ pub type LocationToAccountId = (
 );
 
 /// Means for transacting assets on this chain.
-pub type CurrencyTransactor = FungibleAdapter<
+pub type FungibleTransactor = FungibleAdapter<
 	// Use this currency:
 	Balances,
 	// Use this currency when it is a fungible asset matching the given location or name:
@@ -124,7 +131,11 @@ pub type FungiblesTransactor = FungiblesAdapter<
 	CheckingAccount,
 >;
 
-pub type ForeignAssetsConvertedConcreteId = assets_common::LocationConvertedConcreteId<
+// Using the latest `Location`, we don't need to worry about migrations for Penpal.
+pub type ForeignAssetsAssetId = Location;
+pub type ForeignAssetsConvertedConcreteId = xcm_builder::MatchedConvertedConcreteId<
+	Location,
+	Balance,
 	EverythingBut<(
 		// Here we rely on fact that something like this works:
 		// assert!(Location::new(1,
@@ -132,7 +143,8 @@ pub type ForeignAssetsConvertedConcreteId = assets_common::LocationConvertedConc
 		// assert!([Parachain(100)].into().starts_with(&Here));
 		StartsWith<assets_common::matching::LocalLocationPattern>,
 	)>,
-	Balance,
+	Identity,
+	TryConvertInto,
 >;
 
 /// Means for transacting foreign assets from different global consensus.
@@ -152,7 +164,7 @@ pub type ForeignFungiblesTransactor = FungiblesAdapter<
 >;
 
 /// Means for transacting assets on this chain.
-pub type AssetTransactors = (CurrencyTransactor, ForeignFungiblesTransactor, FungiblesTransactor);
+pub type AssetTransactors = (FungibleTransactor, ForeignFungiblesTransactor, FungiblesTransactor);
 
 /// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance,
 /// ready for dispatching a transaction with Xcm's `Transact`. There is an `OriginKind` which can
@@ -409,8 +421,8 @@ impl cumulus_pallet_xcm::Config for Runtime {
 /// Simple conversion of `u32` into an `AssetId` for use in benchmarking.
 pub struct XcmBenchmarkHelper;
 #[cfg(feature = "runtime-benchmarks")]
-impl pallet_assets::BenchmarkHelper<xcm::v3::Location> for XcmBenchmarkHelper {
-	fn create_asset_id_parameter(id: u32) -> xcm::v3::Location {
-		xcm::v3::Location::new(1, [xcm::v3::Junction::Parachain(id)])
+impl pallet_assets::BenchmarkHelper<ForeignAssetsAssetId> for XcmBenchmarkHelper {
+	fn create_asset_id_parameter(id: u32) -> ForeignAssetsAssetId {
+		Location::new(1, [Parachain(id)])
 	}
 }
diff --git a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs
index 154c2c4600043cc0ceb1b0cd61a84949dcdd9616..df335368be1ca29d40ed4c9fd32b6e385b33b66e 100644
--- a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs
+++ b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs
@@ -346,7 +346,7 @@ pub type LocationToAccountId = (
 );
 
 /// Means for transacting assets on this chain.
-pub type CurrencyTransactor = FungibleAdapter<
+pub type FungibleTransactor = FungibleAdapter<
 	// Use this currency:
 	Balances,
 	// Use this currency when it is a fungible asset matching the given location or name:
@@ -385,7 +385,7 @@ pub type FungiblesTransactor = FungiblesAdapter<
 	CheckingAccount,
 >;
 /// Means for transacting assets on this chain.
-pub type AssetTransactors = (CurrencyTransactor, FungiblesTransactor);
+pub type AssetTransactors = (FungibleTransactor, FungiblesTransactor);
 
 /// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance,
 /// ready for dispatching a transaction with Xcm's `Transact`. There is an `OriginKind` which can
diff --git a/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile b/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile
index 4bfb73acda05880ef49570594d0769d1e5e4b147..938f5cc45a1141a344c223a16a026c5b86494096 100644
--- a/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile
+++ b/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile
@@ -1,7 +1,7 @@
 # this image is built on top of existing Zombienet image
 ARG ZOMBIENET_IMAGE
 # this image uses substrate-relay image built elsewhere
-ARG SUBSTRATE_RELAY_IMAGE=docker.io/paritytech/substrate-relay:v2023-11-07-rococo-westend-initial-relayer
+ARG SUBSTRATE_RELAY_IMAGE=docker.io/paritytech/substrate-relay:v1.2.1
 
 # metadata
 ARG VCS_REF
diff --git a/polkadot/node/core/runtime-api/src/cache.rs b/polkadot/node/core/runtime-api/src/cache.rs
index acdb256ab36ca7392c0947237df6d7feb449247d..7cd1f7ce7281719a0875394871858c65b8f6045a 100644
--- a/polkadot/node/core/runtime-api/src/cache.rs
+++ b/polkadot/node/core/runtime-api/src/cache.rs
@@ -48,6 +48,7 @@ pub(crate) struct RequestResultCache {
 	validation_code: LruMap<(Hash, ParaId, OccupiedCoreAssumption), Option<ValidationCode>>,
 	validation_code_by_hash: LruMap<ValidationCodeHash, Option<ValidationCode>>,
 	candidate_pending_availability: LruMap<(Hash, ParaId), Option<CommittedCandidateReceipt>>,
+	candidates_pending_availability: LruMap<(Hash, ParaId), Vec<CommittedCandidateReceipt>>,
 	candidate_events: LruMap<Hash, Vec<CandidateEvent>>,
 	session_executor_params: LruMap<SessionIndex, Option<ExecutorParams>>,
 	session_info: LruMap<SessionIndex, SessionInfo>,
@@ -86,6 +87,7 @@ impl Default for RequestResultCache {
 			validation_code: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
 			validation_code_by_hash: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
 			candidate_pending_availability: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
+			candidates_pending_availability: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
 			candidate_events: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
 			session_executor_params: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
 			session_info: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
@@ -261,6 +263,21 @@ impl RequestResultCache {
 		self.candidate_pending_availability.insert(key, value);
 	}
 
+	pub(crate) fn candidates_pending_availability(
+		&mut self,
+		key: (Hash, ParaId),
+	) -> Option<&Vec<CommittedCandidateReceipt>> {
+		self.candidates_pending_availability.get(&key).map(|v| &*v)
+	}
+
+	pub(crate) fn cache_candidates_pending_availability(
+		&mut self,
+		key: (Hash, ParaId),
+		value: Vec<CommittedCandidateReceipt>,
+	) {
+		self.candidates_pending_availability.insert(key, value);
+	}
+
 	pub(crate) fn candidate_events(&mut self, relay_parent: &Hash) -> Option<&Vec<CandidateEvent>> {
 		self.candidate_events.get(relay_parent).map(|v| &*v)
 	}
@@ -591,4 +608,5 @@ pub(crate) enum RequestResult {
 	AsyncBackingParams(Hash, async_backing::AsyncBackingParams),
 	NodeFeatures(SessionIndex, NodeFeatures),
 	ClaimQueue(Hash, BTreeMap<CoreIndex, VecDeque<ParaId>>),
+	CandidatesPendingAvailability(Hash, ParaId, Vec<CommittedCandidateReceipt>),
 }
diff --git a/polkadot/node/core/runtime-api/src/lib.rs b/polkadot/node/core/runtime-api/src/lib.rs
index 2b7f6fc2d609f4dfcdf4e94fb63bf499376f6d07..b7995aeeee761e489262504f017e1c79b677b875 100644
--- a/polkadot/node/core/runtime-api/src/lib.rs
+++ b/polkadot/node/core/runtime-api/src/lib.rs
@@ -133,6 +133,9 @@ where
 			CandidatePendingAvailability(relay_parent, para_id, candidate) => self
 				.requests_cache
 				.cache_candidate_pending_availability((relay_parent, para_id), candidate),
+			CandidatesPendingAvailability(relay_parent, para_id, candidates) => self
+				.requests_cache
+				.cache_candidates_pending_availability((relay_parent, para_id), candidates),
 			CandidateEvents(relay_parent, events) =>
 				self.requests_cache.cache_candidate_events(relay_parent, events),
 			SessionExecutorParams(_relay_parent, session_index, index) =>
@@ -252,6 +255,9 @@ where
 			Request::CandidatePendingAvailability(para, sender) =>
 				query!(candidate_pending_availability(para), sender)
 					.map(|sender| Request::CandidatePendingAvailability(para, sender)),
+			Request::CandidatesPendingAvailability(para, sender) =>
+				query!(candidates_pending_availability(para), sender)
+					.map(|sender| Request::CandidatesPendingAvailability(para, sender)),
 			Request::CandidateEvents(sender) =>
 				query!(candidate_events(), sender).map(|sender| Request::CandidateEvents(sender)),
 			Request::SessionExecutorParams(session_index, sender) => {
@@ -531,6 +537,12 @@ where
 			ver = 1,
 			sender
 		),
+		Request::CandidatesPendingAvailability(para, sender) => query!(
+			CandidatesPendingAvailability,
+			candidates_pending_availability(para),
+			ver = Request::CANDIDATES_PENDING_AVAILABILITY_RUNTIME_REQUIREMENT,
+			sender
+		),
 		Request::CandidateEvents(sender) => {
 			query!(CandidateEvents, candidate_events(), ver = 1, sender)
 		},
diff --git a/polkadot/node/core/runtime-api/src/tests.rs b/polkadot/node/core/runtime-api/src/tests.rs
index 73c661c40762effc2efbbfa66f7f46a47bbe2d16..0113de83c89ec56403fec0e42207bc5d4e0c552e 100644
--- a/polkadot/node/core/runtime-api/src/tests.rs
+++ b/polkadot/node/core/runtime-api/src/tests.rs
@@ -47,6 +47,7 @@ struct MockSubsystemClient {
 	validation_outputs_results: HashMap<ParaId, bool>,
 	session_index_for_child: SessionIndex,
 	candidate_pending_availability: HashMap<ParaId, CommittedCandidateReceipt>,
+	candidates_pending_availability: HashMap<ParaId, Vec<CommittedCandidateReceipt>>,
 	dmq_contents: HashMap<ParaId, Vec<InboundDownwardMessage>>,
 	hrmp_channels: HashMap<ParaId, BTreeMap<ParaId, Vec<InboundHrmpMessage>>>,
 	validation_code_by_hash: HashMap<ValidationCodeHash, ValidationCode>,
@@ -140,6 +141,14 @@ impl RuntimeApiSubsystemClient for MockSubsystemClient {
 		Ok(self.candidate_pending_availability.get(&para_id).cloned())
 	}
 
+	async fn candidates_pending_availability(
+		&self,
+		_: Hash,
+		para_id: ParaId,
+	) -> Result<Vec<CommittedCandidateReceipt<Hash>>, ApiError> {
+		Ok(self.candidates_pending_availability.get(&para_id).cloned().unwrap_or_default())
+	}
+
 	async fn candidate_events(&self, _: Hash) -> Result<Vec<CandidateEvent<Hash>>, ApiError> {
 		Ok(self.candidate_events.clone())
 	}
diff --git a/polkadot/node/service/chain-specs/paseo.json b/polkadot/node/service/chain-specs/paseo.json
index 2e659716766ede8cf4ec2b183013b5e925dff998..19eefd328994ed67c269fafc06acefaf28c751bf 100644
--- a/polkadot/node/service/chain-specs/paseo.json
+++ b/polkadot/node/service/chain-specs/paseo.json
@@ -16,7 +16,9 @@
     "/dns/boot.gatotech.network/tcp/33400/p2p/12D3KooWEvz5Ygv3MhCUNTVQbUTVhzhvf4KKcNoe5M5YbVLPBeeW",
     "/dns/boot.gatotech.network/tcp/35400/wss/p2p/12D3KooWEvz5Ygv3MhCUNTVQbUTVhzhvf4KKcNoe5M5YbVLPBeeW",
     "/dns/paseo-bootnode.turboflakes.io/tcp/30630/p2p/12D3KooWMjCN2CrnN71hAdehn6M2iYKeGdGbZ1A3SKhf4hxrgG9e",
-    "/dns/paseo-bootnode.turboflakes.io/tcp/30730/wss/p2p/12D3KooWMjCN2CrnN71hAdehn6M2iYKeGdGbZ1A3SKhf4hxrgG9e"
+    "/dns/paseo-bootnode.turboflakes.io/tcp/30730/wss/p2p/12D3KooWMjCN2CrnN71hAdehn6M2iYKeGdGbZ1A3SKhf4hxrgG9e",
+    "/dns/paseo-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWBLLFKDGBxCwq3QmU3YwWKXUx953WwprRshJQicYu4Cfr",
+    "/dns/paseo-boot-ng.dwellir.com/tcp/30354/p2p/12D3KooWBLLFKDGBxCwq3QmU3YwWKXUx953WwprRshJQicYu4Cfr"
   ],
   "telemetryEndpoints": null,
   "protocolId": "pas",
@@ -419,4 +421,4 @@
       "childrenDefault": {}
     }
   }
-}
\ No newline at end of file
+}
diff --git a/polkadot/node/subsystem-types/src/messages.rs b/polkadot/node/subsystem-types/src/messages.rs
index 2ca6728af012166649ccfee136b098cff9b9fe4a..e75d80395c4ba0371b58ce1383db2a1f364eaad8 100644
--- a/polkadot/node/subsystem-types/src/messages.rs
+++ b/polkadot/node/subsystem-types/src/messages.rs
@@ -670,7 +670,7 @@ pub enum RuntimeApiRequest {
 	/// Get validation code by its hash, either past, current or future code can be returned, as
 	/// long as state is still available.
 	ValidationCodeByHash(ValidationCodeHash, RuntimeApiSender<Option<ValidationCode>>),
-	/// Get a the candidate pending availability for a particular parachain by parachain / core
+	/// Get the candidate pending availability for a particular parachain by parachain / core
 	/// index
 	CandidatePendingAvailability(ParaId, RuntimeApiSender<Option<CommittedCandidateReceipt>>),
 	/// Get all events concerning candidates (backing, inclusion, time-out) in the parent of
@@ -739,6 +739,9 @@ pub enum RuntimeApiRequest {
 	/// Fetch the `ClaimQueue` from scheduler pallet
 	/// `V11`
 	ClaimQueue(RuntimeApiSender<BTreeMap<CoreIndex, VecDeque<ParaId>>>),
+	/// Get the candidates pending availability for a particular parachain
+	/// `V11`
+	CandidatesPendingAvailability(ParaId, RuntimeApiSender<Vec<CommittedCandidateReceipt>>),
 }
 
 impl RuntimeApiRequest {
@@ -776,6 +779,9 @@ impl RuntimeApiRequest {
 
 	/// `ClaimQueue`
 	pub const CLAIM_QUEUE_RUNTIME_REQUIREMENT: u32 = 11;
+
+	/// `candidates_pending_availability`
+	pub const CANDIDATES_PENDING_AVAILABILITY_RUNTIME_REQUIREMENT: u32 = 11;
 }
 
 /// A message to the Runtime API subsystem.
diff --git a/polkadot/node/subsystem-types/src/runtime_client.rs b/polkadot/node/subsystem-types/src/runtime_client.rs
index 664d10ed1af5fdb77c3105ab995ab5ff89df2220..e5e1e4d24ef96e8fa7689a41cd7897832b5aa271 100644
--- a/polkadot/node/subsystem-types/src/runtime_client.rs
+++ b/polkadot/node/subsystem-types/src/runtime_client.rs
@@ -333,6 +333,14 @@ pub trait RuntimeApiSubsystemClient {
 	// == v11: Claim queue ==
 	/// Fetch the `ClaimQueue` from scheduler pallet
 	async fn claim_queue(&self, at: Hash) -> Result<BTreeMap<CoreIndex, VecDeque<Id>>, ApiError>;
+
+	// == v11: Elastic scaling support ==
+	/// Get the receipts of all candidates pending availability for a `ParaId`.
+	async fn candidates_pending_availability(
+		&self,
+		at: Hash,
+		para_id: Id,
+	) -> Result<Vec<CommittedCandidateReceipt<Hash>>, ApiError>;
 }
 
 /// Default implementation of [`RuntimeApiSubsystemClient`] using the client.
@@ -428,6 +436,14 @@ where
 		self.client.runtime_api().candidate_pending_availability(at, para_id)
 	}
 
+	async fn candidates_pending_availability(
+		&self,
+		at: Hash,
+		para_id: Id,
+	) -> Result<Vec<CommittedCandidateReceipt<Hash>>, ApiError> {
+		self.client.runtime_api().candidates_pending_availability(at, para_id)
+	}
+
 	async fn candidate_events(&self, at: Hash) -> Result<Vec<CandidateEvent<Hash>>, ApiError> {
 		self.client.runtime_api().candidate_events(at)
 	}
diff --git a/polkadot/node/subsystem-util/src/lib.rs b/polkadot/node/subsystem-util/src/lib.rs
index 83b046f0bf0ac54533958da36dc230e8c8ce2922..b93818070a183e43e71f327bcc1770a71fae1cb6 100644
--- a/polkadot/node/subsystem-util/src/lib.rs
+++ b/polkadot/node/subsystem-util/src/lib.rs
@@ -296,6 +296,7 @@ specialize_requests! {
 	fn request_validation_code(para_id: ParaId, assumption: OccupiedCoreAssumption) -> Option<ValidationCode>; ValidationCode;
 	fn request_validation_code_by_hash(validation_code_hash: ValidationCodeHash) -> Option<ValidationCode>; ValidationCodeByHash;
 	fn request_candidate_pending_availability(para_id: ParaId) -> Option<CommittedCandidateReceipt>; CandidatePendingAvailability;
+	fn request_candidates_pending_availability(para_id: ParaId) -> Vec<CommittedCandidateReceipt>; CandidatesPendingAvailability;
 	fn request_candidate_events() -> Vec<CandidateEvent>; CandidateEvents;
 	fn request_session_info(index: SessionIndex) -> Option<SessionInfo>; SessionInfo;
 	fn request_validation_code_hash(para_id: ParaId, assumption: OccupiedCoreAssumption)
diff --git a/polkadot/primitives/src/runtime_api.rs b/polkadot/primitives/src/runtime_api.rs
index f611936f2701d367283503e761176e9b61e72cbe..7bd92be35c159db35b3db7b9dd91afcc73eebd81 100644
--- a/polkadot/primitives/src/runtime_api.rs
+++ b/polkadot/primitives/src/runtime_api.rs
@@ -288,5 +288,10 @@ sp_api::decl_runtime_apis! {
 		/// Claim queue
 		#[api_version(11)]
 		fn claim_queue() -> BTreeMap<CoreIndex, VecDeque<ppp::Id>>;
+
+		/***** Added in v11 *****/
+		/// Elastic scaling support
+		#[api_version(11)]
+		fn candidates_pending_availability(para_id: ppp::Id) -> Vec<CommittedCandidateReceipt<Hash>>;
 	}
 }
diff --git a/polkadot/roadmap/implementers-guide/src/runtime-api/candidate-pending-availability.md b/polkadot/roadmap/implementers-guide/src/runtime-api/candidate-pending-availability.md
index e118757d83cede12468be2d8d444958ca847eadf..b9f03748d89bafaad448b7515f08eb73a44b0996 100644
--- a/polkadot/roadmap/implementers-guide/src/runtime-api/candidate-pending-availability.md
+++ b/polkadot/roadmap/implementers-guide/src/runtime-api/candidate-pending-availability.md
@@ -4,5 +4,8 @@ Get the receipt of a candidate pending availability. This returns `Some` for any
 `availability_cores` and `None` otherwise.
 
 ```rust
+// Deprectated.
 fn candidate_pending_availability(at: Block, ParaId) -> Option<CommittedCandidateReceipt>;
+// Use this one
+fn candidates_pending_availability(at: Block, ParaId) -> Vec<CommittedCandidateReceipt>;
 ```
diff --git a/polkadot/roadmap/implementers-guide/src/runtime/inclusion.md b/polkadot/roadmap/implementers-guide/src/runtime/inclusion.md
index fd74f33253b72bd629313b5763e2cbbed75a6314..0700a781d426324c2c35e72628caa65b577ef979 100644
--- a/polkadot/roadmap/implementers-guide/src/runtime/inclusion.md
+++ b/polkadot/roadmap/implementers-guide/src/runtime/inclusion.md
@@ -154,6 +154,8 @@ All failed checks should lead to an unrecoverable error making the block invalid
   where the changes to the state are expected to be discarded directly after.
 * `candidate_pending_availability(ParaId) -> Option<CommittedCandidateReceipt>`: returns the `CommittedCandidateReceipt`
   pending availability for the para provided, if any.
+* `candidates_pending_availability(ParaId) -> Vec<CommittedCandidateReceipt>`: returns the `CommittedCandidateReceipt`s
+  pending availability for the para provided, if any.
 * `pending_availability(ParaId) -> Option<CandidatePendingAvailability>`: returns the metadata around the candidate
   pending availability for the para, if any.
 * `free_disputed(disputed: Vec<CandidateHash>) -> Vec<CoreIndex>`: Sweeps through all paras pending availability. If
@@ -164,10 +166,10 @@ These functions were formerly part of the UMP pallet:
 
 * `check_upward_messages(P: ParaId, Vec<UpwardMessage>)`:
     1. Checks that the parachain is not currently offboarding and error otherwise.
-    1. Checks that there are at most `config.max_upward_message_num_per_candidate` messages to be enqueued.
-    1. Checks that no message exceeds `config.max_upward_message_size`.
-    1. Checks that the total resulting queue size would not exceed `co`.
-    1. Verify that queuing up the messages could not result in exceeding the queue's footprint according to the config
+    2. Checks that there are at most `config.max_upward_message_num_per_candidate` messages to be enqueued.
+    3. Checks that no message exceeds `config.max_upward_message_size`.
+    4. Checks that the total resulting queue size would not exceed `co`.
+    5. Verify that queuing up the messages could not result in exceeding the queue's footprint according to the config
     items `config.max_upward_queue_count` and `config.max_upward_queue_size`. The queue's current footprint is provided
     in `well_known_keys` in order to facilitate oraclisation on to the para.
 
diff --git a/polkadot/runtime/parachains/src/hrmp.rs b/polkadot/runtime/parachains/src/hrmp.rs
index 05a540aef828b95b602439c89d6e44ad95c3174a..65652b38577b361353aca1920255e9788c838028 100644
--- a/polkadot/runtime/parachains/src/hrmp.rs
+++ b/polkadot/runtime/parachains/src/hrmp.rs
@@ -65,6 +65,7 @@ pub trait WeightInfo {
 	fn force_open_hrmp_channel(c: u32) -> Weight;
 	fn establish_system_channel() -> Weight;
 	fn poke_channel_deposits() -> Weight;
+	fn establish_channel_with_system() -> Weight;
 }
 
 /// A weight info that is only suitable for testing.
@@ -104,6 +105,9 @@ impl WeightInfo for TestWeightInfo {
 	fn poke_channel_deposits() -> Weight {
 		Weight::MAX
 	}
+	fn establish_channel_with_system() -> Weight {
+		Weight::MAX
+	}
 }
 
 /// A description of a request to open an HRMP channel.
@@ -270,6 +274,10 @@ pub mod pallet {
 		/// implementation should be the same as `Balance` as used in the `Configuration`.
 		type Currency: ReservableCurrency<Self::AccountId>;
 
+		/// The default channel size and capacity to use when opening a channel to a system
+		/// parachain.
+		type DefaultChannelSizeAndCapacityWithSystem: Get<(u32, u32)>;
+
 		/// Something that provides the weight of this pallet.
 		type WeightInfo: WeightInfo;
 	}
@@ -297,7 +305,7 @@ pub mod pallet {
 			proposed_max_capacity: u32,
 			proposed_max_message_size: u32,
 		},
-		/// An HRMP channel was opened between two system chains.
+		/// An HRMP channel was opened with a system chain.
 		HrmpSystemChannelOpened {
 			sender: ParaId,
 			recipient: ParaId,
@@ -836,6 +844,50 @@ pub mod pallet {
 
 			Ok(())
 		}
+
+		/// Establish a bidirectional HRMP channel between a parachain and a system chain.
+		///
+		/// Arguments:
+		///
+		/// - `target_system_chain`: A system chain, `ParaId`.
+		///
+		/// The origin needs to be the parachain origin.
+		#[pallet::call_index(10)]
+		#[pallet::weight(<T as Config>::WeightInfo::establish_channel_with_system())]
+		pub fn establish_channel_with_system(
+			origin: OriginFor<T>,
+			target_system_chain: ParaId,
+		) -> DispatchResultWithPostInfo {
+			let sender = ensure_parachain(<T as Config>::RuntimeOrigin::from(origin))?;
+
+			ensure!(target_system_chain.is_system(), Error::<T>::ChannelCreationNotAuthorized);
+
+			let (max_message_size, max_capacity) =
+				T::DefaultChannelSizeAndCapacityWithSystem::get();
+
+			// create bidirectional channel
+			Self::init_open_channel(sender, target_system_chain, max_capacity, max_message_size)?;
+			Self::accept_open_channel(target_system_chain, sender)?;
+
+			Self::init_open_channel(target_system_chain, sender, max_capacity, max_message_size)?;
+			Self::accept_open_channel(sender, target_system_chain)?;
+
+			Self::deposit_event(Event::HrmpSystemChannelOpened {
+				sender,
+				recipient: target_system_chain,
+				proposed_max_capacity: max_capacity,
+				proposed_max_message_size: max_message_size,
+			});
+
+			Self::deposit_event(Event::HrmpSystemChannelOpened {
+				sender: target_system_chain,
+				recipient: sender,
+				proposed_max_capacity: max_capacity,
+				proposed_max_message_size: max_message_size,
+			});
+
+			Ok(Pays::No.into())
+		}
 	}
 }
 
diff --git a/polkadot/runtime/parachains/src/hrmp/benchmarking.rs b/polkadot/runtime/parachains/src/hrmp/benchmarking.rs
index 13f4cdfe3eeaf767f6c04bab6a627610db16939d..8bf444e647e2475cd9d24193c6bdb8d268c5228a 100644
--- a/polkadot/runtime/parachains/src/hrmp/benchmarking.rs
+++ b/polkadot/runtime/parachains/src/hrmp/benchmarking.rs
@@ -50,6 +50,13 @@ fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
 	assert_eq!(event, &system_event);
 }
 
+fn assert_has_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
+	let events = frame_system::Pallet::<T>::events();
+	let system_event: <T as frame_system::Config>::RuntimeEvent = generic_event.into();
+
+	assert!(events.iter().any(|record| record.event == system_event));
+}
+
 /// Enumerates the phase in the setup process of a channel between two parachains.
 enum ParachainSetupStep {
 	/// A channel open has been requested
@@ -517,6 +524,43 @@ mod benchmarks {
 		);
 	}
 
+	#[benchmark]
+	fn establish_channel_with_system() {
+		let sender_id = 1u32;
+		let recipient_id: ParaId = 2u32.into();
+
+		let sender_origin: crate::Origin = sender_id.into();
+
+		// make sure para is registered, and has zero balance.
+		register_parachain_with_balance::<T>(sender_id.into(), Zero::zero());
+		register_parachain_with_balance::<T>(recipient_id, Zero::zero());
+
+		#[extrinsic_call]
+		_(sender_origin, recipient_id);
+
+		let (max_message_size, max_capacity) = T::DefaultChannelSizeAndCapacityWithSystem::get();
+
+		assert_has_event::<T>(
+			Event::<T>::HrmpSystemChannelOpened {
+				sender: sender_id.into(),
+				recipient: recipient_id,
+				proposed_max_capacity: max_capacity,
+				proposed_max_message_size: max_message_size,
+			}
+			.into(),
+		);
+
+		assert_has_event::<T>(
+			Event::<T>::HrmpSystemChannelOpened {
+				sender: recipient_id,
+				recipient: sender_id.into(),
+				proposed_max_capacity: max_capacity,
+				proposed_max_message_size: max_message_size,
+			}
+			.into(),
+		);
+	}
+
 	impl_benchmark_test_suite!(
 		Hrmp,
 		crate::mock::new_test_ext(crate::hrmp::tests::GenesisConfigBuilder::default().build()),
diff --git a/polkadot/runtime/parachains/src/hrmp/tests.rs b/polkadot/runtime/parachains/src/hrmp/tests.rs
index 8d43b866bc19767ad3308c4f9730c1ad894d4c0c..2f767ab7e1b19d311f53d194638bb23066c37d31 100644
--- a/polkadot/runtime/parachains/src/hrmp/tests.rs
+++ b/polkadot/runtime/parachains/src/hrmp/tests.rs
@@ -27,7 +27,7 @@ use crate::{
 	},
 	shared,
 };
-use frame_support::{assert_noop, assert_ok};
+use frame_support::{assert_noop, assert_ok, error::BadOrigin};
 use primitives::BlockNumber;
 use std::collections::BTreeMap;
 
@@ -935,3 +935,72 @@ fn watermark_maxed_out_at_relay_parent() {
 		Hrmp::assert_storage_consistency_exhaustive();
 	});
 }
+
+#[test]
+fn establish_channel_with_system_works() {
+	let para_a = 2000.into();
+	let para_a_origin: crate::Origin = 2000.into();
+	let para_b = 3.into();
+
+	new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
+		// We need both A & B to be registered and live parachains.
+		register_parachain(para_a);
+		register_parachain(para_b);
+
+		run_to_block(5, Some(vec![4, 5]));
+		Hrmp::establish_channel_with_system(para_a_origin.into(), para_b).unwrap();
+		Hrmp::assert_storage_consistency_exhaustive();
+		assert!(System::events().iter().any(|record| record.event ==
+			MockEvent::Hrmp(Event::HrmpSystemChannelOpened {
+				sender: para_a,
+				recipient: para_b,
+				proposed_max_capacity: 1,
+				proposed_max_message_size: 4
+			})));
+
+		assert!(System::events().iter().any(|record| record.event ==
+			MockEvent::Hrmp(Event::HrmpSystemChannelOpened {
+				sender: para_b,
+				recipient: para_a,
+				proposed_max_capacity: 1,
+				proposed_max_message_size: 4
+			})));
+
+		// Advance to a block 6, but without session change. That means that the channel has
+		// not been created yet.
+		run_to_block(6, None);
+		assert!(!channel_exists(para_a, para_b));
+		assert!(!channel_exists(para_b, para_a));
+		Hrmp::assert_storage_consistency_exhaustive();
+
+		// Now let the session change happen and thus open the channel.
+		run_to_block(8, Some(vec![8]));
+		assert!(channel_exists(para_a, para_b));
+		assert!(channel_exists(para_b, para_a));
+		Hrmp::assert_storage_consistency_exhaustive();
+	});
+}
+
+#[test]
+fn establish_channel_with_system_with_invalid_args() {
+	let para_a = 2001.into();
+	let para_a_origin: crate::Origin = 2000.into();
+	let para_b = 2003.into();
+
+	new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
+		// We need both A & B to be registered and live parachains.
+		register_parachain(para_a);
+		register_parachain(para_b);
+
+		run_to_block(5, Some(vec![4, 5]));
+		assert_noop!(
+			Hrmp::establish_channel_with_system(RuntimeOrigin::signed(1), para_b),
+			BadOrigin
+		);
+		assert_noop!(
+			Hrmp::establish_channel_with_system(para_a_origin.into(), para_b),
+			Error::<Test>::ChannelCreationNotAuthorized
+		);
+		Hrmp::assert_storage_consistency_exhaustive();
+	});
+}
diff --git a/polkadot/runtime/parachains/src/inclusion/mod.rs b/polkadot/runtime/parachains/src/inclusion/mod.rs
index 9d60bbb23b6fae7f34b5b3152d01afe824c5936f..903d01aa5c9c9ff5ec9b2d5dd15ca2c2771fde00 100644
--- a/polkadot/runtime/parachains/src/inclusion/mod.rs
+++ b/polkadot/runtime/parachains/src/inclusion/mod.rs
@@ -1104,6 +1104,24 @@ impl<T: Config> Pallet<T> {
 		})
 	}
 
+	/// Returns all the `CommittedCandidateReceipt` pending availability for the para provided, if
+	/// any.
+	pub(crate) fn candidates_pending_availability(
+		para: ParaId,
+	) -> Vec<CommittedCandidateReceipt<T::Hash>> {
+		<PendingAvailability<T>>::get(&para)
+			.map(|candidates| {
+				candidates
+					.into_iter()
+					.map(|candidate| CommittedCandidateReceipt {
+						descriptor: candidate.descriptor.clone(),
+						commitments: candidate.commitments.clone(),
+					})
+					.collect()
+			})
+			.unwrap_or_default()
+	}
+
 	/// Returns the metadata around the first candidate pending availability for the
 	/// para provided, if any.
 	pub(crate) fn pending_availability(
diff --git a/polkadot/runtime/parachains/src/mock.rs b/polkadot/runtime/parachains/src/mock.rs
index 461b9f4b431acea91614b295d47e3d4c273adccf..c91f5be127cd4be06c5ca7f255804328bab7def7 100644
--- a/polkadot/runtime/parachains/src/mock.rs
+++ b/polkadot/runtime/parachains/src/mock.rs
@@ -248,6 +248,7 @@ impl crate::dmp::Config for Test {}
 
 parameter_types! {
 	pub const FirstMessageFactorPercent: u64 = 100;
+	pub const DefaultChannelSizeAndCapacityWithSystem: (u32, u32) = (4, 1);
 }
 
 impl crate::hrmp::Config for Test {
@@ -255,6 +256,7 @@ impl crate::hrmp::Config for Test {
 	type RuntimeEvent = RuntimeEvent;
 	type ChannelManager = frame_system::EnsureRoot<u64>;
 	type Currency = pallet_balances::Pallet<Test>;
+	type DefaultChannelSizeAndCapacityWithSystem = DefaultChannelSizeAndCapacityWithSystem;
 	type WeightInfo = crate::hrmp::TestWeightInfo;
 }
 
diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/v10.rs b/polkadot/runtime/parachains/src/runtime_api_impl/v10.rs
index 39d4f520994a9993e64f448b456086d4aca99a83..3dca38050a0ac8c43bbb44cdd605572fd6760a82 100644
--- a/polkadot/runtime/parachains/src/runtime_api_impl/v10.rs
+++ b/polkadot/runtime/parachains/src/runtime_api_impl/v10.rs
@@ -301,6 +301,10 @@ pub fn validation_code<T: initializer::Config>(
 }
 
 /// Implementation for the `candidate_pending_availability` function of the runtime API.
+#[deprecated(
+	note = "`candidate_pending_availability` will be removed. Use `candidates_pending_availability` to query 
+	all candidates pending availability"
+)]
 pub fn candidate_pending_availability<T: initializer::Config>(
 	para_id: ParaId,
 ) -> Option<CommittedCandidateReceipt<T::Hash>> {
diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs b/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs
index 9ea29a2d374007fa54337fcc8d0fd2f8092553be..32bbdca84a3cce4946ce39edb4e3a3f8e5211188 100644
--- a/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs
+++ b/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs
@@ -16,8 +16,8 @@
 
 //! Put implementations of functions from staging APIs here.
 
-use crate::scheduler;
-use primitives::{CoreIndex, Id as ParaId};
+use crate::{inclusion, initializer, scheduler};
+use primitives::{CommittedCandidateReceipt, CoreIndex, Id as ParaId};
 use sp_runtime::traits::One;
 use sp_std::{
 	collections::{btree_map::BTreeMap, vec_deque::VecDeque},
@@ -41,3 +41,11 @@ pub fn claim_queue<T: scheduler::Config>() -> BTreeMap<CoreIndex, VecDeque<ParaI
 		})
 		.collect()
 }
+
+/// Returns all the candidates that are pending availability for a given `ParaId`.
+/// Deprecates `candidate_pending_availability` in favor of supporting elastic scaling.
+pub fn candidates_pending_availability<T: initializer::Config>(
+	para_id: ParaId,
+) -> Vec<CommittedCandidateReceipt<T::Hash>> {
+	<inclusion::Pallet<T>>::candidates_pending_availability(para_id)
+}
diff --git a/polkadot/runtime/rococo/Cargo.toml b/polkadot/runtime/rococo/Cargo.toml
index 20a914fb8085fcc917b83fbf5f879e603609904b..bbe19310f970ade37e25f5c1e2c983758c617412 100644
--- a/polkadot/runtime/rococo/Cargo.toml
+++ b/polkadot/runtime/rococo/Cargo.toml
@@ -70,6 +70,7 @@ pallet-mmr = { path = "../../../substrate/frame/merkle-mountain-range", default-
 pallet-multisig = { path = "../../../substrate/frame/multisig", default-features = false }
 pallet-nis = { path = "../../../substrate/frame/nis", default-features = false }
 pallet-offences = { path = "../../../substrate/frame/offences", default-features = false }
+pallet-parameters = { path = "../../../substrate/frame/parameters", default-features = false }
 pallet-preimage = { path = "../../../substrate/frame/preimage", default-features = false }
 pallet-proxy = { path = "../../../substrate/frame/proxy", default-features = false }
 pallet-ranked-collective = { path = "../../../substrate/frame/ranked-collective", default-features = false }
@@ -164,6 +165,7 @@ std = [
 	"pallet-multisig/std",
 	"pallet-nis/std",
 	"pallet-offences/std",
+	"pallet-parameters/std",
 	"pallet-preimage/std",
 	"pallet-proxy/std",
 	"pallet-ranked-collective/std",
@@ -239,6 +241,7 @@ runtime-benchmarks = [
 	"pallet-multisig/runtime-benchmarks",
 	"pallet-nis/runtime-benchmarks",
 	"pallet-offences/runtime-benchmarks",
+	"pallet-parameters/runtime-benchmarks",
 	"pallet-preimage/runtime-benchmarks",
 	"pallet-proxy/runtime-benchmarks",
 	"pallet-ranked-collective/runtime-benchmarks",
@@ -294,6 +297,7 @@ try-runtime = [
 	"pallet-multisig/try-runtime",
 	"pallet-nis/try-runtime",
 	"pallet-offences/try-runtime",
+	"pallet-parameters/try-runtime",
 	"pallet-preimage/try-runtime",
 	"pallet-proxy/try-runtime",
 	"pallet-ranked-collective/try-runtime",
diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs
index 894d7fac2f0a4d0e07f1b97f19b3f6d73147dd23..740a6240d39526cec795ba082848a480c8a7e3a3 100644
--- a/polkadot/runtime/rococo/src/lib.rs
+++ b/polkadot/runtime/rococo/src/lib.rs
@@ -25,6 +25,7 @@ use beefy_primitives::{
 	ecdsa_crypto::{AuthorityId as BeefyId, Signature as BeefySignature},
 	mmr::{BeefyDataProvider, MmrLeafVersion},
 };
+use frame_support::dynamic_params::{dynamic_pallet_params, dynamic_params};
 use pallet_nis::WithMaximumOf;
 use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
 use primitives::{
@@ -73,9 +74,10 @@ use frame_support::{
 	genesis_builder_helper::{build_state, get_preset},
 	parameter_types,
 	traits::{
-		fungible::HoldConsideration, Contains, EitherOf, EitherOfDiverse, EverythingBut,
-		InstanceFilter, KeyOwnerProofSystem, LinearStoragePrice, PrivilegeCmp, ProcessMessage,
-		ProcessMessageError, StorageMapShim, WithdrawReasons,
+		fungible::HoldConsideration, Contains, EitherOf, EitherOfDiverse, EnsureOrigin,
+		EnsureOriginWithArg, EverythingBut, InstanceFilter, KeyOwnerProofSystem,
+		LinearStoragePrice, PrivilegeCmp, ProcessMessage, ProcessMessageError, StorageMapShim,
+		WithdrawReasons,
 	},
 	weights::{ConstantMultiplier, WeightMeter, WeightToFee as _},
 	PalletId,
@@ -234,6 +236,72 @@ impl PrivilegeCmp<OriginCaller> for OriginPrivilegeCmp {
 	}
 }
 
+/// Dynamic params that can be adjusted at runtime.
+#[dynamic_params(RuntimeParameters, pallet_parameters::Parameters::<Runtime>)]
+pub mod dynamic_params {
+	use super::*;
+
+	#[dynamic_pallet_params]
+	#[codec(index = 0)]
+	pub mod nis {
+		use super::*;
+
+		#[codec(index = 0)]
+		pub static Target: Perquintill = Perquintill::zero();
+
+		#[codec(index = 1)]
+		pub static MinBid: Balance = 100 * UNITS;
+	}
+
+	#[dynamic_pallet_params]
+	#[codec(index = 1)]
+	pub mod preimage {
+		use super::*;
+
+		#[codec(index = 0)]
+		pub static BaseDeposit: Balance = deposit(2, 64);
+
+		#[codec(index = 1)]
+		pub static ByteDeposit: Balance = deposit(0, 1);
+	}
+}
+
+#[cfg(feature = "runtime-benchmarks")]
+impl Default for RuntimeParameters {
+	fn default() -> Self {
+		RuntimeParameters::Preimage(dynamic_params::preimage::Parameters::BaseDeposit(
+			dynamic_params::preimage::BaseDeposit,
+			Some(1u32.into()),
+		))
+	}
+}
+
+/// Defines what origin can modify which dynamic parameters.
+pub struct DynamicParameterOrigin;
+impl EnsureOriginWithArg<RuntimeOrigin, RuntimeParametersKey> for DynamicParameterOrigin {
+	type Success = ();
+
+	fn try_origin(
+		origin: RuntimeOrigin,
+		key: &RuntimeParametersKey,
+	) -> Result<Self::Success, RuntimeOrigin> {
+		use crate::{dynamic_params::*, governance::*, RuntimeParametersKey::*};
+
+		match key {
+			Nis(nis::ParametersKey::MinBid(_)) => StakingAdmin::ensure_origin(origin.clone()),
+			Nis(nis::ParametersKey::Target(_)) => GeneralAdmin::ensure_origin(origin.clone()),
+			Preimage(_) => frame_system::ensure_root(origin.clone()),
+		}
+		.map_err(|_| origin)
+	}
+
+	#[cfg(feature = "runtime-benchmarks")]
+	fn try_successful_origin(_key: &RuntimeParametersKey) -> Result<RuntimeOrigin, ()> {
+		// Provide the origin for the parameter returned by `Default`:
+		Ok(RuntimeOrigin::root())
+	}
+}
+
 impl pallet_scheduler::Config for Runtime {
 	type RuntimeOrigin = RuntimeOrigin;
 	type RuntimeEvent = RuntimeEvent;
@@ -250,8 +318,6 @@ impl pallet_scheduler::Config for Runtime {
 }
 
 parameter_types! {
-	pub const PreimageBaseDeposit: Balance = deposit(2, 64);
-	pub const PreimageByteDeposit: Balance = deposit(0, 1);
 	pub const PreimageHoldReason: RuntimeHoldReason = RuntimeHoldReason::Preimage(pallet_preimage::HoldReason::Preimage);
 }
 
@@ -264,7 +330,11 @@ impl pallet_preimage::Config for Runtime {
 		AccountId,
 		Balances,
 		PreimageHoldReason,
-		LinearStoragePrice<PreimageBaseDeposit, PreimageByteDeposit, Balance>,
+		LinearStoragePrice<
+			dynamic_params::preimage::BaseDeposit,
+			dynamic_params::preimage::ByteDeposit,
+			Balance,
+		>,
 	>;
 }
 
@@ -950,11 +1020,16 @@ impl pallet_message_queue::Config for Runtime {
 
 impl parachains_dmp::Config for Runtime {}
 
+parameter_types! {
+	pub const DefaultChannelSizeAndCapacityWithSystem: (u32, u32) = (51200, 500);
+}
+
 impl parachains_hrmp::Config for Runtime {
 	type RuntimeOrigin = RuntimeOrigin;
 	type RuntimeEvent = RuntimeEvent;
 	type ChannelManager = EnsureRoot<AccountId>;
 	type Currency = Balances;
+	type DefaultChannelSizeAndCapacityWithSystem = DefaultChannelSizeAndCapacityWithSystem;
 	type WeightInfo = weights::runtime_parachains_hrmp::WeightInfo<Runtime>;
 }
 
@@ -1123,12 +1198,10 @@ impl pallet_balances::Config<NisCounterpartInstance> for Runtime {
 
 parameter_types! {
 	pub const NisBasePeriod: BlockNumber = 30 * DAYS;
-	pub const MinBid: Balance = 100 * UNITS;
 	pub MinReceipt: Perquintill = Perquintill::from_rational(1u64, 10_000_000u64);
 	pub const IntakePeriod: BlockNumber = 5 * MINUTES;
 	pub MaxIntakeWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 10;
 	pub const ThawThrottle: (Perquintill, BlockNumber) = (Perquintill::from_percent(25), 5);
-	pub storage NisTarget: Perquintill = Perquintill::zero();
 	pub const NisPalletId: PalletId = PalletId(*b"py/nis  ");
 }
 
@@ -1142,13 +1215,13 @@ impl pallet_nis::Config for Runtime {
 	type CounterpartAmount = WithMaximumOf<ConstU128<21_000_000_000_000_000_000u128>>;
 	type Deficit = (); // Mint
 	type IgnoredIssuance = ();
-	type Target = NisTarget;
+	type Target = dynamic_params::nis::Target;
 	type PalletId = NisPalletId;
 	type QueueCount = ConstU32<300>;
 	type MaxQueueLen = ConstU32<1000>;
 	type FifoQueueLen = ConstU32<250>;
 	type BasePeriod = NisBasePeriod;
-	type MinBid = MinBid;
+	type MinBid = dynamic_params::nis::MinBid;
 	type MinReceipt = MinReceipt;
 	type IntakePeriod = IntakePeriod;
 	type MaxIntakeWeight = MaxIntakeWeight;
@@ -1158,6 +1231,13 @@ impl pallet_nis::Config for Runtime {
 	type BenchmarkSetup = ();
 }
 
+impl pallet_parameters::Config for Runtime {
+	type RuntimeEvent = RuntimeEvent;
+	type RuntimeParameters = RuntimeParameters;
+	type AdminOrigin = DynamicParameterOrigin;
+	type WeightInfo = weights::pallet_parameters::WeightInfo<Runtime>;
+}
+
 parameter_types! {
 	pub BeefySetIdSessionEntries: u32 = BondingDuration::get() * SessionsPerEra::get();
 }
@@ -1190,6 +1270,7 @@ impl pallet_mmr::Config for Runtime {
 	type OnNewRoot = pallet_beefy_mmr::DepositBeefyDigest<Runtime>;
 	type WeightInfo = ();
 	type LeafData = pallet_beefy_mmr::Pallet<Runtime>;
+	type BlockHashProvider = pallet_mmr::DefaultBlockHashProvider<Runtime>;
 }
 
 parameter_types! {
@@ -1285,6 +1366,7 @@ construct_runtime! {
 		Timestamp: pallet_timestamp = 2,
 		Indices: pallet_indices = 3,
 		Balances: pallet_balances = 4,
+		Parameters: pallet_parameters = 6,
 		TransactionPayment: pallet_transaction_payment = 33,
 
 		// Consensus support.
@@ -1625,6 +1707,7 @@ mod benches {
 		[pallet_indices, Indices]
 		[pallet_message_queue, MessageQueue]
 		[pallet_multisig, Multisig]
+		[pallet_parameters, Parameters]
 		[pallet_preimage, Preimage]
 		[pallet_proxy, Proxy]
 		[pallet_ranked_collective, FellowshipCollective]
@@ -1790,6 +1873,7 @@ sp_api::impl_runtime_apis! {
 		}
 
 		fn candidate_pending_availability(para_id: ParaId) -> Option<CommittedCandidateReceipt<Hash>> {
+			#[allow(deprecated)]
 			parachains_runtime_api_impl::candidate_pending_availability::<Runtime>(para_id)
 		}
 
@@ -1903,6 +1987,10 @@ sp_api::impl_runtime_apis! {
 		fn claim_queue() -> BTreeMap<CoreIndex, VecDeque<ParaId>> {
 			vstaging_parachains_runtime_api_impl::claim_queue::<Runtime>()
 		}
+
+		fn candidates_pending_availability(para_id: ParaId) -> Vec<CommittedCandidateReceipt<Hash>> {
+			vstaging_parachains_runtime_api_impl::candidates_pending_availability::<Runtime>(para_id)
+		}
 	}
 
 	#[api_version(3)]
diff --git a/polkadot/runtime/rococo/src/weights/mod.rs b/polkadot/runtime/rococo/src/weights/mod.rs
index 7328dca9393693e335b49dc317d6754f9fd6e840..3c6845dfb43e65e85dc33b2e288f35a9a8742e1e 100644
--- a/polkadot/runtime/rococo/src/weights/mod.rs
+++ b/polkadot/runtime/rococo/src/weights/mod.rs
@@ -27,6 +27,7 @@ pub mod pallet_indices;
 pub mod pallet_message_queue;
 pub mod pallet_multisig;
 pub mod pallet_nis;
+pub mod pallet_parameters;
 pub mod pallet_preimage;
 pub mod pallet_proxy;
 pub mod pallet_ranked_collective;
diff --git a/polkadot/runtime/rococo/src/weights/pallet_parameters.rs b/polkadot/runtime/rococo/src/weights/pallet_parameters.rs
new file mode 100644
index 0000000000000000000000000000000000000000..bd2bcf960e9baae96f156b6424852b02a1536b00
--- /dev/null
+++ b/polkadot/runtime/rococo/src/weights/pallet_parameters.rs
@@ -0,0 +1,63 @@
+// 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 <http://www.gnu.org/licenses/>.
+
+//! Autogenerated weights for `pallet_parameters`
+//!
+//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
+//! DATE: 2024-04-05, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! WORST CASE MAP SIZE: `1000000`
+//! HOSTNAME: `runner-anb7yjbi-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
+//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("rococo-dev")`, DB CACHE: 1024
+
+// Executed Command:
+// target/production/polkadot
+// benchmark
+// pallet
+// --steps=50
+// --repeat=20
+// --extrinsic=*
+// --wasm-execution=compiled
+// --heap-pages=4096
+// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json
+// --pallet=pallet_parameters
+// --chain=rococo-dev
+// --header=./polkadot/file_header.txt
+// --output=./polkadot/runtime/rococo/src/weights/
+
+#![cfg_attr(rustfmt, rustfmt_skip)]
+#![allow(unused_parens)]
+#![allow(unused_imports)]
+#![allow(missing_docs)]
+
+use frame_support::{traits::Get, weights::Weight};
+use core::marker::PhantomData;
+
+/// Weight functions for `pallet_parameters`.
+pub struct WeightInfo<T>(PhantomData<T>);
+impl<T: frame_system::Config> pallet_parameters::WeightInfo for WeightInfo<T> {
+	/// Storage: `Parameters::Parameters` (r:1 w:1)
+	/// Proof: `Parameters::Parameters` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`)
+	fn set_parameter() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `4`
+		//  Estimated: `3493`
+		// Minimum execution time: 6_937_000 picoseconds.
+		Weight::from_parts(7_242_000, 0)
+			.saturating_add(Weight::from_parts(0, 3493))
+			.saturating_add(T::DbWeight::get().reads(1))
+			.saturating_add(T::DbWeight::get().writes(1))
+	}
+}
diff --git a/polkadot/runtime/rococo/src/weights/runtime_parachains_hrmp.rs b/polkadot/runtime/rococo/src/weights/runtime_parachains_hrmp.rs
index 417820e6627f6e39c4107df722f4b83f9cca6a4b..97b84155b36a6adb2258a714e65bf5c70bc9aa50 100644
--- a/polkadot/runtime/rococo/src/weights/runtime_parachains_hrmp.rs
+++ b/polkadot/runtime/rococo/src/weights/runtime_parachains_hrmp.rs
@@ -331,4 +331,14 @@ impl<T: frame_system::Config> runtime_parachains::hrmp::WeightInfo for WeightInf
 			.saturating_add(T::DbWeight::get().reads(1))
 			.saturating_add(T::DbWeight::get().writes(1))
 	}
+	fn establish_channel_with_system() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `417`
+		//  Estimated: `6357`
+		// Minimum execution time: 629_674_000 picoseconds.
+		Weight::from_parts(640_174_000, 0)
+			.saturating_add(Weight::from_parts(0, 6357))
+			.saturating_add(T::DbWeight::get().reads(12))
+			.saturating_add(T::DbWeight::get().writes(8))
+	}
 }
diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs
index f01382509501186bbe6efbdad67eea536ce59990..514643c0a20169291cde30fd9dfc259bc936853f 100644
--- a/polkadot/runtime/test-runtime/src/lib.rs
+++ b/polkadot/runtime/test-runtime/src/lib.rs
@@ -22,15 +22,19 @@
 
 use pallet_transaction_payment::FungibleAdapter;
 use parity_scale_codec::Encode;
-use sp_std::{collections::btree_map::BTreeMap, prelude::*};
+use sp_std::{
+	collections::{btree_map::BTreeMap, vec_deque::VecDeque},
+	prelude::*,
+};
 
 use polkadot_runtime_parachains::{
 	assigner_parachains as parachains_assigner_parachains,
 	configuration as parachains_configuration, disputes as parachains_disputes,
-	disputes::slashing as parachains_slashing, dmp as parachains_dmp, hrmp as parachains_hrmp,
-	inclusion as parachains_inclusion, initializer as parachains_initializer,
-	origin as parachains_origin, paras as parachains_paras,
-	paras_inherent as parachains_paras_inherent, runtime_api_impl::v10 as runtime_impl,
+	disputes::slashing as parachains_slashing,
+	dmp as parachains_dmp, hrmp as parachains_hrmp, inclusion as parachains_inclusion,
+	initializer as parachains_initializer, origin as parachains_origin, paras as parachains_paras,
+	paras_inherent as parachains_paras_inherent,
+	runtime_api_impl::{v10 as runtime_impl, vstaging as vstaging_parachains_runtime_api_impl},
 	scheduler as parachains_scheduler, session_info as parachains_session_info,
 	shared as parachains_shared,
 };
@@ -53,9 +57,9 @@ use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo};
 use polkadot_runtime_parachains::reward_points::RewardValidatorsWithEraPoints;
 use primitives::{
 	slashing, AccountId, AccountIndex, Balance, BlockNumber, CandidateEvent, CandidateHash,
-	CommittedCandidateReceipt, CoreState, DisputeState, ExecutorParams, GroupRotationInfo,
-	Hash as HashT, Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, Moment, Nonce,
-	OccupiedCoreAssumption, PersistedValidationData, ScrapedOnChainVotes,
+	CommittedCandidateReceipt, CoreIndex, CoreState, DisputeState, ExecutorParams,
+	GroupRotationInfo, Hash as HashT, Id as ParaId, InboundDownwardMessage, InboundHrmpMessage,
+	Moment, Nonce, OccupiedCoreAssumption, PersistedValidationData, ScrapedOnChainVotes,
 	SessionInfo as SessionInfoData, Signature, ValidationCode, ValidationCodeHash, ValidatorId,
 	ValidatorIndex, PARACHAIN_KEY_TYPE_ID,
 };
@@ -554,6 +558,7 @@ impl parachains_dmp::Config for Runtime {}
 
 parameter_types! {
 	pub const FirstMessageFactorPercent: u64 = 100;
+	pub const DefaultChannelSizeAndCapacityWithSystem: (u32, u32) = (51200, 500);
 }
 
 impl parachains_hrmp::Config for Runtime {
@@ -561,6 +566,7 @@ impl parachains_hrmp::Config for Runtime {
 	type RuntimeEvent = RuntimeEvent;
 	type ChannelManager = frame_system::EnsureRoot<AccountId>;
 	type Currency = Balances;
+	type DefaultChannelSizeAndCapacityWithSystem = DefaultChannelSizeAndCapacityWithSystem;
 	type WeightInfo = parachains_hrmp::TestWeightInfo;
 }
 
@@ -829,7 +835,7 @@ sp_api::impl_runtime_apis! {
 		}
 	}
 
-	#[api_version(10)]
+	#[api_version(11)]
 	impl primitives::runtime_api::ParachainHost<Block> for Runtime {
 		fn validators() -> Vec<ValidatorId> {
 			runtime_impl::validators::<Runtime>()
@@ -877,6 +883,7 @@ sp_api::impl_runtime_apis! {
 		}
 
 		fn candidate_pending_availability(para_id: ParaId) -> Option<CommittedCandidateReceipt<Hash>> {
+			#[allow(deprecated)]
 			runtime_impl::candidate_pending_availability::<Runtime>(para_id)
 		}
 
@@ -981,6 +988,14 @@ sp_api::impl_runtime_apis! {
 		fn node_features() -> primitives::NodeFeatures {
 			runtime_impl::node_features::<Runtime>()
 		}
+
+		fn claim_queue() -> BTreeMap<CoreIndex, VecDeque<ParaId>> {
+			vstaging_parachains_runtime_api_impl::claim_queue::<Runtime>()
+		}
+
+		fn candidates_pending_availability(para_id: ParaId) -> Vec<CommittedCandidateReceipt<Hash>> {
+			vstaging_parachains_runtime_api_impl::candidates_pending_availability::<Runtime>(para_id)
+		}
 	}
 
 	impl beefy_primitives::BeefyApi<Block, BeefyId> for Runtime {
diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs
index 4930610c1d80a3e632232b2f72c81ece97ae1955..a119d78b83ab8a76642d0bc1cf407ec4935ab5dc 100644
--- a/polkadot/runtime/westend/src/lib.rs
+++ b/polkadot/runtime/westend/src/lib.rs
@@ -333,6 +333,7 @@ impl pallet_mmr::Config for Runtime {
 	type OnNewRoot = pallet_beefy_mmr::DepositBeefyDigest<Runtime>;
 	type WeightInfo = ();
 	type LeafData = pallet_beefy_mmr::Pallet<Runtime>;
+	type BlockHashProvider = pallet_mmr::DefaultBlockHashProvider<Runtime>;
 }
 
 /// MMR helper types.
@@ -1153,11 +1154,16 @@ impl pallet_message_queue::Config for Runtime {
 
 impl parachains_dmp::Config for Runtime {}
 
+parameter_types! {
+	pub const DefaultChannelSizeAndCapacityWithSystem: (u32, u32) = (4096, 4);
+}
+
 impl parachains_hrmp::Config for Runtime {
 	type RuntimeOrigin = RuntimeOrigin;
 	type RuntimeEvent = RuntimeEvent;
 	type ChannelManager = EnsureRoot<AccountId>;
 	type Currency = Balances;
+	type DefaultChannelSizeAndCapacityWithSystem = DefaultChannelSizeAndCapacityWithSystem;
 	type WeightInfo = weights::runtime_parachains_hrmp::WeightInfo<Self>;
 }
 
@@ -1848,6 +1854,7 @@ sp_api::impl_runtime_apis! {
 		}
 
 		fn candidate_pending_availability(para_id: ParaId) -> Option<CommittedCandidateReceipt<Hash>> {
+			#[allow(deprecated)]
 			parachains_runtime_api_impl::candidate_pending_availability::<Runtime>(para_id)
 		}
 
@@ -1961,6 +1968,10 @@ sp_api::impl_runtime_apis! {
 		fn claim_queue() -> BTreeMap<CoreIndex, VecDeque<ParaId>> {
 			vstaging_parachains_runtime_api_impl::claim_queue::<Runtime>()
 		}
+
+		fn candidates_pending_availability(para_id: ParaId) -> Vec<CommittedCandidateReceipt<Hash>> {
+			vstaging_parachains_runtime_api_impl::candidates_pending_availability::<Runtime>(para_id)
+		}
 	}
 
 	impl beefy_primitives::BeefyApi<Block, BeefyId> for Runtime {
diff --git a/polkadot/runtime/westend/src/weights/runtime_parachains_hrmp.rs b/polkadot/runtime/westend/src/weights/runtime_parachains_hrmp.rs
index 9beb15303d871cd4ae0b6b0ce3148291d745395b..3d2ab827b8fd125b74a2c892803038557998b8be 100644
--- a/polkadot/runtime/westend/src/weights/runtime_parachains_hrmp.rs
+++ b/polkadot/runtime/westend/src/weights/runtime_parachains_hrmp.rs
@@ -324,4 +324,14 @@ impl<T: frame_system::Config> runtime_parachains::hrmp::WeightInfo for WeightInf
 			.saturating_add(T::DbWeight::get().reads(1))
 			.saturating_add(T::DbWeight::get().writes(1))
 	}
+	fn establish_channel_with_system() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `417`
+		//  Estimated: `6357`
+		// Minimum execution time: 629_674_000 picoseconds.
+		Weight::from_parts(640_174_000, 0)
+			.saturating_add(Weight::from_parts(0, 6357))
+			.saturating_add(T::DbWeight::get().reads(12))
+			.saturating_add(T::DbWeight::get().writes(8))
+	}
 }
diff --git a/polkadot/xcm/pallet-xcm/src/lib.rs b/polkadot/xcm/pallet-xcm/src/lib.rs
index ef255068734aae29d879e16c575a8a5cea4c5e59..cf22b86cf82c9e4552beee53be5786e57db0f896 100644
--- a/polkadot/xcm/pallet-xcm/src/lib.rs
+++ b/polkadot/xcm/pallet-xcm/src/lib.rs
@@ -1299,64 +1299,20 @@ pub mod pallet {
 			);
 
 			ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
-			let mut assets = assets.into_inner();
+			let assets = assets.into_inner();
 			let fee_asset_item = fee_asset_item as usize;
-			let fees = assets.get(fee_asset_item as usize).ok_or(Error::<T>::Empty)?.clone();
 			// Find transfer types for fee and non-fee assets.
 			let (fees_transfer_type, assets_transfer_type) =
 				Self::find_fee_and_assets_transfer_types(&assets, fee_asset_item, &dest)?;
 
-			// local and remote XCM programs to potentially handle fees separately
-			let fees = if fees_transfer_type == assets_transfer_type {
-				// no need for custom fees instructions, fees are batched with assets
-				FeesHandling::Batched { fees }
-			} else {
-				// Disallow _remote reserves_ unless assets & fees have same remote reserve (covered
-				// by branch above). The reason for this is that we'd need to send XCMs to separate
-				// chains with no guarantee of delivery order on final destination; therefore we
-				// cannot guarantee to have fees in place on final destination chain to pay for
-				// assets transfer.
-				ensure!(
-					!matches!(assets_transfer_type, TransferType::RemoteReserve(_)),
-					Error::<T>::InvalidAssetUnsupportedReserve
-				);
-				let weight_limit = weight_limit.clone();
-				// remove `fees` from `assets` and build separate fees transfer instructions to be
-				// added to assets transfers XCM programs
-				let fees = assets.remove(fee_asset_item);
-				let (local_xcm, remote_xcm) = match fees_transfer_type {
-					TransferType::LocalReserve => Self::local_reserve_fees_instructions(
-						origin.clone(),
-						dest.clone(),
-						fees,
-						weight_limit,
-					)?,
-					TransferType::DestinationReserve =>
-						Self::destination_reserve_fees_instructions(
-							origin.clone(),
-							dest.clone(),
-							fees,
-							weight_limit,
-						)?,
-					TransferType::Teleport => Self::teleport_fees_instructions(
-						origin.clone(),
-						dest.clone(),
-						fees,
-						weight_limit,
-					)?,
-					TransferType::RemoteReserve(_) =>
-						return Err(Error::<T>::InvalidAssetUnsupportedReserve.into()),
-				};
-				FeesHandling::Separate { local_xcm, remote_xcm }
-			};
-
-			Self::build_and_execute_xcm_transfer_type(
+			Self::do_transfer_assets(
 				origin,
 				dest,
 				beneficiary,
 				assets,
 				assets_transfer_type,
-				fees,
+				fee_asset_item,
+				fees_transfer_type,
 				weight_limit,
 			)
 		}
@@ -1443,6 +1399,87 @@ pub mod pallet {
 			<Self as SendController<_>>::send_blob(origin, dest, encoded_message)?;
 			Ok(())
 		}
+
+		/// Transfer assets from the local chain to the destination chain using explicit transfer
+		/// types for assets and fees.
+		///
+		/// `assets` must have same reserve location or may be teleportable to `dest`. Caller must
+		/// provide the `assets_transfer_type` to be used for `assets`:
+		///  - `TransferType::LocalReserve`: transfer assets to sovereign account of destination
+		///    chain and forward a notification XCM to `dest` to mint and deposit reserve-based
+		///    assets to `beneficiary`.
+		///  - `TransferType::DestinationReserve`: burn local assets and forward a notification to
+		///    `dest` chain to withdraw the reserve assets from this chain's sovereign account and
+		///    deposit them to `beneficiary`.
+		///  - `TransferType::RemoteReserve(reserve)`: burn local assets, forward XCM to `reserve`
+		///    chain to move reserves from this chain's SA to `dest` chain's SA, and forward another
+		///    XCM to `dest` to mint and deposit reserve-based assets to `beneficiary`. Typically
+		///    the remote `reserve` is Asset Hub.
+		///  - `TransferType::Teleport`: burn local assets and forward XCM to `dest` chain to
+		///    mint/teleport assets and deposit them to `beneficiary`.
+		///
+		/// Fee payment on the source, destination and all intermediary hops, is specified through
+		/// `fees_id`, but make sure enough of the specified `fees_id` asset is included in the
+		/// given list of `assets`. `fees_id` should be enough to pay for `weight_limit`. If more
+		/// weight is needed than `weight_limit`, then the operation will fail and the sent assets
+		/// may be at risk.
+		///
+		/// `fees_id` may use different transfer type than rest of `assets` and can be specified
+		/// through `fees_transfer_type`.
+		///
+		/// - `origin`: Must be capable of withdrawing the `assets` and executing XCM.
+		/// - `dest`: Destination context for the assets. Will typically be `[Parent,
+		///   Parachain(..)]` to send from parachain to parachain, or `[Parachain(..)]` to send from
+		///   relay to parachain, or `(parents: 2, (GlobalConsensus(..), ..))` to send from
+		///   parachain across a bridge to another ecosystem destination.
+		/// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will
+		///   generally be an `AccountId32` value.
+		/// - `assets`: The assets to be withdrawn. This should include the assets used to pay the
+		///   fee on the `dest` (and possibly reserve) chains.
+		/// - `assets_transfer_type`: The XCM `TransferType` used to transfer the `assets`.
+		/// - `fees_id`: One of the included `assets` to be be used to pay fees.
+		/// - `fees_transfer_type`: The XCM `TransferType` used to transfer the `fees` assets.
+		/// - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase.
+		#[pallet::call_index(15)]
+		#[pallet::weight(T::WeightInfo::transfer_assets())]
+		pub fn transfer_assets_using_type(
+			origin: OriginFor<T>,
+			dest: Box<VersionedLocation>,
+			beneficiary: Box<VersionedLocation>,
+			assets: Box<VersionedAssets>,
+			assets_transfer_type: Box<TransferType>,
+			fees_id: Box<VersionedAssetId>,
+			fees_transfer_type: Box<TransferType>,
+			weight_limit: WeightLimit,
+		) -> DispatchResult {
+			let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
+			let dest: Location = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
+			let beneficiary: Location =
+				(*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
+			let assets: Assets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
+			let fees_id: AssetId = (*fees_id).try_into().map_err(|()| Error::<T>::BadVersion)?;
+			log::debug!(
+				target: "xcm::pallet_xcm::transfer_assets_using_type",
+				"origin {:?}, dest {:?}, beneficiary {:?}, assets {:?} through {:?}, fees-id {:?} through {:?}",
+				origin_location, dest, beneficiary, assets, assets_transfer_type, fees_id, fees_transfer_type,
+			);
+
+			let assets = assets.into_inner();
+			ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
+
+			let fee_asset_index =
+				assets.iter().position(|a| a.id == fees_id).ok_or(Error::<T>::FeesNotMet)?;
+			Self::do_transfer_assets(
+				origin_location,
+				dest,
+				beneficiary,
+				assets,
+				*assets_transfer_type,
+				fee_asset_index,
+				*fees_transfer_type,
+				weight_limit,
+			)
+		}
 	}
 }
 
@@ -1607,15 +1644,16 @@ impl<T: Config> Pallet<T> {
 		// Ensure all assets (including fees) have same reserve location.
 		ensure!(assets_transfer_type == fees_transfer_type, Error::<T>::TooManyReserves);
 
-		Self::build_and_execute_xcm_transfer_type(
-			origin,
-			dest,
+		let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
+			origin.clone(),
+			dest.clone(),
 			beneficiary,
 			assets,
 			assets_transfer_type,
 			FeesHandling::Batched { fees },
 			weight_limit,
-		)
+		)?;
+		Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm)
 	}
 
 	fn do_teleport_assets(
@@ -1648,18 +1686,85 @@ impl<T: Config> Pallet<T> {
 		}
 		let fees = assets.get(fee_asset_item as usize).ok_or(Error::<T>::Empty)?.clone();
 
-		Self::build_and_execute_xcm_transfer_type(
-			origin_location,
-			dest,
+		let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
+			origin_location.clone(),
+			dest.clone(),
 			beneficiary,
 			assets,
 			TransferType::Teleport,
 			FeesHandling::Batched { fees },
 			weight_limit,
-		)
+		)?;
+		Self::execute_xcm_transfer(origin_location, dest, local_xcm, remote_xcm)
+	}
+
+	fn do_transfer_assets(
+		origin: Location,
+		dest: Location,
+		beneficiary: Location,
+		mut assets: Vec<Asset>,
+		assets_transfer_type: TransferType,
+		fee_asset_index: usize,
+		fees_transfer_type: TransferType,
+		weight_limit: WeightLimit,
+	) -> DispatchResult {
+		// local and remote XCM programs to potentially handle fees separately
+		let fees = if fees_transfer_type == assets_transfer_type {
+			let fees = assets.get(fee_asset_index).ok_or(Error::<T>::Empty)?.clone();
+			// no need for custom fees instructions, fees are batched with assets
+			FeesHandling::Batched { fees }
+		} else {
+			// Disallow _remote reserves_ unless assets & fees have same remote reserve (covered
+			// by branch above). The reason for this is that we'd need to send XCMs to separate
+			// chains with no guarantee of delivery order on final destination; therefore we
+			// cannot guarantee to have fees in place on final destination chain to pay for
+			// assets transfer.
+			ensure!(
+				!matches!(assets_transfer_type, TransferType::RemoteReserve(_)),
+				Error::<T>::InvalidAssetUnsupportedReserve
+			);
+			let weight_limit = weight_limit.clone();
+			// remove `fees` from `assets` and build separate fees transfer instructions to be
+			// added to assets transfers XCM programs
+			let fees = assets.remove(fee_asset_index);
+			let (local_xcm, remote_xcm) = match fees_transfer_type {
+				TransferType::LocalReserve => Self::local_reserve_fees_instructions(
+					origin.clone(),
+					dest.clone(),
+					fees,
+					weight_limit,
+				)?,
+				TransferType::DestinationReserve => Self::destination_reserve_fees_instructions(
+					origin.clone(),
+					dest.clone(),
+					fees,
+					weight_limit,
+				)?,
+				TransferType::Teleport => Self::teleport_fees_instructions(
+					origin.clone(),
+					dest.clone(),
+					fees,
+					weight_limit,
+				)?,
+				TransferType::RemoteReserve(_) =>
+					return Err(Error::<T>::InvalidAssetUnsupportedReserve.into()),
+			};
+			FeesHandling::Separate { local_xcm, remote_xcm }
+		};
+
+		let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
+			origin.clone(),
+			dest.clone(),
+			beneficiary,
+			assets,
+			assets_transfer_type,
+			fees,
+			weight_limit,
+		)?;
+		Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm)
 	}
 
-	fn build_and_execute_xcm_transfer_type(
+	fn build_xcm_transfer_type(
 		origin: Location,
 		dest: Location,
 		beneficiary: Location,
@@ -1667,14 +1772,14 @@ impl<T: Config> Pallet<T> {
 		transfer_type: TransferType,
 		fees: FeesHandling<T>,
 		weight_limit: WeightLimit,
-	) -> DispatchResult {
+	) -> Result<(Xcm<<T as Config>::RuntimeCall>, Option<Xcm<()>>), Error<T>> {
 		log::debug!(
-			target: "xcm::pallet_xcm::build_and_execute_xcm_transfer_type",
+			target: "xcm::pallet_xcm::build_xcm_transfer_type",
 			"origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, transfer_type {:?}, \
 			fees_handling {:?}, weight_limit: {:?}",
 			origin, dest, beneficiary, assets, transfer_type, fees, weight_limit,
 		);
-		let (mut local_xcm, remote_xcm) = match transfer_type {
+		Ok(match transfer_type {
 			TransferType::LocalReserve => {
 				let (local, remote) = Self::local_reserve_transfer_programs(
 					origin.clone(),
@@ -1704,7 +1809,7 @@ impl<T: Config> Pallet<T> {
 				};
 				let local = Self::remote_reserve_transfer_program(
 					origin.clone(),
-					reserve,
+					reserve.try_into().map_err(|()| Error::<T>::BadVersion)?,
 					dest.clone(),
 					beneficiary,
 					assets,
@@ -1724,7 +1829,21 @@ impl<T: Config> Pallet<T> {
 				)?;
 				(local, Some(remote))
 			},
-		};
+		})
+	}
+
+	fn execute_xcm_transfer(
+		origin: Location,
+		dest: Location,
+		mut local_xcm: Xcm<<T as Config>::RuntimeCall>,
+		remote_xcm: Option<Xcm<()>>,
+	) -> DispatchResult {
+		log::debug!(
+			target: "xcm::pallet_xcm::execute_xcm_transfer",
+			"origin {:?}, dest {:?}, local_xcm {:?}, remote_xcm {:?}",
+			origin, dest, local_xcm, remote_xcm,
+		);
+
 		let weight =
 			T::Weigher::weight(&mut local_xcm).map_err(|()| Error::<T>::UnweighableMessage)?;
 		let mut hash = local_xcm.using_encoded(sp_io::hashing::blake2_256);
@@ -1738,7 +1857,7 @@ impl<T: Config> Pallet<T> {
 		Self::deposit_event(Event::Attempted { outcome: outcome.clone() });
 		outcome.ensure_complete().map_err(|error| {
 			log::error!(
-				target: "xcm::pallet_xcm::build_and_execute_xcm_transfer_type",
+				target: "xcm::pallet_xcm::execute_xcm_transfer",
 				"XCM execution failed with error {:?}", error
 			);
 			Error::<T>::LocalExecutionIncomplete
@@ -1750,7 +1869,7 @@ impl<T: Config> Pallet<T> {
 			if origin != Here.into_location() {
 				Self::charge_fees(origin.clone(), price).map_err(|error| {
 					log::error!(
-						target: "xcm::pallet_xcm::build_and_execute_xcm_transfer_type",
+						target: "xcm::pallet_xcm::execute_xcm_transfer",
 						"Unable to charge fee with error {:?}", error
 					);
 					Error::<T>::FeesNotMet
@@ -1935,6 +2054,7 @@ impl<T: Config> Pallet<T> {
 		]);
 		// handle fees
 		Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;
+
 		// deposit all remaining assets in holding to `beneficiary` location
 		xcm_on_dest
 			.inner_mut()
diff --git a/polkadot/xcm/xcm-builder/src/asset_conversion.rs b/polkadot/xcm/xcm-builder/src/asset_conversion.rs
index e38af149be541f3f85ed47109e979351d6569f0c..520ce87448ea4f868fbb62130a7da45353d220b6 100644
--- a/polkadot/xcm/xcm-builder/src/asset_conversion.rs
+++ b/polkadot/xcm/xcm-builder/src/asset_conversion.rs
@@ -107,17 +107,6 @@ impl<
 #[deprecated = "Use `ConvertedConcreteId` instead"]
 pub type ConvertedConcreteAssetId<A, B, C, O> = ConvertedConcreteId<A, B, C, O>;
 
-pub struct V4V3LocationConverter;
-impl MaybeEquivalence<xcm::v4::Location, xcm::v3::Location> for V4V3LocationConverter {
-	fn convert(old: &xcm::v4::Location) -> Option<xcm::v3::Location> {
-		(*old).clone().try_into().ok()
-	}
-
-	fn convert_back(new: &xcm::v3::Location) -> Option<xcm::v4::Location> {
-		(*new).try_into().ok()
-	}
-}
-
 pub struct MatchedConvertedConcreteId<AssetId, Balance, MatchAssetId, ConvertAssetId, ConvertOther>(
 	PhantomData<(AssetId, Balance, MatchAssetId, ConvertAssetId, ConvertOther)>,
 );
diff --git a/polkadot/xcm/xcm-builder/src/lib.rs b/polkadot/xcm/xcm-builder/src/lib.rs
index 46d0ad227bfdf8e5e77188165fc259b0c1aec585..c3400cc72b48e97cb2cdfd85a028b21b7c032963 100644
--- a/polkadot/xcm/xcm-builder/src/lib.rs
+++ b/polkadot/xcm/xcm-builder/src/lib.rs
@@ -30,7 +30,7 @@ mod asset_conversion;
 #[allow(deprecated)]
 pub use asset_conversion::ConvertedConcreteAssetId;
 pub use asset_conversion::{
-	AsPrefixedGeneralIndex, ConvertedConcreteId, MatchedConvertedConcreteId, V4V3LocationConverter,
+	AsPrefixedGeneralIndex, ConvertedConcreteId, MatchedConvertedConcreteId,
 };
 
 mod barriers;
@@ -81,7 +81,9 @@ pub use location_conversion::{
 };
 
 mod matches_location;
-pub use matches_location::{StartsWith, StartsWithExplicitGlobalConsensus};
+pub use matches_location::{
+	StartsWith, StartsWithExplicitGlobalConsensus, WithLatestLocationConverter,
+};
 
 mod matches_token;
 pub use matches_token::IsConcrete;
diff --git a/polkadot/xcm/xcm-builder/src/matches_location.rs b/polkadot/xcm/xcm-builder/src/matches_location.rs
index 1664c24772909a8a287cf620da308b07158270bb..b6c2807e6b29db302e2d1182729c5acb4426b24b 100644
--- a/polkadot/xcm/xcm-builder/src/matches_location.rs
+++ b/polkadot/xcm/xcm-builder/src/matches_location.rs
@@ -18,6 +18,8 @@
 //! `InteriorLocation` types.
 
 use frame_support::traits::{Contains, Get};
+use sp_runtime::traits::MaybeEquivalence;
+use sp_std::marker::PhantomData;
 use xcm::latest::{InteriorLocation, Location, NetworkId};
 
 /// An implementation of `Contains` that checks for `Location` or
@@ -51,3 +53,18 @@ impl<T: Get<NetworkId>> Contains<InteriorLocation> for StartsWithExplicitGlobalC
 		matches!(location.global_consensus(), Ok(requested_network) if requested_network.eq(&T::get()))
 	}
 }
+
+/// An adapter implementation of `MaybeEquivalence` which can convert between the latest `Location`
+/// and other versions that implement `TryInto<Location>` and `TryFrom<Location>`.
+pub struct WithLatestLocationConverter<Target>(PhantomData<Target>);
+impl<Target: TryInto<Location> + TryFrom<Location> + Clone> MaybeEquivalence<Location, Target>
+	for WithLatestLocationConverter<Target>
+{
+	fn convert(old: &Location) -> Option<Target> {
+		(*old).clone().try_into().ok()
+	}
+
+	fn convert_back(new: &Target) -> Option<Location> {
+		new.clone().try_into().ok()
+	}
+}
diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs
index 81b81fe6a17154c74e03ebbddf8bfd81e4f7181c..e673a46c4ac683cfcdbb0e8197cebc96e35c33d7 100644
--- a/polkadot/xcm/xcm-executor/src/lib.rs
+++ b/polkadot/xcm/xcm-executor/src/lib.rs
@@ -347,6 +347,9 @@ impl<Config: config::Config> XcmExecutor<Config> {
 		msg: Xcm<()>,
 		reason: FeeReason,
 	) -> Result<XcmHash, XcmError> {
+		log::trace!(
+			target: "xcm::send", "Sending msg: {msg:?}, to destination: {dest:?}, (reason: {reason:?})"
+		);
 		let (ticket, fee) = validate_send::<Config::XcmSender>(dest, msg)?;
 		self.take_fee(fee, reason)?;
 		Config::XcmSender::deliver(ticket).map_err(Into::into)
diff --git a/polkadot/xcm/xcm-executor/src/traits/asset_transfer.rs b/polkadot/xcm/xcm-executor/src/traits/asset_transfer.rs
index 5da3d1da37c8117d2f0f754c4cfea1b3e638cb06..6d72eaf680fda558984da1958219d539e8913707 100644
--- a/polkadot/xcm/xcm-executor/src/traits/asset_transfer.rs
+++ b/polkadot/xcm/xcm-executor/src/traits/asset_transfer.rs
@@ -30,7 +30,7 @@ pub enum Error {
 }
 
 /// Specify which type of asset transfer is required for a particular `(asset, dest)` combination.
-#[derive(Clone, PartialEq, Debug)]
+#[derive(Clone, Encode, Decode, PartialEq, Debug, TypeInfo)]
 pub enum TransferType {
 	/// should teleport `asset` to `dest`
 	Teleport,
@@ -39,7 +39,7 @@ pub enum TransferType {
 	/// should reserve-transfer `asset` to `dest`, using `dest` as reserve
 	DestinationReserve,
 	/// should reserve-transfer `asset` to `dest`, using remote chain `Location` as reserve
-	RemoteReserve(Location),
+	RemoteReserve(VersionedLocation),
 }
 
 /// A trait for identifying asset transfer type based on `IsTeleporter` and `IsReserve`
@@ -77,7 +77,7 @@ pub trait XcmAssetTransfers {
 			Ok(TransferType::LocalReserve)
 		} else if Self::IsReserve::contains(asset, &asset_location) {
 			// remote location that is recognized as reserve location for asset
-			Ok(TransferType::RemoteReserve(asset_location))
+			Ok(TransferType::RemoteReserve(asset_location.into()))
 		} else {
 			// remote location that is not configured either as teleporter or reserve => cannot
 			// determine asset reserve
diff --git a/polkadot/zombienet_tests/elastic_scaling/0001-basic-3cores-6s-blocks.toml b/polkadot/zombienet_tests/elastic_scaling/0001-basic-3cores-6s-blocks.toml
index db508e14dbaa5c443e30c6c50d9d835927055162..83f5434edddb19afefcba93a9ce7bb305909c07f 100644
--- a/polkadot/zombienet_tests/elastic_scaling/0001-basic-3cores-6s-blocks.toml
+++ b/polkadot/zombienet_tests/elastic_scaling/0001-basic-3cores-6s-blocks.toml
@@ -20,8 +20,8 @@ chain = "rococo-local"
 default_command = "polkadot"
 
   [relaychain.default_resources]
-  limits = { memory = "4G", cpu = "2" }
-  requests = { memory = "2G", cpu = "1" }
+  limits = { memory = "4G", cpu = "3" }
+  requests = { memory = "4G", cpu = "3" }
 
   [[relaychain.node_groups]]
   name = "elastic-validator"
@@ -32,11 +32,20 @@ default_command = "polkadot"
 [[parachains]]
 id = {{id}}
 addToGenesis = true
+    [parachains.default_resources]
+    limits = { memory = "4G", cpu = "3" }
+    requests = { memory = "4G", cpu = "3" }
 
     [parachains.collator]
     name = "some-parachain"
     image = "{{COL_IMAGE}}"
     command = "adder-collator"
     args = ["-lparachain::collation-generation=trace,parachain::collator-protocol=trace,parachain=debug"]
+
 {% endfor %}
 
+# This represents the layout of the adder collator block header.
+[types.Header]
+number = "u64"
+parent_hash = "Hash"
+post_state = "Hash"
\ No newline at end of file
diff --git a/polkadot/zombienet_tests/elastic_scaling/0001-basic-3cores-6s-blocks.zndsl b/polkadot/zombienet_tests/elastic_scaling/0001-basic-3cores-6s-blocks.zndsl
index b9c002457549788c04673f291f54599757cd67d7..d624cbaf9df6a62448db2cef637e6d29a0d419b5 100644
--- a/polkadot/zombienet_tests/elastic_scaling/0001-basic-3cores-6s-blocks.zndsl
+++ b/polkadot/zombienet_tests/elastic_scaling/0001-basic-3cores-6s-blocks.zndsl
@@ -18,11 +18,11 @@ elastic-validator-0: js-script ./assign-core.js with "2000,1" return is 0 within
 elastic-validator-0: reports substrate_block_height{status="best"} is at least 20 within 600 seconds
 
 # Non elastic parachain should progress normally
-some-parachain-1: count of log lines containing "Parachain velocity: 1" is at least 9 within 20 seconds
+some-parachain-1: count of log lines containing "Parachain velocity: 1" is at least 5 within 20 seconds
 # Sanity
-some-parachain-1: count of log lines containing "Parachain velocity: 2" is 0 within 20 seconds
+some-parachain-1: count of log lines containing "Parachain velocity: 2" is 0
 
-# Parachain should progress 3 blocks per relay chain block ideally, however this measurement does 
-# `ceil()` on the actual velocity to account for CI overload.
-some-parachain: count of log lines containing "Parachain velocity: 3" is at least 9 within 20 seconds
+# Parachain should progress 3 blocks per relay chain block ideally, however CI might not be
+# the most performant environment so we'd just use a lower bound of 2 blocks per RCB
+elastic-validator-0: parachain 2000 block height is at least 20 within 200 seconds
 
diff --git a/polkadot/zombienet_tests/elastic_scaling/assign-core.js b/polkadot/zombienet_tests/elastic_scaling/assign-core.js
index 2e5f9d8cfa58ae26ef058f334ad782f1fbd9eb9f..add63b6d30859d2c50a38a73e33ec75ed3669433 100644
--- a/polkadot/zombienet_tests/elastic_scaling/assign-core.js
+++ b/polkadot/zombienet_tests/elastic_scaling/assign-core.js
@@ -1,6 +1,6 @@
 async function run(nodeName, networkInfo, args) {
-  const { wsUri, userDefinedTypes } = networkInfo.nodesByName[nodeName];
-  const api = await zombie.connect(wsUri, userDefinedTypes);
+  const wsUri = networkInfo.nodesByName[nodeName].wsUri;
+  const api = await zombie.connect(wsUri);
 
   let para = Number(args[0]);
   let core = Number(args[1]);
@@ -33,8 +33,6 @@ async function run(nodeName, networkInfo, args) {
       });
   });
 
-
-
   return 0;
 }
 
diff --git a/prdoc/pr_3695.prdoc b/prdoc/pr_3695.prdoc
new file mode 100644
index 0000000000000000000000000000000000000000..2c2c2b2e6917819f5f4c3b2c0381e3f7e4890a17
--- /dev/null
+++ b/prdoc/pr_3695.prdoc
@@ -0,0 +1,38 @@
+# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
+# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json
+
+title: "pallet-xcm: add new extrinsic for asset transfers using explicit reserve"
+
+doc:
+  - audience: Runtime User
+    description: |
+      pallet-xcm has a new extrinsic `transfer_assets_using_type` for transferring
+      assets from local chain to destination chain using an explicit XCM transfer
+      types for transferring the assets and the fees:
+      - `TransferType::LocalReserve`: transfer assets to sovereign account of destination
+      	chain and forward a notification XCM to `dest` to mint and deposit reserve-based
+      	assets to `beneficiary`.
+      - `TransferType::DestinationReserve`: burn local assets and forward a notification to
+      	`dest` chain to withdraw the reserve assets from this chain's sovereign account and
+      	deposit them to `beneficiary`.
+      - `TransferType::RemoteReserve(reserve)`: burn local assets, forward XCM to `reserve`
+      	chain to move reserves from this chain's SA to `dest` chain's SA, and forward another
+      	XCM to `dest` to mint and deposit reserve-based assets to `beneficiary`. Typically
+      	the remote `reserve` is Asset Hub.
+      - `TransferType::Teleport`: burn local assets and forward XCM to `dest` chain to
+      	mint/teleport assets and deposit them to `beneficiary`.
+      By default, an asset's reserve is its origin chain. But sometimes we may want to
+      explicitly use another chain as reserve (as long as allowed by runtime IsReserve
+      filter).
+      This is very helpful for transferring assets with multiple configured reserves
+      (such as Asset Hub ForeignAssets), when the transfer strictly depends on the used
+      reserve location.
+
+      E.g. For transferring a bridged Foreign Assets between local parachains, Asset Hub
+      or the parachain that bridged the asset over must be used as the reserve location.
+      Same when transferring bridged assets back across the bridge, the local bridging
+      parachain must be used as the explicit reserve location.
+
+crates:
+- name: pallet-xcm
+  bump: minor
diff --git a/prdoc/pr_3721.prdoc b/prdoc/pr_3721.prdoc
new file mode 100644
index 0000000000000000000000000000000000000000..be36103c474286cea613696e05dbba6ea5fc5782
--- /dev/null
+++ b/prdoc/pr_3721.prdoc
@@ -0,0 +1,13 @@
+# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
+# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json
+
+title: New call `hrmp.establish_channel_with_system` to allow parachains to establish a channel with a system parachain
+
+doc:
+  - audience: Runtime Dev
+    description: |
+      This PR adds a new call `hrmp.establish_channel_with_system` that allows a parachain origin to open a bidirectional channel with a system parachain.
+
+crates: 
+- name: polkadot-runtime-parachains
+  bump: minor
diff --git a/prdoc/pr_4006.prdoc b/prdoc/pr_4006.prdoc
new file mode 100644
index 0000000000000000000000000000000000000000..e6c339c406ac743f149ef14b3777a3214cfc10d2
--- /dev/null
+++ b/prdoc/pr_4006.prdoc
@@ -0,0 +1,19 @@
+# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
+# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json
+
+title: "Deploy pallet-parameters to rococo and fix dynamic_params name expand"
+
+doc:
+  - audience: Runtime Dev
+    description: |
+      Fix the expanded names of `dynamic_params` to not remove suffix "s".
+      
+      Also deploy the parameters pallet to the rococo-runtime.
+
+crates:
+  - name: frame-support-procedural
+    bump: major
+  - name: rococo-runtime
+    bump: major
+  - name: pallet-parameters
+    bump: patch
diff --git a/prdoc/pr_4027.prdoc b/prdoc/pr_4027.prdoc
new file mode 100644
index 0000000000000000000000000000000000000000..c85fd196a6c4a4af81e2d6882a424fb7a2d8b28d
--- /dev/null
+++ b/prdoc/pr_4027.prdoc
@@ -0,0 +1,25 @@
+title: Add `candidates_pending_availability` Runtime API
+
+doc:
+  - audience: "Node Dev"
+    description: |
+     This new API retrieves all `CommittedCandidateReceipts` of all candidates pending availability
+     for a parachain at a given relay chain block number. It is required by collators that make use
+     of elastic scaling capability in the context of PoV recovery and block import. The old API 
+     `candidate_pending_availability` is now deprectated and will be removed in the future.
+
+crates:
+  - name: polkadot-node-core-runtime-api
+    bump: minor
+  - name: polkadot-node-subsystem-types
+    bump: minor
+  - name: polkadot-node-subsystem-util
+    bump: minor
+  - name: polkadot-primitives
+    bump: minor
+  - name: polkadot-runtime-parachains
+    bump: minor
+  - name: cumulus-relay-chain-rpc-interface
+    bump: minor
+  - name: cumulus-relay-chain-minimal-node
+    bump: minor
diff --git a/prdoc/pr_4037.prdoc b/prdoc/pr_4037.prdoc
new file mode 100644
index 0000000000000000000000000000000000000000..7071875a7e370fdf11a0e88df1f6c0c5bb0a35a7
--- /dev/null
+++ b/prdoc/pr_4037.prdoc
@@ -0,0 +1,26 @@
+# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
+# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json
+
+title: "Remove `xcm::v3` from `assets-common` nits"
+
+doc:
+  - audience: Runtime Dev
+    description: |
+      Remove `xcm::v3` imports from `assets-common` to make it more generic and facilitate the transition to newer XCM versions.
+      The implementations `AssetIdForTrustBackedAssetsConvert`, `ForeignAssetsConvertedConcreteId`, or `TrustBackedAssetsAsLocation`
+      used hard-coded `xcm::v3::Location`, which has been changed to use `xcm::latest::Location` by default.
+      Alternatively, the providing runtime can configure them according to its needs, such as with a lower XCM version.
+
+      Example:
+      ```patch
+      - AssetIdForTrustBackedAssetsConvert<TrustBackedAssetsPalletLocationV3>,
+      + AssetIdForTrustBackedAssetsConvert<TrustBackedAssetsPalletLocationV3, xcm::v3::Location>,
+      ```
+
+      Another change is that the removed `xcm_builder::V4V3LocationConverter` can be replaced with `WithLatestLocationConverter`.
+
+crates:
+- name: assets-common
+  bump: patch
+- name: staging-xcm-builder
+  bump: patch
diff --git a/prdoc/pr_4059.prdoc b/prdoc/pr_4059.prdoc
new file mode 100644
index 0000000000000000000000000000000000000000..92753328a433a65f327572c255375a9b5f8e45fd
--- /dev/null
+++ b/prdoc/pr_4059.prdoc
@@ -0,0 +1,13 @@
+# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
+# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json
+
+title: Remove redundent logging code
+
+doc:
+  - audience: Node Dev
+    description: |
+      Simplified logging code, now does slightly less work while logging.
+
+crates:
+- name: sc-tracing
+  bump: minor
diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs
index ef533b3cc80d2bb96c4be4e50e5f51ce9803662d..096b43a3739abb785bdd0a73b026c1eff5019cd9 100644
--- a/substrate/bin/node/runtime/src/lib.rs
+++ b/substrate/bin/node/runtime/src/lib.rs
@@ -1602,6 +1602,7 @@ impl pallet_mmr::Config for Runtime {
 	type Hashing = Keccak256;
 	type LeafData = pallet_mmr::ParentNumberAndHash<Self>;
 	type OnNewRoot = pallet_beefy_mmr::DepositBeefyDigest<Runtime>;
+	type BlockHashProvider = pallet_mmr::DefaultBlockHashProvider<Runtime>;
 	type WeightInfo = ();
 }
 
@@ -2257,7 +2258,7 @@ impl EnsureOriginWithArg<RuntimeOrigin, RuntimeParametersKey> for DynamicParamet
 				frame_system::ensure_root(origin.clone()).map_err(|_| origin)?;
 				return Ok(())
 			},
-			RuntimeParametersKey::Contract(_) => {
+			RuntimeParametersKey::Contracts(_) => {
 				frame_system::ensure_root(origin.clone()).map_err(|_| origin)?;
 				return Ok(())
 			},
diff --git a/substrate/client/network/test/src/lib.rs b/substrate/client/network/test/src/lib.rs
index 1dfe7d4454e9949f9ba9be08998363d77b5993a6..48a4b3d6e6e16a4281c6e504d1c0d9511d2eb677 100644
--- a/substrate/client/network/test/src/lib.rs
+++ b/substrate/client/network/test/src/lib.rs
@@ -393,13 +393,14 @@ where
 
 			futures::executor::block_on(self.block_import.import_block(import_block))
 				.expect("block_import failed");
-			if announce_block {
-				self.sync_service.announce_block(hash, None);
-			}
 			hashes.push(hash);
 			at = hash;
 		}
 
+		if announce_block {
+			self.sync_service.announce_block(at, None);
+		}
+
 		if inform_sync_about_new_best_block {
 			self.sync_service.new_best_block_imported(
 				at,
diff --git a/substrate/client/tracing/src/logging/event_format.rs b/substrate/client/tracing/src/logging/event_format.rs
index 235d66cadc78a7a25358f24a40ffcfc1cb18e8ea..9589c1dfee28d11fe0dd7d409f9cec6e3563b812 100644
--- a/substrate/client/tracing/src/logging/event_format.rs
+++ b/substrate/client/tracing/src/logging/event_format.rs
@@ -21,12 +21,9 @@ use ansi_term::Colour;
 use regex::Regex;
 use std::fmt::{self, Write};
 use tracing::{Event, Level, Subscriber};
-use tracing_log::NormalizeEvent;
 use tracing_subscriber::{
-	field::RecordFields,
 	fmt::{format, time::FormatTime, FmtContext, FormatEvent, FormatFields},
-	layer::Context,
-	registry::{LookupSpan, SpanRef},
+	registry::LookupSpan,
 };
 
 /// A pre-configured event formatter.
@@ -54,7 +51,7 @@ where
 	//       https://github.com/tokio-rs/tracing/blob/2f59b32/tracing-subscriber/src/fmt/format/mod.rs#L449
 	pub(crate) fn format_event_custom<'b, 'w, S, N>(
 		&self,
-		ctx: CustomFmtContext<'b, S, N>,
+		ctx: &FmtContext<'b, S, N>,
 		writer: format::Writer<'w>,
 		event: &Event,
 	) -> fmt::Result
@@ -63,12 +60,10 @@ where
 		N: for<'a> FormatFields<'a> + 'static,
 	{
 		let mut writer = &mut ControlCodeSanitizer::new(!self.enable_color, writer);
-		let normalized_meta = event.normalized_metadata();
-		let meta = normalized_meta.as_ref().unwrap_or_else(|| event.metadata());
 		time::write(&self.timer, &mut format::Writer::new(&mut writer), self.enable_color)?;
 
 		if self.display_level {
-			let fmt_level = { FmtLevel::new(meta.level(), self.enable_color) };
+			let fmt_level = FmtLevel::new(event.metadata().level(), self.enable_color);
 			write!(writer, "{} ", fmt_level)?;
 		}
 
@@ -86,7 +81,7 @@ where
 		}
 
 		if self.display_target {
-			write!(writer, "{}: ", meta.target())?;
+			write!(writer, "{}: ", event.metadata().target())?;
 		}
 
 		// Custom code to display node name
@@ -137,12 +132,12 @@ where
 		{
 			let mut out = String::new();
 			let buf_writer = format::Writer::new(&mut out);
-			self.format_event_custom(CustomFmtContext::FmtContext(ctx), buf_writer, event)?;
+			self.format_event_custom(ctx, buf_writer, event)?;
 			writer.write_str(&out)?;
 			print!("{}", out);
 			Ok(())
 		} else {
-			self.format_event_custom(CustomFmtContext::FmtContext(ctx), writer, event)
+			self.format_event_custom(ctx, writer, event)
 		}
 	}
 }
@@ -261,48 +256,6 @@ mod time {
 	}
 }
 
-// NOTE: `FmtContext`'s fields are private. This enum allows us to make a `format_event` function
-//       that works with `FmtContext` or `Context` with `FormatFields`
-#[allow(dead_code)]
-pub(crate) enum CustomFmtContext<'a, S, N> {
-	FmtContext(&'a FmtContext<'a, S, N>),
-	ContextWithFormatFields(&'a Context<'a, S>, &'a N),
-}
-
-impl<'a, S, N> FormatFields<'a> for CustomFmtContext<'a, S, N>
-where
-	S: Subscriber + for<'lookup> LookupSpan<'lookup>,
-	N: for<'writer> FormatFields<'writer> + 'static,
-{
-	fn format_fields<R: RecordFields>(&self, writer: format::Writer<'_>, fields: R) -> fmt::Result {
-		match self {
-			CustomFmtContext::FmtContext(fmt_ctx) => fmt_ctx.format_fields(writer, fields),
-			CustomFmtContext::ContextWithFormatFields(_ctx, fmt_fields) =>
-				fmt_fields.format_fields(writer, fields),
-		}
-	}
-}
-
-// NOTE: the following code has been duplicated from tracing-subscriber
-//
-//       https://github.com/tokio-rs/tracing/blob/2f59b32/tracing-subscriber/src/fmt/fmt_layer.rs#L788
-impl<'a, S, N> CustomFmtContext<'a, S, N>
-where
-	S: Subscriber + for<'lookup> LookupSpan<'lookup>,
-	N: for<'writer> FormatFields<'writer> + 'static,
-{
-	#[inline]
-	pub fn lookup_current(&self) -> Option<SpanRef<'_, S>>
-	where
-		S: for<'lookup> LookupSpan<'lookup>,
-	{
-		match self {
-			CustomFmtContext::FmtContext(fmt_ctx) => fmt_ctx.lookup_current(),
-			CustomFmtContext::ContextWithFormatFields(ctx, _) => ctx.lookup_current(),
-		}
-	}
-}
-
 /// A writer which (optionally) strips out terminal control codes from the logs.
 ///
 /// This is used by [`EventFormat`] to sanitize the log messages.
diff --git a/substrate/frame/beefy-mmr/src/mock.rs b/substrate/frame/beefy-mmr/src/mock.rs
index 9d1ece7a1d8ecf433f6d244e55fb1f6e6211e77e..d59c219d3e71eae1c38975fc8bec137cc27b9076 100644
--- a/substrate/frame/beefy-mmr/src/mock.rs
+++ b/substrate/frame/beefy-mmr/src/mock.rs
@@ -90,6 +90,8 @@ impl pallet_mmr::Config for Test {
 
 	type OnNewRoot = pallet_beefy_mmr::DepositBeefyDigest<Test>;
 
+	type BlockHashProvider = pallet_mmr::DefaultBlockHashProvider<Test>;
+
 	type WeightInfo = ();
 }
 
diff --git a/substrate/frame/broker/src/dispatchable_impls.rs b/substrate/frame/broker/src/dispatchable_impls.rs
index ef20bc8fb80b19eaddef8fa6011dac17ffa22609..c2e731462ca58bb042ffe08c3b2c59af2cf3954c 100644
--- a/substrate/frame/broker/src/dispatchable_impls.rs
+++ b/substrate/frame/broker/src/dispatchable_impls.rs
@@ -81,7 +81,8 @@ impl<T: Config> Pallet<T> {
 			last_timeslice: Self::current_timeslice(),
 		};
 		let now = frame_system::Pallet::<T>::block_number();
-		let new_sale = SaleInfoRecord {
+		// Imaginary old sale for bootstrapping the first actual sale:
+		let old_sale = SaleInfoRecord {
 			sale_start: now,
 			leadin_length: Zero::zero(),
 			price,
@@ -94,7 +95,7 @@ impl<T: Config> Pallet<T> {
 			cores_sold: 0,
 		};
 		Self::deposit_event(Event::<T>::SalesStarted { price, core_count });
-		Self::rotate_sale(new_sale, &config, &status);
+		Self::rotate_sale(old_sale, &config, &status);
 		Status::<T>::put(&status);
 		Ok(())
 	}
diff --git a/substrate/frame/broker/src/lib.rs b/substrate/frame/broker/src/lib.rs
index a39576b09013f7fef9893ff87ae093eae467af5d..330491eb2087f032435bb07636694bd27da470ef 100644
--- a/substrate/frame/broker/src/lib.rs
+++ b/substrate/frame/broker/src/lib.rs
@@ -552,16 +552,27 @@ pub mod pallet {
 		///
 		/// - `origin`: Must be Root or pass `AdminOrigin`.
 		/// - `initial_price`: The price of Bulk Coretime in the first sale.
-		/// - `core_count`: The number of cores which can be allocated.
+		/// - `total_core_count`: This is the total number of cores the relay chain should have
+		/// after the sale concludes.
+		///
+		/// NOTE: This function does not actually request that new core count from the relay chain.
+		/// You need to make sure to call `request_core_count` afterwards to bring the relay chain
+		/// in sync.
+		///
+		/// When to call the function depends on the new core count. If it is larger than what it
+		/// was before, you can call it immediately or even before `start_sales` as non allocated
+		/// cores will just be `Idle`. If you are actually reducing the number of cores, you should
+		/// call `request_core_count`, right before the next sale, to avoid shutting down tasks too
+		/// early.
 		#[pallet::call_index(4)]
-		#[pallet::weight(T::WeightInfo::start_sales((*core_count).into()))]
+		#[pallet::weight(T::WeightInfo::start_sales((*total_core_count).into()))]
 		pub fn start_sales(
 			origin: OriginFor<T>,
 			initial_price: BalanceOf<T>,
-			core_count: CoreIndex,
+			total_core_count: CoreIndex,
 		) -> DispatchResultWithPostInfo {
 			T::AdminOrigin::ensure_origin_or_root(origin)?;
-			Self::do_start_sales(initial_price, core_count)?;
+			Self::do_start_sales(initial_price, total_core_count)?;
 			Ok(Pays::No.into())
 		}
 
diff --git a/substrate/frame/contracts/mock-network/src/parachain/contracts_config.rs b/substrate/frame/contracts/mock-network/src/parachain/contracts_config.rs
index 3c06131dd6088a2b044b68cfbf3b4a918eb37e3e..bf3c00b3ff1f5f3e78c164875d92742ea74a0cf0 100644
--- a/substrate/frame/contracts/mock-network/src/parachain/contracts_config.rs
+++ b/substrate/frame/contracts/mock-network/src/parachain/contracts_config.rs
@@ -15,87 +15,19 @@
 // along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
 
 use super::{Balances, Runtime, RuntimeCall, RuntimeEvent};
-use crate::{
-	parachain,
-	parachain::RuntimeHoldReason,
-	primitives::{Balance, CENTS},
-};
-use frame_support::{
-	parameter_types,
-	traits::{ConstBool, ConstU32, Contains, Randomness},
-	weights::Weight,
-};
-use frame_system::{pallet_prelude::BlockNumberFor, EnsureSigned};
-use pallet_xcm::BalanceOf;
-use sp_runtime::{traits::Convert, Perbill};
-
-pub const fn deposit(items: u32, bytes: u32) -> Balance {
-	items as Balance * 1 * CENTS + (bytes as Balance) * 1 * CENTS
-}
+use crate::parachain::RuntimeHoldReason;
+use frame_support::{derive_impl, parameter_types};
 
 parameter_types! {
-	pub const DepositPerItem: Balance = deposit(1, 0);
-	pub const DepositPerByte: Balance = deposit(0, 1);
-	pub const DefaultDepositLimit: Balance = deposit(1024, 1024 * 1024);
 	pub Schedule: pallet_contracts::Schedule<Runtime> = Default::default();
-	pub const CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(0);
-	pub const MaxDelegateDependencies: u32 = 32;
-}
-
-pub struct DummyRandomness<T: pallet_contracts::Config>(sp_std::marker::PhantomData<T>);
-
-impl<T: pallet_contracts::Config> Randomness<T::Hash, BlockNumberFor<T>> for DummyRandomness<T> {
-	fn random(_subject: &[u8]) -> (T::Hash, BlockNumberFor<T>) {
-		(Default::default(), Default::default())
-	}
-}
-
-impl Convert<Weight, BalanceOf<Self>> for Runtime {
-	fn convert(w: Weight) -> BalanceOf<Self> {
-		w.ref_time().into()
-	}
-}
-
-#[derive(Clone, Default)]
-pub struct Filters;
-
-impl Contains<RuntimeCall> for Filters {
-	fn contains(call: &RuntimeCall) -> bool {
-		match call {
-			parachain::RuntimeCall::Contracts(_) => true,
-			_ => false,
-		}
-	}
 }
 
+#[derive_impl(pallet_contracts::config_preludes::TestDefaultConfig)]
 impl pallet_contracts::Config for Runtime {
 	type AddressGenerator = pallet_contracts::DefaultAddressGenerator;
-	type CallFilter = Filters;
 	type CallStack = [pallet_contracts::Frame<Self>; 5];
-	type ChainExtension = ();
-	type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent;
 	type Currency = Balances;
-	type DefaultDepositLimit = DefaultDepositLimit;
-	type DepositPerByte = DepositPerByte;
-	type DepositPerItem = DepositPerItem;
-	type MaxCodeLen = ConstU32<{ 123 * 1024 }>;
-	type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>;
-	type MaxDelegateDependencies = MaxDelegateDependencies;
-	type MaxStorageKeyLen = ConstU32<128>;
-	type Migrations = ();
-	type Randomness = DummyRandomness<Self>;
-	type RuntimeCall = RuntimeCall;
-	type RuntimeEvent = RuntimeEvent;
-	type RuntimeHoldReason = RuntimeHoldReason;
 	type Schedule = Schedule;
 	type Time = super::Timestamp;
-	type UnsafeUnstableInterface = ConstBool<true>;
-	type UploadOrigin = EnsureSigned<Self::AccountId>;
-	type InstantiateOrigin = EnsureSigned<Self::AccountId>;
-	type WeightInfo = ();
-	type WeightPrice = Self;
-	type Debug = ();
-	type Environment = ();
-	type ApiVersion = ();
 	type Xcm = pallet_xcm::Pallet<Self>;
 }
diff --git a/substrate/frame/contracts/src/lib.rs b/substrate/frame/contracts/src/lib.rs
index 73c70a7704e202845a6ce4edc361527b6c125fbe..edc4c872bfce14e21d84ca14fc8877d88c362ff0 100644
--- a/substrate/frame/contracts/src/lib.rs
+++ b/substrate/frame/contracts/src/lib.rs
@@ -250,7 +250,7 @@ pub mod pallet {
 	#[pallet::storage_version(STORAGE_VERSION)]
 	pub struct Pallet<T>(_);
 
-	#[pallet::config]
+	#[pallet::config(with_default)]
 	pub trait Config: frame_system::Config {
 		/// The time implementation used to supply timestamps to contracts through `seal_now`.
 		type Time: Time;
@@ -263,22 +263,30 @@ pub mod pallet {
 		/// be instantiated from existing codes that use this deprecated functionality. It will
 		/// be removed eventually. Hence for new `pallet-contracts` deployments it is okay
 		/// to supply a dummy implementation for this type (because it is never used).
+		#[pallet::no_default_bounds]
 		type Randomness: Randomness<Self::Hash, BlockNumberFor<Self>>;
 
 		/// The fungible in which fees are paid and contract balances are held.
+		#[pallet::no_default]
 		type Currency: Inspect<Self::AccountId>
 			+ Mutate<Self::AccountId>
 			+ MutateHold<Self::AccountId, Reason = Self::RuntimeHoldReason>;
 
 		/// The overarching event type.
+		#[pallet::no_default_bounds]
 		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
 
 		/// The overarching call type.
+		#[pallet::no_default_bounds]
 		type RuntimeCall: Dispatchable<RuntimeOrigin = Self::RuntimeOrigin, PostInfo = PostDispatchInfo>
 			+ GetDispatchInfo
 			+ codec::Decode
 			+ IsType<<Self as frame_system::Config>::RuntimeCall>;
 
+		/// Overarching hold reason.
+		#[pallet::no_default_bounds]
+		type RuntimeHoldReason: From<HoldReason>;
+
 		/// Filter that is applied to calls dispatched by contracts.
 		///
 		/// Use this filter to control which dispatchables are callable by contracts.
@@ -301,10 +309,12 @@ pub mod pallet {
 		///
 		/// This filter does not apply to XCM transact calls. To impose restrictions on XCM transact
 		/// calls, you must configure them separately within the XCM pallet itself.
+		#[pallet::no_default_bounds]
 		type CallFilter: Contains<<Self as frame_system::Config>::RuntimeCall>;
 
 		/// Used to answer contracts' queries regarding the current weight price. This is **not**
 		/// used to calculate the actual fee and is only for informational purposes.
+		#[pallet::no_default_bounds]
 		type WeightPrice: Convert<Weight, BalanceOf<Self>>;
 
 		/// Describes the weights of the dispatchables of this module and is also used to
@@ -312,10 +322,12 @@ pub mod pallet {
 		type WeightInfo: WeightInfo;
 
 		/// Type that allows the runtime authors to add new host functions for a contract to call.
+		#[pallet::no_default_bounds]
 		type ChainExtension: chain_extension::ChainExtension<Self> + Default;
 
 		/// Cost schedule and limits.
 		#[pallet::constant]
+		#[pallet::no_default]
 		type Schedule: Get<Schedule<Self>>;
 
 		/// The type of the call stack determines the maximum nesting depth of contract calls.
@@ -326,6 +338,7 @@ pub mod pallet {
 		///
 		/// This setting along with [`MaxCodeLen`](#associatedtype.MaxCodeLen) directly affects
 		/// memory usage of your runtime.
+		#[pallet::no_default]
 		type CallStack: Array<Item = Frame<Self>>;
 
 		/// The amount of balance a caller has to pay for each byte of storage.
@@ -334,10 +347,12 @@ pub mod pallet {
 		///
 		/// Changing this value for an existing chain might need a storage migration.
 		#[pallet::constant]
+		#[pallet::no_default_bounds]
 		type DepositPerByte: Get<BalanceOf<Self>>;
 
 		/// Fallback value to limit the storage deposit if it's not being set by the caller.
 		#[pallet::constant]
+		#[pallet::no_default_bounds]
 		type DefaultDepositLimit: Get<BalanceOf<Self>>;
 
 		/// The amount of balance a caller has to pay for each storage item.
@@ -346,6 +361,7 @@ pub mod pallet {
 		///
 		/// Changing this value for an existing chain might need a storage migration.
 		#[pallet::constant]
+		#[pallet::no_default_bounds]
 		type DepositPerItem: Get<BalanceOf<Self>>;
 
 		/// The percentage of the storage deposit that should be held for using a code hash.
@@ -356,6 +372,7 @@ pub mod pallet {
 		type CodeHashLockupDepositPercent: Get<Perbill>;
 
 		/// The address generator used to generate the addresses of contracts.
+		#[pallet::no_default_bounds]
 		type AddressGenerator: AddressGenerator<Self>;
 
 		/// The maximum length of a contract code in bytes.
@@ -395,6 +412,7 @@ pub mod pallet {
 		///
 		/// By default, it is safe to set this to `EnsureSigned`, allowing anyone to upload contract
 		/// code.
+		#[pallet::no_default_bounds]
 		type UploadOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Self::AccountId>;
 
 		/// Origin allowed to instantiate code.
@@ -407,11 +425,9 @@ pub mod pallet {
 		///
 		/// By default, it is safe to set this to `EnsureSigned`, allowing anyone to instantiate
 		/// contract code.
+		#[pallet::no_default_bounds]
 		type InstantiateOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Self::AccountId>;
 
-		/// Overarching hold reason.
-		type RuntimeHoldReason: From<HoldReason>;
-
 		/// The sequence of migration steps that will be applied during a migration.
 		///
 		/// # Examples
@@ -435,6 +451,7 @@ pub mod pallet {
 		/// For most production chains, it's recommended to use the `()` implementation of this
 		/// trait. This implementation offers additional logging when the log target
 		/// "runtime::contracts" is set to trace.
+		#[pallet::no_default_bounds]
 		type Debug: Debugger<Self>;
 
 		/// Type that bundles together all the runtime configurable interface types.
@@ -442,16 +459,19 @@ pub mod pallet {
 		/// This is not a real config. We just mention the type here as constant so that
 		/// its type appears in the metadata. Only valid value is `()`.
 		#[pallet::constant]
+		#[pallet::no_default_bounds]
 		type Environment: Get<Environment<Self>>;
 
 		/// The version of the HostFn APIs that are available in the runtime.
 		///
 		/// Only valid value is `()`.
 		#[pallet::constant]
+		#[pallet::no_default_bounds]
 		type ApiVersion: Get<ApiVersion>;
 
 		/// A type that exposes XCM APIs, allowing contracts to interact with other parachains, and
 		/// execute XCM programs.
+		#[pallet::no_default_bounds]
 		type Xcm: xcm_builder::Controller<
 			OriginFor<Self>,
 			<Self as frame_system::Config>::RuntimeCall,
@@ -459,6 +479,95 @@ pub mod pallet {
 		>;
 	}
 
+	/// Container for different types that implement [`DefaultConfig`]` of this pallet.
+	pub mod config_preludes {
+		use super::*;
+		use frame_support::{
+			derive_impl,
+			traits::{ConstBool, ConstU32},
+		};
+		use frame_system::EnsureSigned;
+		use sp_core::parameter_types;
+
+		type AccountId = sp_runtime::AccountId32;
+		type Balance = u64;
+		const UNITS: Balance = 10_000_000_000;
+		const CENTS: Balance = UNITS / 100;
+
+		const fn deposit(items: u32, bytes: u32) -> Balance {
+			items as Balance * 1 * CENTS + (bytes as Balance) * 1 * CENTS
+		}
+
+		parameter_types! {
+			pub const DepositPerItem: Balance = deposit(1, 0);
+			pub const DepositPerByte: Balance = deposit(0, 1);
+			pub const DefaultDepositLimit: Balance = deposit(1024, 1024 * 1024);
+			pub const CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(0);
+			pub const MaxDelegateDependencies: u32 = 32;
+		}
+
+		/// A type providing default configurations for this pallet in testing environment.
+		pub struct TestDefaultConfig;
+
+		impl<Output, BlockNumber> Randomness<Output, BlockNumber> for TestDefaultConfig {
+			fn random(_subject: &[u8]) -> (Output, BlockNumber) {
+				unimplemented!("No default `random` implementation in `TestDefaultConfig`, provide a custom `T::Randomness` type.")
+			}
+		}
+
+		impl Time for TestDefaultConfig {
+			type Moment = u64;
+			fn now() -> Self::Moment {
+				unimplemented!("No default `now` implementation in `TestDefaultConfig` provide a custom `T::Time` type.")
+			}
+		}
+
+		impl<T: From<u64>> Convert<Weight, T> for TestDefaultConfig {
+			fn convert(w: Weight) -> T {
+				w.ref_time().into()
+			}
+		}
+
+		#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig, no_aggregated_types)]
+		impl frame_system::DefaultConfig for TestDefaultConfig {}
+
+		#[frame_support::register_default_impl(TestDefaultConfig)]
+		impl DefaultConfig for TestDefaultConfig {
+			#[inject_runtime_type]
+			type RuntimeEvent = ();
+
+			#[inject_runtime_type]
+			type RuntimeHoldReason = ();
+
+			#[inject_runtime_type]
+			type RuntimeCall = ();
+
+			type AddressGenerator = DefaultAddressGenerator;
+			type CallFilter = ();
+			type ChainExtension = ();
+			type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent;
+			type DefaultDepositLimit = DefaultDepositLimit;
+			type DepositPerByte = DepositPerByte;
+			type DepositPerItem = DepositPerItem;
+			type MaxCodeLen = ConstU32<{ 123 * 1024 }>;
+			type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>;
+			type MaxDelegateDependencies = MaxDelegateDependencies;
+			type MaxStorageKeyLen = ConstU32<128>;
+			type Migrations = ();
+			type Time = Self;
+			type Randomness = Self;
+			type UnsafeUnstableInterface = ConstBool<true>;
+			type UploadOrigin = EnsureSigned<AccountId>;
+			type InstantiateOrigin = EnsureSigned<AccountId>;
+			type WeightInfo = ();
+			type WeightPrice = Self;
+			type Debug = ();
+			type Environment = ();
+			type ApiVersion = ();
+			type Xcm = ();
+		}
+	}
+
 	#[pallet::hooks]
 	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
 		fn on_idle(_block: BlockNumberFor<T>, limit: Weight) -> Weight {
diff --git a/substrate/frame/contracts/src/tests.rs b/substrate/frame/contracts/src/tests.rs
index 0b83358a7f56a134a938076aab5f20b5915f0826..57b804a51e4175a9c92683e9431a95de8c2cb397 100644
--- a/substrate/frame/contracts/src/tests.rs
+++ b/substrate/frame/contracts/src/tests.rs
@@ -334,34 +334,24 @@ parameter_types! {
 
 #[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
 impl frame_system::Config for Test {
+	type Block = Block;
 	type AccountId = AccountId32;
 	type Lookup = IdentityLookup<Self::AccountId>;
-	type Block = Block;
 	type AccountData = pallet_balances::AccountData<u64>;
 }
+
 impl pallet_insecure_randomness_collective_flip::Config for Test {}
+
+#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
 impl pallet_balances::Config for Test {
-	type MaxLocks = ();
-	type MaxReserves = ();
-	type ReserveIdentifier = [u8; 8];
-	type Balance = u64;
-	type RuntimeEvent = RuntimeEvent;
-	type DustRemoval = ();
 	type ExistentialDeposit = ExistentialDeposit;
+	type ReserveIdentifier = [u8; 8];
 	type AccountStore = System;
-	type WeightInfo = ();
-	type FreezeIdentifier = ();
-	type MaxFreezes = ();
-	type RuntimeHoldReason = RuntimeHoldReason;
-	type RuntimeFreezeReason = RuntimeFreezeReason;
 }
 
-impl pallet_timestamp::Config for Test {
-	type Moment = u64;
-	type OnTimestampSet = ();
-	type MinimumPeriod = ConstU64<1>;
-	type WeightInfo = ();
-}
+#[derive_impl(pallet_timestamp::config_preludes::TestDefaultConfig)]
+impl pallet_timestamp::Config for Test {}
+
 impl pallet_utility::Config for Test {
 	type RuntimeEvent = RuntimeEvent;
 	type RuntimeCall = RuntimeCall;
@@ -467,16 +457,13 @@ parameter_types! {
 	pub static UnstableInterface: bool = true;
 }
 
+#[derive_impl(crate::config_preludes::TestDefaultConfig)]
 impl Config for Test {
 	type Time = Timestamp;
 	type Randomness = Randomness;
 	type Currency = Balances;
-	type RuntimeEvent = RuntimeEvent;
-	type RuntimeCall = RuntimeCall;
 	type CallFilter = TestFilter;
 	type CallStack = [Frame<Self>; 5];
-	type WeightPrice = Self;
-	type WeightInfo = ();
 	type ChainExtension =
 		(TestExtension, DisabledExtension, RevertingExtension, TempStorageExtension);
 	type Schedule = MySchedule;
@@ -484,20 +471,13 @@ impl Config for Test {
 	type DepositPerItem = DepositPerItem;
 	type DefaultDepositLimit = DefaultDepositLimit;
 	type AddressGenerator = DefaultAddressGenerator;
-	type MaxCodeLen = ConstU32<{ 123 * 1024 }>;
-	type MaxStorageKeyLen = ConstU32<128>;
 	type UnsafeUnstableInterface = UnstableInterface;
 	type UploadOrigin = EnsureAccount<Self, UploadAccount>;
 	type InstantiateOrigin = EnsureAccount<Self, InstantiateAccount>;
-	type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>;
-	type RuntimeHoldReason = RuntimeHoldReason;
 	type Migrations = crate::migration::codegen::BenchMigrations;
 	type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent;
 	type MaxDelegateDependencies = MaxDelegateDependencies;
 	type Debug = TestDebug;
-	type Environment = ();
-	type ApiVersion = ();
-	type Xcm = ();
 }
 
 pub const ALICE: AccountId32 = AccountId32::new([1u8; 32]);
diff --git a/substrate/frame/merkle-mountain-range/src/lib.rs b/substrate/frame/merkle-mountain-range/src/lib.rs
index 7b6edb37b7f7bc8608a417aa9eff6f0713edf9df..e2b40974579e8376723f466198d74755603285d0 100644
--- a/substrate/frame/merkle-mountain-range/src/lib.rs
+++ b/substrate/frame/merkle-mountain-range/src/lib.rs
@@ -103,6 +103,24 @@ impl<T: frame_system::Config> LeafDataProvider for ParentNumberAndHash<T> {
 	}
 }
 
+/// Block hash provider for a given block number.
+pub trait BlockHashProvider<BlockNumber, BlockHash> {
+	fn block_hash(block_number: BlockNumber) -> BlockHash;
+}
+
+/// Default implementation of BlockHashProvider using frame_system.
+pub struct DefaultBlockHashProvider<T: frame_system::Config> {
+	_phantom: sp_std::marker::PhantomData<T>,
+}
+
+impl<T: frame_system::Config> BlockHashProvider<BlockNumberFor<T>, T::Hash>
+	for DefaultBlockHashProvider<T>
+{
+	fn block_hash(block_number: BlockNumberFor<T>) -> T::Hash {
+		frame_system::Pallet::<T>::block_hash(block_number)
+	}
+}
+
 pub trait WeightInfo {
 	fn on_initialize(peaks: NodeIndex) -> Weight;
 }
@@ -177,6 +195,12 @@ pub mod pallet {
 		/// Clients. Hook complexity should be `O(1)`.
 		type OnNewRoot: primitives::OnNewRoot<HashOf<Self, I>>;
 
+		/// Block hash provider for a given block number.
+		type BlockHashProvider: BlockHashProvider<
+			BlockNumberFor<Self>,
+			<Self as frame_system::Config>::Hash,
+		>;
+
 		/// Weights for this pallet.
 		type WeightInfo: WeightInfo;
 	}
diff --git a/substrate/frame/merkle-mountain-range/src/mmr/storage.rs b/substrate/frame/merkle-mountain-range/src/mmr/storage.rs
index 96a20c3445eedea0bfa3d02e99ed07022d30da80..f2acc35a137ffb46a39396762eafe30d7296e017 100644
--- a/substrate/frame/merkle-mountain-range/src/mmr/storage.rs
+++ b/substrate/frame/merkle-mountain-range/src/mmr/storage.rs
@@ -29,7 +29,7 @@ use sp_std::prelude::*;
 use crate::{
 	mmr::{Node, NodeOf},
 	primitives::{self, NodeIndex},
-	Config, Nodes, NumberOfLeaves, Pallet,
+	BlockHashProvider, Config, Nodes, NumberOfLeaves, Pallet,
 };
 
 /// A marker type for runtime-specific storage implementation.
@@ -87,7 +87,7 @@ where
 		// Fall through to searching node using fork-specific key.
 		let ancestor_parent_block_num =
 			Pallet::<T, I>::leaf_index_to_parent_block_num(ancestor_leaf_idx, leaves);
-		let ancestor_parent_hash = <frame_system::Pallet<T>>::block_hash(ancestor_parent_block_num);
+		let ancestor_parent_hash = T::BlockHashProvider::block_hash(ancestor_parent_block_num);
 		let temp_key = Pallet::<T, I>::node_temp_offchain_key(pos, ancestor_parent_hash);
 		debug!(
 			target: "runtime::mmr::offchain",
diff --git a/substrate/frame/merkle-mountain-range/src/mock.rs b/substrate/frame/merkle-mountain-range/src/mock.rs
index 212012a052a027cd0582b90e3a29a409980c4e66..8318b20e83074cd3bcb540b854fd7abae998438e 100644
--- a/substrate/frame/merkle-mountain-range/src/mock.rs
+++ b/substrate/frame/merkle-mountain-range/src/mock.rs
@@ -44,6 +44,7 @@ impl Config for Test {
 	type Hashing = Keccak256;
 	type LeafData = Compact<Keccak256, (ParentNumberAndHash<Test>, LeafData)>;
 	type OnNewRoot = ();
+	type BlockHashProvider = DefaultBlockHashProvider<Test>;
 	type WeightInfo = ();
 }
 
diff --git a/substrate/frame/parameters/src/tests/mock.rs b/substrate/frame/parameters/src/tests/mock.rs
index 4c7dda639a9ae762c94b355174e7e4ca59869afe..6cfd7c8f30b8119ff1e1f0a63165c98afba1577b 100644
--- a/substrate/frame/parameters/src/tests/mock.rs
+++ b/substrate/frame/parameters/src/tests/mock.rs
@@ -16,6 +16,7 @@
 // limitations under the License.
 
 #![cfg(any(test, feature = "runtime-benchmarks"))]
+#![allow(non_snake_case)]
 
 //! Mock runtime that configures the `pallet_example_basic` to use dynamic params for testing.
 
@@ -66,6 +67,20 @@ pub mod dynamic_params {
 		#[codec(index = 0)]
 		pub static Key3: u128 = 4;
 	}
+
+	#[dynamic_pallet_params]
+	#[codec(index = 2)]
+	pub mod nis {
+		#[codec(index = 0)]
+		pub static Target: u64 = 0;
+	}
+
+	#[dynamic_pallet_params]
+	#[codec(index = 3)]
+	pub mod somE_weird_SPElLInG_s {
+		#[codec(index = 0)]
+		pub static V: u64 = 0;
+	}
 }
 
 #[docify::export(benchmarking_default)]
@@ -98,6 +113,8 @@ mod custom_origin {
 			}
 
 			match key {
+				RuntimeParametersKey::SomEWeirdSPElLInGS(_) |
+				RuntimeParametersKey::Nis(_) |
 				RuntimeParametersKey::Pallet1(_) => ensure_root(origin.clone()),
 				RuntimeParametersKey::Pallet2(_) => ensure_signed(origin.clone()).map(|_| ()),
 			}
diff --git a/substrate/frame/support/procedural/src/dynamic_params.rs b/substrate/frame/support/procedural/src/dynamic_params.rs
index 29399a885bc671ddfeefe3d8eefba3d235ad4899..ad62f59e6b0a04ea50c02375d6e1f421fc5689aa 100644
--- a/substrate/frame/support/procedural/src/dynamic_params.rs
+++ b/substrate/frame/support/procedural/src/dynamic_params.rs
@@ -91,7 +91,7 @@ impl ToTokens for DynamicParamModAttr {
 		let mut quoted_enum = quote! {};
 		for m in self.inner_mods() {
 			let aggregate_name =
-				syn::Ident::new(&m.ident.to_string().to_class_case(), m.ident.span());
+				syn::Ident::new(&m.ident.to_string().to_pascal_case(), m.ident.span());
 			let mod_name = &m.ident;
 
 			let mut attrs = m.attrs.clone();
@@ -222,8 +222,10 @@ impl ToTokens for DynamicPalletParamAttr {
 		let (params_mod, parameter_pallet, runtime_params) =
 			(&self.inner_mod, &self.meta.parameter_pallet, &self.meta.runtime_params);
 
-		let aggregate_name =
-			syn::Ident::new(&params_mod.ident.to_string().to_class_case(), params_mod.ident.span());
+		let aggregate_name = syn::Ident::new(
+			&params_mod.ident.to_string().to_pascal_case(),
+			params_mod.ident.span(),
+		);
 		let (mod_name, vis) = (&params_mod.ident, &params_mod.vis);
 		let statics = self.statics();