From e7651cf41b2d775dedcc897e17ead16e200a4fff Mon Sep 17 00:00:00 2001 From: Adrian Catangiu <adrian@parity.io> Date: Wed, 6 Dec 2023 13:18:12 +0200 Subject: [PATCH] pallet-xcm: add new flexible `transfer_assets()` call/extrinsic (#2388) # Motivation (+testing) ### Enable easy `ForeignAssets` transfers using `pallet-xcm` We had just previously added capabilities to teleport fees during reserve-based transfers, but what about reserve-transferring fees when needing to teleport some non-fee asset? This PR aligns everything under either explicit reserve-transfer, explicit teleport, or this new flexible `transfer_assets()` which can mix and match as needed with fewer artificial constraints imposed to the user. This will enable, for example, a (non-system) parachain to teleport their `ForeignAssets` assets to AssetHub while using DOT to pay fees. (the assets are teleported - as foreign assets should from their owner chain - while DOT used for fees can only be reserve-based transferred between said parachain and AssetHub). Added `xcm-emulator` tests for this scenario ^. # Description Reverts `(limited_)reserve_transfer_assets` to only allow reserve-based transfers for all `assets` including fees. Similarly `(limited_)teleport_assets` only allows teleports for all `assets` including fees. For complex combinations of asset transfers where assets and fees may have different reserves or different reserve/teleport trust configurations, users can use the newly added `transfer_assets()` extrinsic which is more flexible in allowing more complex scenarios. `assets` (excluding `fees`) must have same reserve location or otherwise be teleportable to `dest`. No limitations imposed on `fees`. - for local reserve: transfer assets to sovereign account of destination chain and forward a notification XCM to `dest` to mint and deposit reserve-based assets to `beneficiary`. - for destination reserve: 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`. - for remote 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`. - for teleports: burn local assets and forward XCM to `dest` chain to mint/teleport assets and deposit them to `beneficiary`. ## Review notes Only around 500 lines are prod code (see `pallet_xcm/src/lib.rs`), the rest of the PR is new tests and improving existing tests. --------- Co-authored-by: command-bot <> --- Cargo.lock | 2 - .../parachains/testing/penpal/src/lib.rs | 2 + .../emulated/common/src/macros.rs | 99 ++ .../emulated/common/src/xcm_helpers.rs | 14 + .../tests/assets/asset-hub-rococo/Cargo.toml | 1 - .../assets/asset-hub-rococo/src/tests/mod.rs | 8 + .../src/tests/reserve_transfer.rs | 28 +- .../assets/asset-hub-rococo/src/tests/swap.rs | 157 +- .../asset-hub-rococo/src/tests/teleport.rs | 337 +++++ .../tests/assets/asset-hub-westend/Cargo.toml | 1 - .../assets/asset-hub-westend/src/tests/mod.rs | 8 + .../src/tests/reserve_transfer.rs | 16 +- .../asset-hub-westend/src/tests/swap.rs | 156 +- .../asset-hub-westend/src/tests/teleport.rs | 337 +++++ .../assets/asset-hub-rococo/src/lib.rs | 49 + .../src/weights/pallet_xcm.rs | 154 +- .../assets/asset-hub-westend/src/lib.rs | 49 + .../src/weights/pallet_xcm.rs | 154 +- .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 12 + .../src/weights/pallet_xcm.rs | 191 ++- .../bridge-hubs/bridge-hub-westend/src/lib.rs | 12 + .../src/weights/pallet_xcm.rs | 220 +-- .../collectives-westend/src/lib.rs | 12 + .../src/weights/pallet_xcm.rs | 117 +- .../contracts/contracts-rococo/src/lib.rs | 12 + .../runtimes/testing/penpal/src/xcm_config.rs | 47 +- polkadot/runtime/rococo/src/lib.rs | 14 + .../runtime/rococo/src/weights/pallet_xcm.rs | 4 + polkadot/runtime/westend/src/lib.rs | 15 + .../runtime/westend/src/weights/pallet_xcm.rs | 4 + polkadot/xcm/pallet-xcm/src/benchmarking.rs | 71 +- polkadot/xcm/pallet-xcm/src/lib.rs | 568 +++++-- polkadot/xcm/pallet-xcm/src/mock.rs | 45 + .../pallet-xcm/src/tests/assets_transfer.rs | 1331 ++++++++++++++--- polkadot/xcm/pallet-xcm/src/tests/mod.rs | 2 +- prdoc/pr_2388.prdoc | 26 + substrate/frame/assets/src/benchmarking.rs | 2 +- substrate/frame/assets/src/lib.rs | 2 +- 38 files changed, 3322 insertions(+), 957 deletions(-) create mode 100644 prdoc/pr_2388.prdoc diff --git a/Cargo.lock b/Cargo.lock index 0afacd1fc21..f2693730b91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -761,7 +761,6 @@ dependencies = [ "pallet-xcm", "parachains-common", "parity-scale-codec", - "penpal-runtime", "rococo-runtime", "rococo-system-emulated-network", "sp-runtime", @@ -884,7 +883,6 @@ dependencies = [ "pallet-xcm", "parachains-common", "parity-scale-codec", - "penpal-runtime", "polkadot-runtime-common", "sp-runtime", "staging-xcm", 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 c76120adb79..62bafb5cb30 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 @@ -15,6 +15,7 @@ mod genesis; pub use genesis::{genesis, ED, PARA_ID_A, PARA_ID_B}; +pub use penpal_runtime::xcm_config::{LocalTeleportableToAssetHub, XcmConfig}; // Substrate use frame_support::traits::OnInitialize; @@ -67,6 +68,7 @@ decl_test_parachains! { // Penpal implementation impl_accounts_helpers_for_parachain!(PenpalA); +impl_accounts_helpers_for_parachain!(PenpalB); impl_assets_helpers_for_parachain!(PenpalA, Rococo); impl_assets_helpers_for_parachain!(PenpalB, Westend); impl_assert_events_helpers_for_parachain!(PenpalA); diff --git a/cumulus/parachains/integration-tests/emulated/common/src/macros.rs b/cumulus/parachains/integration-tests/emulated/common/src/macros.rs index 6ea3524ed4a..8718f1e83a0 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/macros.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/macros.rs @@ -120,3 +120,102 @@ macro_rules! test_parachain_is_trusted_teleporter { } }; } + +#[macro_export] +macro_rules! include_penpal_create_foreign_asset_on_asset_hub { + ( $penpal:ident, $asset_hub:ident, $relay_ed:expr, $weight_to_fee:expr) => { + $crate::impls::paste::paste! { + pub fn penpal_create_foreign_asset_on_asset_hub( + asset_id_on_penpal: u32, + foreign_asset_at_asset_hub: MultiLocation, + ah_as_seen_by_penpal: MultiLocation, + is_sufficient: bool, + asset_owner: AccountId, + prefund_amount: u128, + ) { + use frame_support::weights::WeightToFee; + let ah_check_account = $asset_hub::execute_with(|| { + <$asset_hub as [<$asset_hub Pallet>]>::PolkadotXcm::check_account() + }); + let penpal_check_account = + $penpal::execute_with(|| <$penpal as [<$penpal Pallet>]>::PolkadotXcm::check_account()); + let penpal_as_seen_by_ah = $asset_hub::sibling_location_of($penpal::para_id()); + + // prefund SA of Penpal on AssetHub with enough native tokens to pay for creating + // new foreign asset, also prefund CheckingAccount with ED, because teleported asset + // itself might not be sufficient and CheckingAccount cannot be created otherwise + let sov_penpal_on_ah = $asset_hub::sovereign_account_id_of(penpal_as_seen_by_ah); + $asset_hub::fund_accounts(vec![ + (sov_penpal_on_ah.clone().into(), $relay_ed * 100_000_000_000), + (ah_check_account.clone().into(), $relay_ed * 1000), + ]); + + // prefund SA of AssetHub on Penpal with native asset + let sov_ah_on_penpal = $penpal::sovereign_account_id_of(ah_as_seen_by_penpal); + $penpal::fund_accounts(vec![ + (sov_ah_on_penpal.into(), $relay_ed * 1_000_000_000), + (penpal_check_account.clone().into(), $relay_ed * 1000), + ]); + + // Force create asset on $penpal and prefund [<$penpal Sender>] + $penpal::force_create_and_mint_asset( + asset_id_on_penpal, + ASSET_MIN_BALANCE, + is_sufficient, + asset_owner, + None, + prefund_amount, + ); + + let require_weight_at_most = Weight::from_parts(1_100_000_000_000, 30_000); + // `OriginKind::Xcm` required by ForeignCreators pallet-assets origin filter + let origin_kind = OriginKind::Xcm; + let call_create_foreign_assets = + <$asset_hub as Chain>::RuntimeCall::ForeignAssets(pallet_assets::Call::< + <$asset_hub as Chain>::Runtime, + pallet_assets::Instance2, + >::create { + id: foreign_asset_at_asset_hub, + min_balance: ASSET_MIN_BALANCE, + admin: sov_penpal_on_ah.into(), + }) + .encode(); + let buy_execution_fee_amount = $weight_to_fee::weight_to_fee( + &Weight::from_parts(10_100_000_000_000, 300_000), + ); + let buy_execution_fee = MultiAsset { + id: Concrete(MultiLocation { parents: 1, interior: Here }), + fun: Fungible(buy_execution_fee_amount), + }; + let xcm = VersionedXcm::from(Xcm(vec![ + WithdrawAsset { 0: vec![buy_execution_fee.clone()].into() }, + BuyExecution { fees: buy_execution_fee.clone(), weight_limit: Unlimited }, + Transact { require_weight_at_most, origin_kind, call: call_create_foreign_assets.into() }, + ExpectTransactStatus(MaybeErrorCode::Success), + RefundSurplus, + DepositAsset { assets: All.into(), beneficiary: penpal_as_seen_by_ah }, + ])); + // Send XCM message from penpal => asset_hub + let sudo_penpal_origin = <$penpal as Chain>::RuntimeOrigin::root(); + $penpal::execute_with(|| { + assert_ok!(<$penpal as [<$penpal Pallet>]>::PolkadotXcm::send( + sudo_penpal_origin.clone(), + bx!(ah_as_seen_by_penpal.into()), + bx!(xcm), + )); + type RuntimeEvent = <$penpal as Chain>::RuntimeEvent; + assert_expected_events!( + $penpal, + vec![ + RuntimeEvent::PolkadotXcm(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + $asset_hub::execute_with(|| { + type ForeignAssets = <$asset_hub as [<$asset_hub Pallet>]>::ForeignAssets; + assert!(ForeignAssets::asset_exists(foreign_asset_at_asset_hub)); + }); + } + } + }; +} diff --git a/cumulus/parachains/integration-tests/emulated/common/src/xcm_helpers.rs b/cumulus/parachains/integration-tests/emulated/common/src/xcm_helpers.rs index 47e92ed075f..70a9408c309 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/xcm_helpers.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/xcm_helpers.rs @@ -59,3 +59,17 @@ pub fn xcm_transact_unpaid_execution( Transact { require_weight_at_most, origin_kind, call }, ])) } + +/// Helper method to get the non-fee asset used in multiple assets transfer +pub fn non_fee_asset(assets: &MultiAssets, fee_idx: usize) -> Option<(MultiLocation, u128)> { + let asset = assets.inner().into_iter().enumerate().find(|a| a.0 != fee_idx)?.1.clone(); + let asset_id = match asset.id { + Concrete(id) => id, + _ => return None, + }; + let asset_amount = match asset.fun { + Fungible(amount) => amount, + _ => return None, + }; + Some((asset_id, asset_amount)) +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml index 16e2f736bdf..bc4e7cfe376 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml @@ -30,5 +30,4 @@ asset-test-utils = { path = "../../../../../runtimes/assets/test-utils" } parachains-common = { path = "../../../../../../parachains/common" } asset-hub-rococo-runtime = { path = "../../../../../runtimes/assets/asset-hub-rococo" } emulated-integration-tests-common = { path = "../../../common", default-features = false } -penpal-runtime = { path = "../../../../../runtimes/testing/penpal" } rococo-system-emulated-network = { path = "../../../networks/rococo-system" } 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 b3841af0e6c..c9270934ddf 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 @@ -18,3 +18,11 @@ mod send; mod set_xcm_versions; mod swap; mod teleport; + +use crate::*; +emulated_integration_tests_common::include_penpal_create_foreign_asset_on_asset_hub!( + PenpalA, + AssetHubRococo, + ROCOCO_ED, + parachains_common::rococo::fee::WeightToFee +); 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 18483762ae1..e6142e29b7c 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 @@ -15,14 +15,12 @@ use crate::*; use asset_hub_rococo_runtime::xcm_config::XcmConfig as AssetHubRococoXcmConfig; -use penpal_runtime::xcm_config::XcmConfig as PenpalRococoXcmConfig; use rococo_runtime::xcm_config::XcmConfig as RococoXcmConfig; +use rococo_system_emulated_network::penpal_emulated_chain::XcmConfig as PenpalRococoXcmConfig; fn relay_to_para_sender_assertions(t: RelayToParaTest) { type RuntimeEvent = <Rococo as Chain>::RuntimeEvent; - Rococo::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(864_610_000, 8_799))); - assert_expected_events!( Rococo, vec![ @@ -42,12 +40,10 @@ fn relay_to_para_sender_assertions(t: RelayToParaTest) { 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, ))); - assert_expected_events!( AssetHubRococo, vec![ @@ -80,9 +76,7 @@ fn para_receiver_assertions<Test>(_: Test) { 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![ @@ -99,15 +93,13 @@ fn para_to_system_para_sender_assertions(t: ParaToSystemParaTest) { 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()), ); - assert_expected_events!( AssetHubRococo, vec![ - // Amount to reserve transfer is transferred to Parachain's Sovereign account + // Amount to reserve transfer is withdrawn from Parachain's Sovereign account RuntimeEvent::Balances( pallet_balances::Event::Withdraw { who, amount } ) => { @@ -124,12 +116,10 @@ fn para_to_system_para_receiver_assertions(t: ParaToSystemParaTest) { fn system_para_to_para_assets_sender_assertions(t: SystemParaToParaTest) { type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent; - AssetHubRococo::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts( 864_610_000, 8799, ))); - assert_expected_events!( AssetHubRococo, vec![ @@ -162,7 +152,7 @@ fn system_para_to_para_assets_receiver_assertions<Test>(_: Test) { ); } -fn relay_to_para_limited_reserve_transfer_assets(t: RelayToParaTest) -> DispatchResult { +fn relay_to_para_reserve_transfer_assets(t: RelayToParaTest) -> DispatchResult { <Rococo as RococoPallet>::XcmPallet::limited_reserve_transfer_assets( t.signed_origin, bx!(t.args.dest.into()), @@ -173,7 +163,7 @@ fn relay_to_para_limited_reserve_transfer_assets(t: RelayToParaTest) -> Dispatch ) } -fn system_para_to_para_limited_reserve_transfer_assets(t: SystemParaToParaTest) -> DispatchResult { +fn system_para_to_para_reserve_transfer_assets(t: SystemParaToParaTest) -> DispatchResult { <AssetHubRococo as AssetHubRococoPallet>::PolkadotXcm::limited_reserve_transfer_assets( t.signed_origin, bx!(t.args.dest.into()), @@ -184,7 +174,7 @@ fn system_para_to_para_limited_reserve_transfer_assets(t: SystemParaToParaTest) ) } -fn para_to_system_para_limited_reserve_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult { +fn para_to_system_para_reserve_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult { <PenpalA as PenpalAPallet>::PolkadotXcm::limited_reserve_transfer_assets( t.signed_origin, bx!(t.args.dest.into()), @@ -285,7 +275,7 @@ fn reserve_transfer_native_asset_from_relay_to_para() { test.set_assertion::<Rococo>(relay_to_para_sender_assertions); test.set_assertion::<PenpalA>(para_receiver_assertions); - test.set_dispatchable::<Rococo>(relay_to_para_limited_reserve_transfer_assets); + test.set_dispatchable::<Rococo>(relay_to_para_reserve_transfer_assets); test.assert(); let delivery_fees = Rococo::execute_with(|| { @@ -329,7 +319,7 @@ fn reserve_transfer_native_asset_from_system_para_to_para() { test.set_assertion::<AssetHubRococo>(system_para_to_para_sender_assertions); test.set_assertion::<PenpalA>(para_receiver_assertions); - test.set_dispatchable::<AssetHubRococo>(system_para_to_para_limited_reserve_transfer_assets); + test.set_dispatchable::<AssetHubRococo>(system_para_to_para_reserve_transfer_assets); test.assert(); let sender_balance_after = test.sender.balance; @@ -379,7 +369,7 @@ fn reserve_transfer_native_asset_from_para_to_system_para() { 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_system_para_limited_reserve_transfer_assets); + test.set_dispatchable::<PenpalA>(para_to_system_para_reserve_transfer_assets); test.assert(); let sender_balance_after = test.sender.balance; @@ -474,7 +464,7 @@ fn reserve_transfer_assets_from_system_para_to_para() { test.set_assertion::<AssetHubRococo>(system_para_to_para_assets_sender_assertions); test.set_assertion::<PenpalA>(system_para_to_para_assets_receiver_assertions); - test.set_dispatchable::<AssetHubRococo>(system_para_to_para_limited_reserve_transfer_assets); + test.set_dispatchable::<AssetHubRococo>(system_para_to_para_reserve_transfer_assets); test.assert(); let sender_balance_after = test.sender.balance; 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 9e691bf02f1..35a660ed3c4 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 @@ -14,9 +14,10 @@ // limitations under the License. use crate::*; -use frame_support::{instances::Instance2, BoundedVec}; +use frame_support::BoundedVec; use parachains_common::rococo::currency::EXISTENTIAL_DEPOSIT; -use sp_runtime::{DispatchError, ModuleError}; +use rococo_system_emulated_network::penpal_emulated_chain::LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub; +use sp_runtime::ModuleError; #[test] fn swap_locally_on_chain_using_local_assets() { @@ -112,114 +113,37 @@ fn swap_locally_on_chain_using_local_assets() { #[test] fn swap_locally_on_chain_using_foreign_assets() { - use frame_support::weights::WeightToFee; - let asset_native = Box::new(asset_hub_rococo_runtime::xcm_config::TokenLocation::get()); + let ah_as_seen_by_penpal = PenpalA::sibling_location_of(AssetHubRococo::para_id()); + let asset_location_on_penpal = PenpalLocalTeleportableToAssetHub::get(); + let asset_id_on_penpal = match asset_location_on_penpal.last() { + Some(GeneralIndex(id)) => *id as u32, + _ => unreachable!(), + }; + let asset_owner_on_penpal = PenpalASender::get(); + let foreign_asset_at_asset_hub_rococo = + MultiLocation { parents: 1, interior: X1(Parachain(PenpalA::para_id().into())) } + .appended_with(asset_location_on_penpal) + .unwrap(); + + // 1. Create asset on penpal and, 2. Create foreign asset on asset_hub_rococo + super::penpal_create_foreign_asset_on_asset_hub( + asset_id_on_penpal, + foreign_asset_at_asset_hub_rococo, + ah_as_seen_by_penpal, + true, + asset_owner_on_penpal, + ASSET_MIN_BALANCE * 1_000_000, + ); - let foreign_asset1_at_asset_hub_rococo = Box::new(MultiLocation { - parents: 1, - interior: X3( - Parachain(PenpalA::para_id().into()), - PalletInstance(ASSETS_PALLET_ID), - GeneralIndex(ASSET_ID.into()), - ), - }); - - let assets_para_destination: VersionedMultiLocation = - MultiLocation { parents: 1, interior: X1(Parachain(AssetHubRococo::para_id().into())) } - .into(); - - let penpal_location = - MultiLocation { parents: 1, interior: X1(Parachain(PenpalA::para_id().into())) }; - - // 1. Create asset on penpal: - PenpalA::execute_with(|| { - assert_ok!(<PenpalA as PenpalAPallet>::Assets::create( - <PenpalA as Chain>::RuntimeOrigin::signed(PenpalASender::get()), - ASSET_ID.into(), - PenpalASender::get().into(), - 1000, - )); - - assert!(<PenpalA as PenpalAPallet>::Assets::asset_exists(ASSET_ID)); - }); - - // 2. Create foreign asset on asset_hub_rococo: - - let require_weight_at_most = Weight::from_parts(1_100_000_000_000, 30_000); - let origin_kind = OriginKind::Xcm; - let sov_penpal_on_asset_hub_rococo = AssetHubRococo::sovereign_account_id_of(penpal_location); - + let penpal_as_seen_by_ah = AssetHubRococo::sibling_location_of(PenpalA::para_id()); + let sov_penpal_on_ahr = AssetHubRococo::sovereign_account_id_of(penpal_as_seen_by_ah); AssetHubRococo::fund_accounts(vec![ (AssetHubRococoSender::get().into(), 5_000_000 * ROCOCO_ED), /* An account to swap dot * for something else. */ - (sov_penpal_on_asset_hub_rococo.clone().into(), 1000_000_000_000_000_000 * ROCOCO_ED), ]); - let sov_penpal_on_asset_hub_rococo_as_location: MultiLocation = MultiLocation { - parents: 0, - interior: X1(AccountId32Junction { - network: None, - id: sov_penpal_on_asset_hub_rococo.clone().into(), - }), - }; - - let call_foreign_assets_create = - <AssetHubRococo as Chain>::RuntimeCall::ForeignAssets(pallet_assets::Call::< - <AssetHubRococo as Chain>::Runtime, - Instance2, - >::create { - id: *foreign_asset1_at_asset_hub_rococo, - min_balance: 1000, - admin: sov_penpal_on_asset_hub_rococo.clone().into(), - }) - .encode() - .into(); - - let buy_execution_fee_amount = parachains_common::rococo::fee::WeightToFee::weight_to_fee( - &Weight::from_parts(10_100_000_000_000, 300_000), - ); - let buy_execution_fee = MultiAsset { - id: Concrete(MultiLocation { parents: 1, interior: Here }), - fun: Fungible(buy_execution_fee_amount), - }; - - let xcm = VersionedXcm::from(Xcm(vec![ - WithdrawAsset { 0: vec![buy_execution_fee.clone()].into() }, - BuyExecution { fees: buy_execution_fee.clone(), weight_limit: Unlimited }, - Transact { require_weight_at_most, origin_kind, call: call_foreign_assets_create }, - RefundSurplus, - DepositAsset { - assets: All.into(), - beneficiary: sov_penpal_on_asset_hub_rococo_as_location, - }, - ])); - - // Send XCM message from penpal => asset_hub_rococo - let sudo_penpal_origin = <PenpalA as Chain>::RuntimeOrigin::root(); - PenpalA::execute_with(|| { - assert_ok!(<PenpalA as PenpalAPallet>::PolkadotXcm::send( - sudo_penpal_origin.clone(), - bx!(assets_para_destination.clone()), - bx!(xcm), - )); - - type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent; - - assert_expected_events!( - PenpalA, - vec![ - RuntimeEvent::PolkadotXcm(pallet_xcm::Event::Sent { .. }) => {}, - ] - ); - }); - - // Receive XCM message in Assets Parachain AssetHubRococo::execute_with(|| { - assert!(<AssetHubRococo as AssetHubRococoPallet>::ForeignAssets::asset_exists( - *foreign_asset1_at_asset_hub_rococo - )); - // 3: Mint foreign asset on asset_hub_rococo: // // (While it might be nice to use batch, @@ -228,11 +152,9 @@ fn swap_locally_on_chain_using_foreign_assets() { type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent; // 3. Mint foreign asset (in reality this should be a teleport or some such) assert_ok!(<AssetHubRococo as AssetHubRococoPallet>::ForeignAssets::mint( - <AssetHubRococo as Chain>::RuntimeOrigin::signed( - sov_penpal_on_asset_hub_rococo.clone().into() - ), - *foreign_asset1_at_asset_hub_rococo, - sov_penpal_on_asset_hub_rococo.clone().into(), + <AssetHubRococo as Chain>::RuntimeOrigin::signed(sov_penpal_on_ahr.clone().into()), + foreign_asset_at_asset_hub_rococo, + sov_penpal_on_ahr.clone().into(), 3_000_000_000_000, )); @@ -243,11 +165,12 @@ fn swap_locally_on_chain_using_foreign_assets() { ] ); + let foreign_asset_at_asset_hub_rococo = Box::new(foreign_asset_at_asset_hub_rococo); // 4. Create pool: assert_ok!(<AssetHubRococo as AssetHubRococoPallet>::AssetConversion::create_pool( <AssetHubRococo as Chain>::RuntimeOrigin::signed(AssetHubRococoSender::get()), asset_native.clone(), - foreign_asset1_at_asset_hub_rococo.clone(), + foreign_asset_at_asset_hub_rococo.clone(), )); assert_expected_events!( @@ -259,16 +182,14 @@ fn swap_locally_on_chain_using_foreign_assets() { // 5. Add liquidity: assert_ok!(<AssetHubRococo as AssetHubRococoPallet>::AssetConversion::add_liquidity( - <AssetHubRococo as Chain>::RuntimeOrigin::signed( - sov_penpal_on_asset_hub_rococo.clone() - ), + <AssetHubRococo as Chain>::RuntimeOrigin::signed(sov_penpal_on_ahr.clone()), asset_native.clone(), - foreign_asset1_at_asset_hub_rococo.clone(), + foreign_asset_at_asset_hub_rococo.clone(), 1_000_000_000_000, 2_000_000_000_000, 0, 0, - sov_penpal_on_asset_hub_rococo.clone().into() + sov_penpal_on_ahr.clone().into() )); assert_expected_events!( @@ -283,7 +204,7 @@ fn swap_locally_on_chain_using_foreign_assets() { // 6. Swap! let path = BoundedVec::<_, _>::truncate_from(vec![ asset_native.clone(), - foreign_asset1_at_asset_hub_rococo.clone(), + foreign_asset_at_asset_hub_rococo.clone(), ]); assert_ok!( @@ -309,15 +230,13 @@ fn swap_locally_on_chain_using_foreign_assets() { // 7. Remove liquidity assert_ok!(<AssetHubRococo as AssetHubRococoPallet>::AssetConversion::remove_liquidity( - <AssetHubRococo as Chain>::RuntimeOrigin::signed( - sov_penpal_on_asset_hub_rococo.clone() - ), + <AssetHubRococo as Chain>::RuntimeOrigin::signed(sov_penpal_on_ahr.clone()), asset_native, - foreign_asset1_at_asset_hub_rococo, + foreign_asset_at_asset_hub_rococo, 1414213562273 - 2_000_000_000, // all but the 2 EDs can't be retrieved. 0, 0, - sov_penpal_on_asset_hub_rococo.clone().into(), + sov_penpal_on_ahr.clone().into(), )); }); } 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 f8017f7a1c5..e64c02f5258 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 @@ -15,7 +15,9 @@ use crate::*; use asset_hub_rococo_runtime::xcm_config::XcmConfig as AssetHubRococoXcmConfig; +use emulated_integration_tests_common::xcm_helpers::non_fee_asset; use rococo_runtime::xcm_config::XcmConfig as RococoXcmConfig; +use rococo_system_emulated_network::penpal_emulated_chain::LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub; fn relay_origin_assertions(t: RelayToSystemParaTest) { type RuntimeEvent = <Rococo as Chain>::RuntimeEvent; @@ -110,6 +112,123 @@ fn para_dest_assertions(t: RelayToSystemParaTest) { ); } +fn penpal_to_ah_foreign_assets_sender_assertions(t: ParaToSystemParaTest) { + type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent; + PenpalA::assert_xcm_pallet_attempted_complete(None); + 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(); + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::Balances( + pallet_balances::Event::Withdraw { who, amount } + ) => { + who: *who == t.sender.account_id, + amount: *amount == t.args.amount, + }, + RuntimeEvent::Assets(pallet_assets::Event::Burned { asset_id, owner, balance }) => { + asset_id: *asset_id == expected_asset_id, + owner: *owner == t.sender.account_id, + balance: *balance == expected_asset_amount, + }, + ] + ); +} + +fn penpal_to_ah_foreign_assets_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()), + ); + let (expected_foreign_asset_id, expected_foreign_asset_amount) = + non_fee_asset(&t.args.assets, t.args.fee_asset_item as usize).unwrap(); + assert_expected_events!( + AssetHubRococo, + vec![ + // native asset reserve transfer for paying fees, withdrawn from Penpal's sov account + RuntimeEvent::Balances( + pallet_balances::Event::Withdraw { who, amount } + ) => { + who: *who == sov_penpal_on_ahr.clone().into(), + amount: *amount == t.args.amount, + }, + RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + who: *who == t.receiver.account_id, + }, + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, amount }) => { + asset_id: *asset_id == expected_foreign_asset_id, + owner: *owner == t.receiver.account_id, + amount: *amount == expected_foreign_asset_amount, + }, + RuntimeEvent::Balances(pallet_balances::Event::Deposit { .. }) => {}, + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); +} + +fn ah_to_penpal_foreign_assets_sender_assertions(t: SystemParaToParaTest) { + type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent; + AssetHubRococo::assert_xcm_pallet_attempted_complete(None); + let (expected_foreign_asset_id, expected_foreign_asset_amount) = + non_fee_asset(&t.args.assets, t.args.fee_asset_item as usize).unwrap(); + assert_expected_events!( + AssetHubRococo, + vec![ + // native asset used for fees is transferred to Parachain's Sovereign account as reserve + 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 + ), + amount: *amount == t.args.amount, + }, + // foreign asset is burned locally as part of teleportation + RuntimeEvent::ForeignAssets(pallet_assets::Event::Burned { asset_id, owner, balance }) => { + asset_id: *asset_id == expected_foreign_asset_id, + owner: *owner == t.sender.account_id, + balance: *balance == expected_foreign_asset_amount, + }, + ] + ); +} + +fn ah_to_penpal_foreign_assets_receiver_assertions(t: SystemParaToParaTest) { + type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent; + 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(); + let checking_account = <PenpalA as PenpalAPallet>::PolkadotXcm::check_account(); + assert_expected_events!( + PenpalA, + vec![ + // checking account burns local asset as part of incoming teleport + RuntimeEvent::Assets(pallet_assets::Event::Burned { asset_id, owner, balance }) => { + asset_id: *asset_id == expected_asset_id, + owner: *owner == checking_account, + balance: *balance == expected_asset_amount, + }, + // local asset is teleported into account of receiver + RuntimeEvent::Assets(pallet_assets::Event::Issued { asset_id, owner, amount }) => { + asset_id: *asset_id == expected_asset_id, + owner: *owner == t.receiver.account_id, + amount: *amount == expected_asset_amount, + }, + // native asset for fee is deposited to receiver + RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + who: *who == t.receiver.account_id, + }, + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); +} + fn relay_limited_teleport_assets(t: RelayToSystemParaTest) -> DispatchResult { <Rococo as RococoPallet>::XcmPallet::limited_teleport_assets( t.signed_origin, @@ -152,6 +271,28 @@ fn system_para_teleport_assets(t: SystemParaToRelayTest) -> DispatchResult { ) } +fn para_to_system_para_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult { + <PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.beneficiary.into()), + bx!(t.args.assets.into()), + t.args.fee_asset_item, + t.args.weight_limit, + ) +} + +fn system_para_to_para_transfer_assets(t: SystemParaToParaTest) -> DispatchResult { + <AssetHubRococo as AssetHubRococoPallet>::PolkadotXcm::transfer_assets( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.beneficiary.into()), + bx!(t.args.assets.into()), + t.args.fee_asset_item, + t.args.weight_limit, + ) +} + /// Limited Teleport of native asset from Relay Chain to the System Parachain should work #[test] fn limited_teleport_native_assets_from_relay_to_system_para_works() { @@ -410,3 +551,199 @@ fn teleport_to_other_system_parachains_works() { (native_asset, amount) ); } + +/// 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() { + let ah_as_seen_by_penpal = PenpalA::sibling_location_of(AssetHubRococo::para_id()); + let asset_location_on_penpal = PenpalLocalTeleportableToAssetHub::get(); + let asset_id_on_penpal = match asset_location_on_penpal.last() { + Some(GeneralIndex(id)) => *id as u32, + _ => unreachable!(), + }; + let asset_owner_on_penpal = PenpalASender::get(); + let foreign_asset_at_asset_hub_rococo = + MultiLocation { parents: 1, interior: X1(Parachain(PenpalA::para_id().into())) } + .appended_with(asset_location_on_penpal) + .unwrap(); + super::penpal_create_foreign_asset_on_asset_hub( + asset_id_on_penpal, + foreign_asset_at_asset_hub_rococo, + ah_as_seen_by_penpal, + false, + asset_owner_on_penpal, + ASSET_MIN_BALANCE * 1_000_000, + ); + let penpal_to_ah_beneficiary_id = AssetHubRococoReceiver::get(); + + let fee_amount_to_send = ASSET_HUB_ROCOCO_ED * 10_000; + let asset_amount_to_send = ASSET_MIN_BALANCE * 1000; + + let penpal_assets: MultiAssets = vec![ + (Parent, fee_amount_to_send).into(), + (asset_location_on_penpal, asset_amount_to_send).into(), + ] + .into(); + let fee_asset_index = penpal_assets + .inner() + .iter() + .position(|r| r == &(Parent, fee_amount_to_send).into()) + .unwrap() as u32; + + // Penpal to AH test args + let penpal_to_ah_test_args = TestContext { + sender: PenpalASender::get(), + receiver: AssetHubRococoReceiver::get(), + args: para_test_args( + ah_as_seen_by_penpal, + penpal_to_ah_beneficiary_id, + asset_amount_to_send, + penpal_assets, + Some(asset_id_on_penpal), + fee_asset_index, + ), + }; + let mut penpal_to_ah = ParaToSystemParaTest::new(penpal_to_ah_test_args); + + let penpal_sender_balance_before = penpal_to_ah.sender.balance; + let ah_receiver_balance_before = penpal_to_ah.receiver.balance; + + let penpal_sender_assets_before = PenpalA::execute_with(|| { + type Assets = <PenpalA as PenpalAPallet>::Assets; + <Assets as Inspect<_>>::balance(asset_id_on_penpal, &PenpalASender::get()) + }); + let ah_receiver_assets_before = AssetHubRococo::execute_with(|| { + type Assets = <AssetHubRococo as AssetHubRococoPallet>::ForeignAssets; + <Assets as Inspect<_>>::balance( + foreign_asset_at_asset_hub_rococo, + &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.assert(); + + let penpal_sender_balance_after = penpal_to_ah.sender.balance; + let ah_receiver_balance_after = penpal_to_ah.receiver.balance; + + let penpal_sender_assets_after = PenpalA::execute_with(|| { + type Assets = <PenpalA as PenpalAPallet>::Assets; + <Assets as Inspect<_>>::balance(asset_id_on_penpal, &PenpalASender::get()) + }); + let ah_receiver_assets_after = AssetHubRococo::execute_with(|| { + type Assets = <AssetHubRococo as AssetHubRococoPallet>::ForeignAssets; + <Assets as Inspect<_>>::balance( + foreign_asset_at_asset_hub_rococo, + &AssetHubRococoReceiver::get(), + ) + }); + + // Sender's balance is reduced + assert!(penpal_sender_balance_after < penpal_sender_balance_before); + // Receiver's balance is increased + assert!(ah_receiver_balance_after > ah_receiver_balance_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!(ah_receiver_balance_after < ah_receiver_balance_before + fee_amount_to_send); + + // Sender's balance is reduced by exact amount + assert_eq!(penpal_sender_assets_before - asset_amount_to_send, penpal_sender_assets_after); + // Receiver's balance is increased by exact amount + assert_eq!(ah_receiver_assets_after, ah_receiver_assets_before + asset_amount_to_send); + + /////////////////////////////////////////////////////////////////////// + // Now test transferring foreign assets back from AssetHub to Penpal // + /////////////////////////////////////////////////////////////////////// + + // Move funds on AH from AHReceiver to AHSender + AssetHubRococo::execute_with(|| { + type ForeignAssets = <AssetHubRococo as AssetHubRococoPallet>::ForeignAssets; + assert_ok!(ForeignAssets::transfer( + <AssetHubRococo as Chain>::RuntimeOrigin::signed(AssetHubRococoReceiver::get()), + foreign_asset_at_asset_hub_rococo, + AssetHubRococoSender::get().into(), + asset_amount_to_send, + )); + }); + + let ah_to_penpal_beneficiary_id = PenpalAReceiver::get(); + let penpal_as_seen_by_ah = AssetHubRococo::sibling_location_of(PenpalA::para_id()); + let ah_assets: MultiAssets = vec![ + (Parent, fee_amount_to_send).into(), + (foreign_asset_at_asset_hub_rococo, asset_amount_to_send).into(), + ] + .into(); + let fee_asset_index = ah_assets + .inner() + .iter() + .position(|r| r == &(Parent, fee_amount_to_send).into()) + .unwrap() as u32; + + // AH to Penpal test args + let ah_to_penpal_test_args = TestContext { + sender: AssetHubRococoSender::get(), + receiver: PenpalAReceiver::get(), + args: para_test_args( + penpal_as_seen_by_ah, + ah_to_penpal_beneficiary_id, + asset_amount_to_send, + ah_assets, + Some(asset_id_on_penpal), + fee_asset_index, + ), + }; + let mut ah_to_penpal = SystemParaToParaTest::new(ah_to_penpal_test_args); + + let ah_sender_balance_before = ah_to_penpal.sender.balance; + let penpal_receiver_balance_before = ah_to_penpal.receiver.balance; + + let ah_sender_assets_before = AssetHubRococo::execute_with(|| { + type ForeignAssets = <AssetHubRococo as AssetHubRococoPallet>::ForeignAssets; + <ForeignAssets as Inspect<_>>::balance( + foreign_asset_at_asset_hub_rococo, + &AssetHubRococoSender::get(), + ) + }); + let penpal_receiver_assets_before = PenpalA::execute_with(|| { + type Assets = <PenpalA as PenpalAPallet>::Assets; + <Assets as Inspect<_>>::balance(asset_id_on_penpal, &PenpalAReceiver::get()) + }); + + 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.assert(); + + let ah_sender_balance_after = ah_to_penpal.sender.balance; + let penpal_receiver_balance_after = ah_to_penpal.receiver.balance; + + let ah_sender_assets_after = AssetHubRococo::execute_with(|| { + type ForeignAssets = <AssetHubRococo as AssetHubRococoPallet>::ForeignAssets; + <ForeignAssets as Inspect<_>>::balance( + foreign_asset_at_asset_hub_rococo, + &AssetHubRococoSender::get(), + ) + }); + let penpal_receiver_assets_after = PenpalA::execute_with(|| { + type Assets = <PenpalA as PenpalAPallet>::Assets; + <Assets as Inspect<_>>::balance(asset_id_on_penpal, &PenpalAReceiver::get()) + }); + + // Sender's balance is reduced + assert!(ah_sender_balance_after < ah_sender_balance_before); + // Receiver's balance is increased + assert!(penpal_receiver_balance_after > penpal_receiver_balance_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!(penpal_receiver_balance_after < penpal_receiver_balance_before + fee_amount_to_send); + + // Sender's balance is reduced by exact amount + assert_eq!(ah_sender_assets_before - asset_amount_to_send, ah_sender_assets_after); + // Receiver's balance is increased by exact amount + assert_eq!(penpal_receiver_assets_after, penpal_receiver_assets_before + asset_amount_to_send); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml index 74e6660d4d4..cdaa65f02cb 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml @@ -38,5 +38,4 @@ asset-test-utils = { path = "../../../../../runtimes/assets/test-utils" } cumulus-pallet-dmp-queue = { default-features = false, path = "../../../../../../pallets/dmp-queue" } cumulus-pallet-parachain-system = { default-features = false, path = "../../../../../../pallets/parachain-system" } emulated-integration-tests-common = { path = "../../../common", default-features = false } -penpal-runtime = { path = "../../../../../runtimes/testing/penpal" } westend-system-emulated-network = { path = "../../../networks/westend-system" } 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 0c9de89c5f9..d2127b63048 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 @@ -19,3 +19,11 @@ mod set_xcm_versions; mod swap; mod teleport; mod treasury; + +use crate::*; +emulated_integration_tests_common::include_penpal_create_foreign_asset_on_asset_hub!( + PenpalB, + AssetHubWestend, + WESTEND_ED, + parachains_common::westend::fee::WeightToFee +); 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 1a69a4f3f71..7472445c4ba 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 @@ -15,8 +15,8 @@ use crate::*; use asset_hub_westend_runtime::xcm_config::XcmConfig as AssetHubWestendXcmConfig; -use penpal_runtime::xcm_config::XcmConfig as PenpalWestendXcmConfig; use westend_runtime::xcm_config::XcmConfig as WestendXcmConfig; +use westend_system_emulated_network::penpal_emulated_chain::XcmConfig as PenpalWestendXcmConfig; fn relay_to_para_sender_assertions(t: RelayToParaTest) { type RuntimeEvent = <Westend as Chain>::RuntimeEvent; @@ -162,7 +162,7 @@ fn system_para_to_para_assets_receiver_assertions<Test>(_: Test) { ); } -fn relay_to_para_limited_reserve_transfer_assets(t: RelayToParaTest) -> DispatchResult { +fn relay_to_para_reserve_transfer_assets(t: RelayToParaTest) -> DispatchResult { <Westend as WestendPallet>::XcmPallet::limited_reserve_transfer_assets( t.signed_origin, bx!(t.args.dest.into()), @@ -173,7 +173,7 @@ fn relay_to_para_limited_reserve_transfer_assets(t: RelayToParaTest) -> Dispatch ) } -fn system_para_to_para_limited_reserve_transfer_assets(t: SystemParaToParaTest) -> DispatchResult { +fn system_para_to_para_reserve_transfer_assets(t: SystemParaToParaTest) -> DispatchResult { <AssetHubWestend as AssetHubWestendPallet>::PolkadotXcm::limited_reserve_transfer_assets( t.signed_origin, bx!(t.args.dest.into()), @@ -184,7 +184,7 @@ fn system_para_to_para_limited_reserve_transfer_assets(t: SystemParaToParaTest) ) } -fn para_to_system_para_limited_reserve_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult { +fn para_to_system_para_reserve_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult { <PenpalB as PenpalBPallet>::PolkadotXcm::limited_reserve_transfer_assets( t.signed_origin, bx!(t.args.dest.into()), @@ -284,7 +284,7 @@ fn reserve_transfer_native_asset_from_relay_to_para() { test.set_assertion::<Westend>(relay_to_para_sender_assertions); test.set_assertion::<PenpalB>(para_receiver_assertions); - test.set_dispatchable::<Westend>(relay_to_para_limited_reserve_transfer_assets); + test.set_dispatchable::<Westend>(relay_to_para_reserve_transfer_assets); test.assert(); let delivery_fees = Westend::execute_with(|| { @@ -328,7 +328,7 @@ fn reserve_transfer_native_asset_from_system_para_to_para() { test.set_assertion::<AssetHubWestend>(system_para_to_para_sender_assertions); test.set_assertion::<PenpalB>(para_receiver_assertions); - test.set_dispatchable::<AssetHubWestend>(system_para_to_para_limited_reserve_transfer_assets); + test.set_dispatchable::<AssetHubWestend>(system_para_to_para_reserve_transfer_assets); test.assert(); let sender_balance_after = test.sender.balance; @@ -379,7 +379,7 @@ fn reserve_transfer_native_asset_from_para_to_system_para() { test.set_assertion::<PenpalB>(para_to_system_para_sender_assertions); test.set_assertion::<AssetHubWestend>(para_to_system_para_receiver_assertions); - test.set_dispatchable::<PenpalB>(para_to_system_para_limited_reserve_transfer_assets); + test.set_dispatchable::<PenpalB>(para_to_system_para_reserve_transfer_assets); test.assert(); let sender_balance_after = test.sender.balance; @@ -474,7 +474,7 @@ fn reserve_transfer_assets_from_system_para_to_para() { test.set_assertion::<AssetHubWestend>(system_para_to_para_assets_sender_assertions); test.set_assertion::<PenpalB>(system_para_to_para_assets_receiver_assertions); - test.set_dispatchable::<AssetHubWestend>(system_para_to_para_limited_reserve_transfer_assets); + test.set_dispatchable::<AssetHubWestend>(system_para_to_para_reserve_transfer_assets); test.assert(); let sender_balance_after = test.sender.balance; 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 aca60f68802..1fa77bd4565 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 @@ -14,6 +14,7 @@ // limitations under the License. use crate::*; +use westend_system_emulated_network::penpal_emulated_chain::LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub; #[test] fn swap_locally_on_chain_using_local_assets() { @@ -107,113 +108,37 @@ fn swap_locally_on_chain_using_local_assets() { #[test] fn swap_locally_on_chain_using_foreign_assets() { - use frame_support::weights::WeightToFee; - let asset_native = Box::new(asset_hub_westend_runtime::xcm_config::WestendLocation::get()); - - let foreign_asset1_at_asset_hub_westend = Box::new(MultiLocation { - parents: 1, - interior: X3( - Parachain(PenpalB::para_id().into()), - PalletInstance(ASSETS_PALLET_ID), - GeneralIndex(ASSET_ID.into()), - ), - }); - - let assets_para_destination: VersionedMultiLocation = - MultiLocation { parents: 1, interior: X1(Parachain(AssetHubWestend::para_id().into())) } - .into(); - - let penpal_location = - MultiLocation { parents: 1, interior: X1(Parachain(PenpalB::para_id().into())) }; - - // 1. Create asset on penpal: - PenpalB::execute_with(|| { - assert_ok!(<PenpalB as PenpalBPallet>::Assets::create( - <PenpalB as Chain>::RuntimeOrigin::signed(PenpalBSender::get()), - ASSET_ID.into(), - PenpalBSender::get().into(), - 1000, - )); - - assert!(<PenpalB as PenpalBPallet>::Assets::asset_exists(ASSET_ID)); - }); - - // 2. Create foreign asset on asset_hub_westend: - - let require_weight_at_most = Weight::from_parts(1_100_000_000_000, 30_000); - let origin_kind = OriginKind::Xcm; - let sov_penpal_on_asset_hub_westend = AssetHubWestend::sovereign_account_id_of(penpal_location); - - AssetHubWestend::fund_accounts(vec![ - (AssetHubWestendSender::get().into(), 5_000_000 * WESTEND_ED), - (sov_penpal_on_asset_hub_westend.clone().into(), 1000_000_000_000_000_000 * WESTEND_ED), - ]); - - let sov_penpal_on_asset_hub_westend_as_location: MultiLocation = MultiLocation { - parents: 0, - interior: X1(AccountId32Junction { - network: None, - id: sov_penpal_on_asset_hub_westend.clone().into(), - }), + let ah_as_seen_by_penpal = PenpalB::sibling_location_of(AssetHubWestend::para_id()); + let asset_location_on_penpal = PenpalLocalTeleportableToAssetHub::get(); + let asset_id_on_penpal = match asset_location_on_penpal.last() { + Some(GeneralIndex(id)) => *id as u32, + _ => unreachable!(), }; - - let call_foreign_assets_create = - <AssetHubWestend as Chain>::RuntimeCall::ForeignAssets(pallet_assets::Call::< - <AssetHubWestend as Chain>::Runtime, - Instance2, - >::create { - id: *foreign_asset1_at_asset_hub_westend, - min_balance: 1000, - admin: sov_penpal_on_asset_hub_westend.clone().into(), - }) - .encode() - .into(); - - let buy_execution_fee_amount = parachains_common::westend::fee::WeightToFee::weight_to_fee( - &Weight::from_parts(10_100_000_000_000, 300_000), + let asset_owner_on_penpal = PenpalBSender::get(); + let foreign_asset_at_asset_hub_westend = + MultiLocation { parents: 1, interior: X1(Parachain(PenpalB::para_id().into())) } + .appended_with(asset_location_on_penpal) + .unwrap(); + + // 1. Create asset on penpal and, 2. Create foreign asset on asset_hub_westend + super::penpal_create_foreign_asset_on_asset_hub( + asset_id_on_penpal, + foreign_asset_at_asset_hub_westend, + ah_as_seen_by_penpal, + true, + asset_owner_on_penpal, + ASSET_MIN_BALANCE * 1_000_000, ); - let buy_execution_fee = MultiAsset { - id: Concrete(MultiLocation { parents: 1, interior: Here }), - fun: Fungible(buy_execution_fee_amount), - }; - - let xcm = VersionedXcm::from(Xcm(vec![ - WithdrawAsset { 0: vec![buy_execution_fee.clone()].into() }, - BuyExecution { fees: buy_execution_fee.clone(), weight_limit: Unlimited }, - Transact { require_weight_at_most, origin_kind, call: call_foreign_assets_create }, - RefundSurplus, - DepositAsset { - assets: All.into(), - beneficiary: sov_penpal_on_asset_hub_westend_as_location, - }, - ])); - - // Send XCM message from penpal => asset_hub_westend - let sudo_penpal_origin = <PenpalB as Chain>::RuntimeOrigin::root(); - PenpalB::execute_with(|| { - assert_ok!(<PenpalB as PenpalBPallet>::PolkadotXcm::send( - sudo_penpal_origin.clone(), - bx!(assets_para_destination.clone()), - bx!(xcm), - )); - - type RuntimeEvent = <PenpalB as Chain>::RuntimeEvent; - assert_expected_events!( - PenpalB, - vec![ - RuntimeEvent::PolkadotXcm(pallet_xcm::Event::Sent { .. }) => {}, - ] - ); - }); + let penpal_as_seen_by_ah = AssetHubWestend::sibling_location_of(PenpalB::para_id()); + let sov_penpal_on_ahw = AssetHubWestend::sovereign_account_id_of(penpal_as_seen_by_ah); + AssetHubWestend::fund_accounts(vec![ + (AssetHubWestendSender::get().into(), 5_000_000 * WESTEND_ED), /* An account to swap dot + * for something else. */ + ]); - // Receive XCM message in Assets Parachain in the next block. AssetHubWestend::execute_with(|| { - assert!(<AssetHubWestend as AssetHubWestendPallet>::ForeignAssets::asset_exists( - *foreign_asset1_at_asset_hub_westend - )); - // 3: Mint foreign asset on asset_hub_westend: // // (While it might be nice to use batch, @@ -222,11 +147,9 @@ fn swap_locally_on_chain_using_foreign_assets() { type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent; // 3. Mint foreign asset (in reality this should be a teleport or some such) assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::ForeignAssets::mint( - <AssetHubWestend as Chain>::RuntimeOrigin::signed( - sov_penpal_on_asset_hub_westend.clone().into() - ), - *foreign_asset1_at_asset_hub_westend, - sov_penpal_on_asset_hub_westend.clone().into(), + <AssetHubWestend as Chain>::RuntimeOrigin::signed(sov_penpal_on_ahw.clone().into()), + foreign_asset_at_asset_hub_westend, + sov_penpal_on_ahw.clone().into(), 3_000_000_000_000, )); @@ -237,11 +160,12 @@ fn swap_locally_on_chain_using_foreign_assets() { ] ); + let foreign_asset_at_asset_hub_westend = Box::new(foreign_asset_at_asset_hub_westend); // 4. Create pool: assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::AssetConversion::create_pool( <AssetHubWestend as Chain>::RuntimeOrigin::signed(AssetHubWestendSender::get()), asset_native.clone(), - foreign_asset1_at_asset_hub_westend.clone(), + foreign_asset_at_asset_hub_westend.clone(), )); assert_expected_events!( @@ -253,16 +177,14 @@ fn swap_locally_on_chain_using_foreign_assets() { // 5. Add liquidity: assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::AssetConversion::add_liquidity( - <AssetHubWestend as Chain>::RuntimeOrigin::signed( - sov_penpal_on_asset_hub_westend.clone() - ), + <AssetHubWestend as Chain>::RuntimeOrigin::signed(sov_penpal_on_ahw.clone()), asset_native.clone(), - foreign_asset1_at_asset_hub_westend.clone(), + foreign_asset_at_asset_hub_westend.clone(), 1_000_000_000_000, 2_000_000_000_000, 0, 0, - sov_penpal_on_asset_hub_westend.clone().into() + sov_penpal_on_ahw.clone().into() )); assert_expected_events!( @@ -277,7 +199,7 @@ fn swap_locally_on_chain_using_foreign_assets() { // 6. Swap! let path = BoundedVec::<_, _>::truncate_from(vec![ asset_native.clone(), - foreign_asset1_at_asset_hub_westend.clone(), + foreign_asset_at_asset_hub_westend.clone(), ]); assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::AssetConversion::swap_exact_tokens_for_tokens( @@ -301,15 +223,13 @@ fn swap_locally_on_chain_using_foreign_assets() { // 7. Remove liquidity assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::AssetConversion::remove_liquidity( - <AssetHubWestend as Chain>::RuntimeOrigin::signed( - sov_penpal_on_asset_hub_westend.clone() - ), + <AssetHubWestend as Chain>::RuntimeOrigin::signed(sov_penpal_on_ahw.clone()), asset_native, - foreign_asset1_at_asset_hub_westend, + foreign_asset_at_asset_hub_westend, 1414213562273 - 2_000_000_000, // all but the 2 EDs can't be retrieved. 0, 0, - sov_penpal_on_asset_hub_westend.clone().into(), + sov_penpal_on_ahw.clone().into(), )); }); } 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 2c43bb9d801..2dd68ae3a83 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 @@ -15,7 +15,9 @@ use crate::*; use asset_hub_westend_runtime::xcm_config::XcmConfig as AssetHubWestendXcmConfig; +use emulated_integration_tests_common::xcm_helpers::non_fee_asset; use westend_runtime::xcm_config::XcmConfig as WestendXcmConfig; +use westend_system_emulated_network::penpal_emulated_chain::LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub; fn relay_origin_assertions(t: RelayToSystemParaTest) { type RuntimeEvent = <Westend as Chain>::RuntimeEvent; @@ -110,6 +112,123 @@ fn para_dest_assertions(t: RelayToSystemParaTest) { ); } +fn penpal_to_ah_foreign_assets_sender_assertions(t: ParaToSystemParaTest) { + type RuntimeEvent = <PenpalB as Chain>::RuntimeEvent; + PenpalB::assert_xcm_pallet_attempted_complete(None); + 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(); + assert_expected_events!( + PenpalB, + vec![ + RuntimeEvent::Balances( + pallet_balances::Event::Withdraw { who, amount } + ) => { + who: *who == t.sender.account_id, + amount: *amount == t.args.amount, + }, + RuntimeEvent::Assets(pallet_assets::Event::Burned { asset_id, owner, balance }) => { + asset_id: *asset_id == expected_asset_id, + owner: *owner == t.sender.account_id, + balance: *balance == expected_asset_amount, + }, + ] + ); +} + +fn penpal_to_ah_foreign_assets_receiver_assertions(t: ParaToSystemParaTest) { + type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent; + let sov_penpal_on_ahr = AssetHubWestend::sovereign_account_id_of( + AssetHubWestend::sibling_location_of(PenpalB::para_id()), + ); + let (expected_foreign_asset_id, expected_foreign_asset_amount) = + non_fee_asset(&t.args.assets, t.args.fee_asset_item as usize).unwrap(); + assert_expected_events!( + AssetHubWestend, + vec![ + // native asset reserve transfer for paying fees, withdrawn from Penpal's sov account + RuntimeEvent::Balances( + pallet_balances::Event::Withdraw { who, amount } + ) => { + who: *who == sov_penpal_on_ahr.clone().into(), + amount: *amount == t.args.amount, + }, + RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + who: *who == t.receiver.account_id, + }, + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, amount }) => { + asset_id: *asset_id == expected_foreign_asset_id, + owner: *owner == t.receiver.account_id, + amount: *amount == expected_foreign_asset_amount, + }, + RuntimeEvent::Balances(pallet_balances::Event::Deposit { .. }) => {}, + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); +} + +fn ah_to_penpal_foreign_assets_sender_assertions(t: SystemParaToParaTest) { + type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent; + AssetHubWestend::assert_xcm_pallet_attempted_complete(None); + let (expected_foreign_asset_id, expected_foreign_asset_amount) = + non_fee_asset(&t.args.assets, t.args.fee_asset_item as usize).unwrap(); + assert_expected_events!( + AssetHubWestend, + vec![ + // native asset used for fees is transferred to Parachain's Sovereign account as reserve + 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 + ), + amount: *amount == t.args.amount, + }, + // foreign asset is burned locally as part of teleportation + RuntimeEvent::ForeignAssets(pallet_assets::Event::Burned { asset_id, owner, balance }) => { + asset_id: *asset_id == expected_foreign_asset_id, + owner: *owner == t.sender.account_id, + balance: *balance == expected_foreign_asset_amount, + }, + ] + ); +} + +fn ah_to_penpal_foreign_assets_receiver_assertions(t: SystemParaToParaTest) { + type RuntimeEvent = <PenpalB as Chain>::RuntimeEvent; + 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(); + let checking_account = <PenpalB as PenpalBPallet>::PolkadotXcm::check_account(); + assert_expected_events!( + PenpalB, + vec![ + // checking account burns local asset as part of incoming teleport + RuntimeEvent::Assets(pallet_assets::Event::Burned { asset_id, owner, balance }) => { + asset_id: *asset_id == expected_asset_id, + owner: *owner == checking_account, + balance: *balance == expected_asset_amount, + }, + // local asset is teleported into account of receiver + RuntimeEvent::Assets(pallet_assets::Event::Issued { asset_id, owner, amount }) => { + asset_id: *asset_id == expected_asset_id, + owner: *owner == t.receiver.account_id, + amount: *amount == expected_asset_amount, + }, + // native asset for fee is deposited to receiver + RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + who: *who == t.receiver.account_id, + }, + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); +} + fn relay_limited_teleport_assets(t: RelayToSystemParaTest) -> DispatchResult { <Westend as WestendPallet>::XcmPallet::limited_teleport_assets( t.signed_origin, @@ -152,6 +271,28 @@ fn system_para_teleport_assets(t: SystemParaToRelayTest) -> DispatchResult { ) } +fn system_para_to_para_transfer_assets(t: SystemParaToParaTest) -> DispatchResult { + <AssetHubWestend as AssetHubWestendPallet>::PolkadotXcm::transfer_assets( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.beneficiary.into()), + bx!(t.args.assets.into()), + t.args.fee_asset_item, + t.args.weight_limit, + ) +} + +fn para_to_system_para_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult { + <PenpalB as PenpalBPallet>::PolkadotXcm::transfer_assets( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.beneficiary.into()), + bx!(t.args.assets.into()), + t.args.fee_asset_item, + t.args.weight_limit, + ) +} + /// Limited Teleport of native asset from Relay Chain to the System Parachain should work #[test] fn limited_teleport_native_assets_from_relay_to_system_para_works() { @@ -410,3 +551,199 @@ fn teleport_to_other_system_parachains_works() { (native_asset, amount) ); } + +/// 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() { + let ah_as_seen_by_penpal = PenpalB::sibling_location_of(AssetHubWestend::para_id()); + let asset_location_on_penpal = PenpalLocalTeleportableToAssetHub::get(); + let asset_id_on_penpal = match asset_location_on_penpal.last() { + Some(GeneralIndex(id)) => *id as u32, + _ => unreachable!(), + }; + let asset_owner_on_penpal = PenpalBSender::get(); + let foreign_asset_at_asset_hub_westend = + MultiLocation { parents: 1, interior: X1(Parachain(PenpalB::para_id().into())) } + .appended_with(asset_location_on_penpal) + .unwrap(); + super::penpal_create_foreign_asset_on_asset_hub( + asset_id_on_penpal, + foreign_asset_at_asset_hub_westend, + ah_as_seen_by_penpal, + false, + asset_owner_on_penpal, + ASSET_MIN_BALANCE * 1_000_000, + ); + let penpal_to_ah_beneficiary_id = AssetHubWestendReceiver::get(); + + let fee_amount_to_send = ASSET_HUB_WESTEND_ED * 1000; + let asset_amount_to_send = ASSET_MIN_BALANCE * 1000; + + let penpal_assets: MultiAssets = vec![ + (Parent, fee_amount_to_send).into(), + (asset_location_on_penpal, asset_amount_to_send).into(), + ] + .into(); + let fee_asset_index = penpal_assets + .inner() + .iter() + .position(|r| r == &(Parent, fee_amount_to_send).into()) + .unwrap() as u32; + + // Penpal to AH test args + let penpal_to_ah_test_args = TestContext { + sender: PenpalBSender::get(), + receiver: AssetHubWestendReceiver::get(), + args: para_test_args( + ah_as_seen_by_penpal, + penpal_to_ah_beneficiary_id, + asset_amount_to_send, + penpal_assets, + Some(asset_id_on_penpal), + fee_asset_index, + ), + }; + let mut penpal_to_ah = ParaToSystemParaTest::new(penpal_to_ah_test_args); + + let penpal_sender_balance_before = penpal_to_ah.sender.balance; + let ah_receiver_balance_before = penpal_to_ah.receiver.balance; + + let penpal_sender_assets_before = PenpalB::execute_with(|| { + type Assets = <PenpalB as PenpalBPallet>::Assets; + <Assets as Inspect<_>>::balance(asset_id_on_penpal, &PenpalBSender::get()) + }); + let ah_receiver_assets_before = AssetHubWestend::execute_with(|| { + type Assets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets; + <Assets as Inspect<_>>::balance( + foreign_asset_at_asset_hub_westend, + &AssetHubWestendReceiver::get(), + ) + }); + + penpal_to_ah.set_assertion::<PenpalB>(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::<PenpalB>(para_to_system_para_transfer_assets); + penpal_to_ah.assert(); + + let penpal_sender_balance_after = penpal_to_ah.sender.balance; + let ah_receiver_balance_after = penpal_to_ah.receiver.balance; + + let penpal_sender_assets_after = PenpalB::execute_with(|| { + type Assets = <PenpalB as PenpalBPallet>::Assets; + <Assets as Inspect<_>>::balance(asset_id_on_penpal, &PenpalBSender::get()) + }); + let ah_receiver_assets_after = AssetHubWestend::execute_with(|| { + type Assets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets; + <Assets as Inspect<_>>::balance( + foreign_asset_at_asset_hub_westend, + &AssetHubWestendReceiver::get(), + ) + }); + + // Sender's balance is reduced + assert!(penpal_sender_balance_after < penpal_sender_balance_before); + // Receiver's balance is increased + assert!(ah_receiver_balance_after > ah_receiver_balance_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!(ah_receiver_balance_after < ah_receiver_balance_before + fee_amount_to_send); + + // Sender's balance is reduced by exact amount + assert_eq!(penpal_sender_assets_before - asset_amount_to_send, penpal_sender_assets_after); + // Receiver's balance is increased by exact amount + assert_eq!(ah_receiver_assets_after, ah_receiver_assets_before + asset_amount_to_send); + + /////////////////////////////////////////////////////////////////////// + // Now test transferring foreign assets back from AssetHub to Penpal // + /////////////////////////////////////////////////////////////////////// + + // Move funds on AH from AHReceiver to AHSender + AssetHubWestend::execute_with(|| { + type ForeignAssets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets; + assert_ok!(ForeignAssets::transfer( + <AssetHubWestend as Chain>::RuntimeOrigin::signed(AssetHubWestendReceiver::get()), + foreign_asset_at_asset_hub_westend, + AssetHubWestendSender::get().into(), + asset_amount_to_send, + )); + }); + + let ah_to_penpal_beneficiary_id = PenpalBReceiver::get(); + let penpal_as_seen_by_ah = AssetHubWestend::sibling_location_of(PenpalB::para_id()); + let ah_assets: MultiAssets = vec![ + (Parent, fee_amount_to_send).into(), + (foreign_asset_at_asset_hub_westend, asset_amount_to_send).into(), + ] + .into(); + let fee_asset_index = ah_assets + .inner() + .iter() + .position(|r| r == &(Parent, fee_amount_to_send).into()) + .unwrap() as u32; + + // AH to Penpal test args + let ah_to_penpal_test_args = TestContext { + sender: AssetHubWestendSender::get(), + receiver: PenpalBReceiver::get(), + args: para_test_args( + penpal_as_seen_by_ah, + ah_to_penpal_beneficiary_id, + asset_amount_to_send, + ah_assets, + Some(asset_id_on_penpal), + fee_asset_index, + ), + }; + let mut ah_to_penpal = SystemParaToParaTest::new(ah_to_penpal_test_args); + + let ah_sender_balance_before = ah_to_penpal.sender.balance; + let penpal_receiver_balance_before = ah_to_penpal.receiver.balance; + + let ah_sender_assets_before = AssetHubWestend::execute_with(|| { + type ForeignAssets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets; + <ForeignAssets as Inspect<_>>::balance( + foreign_asset_at_asset_hub_westend, + &AssetHubWestendSender::get(), + ) + }); + let penpal_receiver_assets_before = PenpalB::execute_with(|| { + type Assets = <PenpalB as PenpalBPallet>::Assets; + <Assets as Inspect<_>>::balance(asset_id_on_penpal, &PenpalBReceiver::get()) + }); + + ah_to_penpal.set_assertion::<AssetHubWestend>(ah_to_penpal_foreign_assets_sender_assertions); + ah_to_penpal.set_assertion::<PenpalB>(ah_to_penpal_foreign_assets_receiver_assertions); + ah_to_penpal.set_dispatchable::<AssetHubWestend>(system_para_to_para_transfer_assets); + ah_to_penpal.assert(); + + let ah_sender_balance_after = ah_to_penpal.sender.balance; + let penpal_receiver_balance_after = ah_to_penpal.receiver.balance; + + let ah_sender_assets_after = AssetHubWestend::execute_with(|| { + type ForeignAssets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets; + <ForeignAssets as Inspect<_>>::balance( + foreign_asset_at_asset_hub_westend, + &AssetHubWestendSender::get(), + ) + }); + let penpal_receiver_assets_after = PenpalB::execute_with(|| { + type Assets = <PenpalB as PenpalBPallet>::Assets; + <Assets as Inspect<_>>::balance(asset_id_on_penpal, &PenpalBReceiver::get()) + }); + + // Sender's balance is reduced + assert!(ah_sender_balance_after < ah_sender_balance_before); + // Receiver's balance is increased + assert!(penpal_receiver_balance_after > penpal_receiver_balance_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!(penpal_receiver_balance_after < penpal_receiver_balance_before + fee_amount_to_send); + + // Sender's balance is reduced by exact amount + assert_eq!(ah_sender_assets_before - asset_amount_to_send, ah_sender_assets_after); + // Receiver's balance is increased by exact amount + assert_eq!(penpal_receiver_assets_after, penpal_receiver_assets_before + asset_amount_to_send); +} 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 46bca804a9c..86000a20b5b 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -1359,6 +1359,55 @@ impl_runtime_apis! { ParentThen(Parachain(random_para_id).into()).into(), )) } + + fn set_up_complex_asset_transfer( + ) -> Option<(MultiAssets, u32, MultiLocation, Box<dyn FnOnce()>)> { + // Transfer to Relay some local AH asset (local-reserve-transfer) while paying + // fees using teleported native token. + // (We don't care that Relay doesn't accept incoming unknown AH local asset) + let dest = Parent.into(); + + let fee_amount = EXISTENTIAL_DEPOSIT; + let fee_asset: MultiAsset = (MultiLocation::parent(), fee_amount).into(); + + let who = frame_benchmarking::whitelisted_caller(); + // Give some multiple of the existential deposit + let balance = fee_amount + EXISTENTIAL_DEPOSIT * 1000; + let _ = <Balances as frame_support::traits::Currency<_>>::make_free_balance_be( + &who, balance, + ); + // verify initial balance + assert_eq!(Balances::free_balance(&who), balance); + + // set up local asset + let asset_amount = 10u128; + let initial_asset_amount = asset_amount * 10; + let (asset_id, _, _) = pallet_assets::benchmarking::create_default_minted_asset::< + Runtime, + pallet_assets::Instance1 + >(true, initial_asset_amount); + let asset_location = MultiLocation::new( + 0, + X2(PalletInstance(50), GeneralIndex(u32::from(asset_id).into())) + ); + let transfer_asset: MultiAsset = (asset_location, asset_amount).into(); + + let assets: MultiAssets = vec![fee_asset.clone(), transfer_asset].into(); + let fee_index = if assets.get(0).unwrap().eq(&fee_asset) { 0 } else { 1 }; + + // verify transferred successfully + let verify = Box::new(move || { + // verify native balance after transfer, decreased by transferred fee amount + // (plus transport fees) + assert!(Balances::free_balance(&who) <= balance - fee_amount); + // verify asset balance decreased by exactly transferred amount + assert_eq!( + Assets::balance(asset_id.into(), &who), + initial_asset_amount - asset_amount, + ); + }); + Some((assets, fee_index as u32, dest, verify)) + } } impl XcmBridgeHubRouterConfig<ToWestendXcmRouterInstance> for Runtime { diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm.rs index afe85fdaf28..f8820bbb58c 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-11-10, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-12-05, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-yprdrvc7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-r43aesjn-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: @@ -64,37 +64,95 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 24_498_000 picoseconds. - Weight::from_parts(25_385_000, 0) + // Minimum execution time: 25_003_000 picoseconds. + Weight::from_parts(25_800_000, 0) .saturating_add(Weight::from_parts(0, 3610)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w:0) - /// Proof: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w:0) /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn teleport_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `39` - // Estimated: `3504` - // Minimum execution time: 19_746_000 picoseconds. - Weight::from_parts(20_535_000, 0) - .saturating_add(Weight::from_parts(0, 3504)) - .saturating_add(T::DbWeight::get().reads(2)) + // Measured: `145` + // Estimated: `3610` + // Minimum execution time: 88_832_000 picoseconds. + Weight::from_parts(90_491_000, 0) + .saturating_add(Weight::from_parts(0, 3610)) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w:0) - /// Proof: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w:0) /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `XcmpQueue::DeliveryFeeFactor` (r:1 w:0) + /// Proof: `XcmpQueue::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::RelevantMessagingState` (r:1 w:0) + /// Proof: `ParachainSystem::RelevantMessagingState` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:1) + /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `XcmpQueue::OutboundXcmpMessages` (r:0 w:1) + /// Proof: `XcmpQueue::OutboundXcmpMessages` (`max_values`: None, `max_size`: None, mode: `Measured`) fn reserve_transfer_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `39` - // Estimated: `3504` - // Minimum execution time: 15_059_000 picoseconds. - Weight::from_parts(15_386_000, 0) - .saturating_add(Weight::from_parts(0, 3504)) - .saturating_add(T::DbWeight::get().reads(2)) + // Measured: `400` + // Estimated: `6196` + // Minimum execution time: 138_911_000 picoseconds. + Weight::from_parts(142_483_000, 0) + .saturating_add(Weight::from_parts(0, 6196)) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().writes(5)) + } + /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) + /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn transfer_assets() -> Weight { + // Proof Size summary in bytes: + // Measured: `496` + // Estimated: `6208` + // Minimum execution time: 146_932_000 picoseconds. + Weight::from_parts(153_200_000, 0) + .saturating_add(Weight::from_parts(0, 6208)) + .saturating_add(T::DbWeight::get().reads(12)) + .saturating_add(T::DbWeight::get().writes(7)) } /// Storage: `Benchmark::Override` (r:0 w:0) /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -112,8 +170,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_108_000 picoseconds. - Weight::from_parts(7_458_000, 0) + // Minimum execution time: 7_081_000 picoseconds. + Weight::from_parts(7_397_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -123,8 +181,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_205_000 picoseconds. - Weight::from_parts(2_360_000, 0) + // Minimum execution time: 2_007_000 picoseconds. + Weight::from_parts(2_183_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -150,8 +208,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 29_099_000 picoseconds. - Weight::from_parts(29_580_000, 0) + // Minimum execution time: 28_790_000 picoseconds. + Weight::from_parts(29_767_000, 0) .saturating_add(Weight::from_parts(0, 3610)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(5)) @@ -176,8 +234,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `363` // Estimated: `3828` - // Minimum execution time: 31_161_000 picoseconds. - Weight::from_parts(31_933_000, 0) + // Minimum execution time: 30_951_000 picoseconds. + Weight::from_parts(31_804_000, 0) .saturating_add(Weight::from_parts(0, 3828)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) @@ -188,8 +246,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_158_000 picoseconds. - Weight::from_parts(2_316_000, 0) + // Minimum execution time: 2_164_000 picoseconds. + Weight::from_parts(2_311_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -199,8 +257,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `162` // Estimated: `11052` - // Minimum execution time: 16_934_000 picoseconds. - Weight::from_parts(17_655_000, 0) + // Minimum execution time: 16_906_000 picoseconds. + Weight::from_parts(17_612_000, 0) .saturating_add(Weight::from_parts(0, 11052)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) @@ -211,8 +269,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `166` // Estimated: `11056` - // Minimum execution time: 17_658_000 picoseconds. - Weight::from_parts(17_973_000, 0) + // Minimum execution time: 17_443_000 picoseconds. + Weight::from_parts(18_032_000, 0) .saturating_add(Weight::from_parts(0, 11056)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) @@ -223,8 +281,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `173` // Estimated: `13538` - // Minimum execution time: 18_673_000 picoseconds. - Weight::from_parts(19_027_000, 0) + // Minimum execution time: 18_992_000 picoseconds. + Weight::from_parts(19_464_000, 0) .saturating_add(Weight::from_parts(0, 13538)) .saturating_add(T::DbWeight::get().reads(5)) } @@ -246,8 +304,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `212` // Estimated: `6152` - // Minimum execution time: 27_171_000 picoseconds. - Weight::from_parts(27_802_000, 0) + // Minimum execution time: 28_011_000 picoseconds. + Weight::from_parts(28_716_000, 0) .saturating_add(Weight::from_parts(0, 6152)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) @@ -258,8 +316,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `206` // Estimated: `8621` - // Minimum execution time: 9_423_000 picoseconds. - Weight::from_parts(9_636_000, 0) + // Minimum execution time: 9_533_000 picoseconds. + Weight::from_parts(9_856_000, 0) .saturating_add(Weight::from_parts(0, 8621)) .saturating_add(T::DbWeight::get().reads(3)) } @@ -269,8 +327,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `173` // Estimated: `11063` - // Minimum execution time: 17_442_000 picoseconds. - Weight::from_parts(17_941_000, 0) + // Minimum execution time: 17_628_000 picoseconds. + Weight::from_parts(18_146_000, 0) .saturating_add(Weight::from_parts(0, 11063)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) @@ -293,8 +351,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `215` // Estimated: `11105` - // Minimum execution time: 34_340_000 picoseconds. - Weight::from_parts(34_934_000, 0) + // Minimum execution time: 34_877_000 picoseconds. + Weight::from_parts(35_607_000, 0) .saturating_add(Weight::from_parts(0, 11105)) .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(4)) @@ -307,8 +365,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `103` // Estimated: `1588` - // Minimum execution time: 5_496_000 picoseconds. - Weight::from_parts(5_652_000, 0) + // Minimum execution time: 5_370_000 picoseconds. + Weight::from_parts(5_616_000, 0) .saturating_add(Weight::from_parts(0, 1588)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -319,8 +377,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `7740` // Estimated: `11205` - // Minimum execution time: 26_140_000 picoseconds. - Weight::from_parts(26_824_000, 0) + // Minimum execution time: 26_820_000 picoseconds. + Weight::from_parts(27_143_000, 0) .saturating_add(Weight::from_parts(0, 11205)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) 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 dfe4990df96..6709a20b275 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -1431,6 +1431,55 @@ impl_runtime_apis! { ParentThen(Parachain(random_para_id).into()).into(), )) } + + fn set_up_complex_asset_transfer( + ) -> Option<(MultiAssets, u32, MultiLocation, Box<dyn FnOnce()>)> { + // Transfer to Relay some local AH asset (local-reserve-transfer) while paying + // fees using teleported native token. + // (We don't care that Relay doesn't accept incoming unknown AH local asset) + let dest = Parent.into(); + + let fee_amount = EXISTENTIAL_DEPOSIT; + let fee_asset: MultiAsset = (MultiLocation::parent(), fee_amount).into(); + + let who = frame_benchmarking::whitelisted_caller(); + // Give some multiple of the existential deposit + let balance = fee_amount + EXISTENTIAL_DEPOSIT * 1000; + let _ = <Balances as frame_support::traits::Currency<_>>::make_free_balance_be( + &who, balance, + ); + // verify initial balance + assert_eq!(Balances::free_balance(&who), balance); + + // set up local asset + let asset_amount = 10u128; + let initial_asset_amount = asset_amount * 10; + let (asset_id, _, _) = pallet_assets::benchmarking::create_default_minted_asset::< + Runtime, + pallet_assets::Instance1 + >(true, initial_asset_amount); + let asset_location = MultiLocation::new( + 0, + X2(PalletInstance(50), GeneralIndex(u32::from(asset_id).into())) + ); + let transfer_asset: MultiAsset = (asset_location, asset_amount).into(); + + let assets: MultiAssets = vec![fee_asset.clone(), transfer_asset].into(); + let fee_index = if assets.get(0).unwrap().eq(&fee_asset) { 0 } else { 1 }; + + // verify transferred successfully + let verify = Box::new(move || { + // verify native balance after transfer, decreased by transferred fee amount + // (plus transport fees) + assert!(Balances::free_balance(&who) <= balance - fee_amount); + // verify asset balance decreased by exactly transferred amount + assert_eq!( + Assets::balance(asset_id.into(), &who), + initial_asset_amount - asset_amount, + ); + }); + Some((assets, fee_index as u32, dest, verify)) + } } use pallet_xcm_bridge_hub_router::benchmarking::{ diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm.rs index 340edafb0b0..504731f4a9e 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-11-10, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-12-05, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-yprdrvc7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-r43aesjn-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-westend-dev")`, DB CACHE: 1024 // Executed Command: @@ -64,40 +64,102 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 25_534_000 picoseconds. - Weight::from_parts(26_413_000, 0) + // Minimum execution time: 25_482_000 picoseconds. + Weight::from_parts(26_622_000, 0) .saturating_add(Weight::from_parts(0, 3610)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn teleport_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `1489` - // Minimum execution time: 20_513_000 picoseconds. - Weight::from_parts(20_837_000, 0) - .saturating_add(Weight::from_parts(0, 1489)) - .saturating_add(T::DbWeight::get().reads(1)) + // Measured: `145` + // Estimated: `3610` + // Minimum execution time: 87_319_000 picoseconds. + Weight::from_parts(89_764_000, 0) + .saturating_add(Weight::from_parts(0, 3610)) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `XcmpQueue::DeliveryFeeFactor` (r:1 w:0) + /// Proof: `XcmpQueue::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::RelevantMessagingState` (r:1 w:0) + /// Proof: `ParachainSystem::RelevantMessagingState` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:1) + /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `XcmpQueue::OutboundXcmpMessages` (r:0 w:1) + /// Proof: `XcmpQueue::OutboundXcmpMessages` (`max_values`: None, `max_size`: None, mode: `Measured`) fn reserve_transfer_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `1489` - // Minimum execution time: 14_977_000 picoseconds. - Weight::from_parts(15_207_000, 0) - .saturating_add(Weight::from_parts(0, 1489)) - .saturating_add(T::DbWeight::get().reads(1)) + // Measured: `367` + // Estimated: `6196` + // Minimum execution time: 139_133_000 picoseconds. + Weight::from_parts(141_507_000, 0) + .saturating_add(Weight::from_parts(0, 6196)) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().writes(5)) + } + /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) + /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn transfer_assets() -> Weight { + // Proof Size summary in bytes: + // Measured: `496` + // Estimated: `6208` + // Minimum execution time: 144_241_000 picoseconds. + Weight::from_parts(149_709_000, 0) + .saturating_add(Weight::from_parts(0, 6208)) + .saturating_add(T::DbWeight::get().reads(12)) + .saturating_add(T::DbWeight::get().writes(7)) } fn execute() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_440_000 picoseconds. - Weight::from_parts(7_651_000, 0) + // Minimum execution time: 10_392_000 picoseconds. + Weight::from_parts(10_779_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) @@ -106,8 +168,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_253_000 picoseconds. - Weight::from_parts(7_584_000, 0) + // Minimum execution time: 7_088_000 picoseconds. + Weight::from_parts(7_257_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -117,8 +179,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_299_000 picoseconds. - Weight::from_parts(2_435_000, 0) + // Minimum execution time: 2_095_000 picoseconds. + Weight::from_parts(2_136_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -144,8 +206,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 29_440_000 picoseconds. - Weight::from_parts(30_675_000, 0) + // Minimum execution time: 28_728_000 picoseconds. + Weight::from_parts(29_349_000, 0) .saturating_add(Weight::from_parts(0, 3610)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(5)) @@ -170,8 +232,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `363` // Estimated: `3828` - // Minimum execution time: 31_876_000 picoseconds. - Weight::from_parts(32_588_000, 0) + // Minimum execution time: 30_605_000 picoseconds. + Weight::from_parts(31_477_000, 0) .saturating_add(Weight::from_parts(0, 3828)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) @@ -182,8 +244,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_385_000 picoseconds. - Weight::from_parts(2_607_000, 0) + // Minimum execution time: 2_137_000 picoseconds. + Weight::from_parts(2_303_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -193,8 +255,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `162` // Estimated: `11052` - // Minimum execution time: 16_927_000 picoseconds. - Weight::from_parts(17_554_000, 0) + // Minimum execution time: 16_719_000 picoseconds. + Weight::from_parts(17_329_000, 0) .saturating_add(Weight::from_parts(0, 11052)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) @@ -205,8 +267,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `166` // Estimated: `11056` - // Minimum execution time: 16_965_000 picoseconds. - Weight::from_parts(17_807_000, 0) + // Minimum execution time: 16_687_000 picoseconds. + Weight::from_parts(17_405_000, 0) .saturating_add(Weight::from_parts(0, 11056)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) @@ -217,8 +279,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `173` // Estimated: `13538` - // Minimum execution time: 18_763_000 picoseconds. - Weight::from_parts(19_359_000, 0) + // Minimum execution time: 18_751_000 picoseconds. + Weight::from_parts(19_130_000, 0) .saturating_add(Weight::from_parts(0, 13538)) .saturating_add(T::DbWeight::get().reads(5)) } @@ -240,8 +302,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `212` // Estimated: `6152` - // Minimum execution time: 27_371_000 picoseconds. - Weight::from_parts(28_185_000, 0) + // Minimum execution time: 27_189_000 picoseconds. + Weight::from_parts(27_760_000, 0) .saturating_add(Weight::from_parts(0, 6152)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) @@ -252,8 +314,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `206` // Estimated: `8621` - // Minimum execution time: 9_165_000 picoseconds. - Weight::from_parts(9_539_000, 0) + // Minimum execution time: 9_307_000 picoseconds. + Weight::from_parts(9_691_000, 0) .saturating_add(Weight::from_parts(0, 8621)) .saturating_add(T::DbWeight::get().reads(3)) } @@ -263,8 +325,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `173` // Estimated: `11063` - // Minimum execution time: 17_384_000 picoseconds. - Weight::from_parts(17_777_000, 0) + // Minimum execution time: 17_607_000 picoseconds. + Weight::from_parts(18_090_000, 0) .saturating_add(Weight::from_parts(0, 11063)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) @@ -287,8 +349,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `215` // Estimated: `11105` - // Minimum execution time: 34_260_000 picoseconds. - Weight::from_parts(35_428_000, 0) + // Minimum execution time: 34_322_000 picoseconds. + Weight::from_parts(35_754_000, 0) .saturating_add(Weight::from_parts(0, 11105)) .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(4)) @@ -301,8 +363,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `103` // Estimated: `1588` - // Minimum execution time: 4_710_000 picoseconds. - Weight::from_parts(4_900_000, 0) + // Minimum execution time: 4_513_000 picoseconds. + Weight::from_parts(4_754_000, 0) .saturating_add(Weight::from_parts(0, 1588)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -313,8 +375,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `7740` // Estimated: `11205` - // Minimum execution time: 26_843_000 picoseconds. - Weight::from_parts(27_404_000, 0) + // Minimum execution time: 27_860_000 picoseconds. + Weight::from_parts(28_279_000, 0) .saturating_add(Weight::from_parts(0, 11205)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index 01efb9d20cc..7dbf015de50 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -823,6 +823,18 @@ impl_runtime_apis! { // Reserve transfers are disabled on BH. None } + + fn set_up_complex_asset_transfer( + ) -> Option<(MultiAssets, u32, MultiLocation, Box<dyn FnOnce()>)> { + // BH only supports teleports to system parachain. + // Relay/native token can be teleported between BH and Relay. + let native_location = Parent.into(); + let dest = Parent.into(); + pallet_xcm::benchmarking::helpers::native_teleport_as_asset_transfer::<Runtime>( + native_location, + dest + ) + } } use xcm::latest::prelude::*; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_xcm.rs index 5aa4999c624..5faded42aa8 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_xcm.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-11-10, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-12-05, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-yprdrvc7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-r43aesjn-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: @@ -62,26 +62,39 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn send() -> Weight { // Proof Size summary in bytes: - // Measured: `75` - // Estimated: `3540` - // Minimum execution time: 24_179_000 picoseconds. - Weight::from_parts(24_684_000, 0) - .saturating_add(Weight::from_parts(0, 3540)) + // Measured: `38` + // Estimated: `3503` + // Minimum execution time: 23_683_000 picoseconds. + Weight::from_parts(24_199_000, 0) + .saturating_add(Weight::from_parts(0, 3503)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w:0) - /// Proof: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w:0) /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn teleport_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `38` - // Estimated: `3503` - // Minimum execution time: 21_093_000 picoseconds. - Weight::from_parts(21_523_000, 0) - .saturating_add(Weight::from_parts(0, 3503)) - .saturating_add(T::DbWeight::get().reads(2)) + // Measured: `70` + // Estimated: `3593` + // Minimum execution time: 89_524_000 picoseconds. + Weight::from_parts(91_401_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `Benchmark::Override` (r:0 w:0) /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -93,6 +106,32 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { Weight::from_parts(18_446_744_073_709_551_000, 0) .saturating_add(Weight::from_parts(0, 0)) } + /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) + /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn transfer_assets() -> Weight { + // Proof Size summary in bytes: + // Measured: `70` + // Estimated: `3593` + // Minimum execution time: 91_890_000 picoseconds. + Weight::from_parts(93_460_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(3)) + } /// Storage: `Benchmark::Override` (r:0 w:0) /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) fn execute() -> Weight { @@ -109,8 +148,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_938_000 picoseconds. - Weight::from_parts(7_243_000, 0) + // Minimum execution time: 7_152_000 picoseconds. + Weight::from_parts(7_355_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -120,8 +159,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_159_000 picoseconds. - Weight::from_parts(2_290_000, 0) + // Minimum execution time: 2_081_000 picoseconds. + Weight::from_parts(2_258_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -145,11 +184,11 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_subscribe_version_notify() -> Weight { // Proof Size summary in bytes: - // Measured: `75` - // Estimated: `3540` - // Minimum execution time: 28_337_000 picoseconds. - Weight::from_parts(29_265_000, 0) - .saturating_add(Weight::from_parts(0, 3540)) + // Measured: `38` + // Estimated: `3503` + // Minimum execution time: 28_067_000 picoseconds. + Weight::from_parts(28_693_000, 0) + .saturating_add(Weight::from_parts(0, 3503)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(5)) } @@ -171,11 +210,11 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_unsubscribe_version_notify() -> Weight { // Proof Size summary in bytes: - // Measured: `292` - // Estimated: `3757` - // Minimum execution time: 30_599_000 picoseconds. - Weight::from_parts(31_272_000, 0) - .saturating_add(Weight::from_parts(0, 3757)) + // Measured: `255` + // Estimated: `3720` + // Minimum execution time: 30_420_000 picoseconds. + Weight::from_parts(31_373_000, 0) + .saturating_add(Weight::from_parts(0, 3720)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -185,8 +224,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_132_000 picoseconds. - Weight::from_parts(2_280_000, 0) + // Minimum execution time: 2_087_000 picoseconds. + Weight::from_parts(2_243_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -194,11 +233,11 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: - // Measured: `187` - // Estimated: `11077` - // Minimum execution time: 18_262_000 picoseconds. - Weight::from_parts(18_640_000, 0) - .saturating_add(Weight::from_parts(0, 11077)) + // Measured: `95` + // Estimated: `10985` + // Minimum execution time: 15_142_000 picoseconds. + Weight::from_parts(15_598_000, 0) + .saturating_add(Weight::from_parts(0, 10985)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -206,11 +245,11 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: - // Measured: `191` - // Estimated: `11081` - // Minimum execution time: 18_512_000 picoseconds. - Weight::from_parts(18_888_000, 0) - .saturating_add(Weight::from_parts(0, 11081)) + // Measured: `99` + // Estimated: `10989` + // Minimum execution time: 15_041_000 picoseconds. + Weight::from_parts(15_493_000, 0) + .saturating_add(Weight::from_parts(0, 10989)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -218,11 +257,11 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: - // Measured: `198` - // Estimated: `13563` - // Minimum execution time: 19_362_000 picoseconds. - Weight::from_parts(20_056_000, 0) - .saturating_add(Weight::from_parts(0, 13563)) + // Measured: `106` + // Estimated: `13471` + // Minimum execution time: 16_624_000 picoseconds. + Weight::from_parts(17_031_000, 0) + .saturating_add(Weight::from_parts(0, 13471)) .saturating_add(T::DbWeight::get().reads(5)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:2 w:1) @@ -241,11 +280,11 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn notify_current_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `142` - // Estimated: `6082` - // Minimum execution time: 27_318_000 picoseconds. - Weight::from_parts(28_075_000, 0) - .saturating_add(Weight::from_parts(0, 6082)) + // Measured: `106` + // Estimated: `6046` + // Minimum execution time: 26_398_000 picoseconds. + Weight::from_parts(26_847_000, 0) + .saturating_add(Weight::from_parts(0, 6046)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -253,22 +292,22 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `172` - // Estimated: `8587` - // Minimum execution time: 9_930_000 picoseconds. - Weight::from_parts(10_192_000, 0) - .saturating_add(Weight::from_parts(0, 8587)) + // Measured: `136` + // Estimated: `8551` + // Minimum execution time: 8_741_000 picoseconds. + Weight::from_parts(8_954_000, 0) + .saturating_add(Weight::from_parts(0, 8551)) .saturating_add(T::DbWeight::get().reads(3)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `198` - // Estimated: `11088` - // Minimum execution time: 18_305_000 picoseconds. - Weight::from_parts(18_738_000, 0) - .saturating_add(Weight::from_parts(0, 11088)) + // Measured: `106` + // Estimated: `10996` + // Minimum execution time: 15_306_000 picoseconds. + Weight::from_parts(15_760_000, 0) + .saturating_add(Weight::from_parts(0, 10996)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -288,11 +327,11 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn migrate_and_notify_old_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `204` - // Estimated: `11094` - // Minimum execution time: 34_559_000 picoseconds. - Weight::from_parts(35_241_000, 0) - .saturating_add(Weight::from_parts(0, 11094)) + // Measured: `112` + // Estimated: `11002` + // Minimum execution time: 33_127_000 picoseconds. + Weight::from_parts(33_938_000, 0) + .saturating_add(Weight::from_parts(0, 11002)) .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -302,11 +341,11 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn new_query() -> Weight { // Proof Size summary in bytes: - // Measured: `69` - // Estimated: `1554` - // Minimum execution time: 4_512_000 picoseconds. - Weight::from_parts(4_671_000, 0) - .saturating_add(Weight::from_parts(0, 1554)) + // Measured: `32` + // Estimated: `1517` + // Minimum execution time: 4_290_000 picoseconds. + Weight::from_parts(4_450_000, 0) + .saturating_add(Weight::from_parts(0, 1517)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -314,11 +353,11 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn take_response() -> Weight { // Proof Size summary in bytes: - // Measured: `7706` - // Estimated: `11171` - // Minimum execution time: 26_473_000 picoseconds. - Weight::from_parts(26_960_000, 0) - .saturating_add(Weight::from_parts(0, 11171)) + // Measured: `7669` + // Estimated: `11134` + // Minimum execution time: 26_408_000 picoseconds. + Weight::from_parts(26_900_000, 0) + .saturating_add(Weight::from_parts(0, 11134)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index 39ce4e04ed1..e77184351ed 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -813,6 +813,18 @@ impl_runtime_apis! { // Reserve transfers are disabled on BH. None } + + fn set_up_complex_asset_transfer( + ) -> Option<(MultiAssets, u32, MultiLocation, Box<dyn FnOnce()>)> { + // BH only supports teleports to system parachain. + // Relay/native token can be teleported between BH and Relay. + let native_location = Parent.into(); + let dest = Parent.into(); + pallet_xcm::benchmarking::helpers::native_teleport_as_asset_transfer::<Runtime>( + native_location, + dest + ) + } } use xcm::latest::prelude::*; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_xcm.rs index 9f17d327024..83e4260e771 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_xcm.rs @@ -17,27 +17,25 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-12-05, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-r43aesjn-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet -// --chain=bridge-hub-rococo-dev -// --wasm-execution=compiled -// --pallet=pallet_xcm -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/ +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_xcm +// --chain=bridge-hub-westend-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -50,6 +48,8 @@ use core::marker::PhantomData; /// Weight functions for `pallet_xcm`. pub struct WeightInfo<T>(PhantomData<T>); impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { + /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) @@ -62,24 +62,39 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn send() -> Weight { // Proof Size summary in bytes: - // Measured: `75` - // Estimated: `3540` - // Minimum execution time: 29_724_000 picoseconds. - Weight::from_parts(30_440_000, 0) - .saturating_add(Weight::from_parts(0, 3540)) - .saturating_add(T::DbWeight::get().reads(5)) + // Measured: `38` + // Estimated: `3503` + // Minimum execution time: 23_219_000 picoseconds. + Weight::from_parts(23_818_000, 0) + .saturating_add(Weight::from_parts(0, 3503)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn teleport_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `32` - // Estimated: `1489` - // Minimum execution time: 26_779_000 picoseconds. - Weight::from_parts(27_249_000, 0) - .saturating_add(Weight::from_parts(0, 1489)) - .saturating_add(T::DbWeight::get().reads(1)) + // Measured: `70` + // Estimated: `3593` + // Minimum execution time: 90_120_000 picoseconds. + Weight::from_parts(92_545_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `Benchmark::Override` (r:0 w:0) /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -91,6 +106,32 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { Weight::from_parts(18_446_744_073_709_551_000, 0) .saturating_add(Weight::from_parts(0, 0)) } + /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) + /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn transfer_assets() -> Weight { + // Proof Size summary in bytes: + // Measured: `70` + // Estimated: `3593` + // Minimum execution time: 91_339_000 picoseconds. + Weight::from_parts(93_204_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(3)) + } /// Storage: `Benchmark::Override` (r:0 w:0) /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) fn execute() -> Weight { @@ -107,8 +148,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_170_000 picoseconds. - Weight::from_parts(9_629_000, 0) + // Minimum execution time: 6_976_000 picoseconds. + Weight::from_parts(7_284_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -118,8 +159,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_769_000 picoseconds. - Weight::from_parts(2_933_000, 0) + // Minimum execution time: 2_044_000 picoseconds. + Weight::from_parts(2_223_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -127,6 +168,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) /// Proof: `PolkadotXcm::QueryCounter` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) @@ -141,16 +184,18 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_subscribe_version_notify() -> Weight { // Proof Size summary in bytes: - // Measured: `75` - // Estimated: `3540` - // Minimum execution time: 34_547_000 picoseconds. - Weight::from_parts(35_653_000, 0) - .saturating_add(Weight::from_parts(0, 3540)) - .saturating_add(T::DbWeight::get().reads(7)) + // Measured: `38` + // Estimated: `3503` + // Minimum execution time: 27_778_000 picoseconds. + Weight::from_parts(28_318_000, 0) + .saturating_add(Weight::from_parts(0, 3503)) + .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(5)) } /// Storage: `PolkadotXcm::VersionNotifiers` (r:1 w:1) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) @@ -165,12 +210,12 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_unsubscribe_version_notify() -> Weight { // Proof Size summary in bytes: - // Measured: `292` - // Estimated: `3757` - // Minimum execution time: 36_274_000 picoseconds. - Weight::from_parts(37_281_000, 0) - .saturating_add(Weight::from_parts(0, 3757)) - .saturating_add(T::DbWeight::get().reads(6)) + // Measured: `255` + // Estimated: `3720` + // Minimum execution time: 30_446_000 picoseconds. + Weight::from_parts(31_925_000, 0) + .saturating_add(Weight::from_parts(0, 3720)) + .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `PolkadotXcm::XcmExecutionSuspended` (r:0 w:1) @@ -179,8 +224,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_749_000 picoseconds. - Weight::from_parts(2_917_000, 0) + // Minimum execution time: 2_037_000 picoseconds. + Weight::from_parts(2_211_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -188,11 +233,11 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: - // Measured: `187` - // Estimated: `11077` - // Minimum execution time: 17_649_000 picoseconds. - Weight::from_parts(17_964_000, 0) - .saturating_add(Weight::from_parts(0, 11077)) + // Measured: `95` + // Estimated: `10985` + // Minimum execution time: 15_620_000 picoseconds. + Weight::from_parts(15_984_000, 0) + .saturating_add(Weight::from_parts(0, 10985)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -200,11 +245,11 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: - // Measured: `191` - // Estimated: `11081` - // Minimum execution time: 17_551_000 picoseconds. - Weight::from_parts(18_176_000, 0) - .saturating_add(Weight::from_parts(0, 11081)) + // Measured: `99` + // Estimated: `10989` + // Minimum execution time: 15_689_000 picoseconds. + Weight::from_parts(16_093_000, 0) + .saturating_add(Weight::from_parts(0, 10989)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -212,15 +257,17 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: - // Measured: `198` - // Estimated: `13563` - // Minimum execution time: 19_261_000 picoseconds. - Weight::from_parts(19_714_000, 0) - .saturating_add(Weight::from_parts(0, 13563)) + // Measured: `106` + // Estimated: `13471` + // Minimum execution time: 16_946_000 picoseconds. + Weight::from_parts(17_192_000, 0) + .saturating_add(Weight::from_parts(0, 13471)) .saturating_add(T::DbWeight::get().reads(5)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:2 w:1) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) @@ -233,39 +280,41 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn notify_current_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `142` - // Estimated: `6082` - // Minimum execution time: 31_630_000 picoseconds. - Weight::from_parts(32_340_000, 0) - .saturating_add(Weight::from_parts(0, 6082)) - .saturating_add(T::DbWeight::get().reads(7)) + // Measured: `106` + // Estimated: `6046` + // Minimum execution time: 27_164_000 picoseconds. + Weight::from_parts(27_760_000, 0) + .saturating_add(Weight::from_parts(0, 6046)) + .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:3 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `172` - // Estimated: `8587` - // Minimum execution time: 9_218_000 picoseconds. - Weight::from_parts(9_558_000, 0) - .saturating_add(Weight::from_parts(0, 8587)) + // Measured: `136` + // Estimated: `8551` + // Minimum execution time: 8_689_000 picoseconds. + Weight::from_parts(8_874_000, 0) + .saturating_add(Weight::from_parts(0, 8551)) .saturating_add(T::DbWeight::get().reads(3)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `198` - // Estimated: `11088` - // Minimum execution time: 18_133_000 picoseconds. - Weight::from_parts(18_663_000, 0) - .saturating_add(Weight::from_parts(0, 11088)) + // Measured: `106` + // Estimated: `10996` + // Minimum execution time: 15_944_000 picoseconds. + Weight::from_parts(16_381_000, 0) + .saturating_add(Weight::from_parts(0, 10996)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) @@ -278,12 +327,12 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn migrate_and_notify_old_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `204` - // Estimated: `11094` - // Minimum execution time: 38_878_000 picoseconds. - Weight::from_parts(39_779_000, 0) - .saturating_add(Weight::from_parts(0, 11094)) - .saturating_add(T::DbWeight::get().reads(9)) + // Measured: `112` + // Estimated: `11002` + // Minimum execution time: 33_826_000 picoseconds. + Weight::from_parts(34_784_000, 0) + .saturating_add(Weight::from_parts(0, 11002)) + .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) @@ -294,8 +343,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `32` // Estimated: `1517` - // Minimum execution time: 4_142_000 picoseconds. - Weight::from_parts(4_308_000, 0) + // Minimum execution time: 4_257_000 picoseconds. + Weight::from_parts(4_383_000, 0) .saturating_add(Weight::from_parts(0, 1517)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -306,11 +355,10 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `7669` // Estimated: `11134` - // Minimum execution time: 25_814_000 picoseconds. - Weight::from_parts(26_213_000, 0) + // Minimum execution time: 26_924_000 picoseconds. + Weight::from_parts(27_455_000, 0) .saturating_add(Weight::from_parts(0, 11134)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } } - diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs index 842ddbe54f2..d6f8d32628e 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs @@ -969,6 +969,18 @@ impl_runtime_apis! { // Reserve transfers are disabled on Collectives. None } + + fn set_up_complex_asset_transfer( + ) -> Option<(MultiAssets, u32, MultiLocation, Box<dyn FnOnce()>)> { + // Collectives only supports teleports to system parachain. + // Relay/native token can be teleported between Collectives and Relay. + let native_location = Parent.into(); + let dest = Parent.into(); + pallet_xcm::benchmarking::helpers::native_teleport_as_asset_transfer::<Runtime>( + native_location, + dest + ) + } } let whitelist: Vec<TrackedStorageKey> = vec![ diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_xcm.rs index a3b42cb86c4..50dfbffde01 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_xcm.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-11-10, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-12-05, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-yprdrvc7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-r43aesjn-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-westend-dev")`, DB CACHE: 1024 // Executed Command: @@ -64,22 +64,37 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 25_746_000 picoseconds. - Weight::from_parts(26_349_000, 0) + // Minimum execution time: 24_540_000 picoseconds. + Weight::from_parts(25_439_000, 0) .saturating_add(Weight::from_parts(0, 3610)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn teleport_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `69` - // Estimated: `1489` - // Minimum execution time: 22_660_000 picoseconds. - Weight::from_parts(23_173_000, 0) - .saturating_add(Weight::from_parts(0, 1489)) - .saturating_add(T::DbWeight::get().reads(1)) + // Measured: `214` + // Estimated: `3679` + // Minimum execution time: 86_614_000 picoseconds. + Weight::from_parts(88_884_000, 0) + .saturating_add(Weight::from_parts(0, 3679)) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `Benchmark::Override` (r:0 w:0) /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -91,6 +106,32 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { Weight::from_parts(18_446_744_073_709_551_000, 0) .saturating_add(Weight::from_parts(0, 0)) } + /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) + /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn transfer_assets() -> Weight { + // Proof Size summary in bytes: + // Measured: `214` + // Estimated: `3679` + // Minimum execution time: 87_915_000 picoseconds. + Weight::from_parts(90_219_000, 0) + .saturating_add(Weight::from_parts(0, 3679)) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(3)) + } /// Storage: `Benchmark::Override` (r:0 w:0) /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) fn execute() -> Weight { @@ -107,8 +148,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_321_000 picoseconds. - Weight::from_parts(7_542_000, 0) + // Minimum execution time: 6_872_000 picoseconds. + Weight::from_parts(7_110_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -118,8 +159,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_232_000 picoseconds. - Weight::from_parts(2_395_000, 0) + // Minimum execution time: 2_009_000 picoseconds. + Weight::from_parts(2_163_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -145,8 +186,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 29_006_000 picoseconds. - Weight::from_parts(29_777_000, 0) + // Minimum execution time: 28_858_000 picoseconds. + Weight::from_parts(29_355_000, 0) .saturating_add(Weight::from_parts(0, 3610)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(5)) @@ -171,8 +212,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `363` // Estimated: `3828` - // Minimum execution time: 31_245_000 picoseconds. - Weight::from_parts(32_125_000, 0) + // Minimum execution time: 30_598_000 picoseconds. + Weight::from_parts(31_168_000, 0) .saturating_add(Weight::from_parts(0, 3828)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) @@ -183,8 +224,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_255_000 picoseconds. - Weight::from_parts(2_399_000, 0) + // Minimum execution time: 2_090_000 picoseconds. + Weight::from_parts(2_253_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -194,8 +235,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `162` // Estimated: `11052` - // Minimum execution time: 16_521_000 picoseconds. - Weight::from_parts(17_001_000, 0) + // Minimum execution time: 16_133_000 picoseconds. + Weight::from_parts(16_433_000, 0) .saturating_add(Weight::from_parts(0, 11052)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) @@ -206,8 +247,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `166` // Estimated: `11056` - // Minimum execution time: 16_486_000 picoseconds. - Weight::from_parts(16_729_000, 0) + // Minimum execution time: 16_012_000 picoseconds. + Weight::from_parts(16_449_000, 0) .saturating_add(Weight::from_parts(0, 11056)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) @@ -218,8 +259,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `173` // Estimated: `13538` - // Minimum execution time: 18_037_000 picoseconds. - Weight::from_parts(18_310_000, 0) + // Minimum execution time: 17_922_000 picoseconds. + Weight::from_parts(18_426_000, 0) .saturating_add(Weight::from_parts(0, 13538)) .saturating_add(T::DbWeight::get().reads(5)) } @@ -241,8 +282,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `212` // Estimated: `6152` - // Minimum execution time: 27_901_000 picoseconds. - Weight::from_parts(28_566_000, 0) + // Minimum execution time: 27_280_000 picoseconds. + Weight::from_parts(28_026_000, 0) .saturating_add(Weight::from_parts(0, 6152)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) @@ -253,8 +294,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `206` // Estimated: `8621` - // Minimum execution time: 9_299_000 picoseconds. - Weight::from_parts(9_547_000, 0) + // Minimum execution time: 9_387_000 picoseconds. + Weight::from_parts(9_644_000, 0) .saturating_add(Weight::from_parts(0, 8621)) .saturating_add(T::DbWeight::get().reads(3)) } @@ -264,8 +305,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `173` // Estimated: `11063` - // Minimum execution time: 16_768_000 picoseconds. - Weight::from_parts(17_215_000, 0) + // Minimum execution time: 16_649_000 picoseconds. + Weight::from_parts(17_025_000, 0) .saturating_add(Weight::from_parts(0, 11063)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) @@ -288,8 +329,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `215` // Estimated: `11105` - // Minimum execution time: 35_134_000 picoseconds. - Weight::from_parts(35_883_000, 0) + // Minimum execution time: 34_355_000 picoseconds. + Weight::from_parts(35_295_000, 0) .saturating_add(Weight::from_parts(0, 11105)) .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(4)) @@ -302,8 +343,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `103` // Estimated: `1588` - // Minimum execution time: 4_562_000 picoseconds. - Weight::from_parts(4_802_000, 0) + // Minimum execution time: 4_527_000 picoseconds. + Weight::from_parts(4_699_000, 0) .saturating_add(Weight::from_parts(0, 1588)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -314,8 +355,8 @@ impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { // Proof Size summary in bytes: // Measured: `7740` // Estimated: `11205` - // Minimum execution time: 26_865_000 picoseconds. - Weight::from_parts(27_400_000, 0) + // Minimum execution time: 27_011_000 picoseconds. + Weight::from_parts(27_398_000, 0) .saturating_add(Weight::from_parts(0, 11205)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs index 81cc8558a2b..4ab3014cace 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs @@ -723,6 +723,18 @@ impl_runtime_apis! { // Reserve transfers are disabled on Contracts-System-Para. None } + + fn set_up_complex_asset_transfer( + ) -> Option<(MultiAssets, u32, MultiLocation, Box<dyn FnOnce()>)> { + // Contracts-System-Para only supports teleports to system parachain. + // Relay/native token can be teleported between Contracts-System-Para and Relay. + let native_location = Parent.into(); + let dest = Parent.into(); + pallet_xcm::benchmarking::helpers::native_teleport_as_asset_transfer::<Runtime>( + native_location, + dest + ) + } } let whitelist: Vec<TrackedStorageKey> = vec![ diff --git a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs index 74d9a0b071d..21f421f8285 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -94,12 +94,24 @@ pub type FungiblesTransactor = FungiblesAdapter< // Use this fungibles implementation: Assets, // Use this currency when it is a fungible asset matching the given location or name: - ConvertedConcreteId< - AssetIdPalletAssets, - Balance, - AsPrefixedGeneralIndex<SystemAssetHubAssetsPalletLocation, AssetIdPalletAssets, JustTry>, - JustTry, - >, + ( + ConvertedConcreteId< + AssetIdPalletAssets, + Balance, + AsPrefixedGeneralIndex<AssetsPalletLocation, AssetIdPalletAssets, JustTry>, + JustTry, + >, + ConvertedConcreteId< + AssetIdPalletAssets, + Balance, + AsPrefixedGeneralIndex< + SystemAssetHubAssetsPalletLocation, + AssetIdPalletAssets, + JustTry, + >, + JustTry, + >, + ), // Convert an XCM MultiLocation into a local account id: LocationToAccountId, // Our chain's account ID type (we can't get away without mentioning it explicitly): @@ -237,6 +249,8 @@ where } } +// This asset can be added to AH as ForeignAsset and teleported between Penpal and AH +pub const TELEPORTABLE_ASSET_ID: u32 = 2; parameter_types! { /// The location that this chain recognizes as the Relay network's Asset Hub. pub SystemAssetHubLocation: MultiLocation = MultiLocation::new(1, X1(Parachain(1000))); @@ -244,11 +258,30 @@ parameter_types! { // the Relay Chain's Asset Hub's Assets pallet index pub SystemAssetHubAssetsPalletLocation: MultiLocation = MultiLocation::new(1, X2(Parachain(1000), PalletInstance(50))); + pub AssetsPalletLocation: MultiLocation = + MultiLocation::new(0, X1(PalletInstance(50))); pub CheckingAccount: AccountId = PolkadotXcm::check_account(); + pub LocalTeleportableToAssetHub: MultiLocation = MultiLocation::new( + 0, + X2(PalletInstance(50), GeneralIndex(TELEPORTABLE_ASSET_ID.into())) + ); +} + +/// Accepts asset with ID `AssetLocation` and is coming from `Origin` chain. +pub struct AssetFromChain<AssetLocation, Origin>(PhantomData<(AssetLocation, Origin)>); +impl<AssetLocation: Get<MultiLocation>, Origin: Get<MultiLocation>> + ContainsPair<MultiAsset, MultiLocation> for AssetFromChain<AssetLocation, Origin> +{ + fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { + log::trace!(target: "xcm::contains", "AssetFromChain asset: {:?}, origin: {:?}", asset, origin); + *origin == Origin::get() && matches!(asset.id, Concrete(id) if id == AssetLocation::get()) + } } pub type Reserves = (NativeAsset, AssetsFrom<SystemAssetHubLocation>, NativeAssetFrom<SystemAssetHubLocation>); +pub type TrustedTeleporters = + (AssetFromChain<LocalTeleportableToAssetHub, SystemAssetHubLocation>,); pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { @@ -259,7 +292,7 @@ impl xcm_executor::Config for XcmConfig { type OriginConverter = XcmOriginToTransactDispatchOrigin; type IsReserve = Reserves; // no teleport trust established with other chains - type IsTeleporter = NativeAsset; + type IsTeleporter = TrustedTeleporters; type UniversalLocation = UniversalLocation; type Barrier = Barrier; type Weigher = FixedWeightBounds<UnitWeightCost, RuntimeCall, MaxInstructions>; diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index 21d4088df19..26670cdcc23 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -2261,6 +2261,20 @@ sp_api::impl_runtime_apis! { Parachain(43211234).into(), )) } + + fn set_up_complex_asset_transfer( + ) -> Option<(MultiAssets, u32, MultiLocation, Box<dyn FnOnce()>)> { + // Relay supports only native token, either reserve transfer it to non-system parachains, + // or teleport it to system parachain. Use the teleport case for benchmarking as it's + // slightly heavier. + // Relay/native token can be teleported to/from AH. + let native_location = Here.into(); + let dest = crate::xcm_config::AssetHub::get(); + pallet_xcm::benchmarking::helpers::native_teleport_as_asset_transfer::<Runtime>( + native_location, + dest + ) + } } impl pallet_xcm_benchmarks::Config for Runtime { type XcmConfig = XcmConfig; diff --git a/polkadot/runtime/rococo/src/weights/pallet_xcm.rs b/polkadot/runtime/rococo/src/weights/pallet_xcm.rs index aafded3f731..177407ef708 100644 --- a/polkadot/runtime/rococo/src/weights/pallet_xcm.rs +++ b/polkadot/runtime/rococo/src/weights/pallet_xcm.rs @@ -48,6 +48,10 @@ use core::marker::PhantomData; /// Weight functions for `pallet_xcm`. pub struct WeightInfo<T>(PhantomData<T>); impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { + fn transfer_assets() -> Weight { + // TODO: run benchmarks + Weight::zero() + } /// Storage: `Dmp::DeliveryFeeFactor` (r:1 w:0) /// Proof: `Dmp::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `XcmPallet::SupportedVersion` (r:1 w:0) diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index afc2e5ea3da..04723ef0d9b 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -2295,6 +2295,21 @@ sp_api::impl_runtime_apis! { crate::Junction::Parachain(43211234).into(), )) } + + fn set_up_complex_asset_transfer( + ) -> Option<(MultiAssets, u32, MultiLocation, Box<dyn FnOnce()>)> { + // Relay supports only native token, either reserve transfer it to non-system parachains, + // or teleport it to system parachain. Use the teleport case for benchmarking as it's + // slightly heavier. + + // Relay/native token can be teleported to/from AH. + let native_location = Here.into(); + let dest = crate::xcm_config::AssetHub::get(); + pallet_xcm::benchmarking::helpers::native_teleport_as_asset_transfer::<Runtime>( + native_location, + dest + ) + } } impl frame_system_benchmarking::Config for Runtime {} impl pallet_nomination_pools_benchmarking::Config for Runtime {} diff --git a/polkadot/runtime/westend/src/weights/pallet_xcm.rs b/polkadot/runtime/westend/src/weights/pallet_xcm.rs index cca4bdbd91e..493acd0f9e7 100644 --- a/polkadot/runtime/westend/src/weights/pallet_xcm.rs +++ b/polkadot/runtime/westend/src/weights/pallet_xcm.rs @@ -50,6 +50,10 @@ use core::marker::PhantomData; /// Weight functions for `pallet_xcm`. pub struct WeightInfo<T>(PhantomData<T>); impl<T: frame_system::Config> pallet_xcm::WeightInfo for WeightInfo<T> { + fn transfer_assets() -> Weight { + // TODO: run benchmarks + Weight::zero() + } /// Storage: `Dmp::DeliveryFeeFactor` (r:1 w:0) /// Proof: `Dmp::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `XcmPallet::SupportedVersion` (r:1 w:0) diff --git a/polkadot/xcm/pallet-xcm/src/benchmarking.rs b/polkadot/xcm/pallet-xcm/src/benchmarking.rs index f0289512399..28a198f40a0 100644 --- a/polkadot/xcm/pallet-xcm/src/benchmarking.rs +++ b/polkadot/xcm/pallet-xcm/src/benchmarking.rs @@ -44,7 +44,7 @@ pub trait Config: crate::Config { /// /// Implementation should also make sure `dest` is reachable/connected. /// - /// If `None`, the benchmarks that depend on this will be skipped. + /// If `None`, the benchmarks that depend on this will default to `Weight::MAX`. fn teleportable_asset_and_dest() -> Option<(MultiAsset, MultiLocation)> { None } @@ -54,10 +54,27 @@ pub trait Config: crate::Config { /// /// Implementation should also make sure `dest` is reachable/connected. /// - /// If `None`, the benchmarks that depend on this will be skipped. + /// If `None`, the benchmarks that depend on this will default to `Weight::MAX`. fn reserve_transferable_asset_and_dest() -> Option<(MultiAsset, MultiLocation)> { None } + + /// Sets up a complex transfer (usually consisting of a teleport and reserve-based transfer), so + /// that runtime can properly benchmark `transfer_assets()` extrinsic. Should return a tuple + /// `(MultiAsset, u32, MultiLocation, dyn FnOnce())` representing the assets to transfer, the + /// `u32` index of the asset to be used for fees, the destination chain for the transfer, and a + /// `verify()` closure to verify the intended transfer side-effects. + /// + /// Implementation should make sure the provided assets can be transacted by the runtime, there + /// are enough balances in the involved accounts, and that `dest` is reachable/connected. + /// + /// Used only in benchmarks. + /// + /// If `None`, the benchmarks that depend on this will default to `Weight::MAX`. + fn set_up_complex_asset_transfer( + ) -> Option<(MultiAssets, u32, MultiLocation, Box<dyn FnOnce()>)> { + None + } } benchmarks! { @@ -158,6 +175,23 @@ benchmarks! { assert!(pallet_balances::Pallet::<T>::free_balance(&caller) <= balance - transferred_amount); } + transfer_assets { + let (assets, fee_index, destination, verify) = T::set_up_complex_asset_transfer().ok_or( + BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), + )?; + let caller: T::AccountId = whitelisted_caller(); + let send_origin = RawOrigin::Signed(caller.clone()); + let recipient = [0u8; 32]; + let versioned_dest: VersionedMultiLocation = destination.into(); + let versioned_beneficiary: VersionedMultiLocation = + AccountId32 { network: None, id: recipient.into() }.into(); + let versioned_assets: VersionedMultiAssets = assets.into(); + }: _<RuntimeOrigin<T>>(send_origin.into(), Box::new(versioned_dest), Box::new(versioned_beneficiary), Box::new(versioned_assets), 0, WeightLimit::Unlimited) + verify { + // run provided verification function + verify(); + } + execute { let execute_origin = T::ExecuteXcmOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; @@ -302,3 +336,36 @@ benchmarks! { crate::mock::Test ); } + +pub mod helpers { + use super::*; + pub fn native_teleport_as_asset_transfer<T>( + native_asset_location: MultiLocation, + destination: MultiLocation, + ) -> Option<(MultiAssets, u32, MultiLocation, Box<dyn FnOnce()>)> + where + T: Config + pallet_balances::Config, + u128: From<<T as pallet_balances::Config>::Balance>, + { + // Relay/native token can be teleported to/from AH. + let amount = T::ExistentialDeposit::get() * 100u32.into(); + let assets: MultiAssets = + MultiAsset { fun: Fungible(amount.into()), id: Concrete(native_asset_location) }.into(); + let fee_index = 0u32; + + // Give some multiple of transferred amount + let balance = amount * 10u32.into(); + let who = whitelisted_caller(); + let _ = + <pallet_balances::Pallet::<T> as frame_support::traits::Currency<_>>::make_free_balance_be(&who, balance); + // verify initial balance + assert_eq!(pallet_balances::Pallet::<T>::free_balance(&who), balance); + + // verify transferred successfully + let verify = Box::new(move || { + // verify balance after transfer, decreased by transferred amount (and delivery fees) + assert!(pallet_balances::Pallet::<T>::free_balance(&who) <= balance - amount); + }); + Some((assets, fee_index, destination, verify)) + } +} diff --git a/polkadot/xcm/pallet-xcm/src/lib.rs b/polkadot/xcm/pallet-xcm/src/lib.rs index 74a24b132da..ab4403f6caa 100644 --- a/polkadot/xcm/pallet-xcm/src/lib.rs +++ b/polkadot/xcm/pallet-xcm/src/lib.rs @@ -66,6 +66,7 @@ pub trait WeightInfo { fn send() -> Weight; fn teleport_assets() -> Weight; fn reserve_transfer_assets() -> Weight; + fn transfer_assets() -> Weight; fn execute() -> Weight; fn force_xcm_version() -> Weight; fn force_default_xcm_version() -> Weight; @@ -98,6 +99,10 @@ impl WeightInfo for TestWeightInfo { Weight::from_parts(100_000_000, 0) } + fn transfer_assets() -> Weight { + Weight::from_parts(100_000_000, 0) + } + fn execute() -> Weight { Weight::from_parts(100_000_000, 0) } @@ -905,8 +910,8 @@ pub mod pallet { /// from relay to parachain. /// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will /// generally be an `AccountId32` value. - /// - `assets`: The assets to be withdrawn. The first item should be the currency used to to - /// pay the fee on the `dest` side. May not be empty. + /// - `assets`: The assets to be withdrawn. This should include the assets used to pay the + /// fee on the `dest` chain. /// - `fee_asset_item`: The index into `assets` of the item which should be used to pay /// fees. #[pallet::call_index(1)] @@ -937,8 +942,19 @@ pub mod pallet { Self::do_teleport_assets(origin, dest, beneficiary, assets, fee_asset_item, Unlimited) } - /// Transfer some assets from the local chain to the sovereign account of a destination - /// chain and forward a notification XCM. + /// Transfer some assets from the local chain to the destination chain through their local, + /// destination or remote reserve. + /// + /// `assets` must have same reserve location and may not be teleportable to `dest`. + /// - `assets` have local reserve: transfer assets to sovereign account of destination + /// chain and forward a notification XCM to `dest` to mint and deposit reserve-based + /// assets to `beneficiary`. + /// - `assets` have destination reserve: 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`. + /// - `assets` have remote 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`. /// /// **This function is deprecated: Use `limited_reserve_transfer_assets` instead.** /// @@ -953,7 +969,7 @@ pub mod pallet { /// - `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` side. + /// fee on the `dest` (and possibly reserve) chains. /// - `fee_asset_item`: The index into `assets` of the item which should be used to pay /// fees. #[pallet::call_index(2)] @@ -1105,8 +1121,19 @@ pub mod pallet { }) } - /// Transfer some assets from the local chain to the sovereign account of a destination - /// chain and forward a notification XCM. + /// Transfer some assets from the local chain to the destination chain through their local, + /// destination or remote reserve. + /// + /// `assets` must have same reserve location and may not be teleportable to `dest`. + /// - `assets` have local reserve: transfer assets to sovereign account of destination + /// chain and forward a notification XCM to `dest` to mint and deposit reserve-based + /// assets to `beneficiary`. + /// - `assets` have destination reserve: 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`. + /// - `assets` have remote 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`. /// /// Fee payment on the destination side is made from the asset in the `assets` vector of /// index `fee_asset_item`, up to enough to pay for `weight_limit` of weight. If more weight @@ -1120,7 +1147,7 @@ pub mod pallet { /// - `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` side. + /// fee on the `dest` (and possibly reserve) chains. /// - `fee_asset_item`: The index into `assets` of the item which should be used to pay /// fees. /// - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase. @@ -1173,8 +1200,8 @@ pub mod pallet { /// from relay to parachain. /// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will /// generally be an `AccountId32` value. - /// - `assets`: The assets to be withdrawn. The first item should be the currency used to to - /// pay the fee on the `dest` side. May not be empty. + /// - `assets`: The assets to be withdrawn. This should include the assets used to pay the + /// fee on the `dest` chain. /// - `fee_asset_item`: The index into `assets` of the item which should be used to pay /// fees. /// - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase. @@ -1225,12 +1252,162 @@ pub mod pallet { XcmExecutionSuspended::<T>::set(suspended); Ok(()) } + + /// Transfer some assets from the local chain to the destination chain through their local, + /// destination or remote reserve, or through teleports. + /// + /// Fee payment on the destination side is made from the asset in the `assets` vector of + /// index `fee_asset_item` (hence referred to as `fees`), up to enough to pay for + /// `weight_limit` of weight. If more weight is needed than `weight_limit`, then the + /// operation will fail and the assets sent may be at risk. + /// + /// `assets` (excluding `fees`) must have same reserve location or otherwise be teleportable + /// to `dest`, no limitations imposed on `fees`. + /// - for local reserve: transfer assets to sovereign account of destination chain and + /// forward a notification XCM to `dest` to mint and deposit reserve-based assets to + /// `beneficiary`. + /// - for destination reserve: 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`. + /// - for remote 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`. + /// - for teleports: burn local assets and forward XCM to `dest` chain to mint/teleport + /// assets and deposit them to `beneficiary`. + /// + /// - `origin`: Must be capable of withdrawing the `assets` and executing XCM. + /// - `dest`: Destination context for the assets. Will typically be `X2(Parent, + /// Parachain(..))` to send from parachain to parachain, or `X1(Parachain(..))` to send + /// from relay to parachain. + /// - `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. + /// - `fee_asset_item`: The index into `assets` of the item which should be used to pay + /// fees. + /// - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase. + #[pallet::call_index(11)] + #[pallet::weight({ + let maybe_assets: Result<MultiAssets, ()> = (*assets.clone()).try_into(); + let maybe_dest: Result<MultiLocation, ()> = (*dest.clone()).try_into(); + match (maybe_assets, maybe_dest) { + (Ok(assets), Ok(dest)) => { + use sp_std::vec; + // heaviest version of locally executed XCM program: equivalent in weight to withdrawing fees, + // burning them, transferring rest of assets to SA, reanchoring them, extending XCM program, + // and sending onward XCM + let mut message = Xcm(vec![ + SetFeesMode { jit_withdraw: true }, + WithdrawAsset(assets.clone()), + BurnAsset(assets.clone()), + TransferReserveAsset { assets, dest, xcm: Xcm(vec![]) } + ]); + T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| T::WeightInfo::transfer_assets().saturating_add(w)) + } + _ => Weight::MAX, + } + })] + pub fn transfer_assets( + origin: OriginFor<T>, + dest: Box<VersionedMultiLocation>, + beneficiary: Box<VersionedMultiLocation>, + assets: Box<VersionedMultiAssets>, + fee_asset_item: u32, + weight_limit: WeightLimit, + ) -> DispatchResult { + let origin = T::ExecuteXcmOrigin::ensure_origin(origin)?; + let dest = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?; + let beneficiary: MultiLocation = + (*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?; + let assets: MultiAssets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?; + log::debug!( + target: "xcm::pallet_xcm::transfer_assets", + "origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, fee-idx {:?}, weight_limit {:?}", + origin, dest, beneficiary, assets, fee_asset_item, weight_limit, + ); + + ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets); + let mut 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, dest, fees, weight_limit)?, + TransferType::DestinationReserve => + Self::destination_reserve_fees_instructions( + origin, + dest, + fees, + weight_limit, + )?, + TransferType::Teleport => + Self::teleport_fees_instructions(origin, dest, fees, weight_limit)?, + TransferType::RemoteReserve(_) => + return Err(Error::<T>::InvalidAssetUnsupportedReserve.into()), + }; + FeesHandling::Separate { local_xcm, remote_xcm } + }; + + Self::build_and_execute_xcm_transfer_type( + origin, + dest, + beneficiary, + assets, + assets_transfer_type, + fees, + weight_limit, + ) + } } } /// The maximum number of distinct assets allowed to be transferred in a single helper extrinsic. const MAX_ASSETS_FOR_TRANSFER: usize = 2; +/// Specify how assets used for fees are handled during asset transfers. +#[derive(Clone, PartialEq)] +enum FeesHandling<T: Config> { + /// `fees` asset can be batch-transferred with rest of assets using same XCM instructions. + Batched { fees: MultiAsset }, + /// fees cannot be batched, they are handled separately using XCM programs here. + Separate { local_xcm: Xcm<<T as Config>::RuntimeCall>, remote_xcm: Xcm<()> }, +} + +impl<T: Config> sp_std::fmt::Debug for FeesHandling<T> { + fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { + match self { + Self::Batched { fees } => write!(f, "FeesHandling::Batched({:?})", fees), + Self::Separate { local_xcm, remote_xcm } => write!( + f, + "FeesHandling::Separate(local: {:?}, remote: {:?})", + local_xcm, remote_xcm + ), + } + } +} + impl<T: Config> QueryHandler for Pallet<T> { type QueryId = u64; type BlockNumber = BlockNumberFor<T>; @@ -1292,31 +1469,45 @@ impl<T: Config> QueryHandler for Pallet<T> { } impl<T: Config> Pallet<T> { - /// Validate `assets` to be reserve-transferred and return their reserve location. - fn validate_assets_and_find_reserve( + /// Find `TransferType`s for `assets` and fee identified through `fee_asset_item`, when + /// transferring to `dest`. + /// + /// Validate `assets` to all have same `TransferType`. + fn find_fee_and_assets_transfer_types( assets: &[MultiAsset], + fee_asset_item: usize, dest: &MultiLocation, - ) -> Result<TransferType, Error<T>> { - let mut reserve = None; - for asset in assets.iter() { + ) -> Result<(TransferType, TransferType), Error<T>> { + let mut fees_transfer_type = None; + let mut assets_transfer_type = None; + for (idx, asset) in assets.iter().enumerate() { if let Fungible(x) = asset.fun { // If fungible asset, ensure non-zero amount. ensure!(!x.is_zero(), Error::<T>::Empty); } let transfer_type = T::XcmExecutor::determine_for(&asset, dest).map_err(Error::<T>::from)?; - // Ensure asset is not teleportable to `dest`. - ensure!(transfer_type != TransferType::Teleport, Error::<T>::Filtered); - if let Some(reserve) = reserve.as_ref() { - // Ensure transfer for multiple assets uses same reserve location (only fee may have - // different reserve location) - ensure!(reserve == &transfer_type, Error::<T>::TooManyReserves); + if idx == fee_asset_item { + fees_transfer_type = Some(transfer_type); } else { - // asset reserve identified - reserve = Some(transfer_type); + if let Some(existing) = assets_transfer_type.as_ref() { + // Ensure transfer for multiple assets uses same transfer type (only fee may + // have different transfer type/path) + ensure!(existing == &transfer_type, Error::<T>::TooManyReserves); + } else { + // asset reserve identified + assets_transfer_type = Some(transfer_type); + } } } - reserve.ok_or(Error::<T>::Empty) + // single asset also marked as fee item + if assets.len() == 1 { + assets_transfer_type = fees_transfer_type + } + Ok(( + fees_transfer_type.ok_or(Error::<T>::Empty)?, + assets_transfer_type.ok_or(Error::<T>::Empty)?, + )) } fn do_reserve_transfer_assets( @@ -1332,7 +1523,7 @@ impl<T: Config> Pallet<T> { let beneficiary: MultiLocation = (*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?; let assets: MultiAssets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?; - log::trace!( + log::debug!( target: "xcm::pallet_xcm::do_reserve_transfer_assets", "origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, fee-idx {:?}", origin_location, dest, beneficiary, assets, fee_asset_item, @@ -1341,64 +1532,26 @@ impl<T: Config> Pallet<T> { ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets); let value = (origin_location, assets.into_inner()); ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered); - let (origin_location, mut assets) = value; + let (origin, assets) = value; - if fee_asset_item as usize >= assets.len() { - return Err(Error::<T>::Empty.into()) - } - let fees = assets.swap_remove(fee_asset_item as usize); - let fees_transfer_type = - T::XcmExecutor::determine_for(&fees, &dest).map_err(Error::<T>::from)?; - let assets_transfer_type = if assets.is_empty() { - // Single asset to transfer (one used for fees where transfer type is determined above). - ensure!(fees_transfer_type != TransferType::Teleport, Error::<T>::Filtered); - fees_transfer_type - } else { - // Find reserve for non-fee assets. - Self::validate_assets_and_find_reserve(&assets, &dest)? - }; + let fee_asset_item = fee_asset_item as usize; + let fees = assets.get(fee_asset_item as usize).ok_or(Error::<T>::Empty)?.clone(); - // local and remote XCM programs to potentially handle fees separately - let separate_fees_instructions: Option<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>)>; - if fees_transfer_type == assets_transfer_type { - // Same reserve location (fees not teleportable), we can batch together fees and assets - // in same reserve-based-transfer. - assets.push(fees.clone()); - // no need for custom fees instructions, fees are batched with assets - separate_fees_instructions = None; - } 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 fees = fees.clone(); - let weight_limit = weight_limit.clone(); - // build fees transfer instructions to be added to assets transfers XCM programs - separate_fees_instructions = Some(match fees_transfer_type { - TransferType::LocalReserve => - Self::local_reserve_fees_instructions(dest, fees, weight_limit)?, - TransferType::DestinationReserve => - Self::destination_reserve_fees_instructions(dest, fees, weight_limit)?, - TransferType::Teleport => - Self::teleport_fees_instructions(origin_location, dest, fees, weight_limit)?, - TransferType::RemoteReserve(_) => - return Err(Error::<T>::InvalidAssetUnsupportedReserve.into()), - }); - }; + // 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)?; + // Ensure assets (and fees according to check below) are not teleportable to `dest`. + ensure!(assets_transfer_type != TransferType::Teleport, Error::<T>::Filtered); + // 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_location, + origin, dest, beneficiary, assets, assets_transfer_type, - fees, - separate_fees_instructions, + FeesHandling::Batched { fees }, weight_limit, ) } @@ -1416,6 +1569,11 @@ impl<T: Config> Pallet<T> { let beneficiary: MultiLocation = (*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?; let assets: MultiAssets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?; + log::debug!( + target: "xcm::pallet_xcm::do_teleport_assets", + "origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, fee-idx {:?}, weight_limit {:?}", + origin_location, dest, beneficiary, assets, fee_asset_item, weight_limit, + ); ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets); let value = (origin_location, assets.into_inner()); @@ -1424,7 +1582,7 @@ impl<T: Config> Pallet<T> { for asset in assets.iter() { let transfer_type = T::XcmExecutor::determine_for(asset, &dest).map_err(Error::<T>::from)?; - ensure!(matches!(transfer_type, TransferType::Teleport), Error::<T>::Filtered); + ensure!(transfer_type == TransferType::Teleport, Error::<T>::Filtered); } let fees = assets.get(fee_asset_item as usize).ok_or(Error::<T>::Empty)?.clone(); @@ -1434,8 +1592,7 @@ impl<T: Config> Pallet<T> { beneficiary, assets, TransferType::Teleport, - fees, - None, + FeesHandling::Batched { fees }, weight_limit, ) } @@ -1446,54 +1603,65 @@ impl<T: Config> Pallet<T> { beneficiary: MultiLocation, assets: Vec<MultiAsset>, transfer_type: TransferType, - fees: MultiAsset, - separate_fees_instructions: Option<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>)>, + fees: FeesHandling<T>, weight_limit: WeightLimit, ) -> DispatchResult { - log::trace!( + log::debug!( target: "xcm::pallet_xcm::build_and_execute_xcm_transfer_type", "origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, transfer_type {:?}, \ - fees {:?}, fees_xcm: {:?}, weight_limit: {:?}", - origin, dest, beneficiary, assets, transfer_type, fees, separate_fees_instructions, weight_limit, + fees_handling {:?}, weight_limit: {:?}", + origin, dest, beneficiary, assets, transfer_type, fees, weight_limit, ); let (mut local_xcm, remote_xcm) = match transfer_type { TransferType::LocalReserve => { let (local, remote) = Self::local_reserve_transfer_programs( + origin, dest, beneficiary, assets, fees, - separate_fees_instructions, weight_limit, )?; (local, Some(remote)) }, TransferType::DestinationReserve => { let (local, remote) = Self::destination_reserve_transfer_programs( + origin, dest, beneficiary, assets, fees, - separate_fees_instructions, weight_limit, )?; (local, Some(remote)) }, - TransferType::RemoteReserve(reserve) => ( - Self::remote_reserve_transfer_program( + TransferType::RemoteReserve(reserve) => { + let fees = match fees { + FeesHandling::Batched { fees } => fees, + _ => return Err(Error::<T>::InvalidAssetUnsupportedReserve.into()), + }; + let local = Self::remote_reserve_transfer_program( + origin, reserve, dest, beneficiary, assets, fees, weight_limit, - )?, - None, - ), - TransferType::Teleport => ( - Self::teleport_assets_program(dest, beneficiary, assets, fees, weight_limit)?, - None, - ), + )?; + (local, None) + }, + TransferType::Teleport => { + let (local, remote) = Self::teleport_assets_program( + origin, + dest, + beneficiary, + assets, + fees, + weight_limit, + )?; + (local, Some(remote)) + }, }; let weight = T::Weigher::weight(&mut local_xcm).map_err(|()| Error::<T>::UnweighableMessage)?; @@ -1529,11 +1697,45 @@ impl<T: Config> Pallet<T> { Ok(()) } + fn add_fees_to_xcm( + dest: MultiLocation, + fees: FeesHandling<T>, + weight_limit: WeightLimit, + local: &mut Xcm<<T as Config>::RuntimeCall>, + remote: &mut Xcm<()>, + ) -> Result<(), Error<T>> { + match fees { + FeesHandling::Batched { fees } => { + let context = T::UniversalLocation::get(); + // no custom fees instructions, they are batched together with `assets` transfer; + // BuyExecution happens after receiving all `assets` + let reanchored_fees = + fees.reanchored(&dest, context).map_err(|_| Error::<T>::CannotReanchor)?; + // buy execution using `fees` batched together with above `reanchored_assets` + remote.inner_mut().push(BuyExecution { fees: reanchored_fees, weight_limit }); + }, + FeesHandling::Separate { local_xcm: mut local_fees, remote_xcm: mut remote_fees } => { + // fees are handled by separate XCM instructions, prepend fees instructions (for + // remote XCM they have to be prepended instead of appended to pass barriers). + sp_std::mem::swap(local, &mut local_fees); + sp_std::mem::swap(remote, &mut remote_fees); + // these are now swapped so fees actually go first + local.inner_mut().append(&mut local_fees.into_inner()); + remote.inner_mut().append(&mut remote_fees.into_inner()); + }, + } + Ok(()) + } + fn local_reserve_fees_instructions( + origin: MultiLocation, dest: MultiLocation, fees: MultiAsset, weight_limit: WeightLimit, ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> { + let value = (origin, vec![fees.clone()]); + ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered); + let context = T::UniversalLocation::get(); let reanchored_fees = fees .clone() @@ -1554,16 +1756,20 @@ impl<T: Config> Pallet<T> { } fn local_reserve_transfer_programs( + origin: MultiLocation, dest: MultiLocation, beneficiary: MultiLocation, assets: Vec<MultiAsset>, - fees: MultiAsset, - separate_fees_instructions: Option<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>)>, + fees: FeesHandling<T>, weight_limit: WeightLimit, ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> { + let value = (origin, assets); + ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered); + let (_, assets) = value; + // max assets is `assets` (+ potentially separately handled fee) let max_assets = - assets.len() as u32 + separate_fees_instructions.as_ref().map(|_| 1).unwrap_or(0); + assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 }; let assets: MultiAssets = assets.into(); let context = T::UniversalLocation::get(); let mut reanchored_assets = assets.clone(); @@ -1571,45 +1777,37 @@ impl<T: Config> Pallet<T> { .reanchor(&dest, context) .map_err(|_| Error::<T>::CannotReanchor)?; - // fees are either handled through dedicated instructions, or batched together with assets - let fees_already_handled = separate_fees_instructions.is_some(); - let (fees_local_xcm, fees_remote_xcm) = separate_fees_instructions - .map(|(local, remote)| (local.into_inner(), remote.into_inner())) - .unwrap_or_default(); - - // start off with any necessary local fees specific instructions - let mut local_execute_xcm = fees_local_xcm; - // move `assets` to `dest`s local sovereign account - local_execute_xcm.push(TransferAsset { assets, beneficiary: dest }); - - // on destination chain, start off with custom fee instructions - let mut xcm_on_dest = fees_remote_xcm; - // continue with rest of assets - xcm_on_dest.extend_from_slice(&[ + // XCM instructions to be executed on local chain + let mut local_execute_xcm = Xcm(vec![ + // locally move `assets` to `dest`s local sovereign account + TransferAsset { assets, beneficiary: dest }, + ]); + // XCM instructions to be executed on destination chain + let mut xcm_on_dest = Xcm(vec![ // let (dest) chain know assets are in its SA on reserve ReserveAssetDeposited(reanchored_assets), // following instructions are not exec'ed on behalf of origin chain anymore ClearOrigin, ]); - if !fees_already_handled { - // no custom fees instructions, they are batched together with `assets` transfer; - // BuyExecution happens after receiving all `assets` - let reanchored_fees = - fees.reanchored(&dest, context).map_err(|_| Error::<T>::CannotReanchor)?; - // buy execution using `fees` batched together with above `reanchored_assets` - xcm_on_dest.push(BuyExecution { fees: reanchored_fees, weight_limit }); - } + // 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.push(DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }); + xcm_on_dest + .inner_mut() + .push(DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }); - Ok((Xcm(local_execute_xcm), Xcm(xcm_on_dest))) + Ok((local_execute_xcm, xcm_on_dest)) } fn destination_reserve_fees_instructions( + origin: MultiLocation, dest: MultiLocation, fees: MultiAsset, weight_limit: WeightLimit, ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> { + let value = (origin, vec![fees.clone()]); + ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered); + let context = T::UniversalLocation::get(); let reanchored_fees = fees .clone() @@ -1633,16 +1831,20 @@ impl<T: Config> Pallet<T> { } fn destination_reserve_transfer_programs( + origin: MultiLocation, dest: MultiLocation, beneficiary: MultiLocation, assets: Vec<MultiAsset>, - fees: MultiAsset, - separate_fees_instructions: Option<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>)>, + fees: FeesHandling<T>, weight_limit: WeightLimit, ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> { + let value = (origin, assets); + ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered); + let (_, assets) = value; + // max assets is `assets` (+ potentially separately handled fee) let max_assets = - assets.len() as u32 + separate_fees_instructions.as_ref().map(|_| 1).unwrap_or(0); + assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 }; let assets: MultiAssets = assets.into(); let context = T::UniversalLocation::get(); let mut reanchored_assets = assets.clone(); @@ -1650,47 +1852,33 @@ impl<T: Config> Pallet<T> { .reanchor(&dest, context) .map_err(|_| Error::<T>::CannotReanchor)?; - // fees are either handled through dedicated instructions, or batched together with assets - let fees_already_handled = separate_fees_instructions.is_some(); - let (fees_local_xcm, fees_remote_xcm) = separate_fees_instructions - .map(|(local, remote)| (local.into_inner(), remote.into_inner())) - .unwrap_or_default(); - - // start off with any necessary local fees specific instructions - let mut local_execute_xcm = fees_local_xcm; - // continue with rest of assets - local_execute_xcm.extend_from_slice(&[ + // XCM instructions to be executed on local chain + let mut local_execute_xcm = Xcm(vec![ // withdraw reserve-based assets WithdrawAsset(assets.clone()), // burn reserve-based assets BurnAsset(assets), ]); - - // on destination chain, start off with custom fee instructions - let mut xcm_on_dest = fees_remote_xcm; - // continue with rest of assets - xcm_on_dest.extend_from_slice(&[ + // XCM instructions to be executed on destination chain + let mut xcm_on_dest = Xcm(vec![ // withdraw `assets` from origin chain's sovereign account WithdrawAsset(reanchored_assets), // following instructions are not exec'ed on behalf of origin chain anymore ClearOrigin, ]); - if !fees_already_handled { - // no custom fees instructions, they are batched together with `assets` transfer; - // BuyExecution happens after receiving all `assets` - let reanchored_fees = - fees.reanchored(&dest, context).map_err(|_| Error::<T>::CannotReanchor)?; - // buy execution using `fees` batched together with above `reanchored_assets` - xcm_on_dest.push(BuyExecution { fees: reanchored_fees, weight_limit }); - } + // 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.push(DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }); + xcm_on_dest + .inner_mut() + .push(DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }); - Ok((Xcm(local_execute_xcm), Xcm(xcm_on_dest))) + Ok((local_execute_xcm, xcm_on_dest)) } // function assumes fees and assets have the same remote reserve fn remote_reserve_transfer_program( + origin: MultiLocation, reserve: MultiLocation, dest: MultiLocation, beneficiary: MultiLocation, @@ -1698,6 +1886,10 @@ impl<T: Config> Pallet<T> { fees: MultiAsset, weight_limit: WeightLimit, ) -> Result<Xcm<<T as Config>::RuntimeCall>, Error<T>> { + let value = (origin, assets); + ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered); + let (_, assets) = value; + let max_assets = assets.len() as u32; let context = T::UniversalLocation::get(); // we spend up to half of fees for execution on reserve and other half for execution on @@ -1760,6 +1952,8 @@ impl<T: Config> Pallet<T> { &dummy_context, ) .map_err(|_| Error::<T>::CannotCheckOutTeleport)?; + // safe to do this here, we're in a transactional call that will be reverted on any + // errors down the line <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::check_out( &dest, &fees, @@ -1783,24 +1977,74 @@ impl<T: Config> Pallet<T> { } fn teleport_assets_program( + origin: MultiLocation, dest: MultiLocation, beneficiary: MultiLocation, assets: Vec<MultiAsset>, - mut fees: MultiAsset, + fees: FeesHandling<T>, weight_limit: WeightLimit, - ) -> Result<Xcm<<T as Config>::RuntimeCall>, Error<T>> { + ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> { + let value = (origin, assets); + ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered); + let (_, assets) = value; + + // max assets is `assets` (+ potentially separately handled fee) + let max_assets = + assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 }; let context = T::UniversalLocation::get(); - fees.reanchor(&dest, context).map_err(|_| Error::<T>::CannotReanchor)?; - let max_assets = assets.len() as u32; - let xcm_on_dest = Xcm(vec![ - BuyExecution { fees, weight_limit }, - DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }, + let assets: MultiAssets = assets.into(); + let mut reanchored_assets = assets.clone(); + reanchored_assets + .reanchor(&dest, context) + .map_err(|_| Error::<T>::CannotReanchor)?; + + // XcmContext irrelevant in teleports checks + let dummy_context = + XcmContext { origin: None, message_id: Default::default(), topic: None }; + for asset in assets.inner() { + // We should check that the asset can actually be teleported out (for this to + // be in error, there would need to be an accounting violation by ourselves, + // so it's unlikely, but we don't want to allow that kind of bug to leak into + // a trusted chain. + <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::can_check_out( + &dest, + asset, + &dummy_context, + ) + .map_err(|_| Error::<T>::CannotCheckOutTeleport)?; + } + for asset in assets.inner() { + // safe to do this here, we're in a transactional call that will be reverted on any + // errors down the line + <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::check_out( + &dest, + asset, + &dummy_context, + ); + } + + // XCM instructions to be executed on local chain + let mut local_execute_xcm = Xcm(vec![ + // withdraw assets to be teleported + WithdrawAsset(assets.clone()), + // burn assets on local chain + BurnAsset(assets), ]); - Ok(Xcm(vec![ - WithdrawAsset(assets.into()), - SetFeesMode { jit_withdraw: true }, - InitiateTeleport { assets: Wild(AllCounted(max_assets)), dest, xcm: xcm_on_dest }, - ])) + // XCM instructions to be executed on destination chain + let mut xcm_on_dest = Xcm(vec![ + // teleport `assets` in from origin chain + ReceiveTeleportedAsset(reanchored_assets), + // following instructions are not exec'ed on behalf of origin chain anymore + ClearOrigin, + ]); + // 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() + .push(DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }); + + Ok((local_execute_xcm, xcm_on_dest)) } /// Halve `fees` fungible amount. diff --git a/polkadot/xcm/pallet-xcm/src/mock.rs b/polkadot/xcm/pallet-xcm/src/mock.rs index 606d51bb8bc..9a734d0f276 100644 --- a/polkadot/xcm/pallet-xcm/src/mock.rs +++ b/polkadot/xcm/pallet-xcm/src/mock.rs @@ -579,6 +579,51 @@ impl super::benchmarking::Config for Test { Parachain(OTHER_PARA_ID).into(), )) } + + fn set_up_complex_asset_transfer( + ) -> Option<(MultiAssets, u32, MultiLocation, Box<dyn FnOnce()>)> { + use crate::tests::assets_transfer::{into_multiassets_checked, set_up_foreign_asset}; + // Transfer native asset (local reserve) to `USDT_PARA_ID`. Using teleport-trusted USDT for + // fees. + + let asset_amount = 10u128; + let fee_amount = 2u128; + + // create sufficient foreign asset USDT + let usdt_initial_local_amount = fee_amount * 10; + let (usdt_chain, _, usdt_id_multilocation) = + set_up_foreign_asset(USDT_PARA_ID, None, usdt_initial_local_amount, true); + + // native assets transfer destination is USDT chain (teleport trust only for USDT) + let dest = usdt_chain; + let (assets, fee_index, _, _) = into_multiassets_checked( + // USDT for fees (is sufficient on local chain too) - teleported + (usdt_id_multilocation, fee_amount).into(), + // native asset to transfer (not used for fees) - local reserve + (MultiLocation::here(), asset_amount).into(), + ); + + let existential_deposit = ExistentialDeposit::get(); + let caller = frame_benchmarking::whitelisted_caller(); + // Give some multiple of the existential deposit + let balance = asset_amount + existential_deposit * 1000; + let _ = <Balances as frame_support::traits::Currency<_>>::make_free_balance_be( + &caller, balance, + ); + // verify initial balance + assert_eq!(Balances::free_balance(&caller), balance); + + // verify transferred successfully + let verify = Box::new(move || { + // verify balance after transfer, decreased by transferred amount + assert_eq!(Balances::free_balance(&caller), balance - asset_amount); + assert_eq!( + Assets::balance(usdt_id_multilocation, &caller), + usdt_initial_local_amount - fee_amount + ); + }); + Some((assets, fee_index as u32, dest, verify)) + } } pub(crate) fn last_event() -> RuntimeEvent { diff --git a/polkadot/xcm/pallet-xcm/src/tests/assets_transfer.rs b/polkadot/xcm/pallet-xcm/src/tests/assets_transfer.rs index d1b298765e2..fb0bef26ebd 100644 --- a/polkadot/xcm/pallet-xcm/src/tests/assets_transfer.rs +++ b/polkadot/xcm/pallet-xcm/src/tests/assets_transfer.rs @@ -19,6 +19,7 @@ use crate::{ mock::*, tests::{ALICE, BOB, FEE_AMOUNT, INITIAL_BALANCE, SEND_AMOUNT}, + DispatchResult, OriginFor, }; use frame_support::{ assert_ok, @@ -32,6 +33,7 @@ use xcm_executor::traits::ConvertLocation; // Helper function to deduplicate testing different teleport types. fn do_test_and_verify_teleport_assets<Call: FnOnce()>( + origin_location: MultiLocation, expected_beneficiary: MultiLocation, call: Call, expected_weight_limit: WeightLimit, @@ -40,8 +42,9 @@ fn do_test_and_verify_teleport_assets<Call: FnOnce()>( (ALICE, INITIAL_BALANCE), (ParaId::from(OTHER_PARA_ID).into_account_truncating(), INITIAL_BALANCE), ]; + let dest = RelayLocation::get().into(); new_test_ext_with_balances(balances).execute_with(|| { - let weight = BaseXcmWeight::get() * 3; + let weight = BaseXcmWeight::get() * 2; assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); // call extrinsic call(); @@ -49,7 +52,7 @@ fn do_test_and_verify_teleport_assets<Call: FnOnce()>( assert_eq!( sent_xcm(), vec![( - RelayLocation::get().into(), + dest, Xcm(vec![ ReceiveTeleportedAsset((Here, SEND_AMOUNT).into()), ClearOrigin, @@ -63,10 +66,23 @@ fn do_test_and_verify_teleport_assets<Call: FnOnce()>( ); let versioned_sent = VersionedXcm::from(sent_xcm().into_iter().next().unwrap().1); let _check_v2_ok: xcm::v2::Xcm<()> = versioned_sent.try_into().unwrap(); + + let mut last_events = last_events(3).into_iter(); assert_eq!( - last_event(), + last_events.next().unwrap(), RuntimeEvent::XcmPallet(crate::Event::Attempted { outcome: Outcome::Complete(weight) }) ); + assert_eq!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::FeesPaid { + paying: origin_location, + fees: MultiAssets::new(), + }) + ); + assert!(matches!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::Sent { .. }) + )); }); } @@ -76,8 +92,10 @@ fn do_test_and_verify_teleport_assets<Call: FnOnce()>( /// local effects. #[test] fn teleport_assets_works() { + let origin_location: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); let beneficiary: MultiLocation = AccountId32 { network: None, id: BOB.into() }.into(); do_test_and_verify_teleport_assets( + origin_location, beneficiary, || { assert_ok!(XcmPallet::teleport_assets( @@ -98,10 +116,12 @@ fn teleport_assets_works() { /// local effects. #[test] fn limited_teleport_assets_works() { + let origin_location: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); let beneficiary: MultiLocation = AccountId32 { network: None, id: BOB.into() }.into(); let weight_limit = WeightLimit::Limited(Weight::from_parts(5000, 5000)); let expected_weight_limit = weight_limit.clone(); do_test_and_verify_teleport_assets( + origin_location, beneficiary, || { assert_ok!(XcmPallet::limited_teleport_assets( @@ -160,7 +180,7 @@ fn reserve_transfer_assets_with_paid_router_works() { let xcm_router_fee_amount = Para3000PaymentAmount::get(); let weight = BaseXcmWeight::get(); let dest: MultiLocation = - Junction::AccountId32 { network: None, id: user_account.clone().into() }.into(); + AccountId32 { network: None, id: user_account.clone().into() }.into(); assert_eq!(Balances::total_balance(&user_account), INITIAL_BALANCE); assert_ok!(XcmPallet::reserve_transfer_assets( RuntimeOrigin::signed(user_account.clone()), @@ -221,7 +241,7 @@ fn reserve_transfer_assets_with_paid_router_works() { }); } -fn set_up_foreign_asset( +pub(crate) fn set_up_foreign_asset( reserve_para_id: u32, inner_junction: Option<Junction>, initial_amount: u128, @@ -238,7 +258,7 @@ fn set_up_foreign_asset( reserve_location }; - // create sufficient (to be used as fees as well) foreign asset (0 total issuance) + // create sufficient (to be used as fees as well) foreign asset assert_ok!(Assets::force_create( RuntimeOrigin::root(), foreign_asset_id_multilocation, @@ -260,7 +280,7 @@ fn set_up_foreign_asset( // Helper function that provides correct `fee_index` after `sort()` done by // `vec![MultiAsset, MultiAsset].into()`. -fn into_multiassets_checked( +pub(crate) fn into_multiassets_checked( fee_asset: MultiAsset, transfer_asset: MultiAsset, ) -> (MultiAssets, usize, MultiAsset, MultiAsset) { @@ -269,29 +289,31 @@ fn into_multiassets_checked( (assets, fee_index, fee_asset, transfer_asset) } -/// Test `limited_reserve_transfer_assets` with local asset reserve and local fee reserve. +/// Test `tested_call` with local asset reserve and local fee reserve. /// /// Transferring native asset (local reserve) to some `OTHER_PARA_ID` (no teleport trust). /// Using native asset for fees as well. /// -/// ```nocompile -/// Here (source) OTHER_PARA_ID (destination) -/// | `assets` reserve -/// | `fees` reserve -/// | -/// | 1. execute `TransferReserveAsset(assets_and_fees_batched_together)` -/// | \--> sends `ReserveAssetDeposited(both), ClearOrigin, BuyExecution(fees), DepositAsset` -/// \------------------------------------------> -/// ``` -#[test] -fn limited_reserve_transfer_assets_with_local_asset_reserve_and_local_fee_reserve_works() { +/// Verifies `expected_result` +fn local_asset_reserve_and_local_fee_reserve_call<Call>( + tested_call: Call, + expected_result: DispatchResult, +) where + Call: FnOnce( + OriginFor<Test>, + Box<VersionedMultiLocation>, + Box<VersionedMultiLocation>, + Box<VersionedMultiAssets>, + u32, + WeightLimit, + ) -> DispatchResult, +{ let balances = vec![ (ALICE, INITIAL_BALANCE), (ParaId::from(OTHER_PARA_ID).into_account_truncating(), INITIAL_BALANCE), ]; - - let beneficiary: MultiLocation = - Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let origin_location: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); let weight_limit = WeightLimit::Limited(Weight::from_parts(5000, 5000)); let expected_weight_limit = weight_limit.clone(); let expected_beneficiary = beneficiary; @@ -301,14 +323,19 @@ fn limited_reserve_transfer_assets_with_local_asset_reserve_and_local_fee_reserv let weight = BaseXcmWeight::get(); assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); // call extrinsic - assert_ok!(XcmPallet::limited_reserve_transfer_assets( + let result = tested_call( RuntimeOrigin::signed(ALICE), Box::new(dest.into()), Box::new(beneficiary.into()), Box::new((Here, SEND_AMOUNT).into()), 0, weight_limit, - )); + ); + assert_eq!(result, expected_result); + if expected_result.is_err() { + // short-circuit here for tests where we expect failure + return + } // Alice spent amount assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE - SEND_AMOUNT); // Destination account (parachain account) has amount @@ -337,7 +364,7 @@ fn limited_reserve_transfer_assets_with_local_asset_reserve_and_local_fee_reserv assert_eq!( last_events.next().unwrap(), RuntimeEvent::XcmPallet(crate::Event::FeesPaid { - paying: expected_beneficiary, + paying: origin_location, fees: MultiAssets::new(), }) ); @@ -348,33 +375,66 @@ fn limited_reserve_transfer_assets_with_local_asset_reserve_and_local_fee_reserv }); } -/// Test `reserve_transfer_assets` with destination asset reserve and local fee reserve. +/// Test `transfer_assets` with local asset reserve and local fee reserve works. +#[test] +fn transfer_assets_with_local_asset_reserve_and_local_fee_reserve_works() { + let expected_result = Ok(()); + local_asset_reserve_and_local_fee_reserve_call(XcmPallet::transfer_assets, expected_result); +} + +/// Test `limited_reserve_transfer_assets` with local asset reserve and local fee reserve works. +#[test] +fn reserve_transfer_assets_with_local_asset_reserve_and_local_fee_reserve_works() { + let expected_result = Ok(()); + local_asset_reserve_and_local_fee_reserve_call( + XcmPallet::limited_reserve_transfer_assets, + expected_result, + ); +} + +/// Test `limited_teleport_assets` with local asset reserve and local fee reserve disallowed. +#[test] +fn teleport_assets_with_local_asset_reserve_and_local_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered"), + })); + local_asset_reserve_and_local_fee_reserve_call( + XcmPallet::limited_teleport_assets, + expected_result, + ); +} + +/// Test `tested_call` with destination asset reserve and local fee reserve. /// /// Transferring foreign asset (`FOREIGN_ASSET_RESERVE_PARA_ID` reserve) to /// `FOREIGN_ASSET_RESERVE_PARA_ID` (no teleport trust). /// Using native asset (local reserve) for fees. /// -/// ```nocompile -/// Here (source) FOREIGN_ASSET_RESERVE_PARA_ID (destination) -/// | `fees` reserve `assets` reserve -/// | -/// | 1. execute `TransferReserveAsset(fees)` -/// | \-> sends `ReserveAssetDeposited(fees), ClearOrigin, BuyExecution(fees), DepositAsset` -/// | 2. execute `InitiateReserveWithdraw(assets)` -/// | \--> sends `WithdrawAsset(assets), ClearOrigin, BuyExecution(fees), DepositAsset` -/// \------------------------------------------> -/// ``` -/// /// Asserts that the sender's balance is decreased and the beneficiary's balance /// is increased. Verifies the correct message is sent and event is emitted. -#[test] -fn reserve_transfer_assets_with_destination_asset_reserve_and_local_fee_reserve_works() { +/// +/// Verifies `expected_result`. +fn destination_asset_reserve_and_local_fee_reserve_call<Call>( + tested_call: Call, + expected_result: DispatchResult, +) where + Call: FnOnce( + OriginFor<Test>, + Box<VersionedMultiLocation>, + Box<VersionedMultiLocation>, + Box<VersionedMultiAssets>, + u32, + WeightLimit, + ) -> DispatchResult, +{ let weight = BaseXcmWeight::get() * 3; let balances = vec![(ALICE, INITIAL_BALANCE)]; - let beneficiary: MultiLocation = - Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let origin_location: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); new_test_ext_with_balances(balances).execute_with(|| { - // create non-sufficient foreign asset BLA (0 total issuance) + // create non-sufficient foreign asset BLA let foreign_initial_amount = 142; let (reserve_location, reserve_sovereign_account, foreign_asset_id_multilocation) = set_up_foreign_asset( @@ -404,14 +464,19 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_local_fee_reserve_ assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); // do the transfer - assert_ok!(XcmPallet::limited_reserve_transfer_assets( + let result = tested_call( RuntimeOrigin::signed(ALICE), Box::new(dest.into()), Box::new(beneficiary.into()), Box::new(assets.into()), fee_index as u32, Unlimited, - )); + ); + assert_eq!(result, expected_result); + if expected_result.is_err() { + // short-circuit here for tests where we expect failure + return + } let mut last_events = last_events(3).into_iter(); assert_eq!( @@ -454,7 +519,7 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_local_fee_reserve_ assert_eq!( last_events.next().unwrap(), RuntimeEvent::XcmPallet(crate::Event::FeesPaid { - paying: beneficiary, + paying: origin_location, fees: MultiAssets::new(), }) ); @@ -465,17 +530,67 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_local_fee_reserve_ }); } -/// Test `reserve_transfer_assets` with remote asset reserve and local fee reserve. +/// Test `transfer_assets` with destination asset reserve and local fee reserve. +#[test] +fn transfer_assets_with_destination_asset_reserve_and_local_fee_reserve_works() { + let expected_result = Ok(()); + destination_asset_reserve_and_local_fee_reserve_call( + XcmPallet::transfer_assets, + expected_result, + ); +} + +/// Test `limited_reserve_transfer_assets` with destination asset reserve and local fee reserve +/// disallowed. +#[test] +fn reserve_transfer_assets_with_destination_asset_reserve_and_local_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [23, 0, 0, 0], + message: Some("TooManyReserves"), + })); + destination_asset_reserve_and_local_fee_reserve_call( + XcmPallet::limited_reserve_transfer_assets, + expected_result, + ); +} + +/// Test `limited_teleport_assets` with destination asset reserve and local fee reserve +/// disallowed. +#[test] +fn teleport_assets_with_destination_asset_reserve_and_local_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered"), + })); + destination_asset_reserve_and_local_fee_reserve_call( + XcmPallet::limited_teleport_assets, + expected_result, + ); +} + +/// Test `tested_call` with remote asset reserve and local fee reserve is disallowed. /// /// Transferring foreign asset (reserve on `FOREIGN_ASSET_RESERVE_PARA_ID`) to `OTHER_PARA_ID`. /// Using native (local reserve) as fee should be disallowed. -#[test] -fn reserve_transfer_assets_with_remote_asset_reserve_and_local_fee_reserve_disallowed() { +fn remote_asset_reserve_and_local_fee_reserve_call_disallowed<Call>( + tested_call: Call, + expected_result: DispatchResult, +) where + Call: FnOnce( + OriginFor<Test>, + Box<VersionedMultiLocation>, + Box<VersionedMultiLocation>, + Box<VersionedMultiAssets>, + u32, + WeightLimit, + ) -> DispatchResult, +{ let balances = vec![(ALICE, INITIAL_BALANCE)]; - let beneficiary: MultiLocation = - Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); new_test_ext_with_balances(balances).execute_with(|| { - // create non-sufficient foreign asset BLA (0 total issuance) + // create non-sufficient foreign asset BLA let foreign_initial_amount = 142; let (_, _, foreign_asset_id_multilocation) = set_up_foreign_asset( FOREIGN_ASSET_RESERVE_PARA_ID, @@ -500,7 +615,7 @@ fn reserve_transfer_assets_with_remote_asset_reserve_and_local_fee_reserve_disal assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); // try the transfer - let result = XcmPallet::limited_reserve_transfer_assets( + let result = tested_call( RuntimeOrigin::signed(ALICE), Box::new(dest.into()), Box::new(beneficiary.into()), @@ -508,14 +623,7 @@ fn reserve_transfer_assets_with_remote_asset_reserve_and_local_fee_reserve_disal fee_index as u32, Unlimited, ); - assert_eq!( - result, - Err(DispatchError::Module(ModuleError { - index: 4, - error: [22, 0, 0, 0], - message: Some("InvalidAssetUnsupportedReserve") - })) - ); + assert_eq!(result, expected_result); // Alice transferred nothing assert_eq!(Assets::balance(foreign_asset_id_multilocation, ALICE), foreign_initial_amount); @@ -529,28 +637,76 @@ fn reserve_transfer_assets_with_remote_asset_reserve_and_local_fee_reserve_disal }); } -/// Test `reserve_transfer_assets` with local asset reserve and destination fee reserve. +/// Test `transfer_assets` with remote asset reserve and local fee reserve is disallowed. +#[test] +fn transfer_assets_with_remote_asset_reserve_and_local_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [22, 0, 0, 0], + message: Some("InvalidAssetUnsupportedReserve"), + })); + remote_asset_reserve_and_local_fee_reserve_call_disallowed( + XcmPallet::transfer_assets, + expected_result, + ); +} + +/// Test `limited_reserve_transfer_assets` with remote asset reserve and local fee reserve is +/// disallowed. +#[test] +fn reserve_transfer_assets_with_remote_asset_reserve_and_local_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [23, 0, 0, 0], + message: Some("TooManyReserves"), + })); + remote_asset_reserve_and_local_fee_reserve_call_disallowed( + XcmPallet::limited_reserve_transfer_assets, + expected_result, + ); +} + +/// Test `limited_teleport_assets` with remote asset reserve and local fee reserve is disallowed. +#[test] +fn teleport_assets_with_remote_asset_reserve_and_local_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered"), + })); + remote_asset_reserve_and_local_fee_reserve_call_disallowed( + XcmPallet::limited_teleport_assets, + expected_result, + ); +} + +/// Test `tested_call` with local asset reserve and destination fee reserve. /// /// Transferring native asset (local reserve) to `USDC_RESERVE_PARA_ID` (no teleport trust). Using /// foreign asset (`USDC_RESERVE_PARA_ID` reserve) for fees. /// -/// ```nocompile -/// Here (source) USDC_RESERVE_PARA_ID (destination) -/// | `assets` reserve `fees` reserve -/// | -/// | 1. execute `InitiateReserveWithdraw(fees)` -/// | \--> sends `WithdrawAsset(fees), ClearOrigin, BuyExecution(fees), DepositAsset` -/// | 2. execute `TransferReserveAsset(assets)` -/// | \-> sends `ReserveAssetDeposited(assets), ClearOrigin, BuyExecution(fees), DepositAsset` -/// \------------------------------------------> -/// ``` -#[test] -fn reserve_transfer_assets_with_local_asset_reserve_and_destination_fee_reserve_works() { +/// Asserts that the sender's balance is decreased and the beneficiary's balance +/// is increased. Verifies the correct message is sent and event is emitted. +/// +/// Verifies `expected_result`. +fn local_asset_reserve_and_destination_fee_reserve_call<Call>( + tested_call: Call, + expected_result: DispatchResult, +) where + Call: FnOnce( + OriginFor<Test>, + Box<VersionedMultiLocation>, + Box<VersionedMultiLocation>, + Box<VersionedMultiAssets>, + u32, + WeightLimit, + ) -> DispatchResult, +{ let balances = vec![(ALICE, INITIAL_BALANCE)]; - let beneficiary: MultiLocation = - Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let origin_location: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); new_test_ext_with_balances(balances).execute_with(|| { - // create sufficient foreign asset USDC (0 total issuance) + // create sufficient foreign asset USDC let usdc_initial_local_amount = 142; let (usdc_reserve_location, usdc_chain_sovereign_account, usdc_id_multilocation) = set_up_foreign_asset( @@ -580,14 +736,20 @@ fn reserve_transfer_assets_with_local_asset_reserve_and_destination_fee_reserve_ assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); // do the transfer - assert_ok!(XcmPallet::limited_reserve_transfer_assets( + let result = tested_call( RuntimeOrigin::signed(ALICE), Box::new(dest.into()), Box::new(beneficiary.into()), Box::new(assets.into()), fee_index as u32, Unlimited, - )); + ); + assert_eq!(result, expected_result); + if expected_result.is_err() { + // short-circuit here for tests where we expect failure + return + } + let weight = BaseXcmWeight::get() * 3; let mut last_events = last_events(3).into_iter(); assert_eq!( @@ -597,7 +759,7 @@ fn reserve_transfer_assets_with_local_asset_reserve_and_destination_fee_reserve_ assert_eq!( last_events.next().unwrap(), RuntimeEvent::XcmPallet(crate::Event::FeesPaid { - paying: beneficiary, + paying: origin_location, fees: MultiAssets::new(), }) ); @@ -642,22 +804,64 @@ fn reserve_transfer_assets_with_local_asset_reserve_and_destination_fee_reserve_ }); } -/// Test `reserve_transfer_assets` with destination asset reserve and destination fee reserve. -/// -/// ```nocompile -/// Here (source) FOREIGN_ASSET_RESERVE_PARA_ID (destination) -/// | `fees` reserve -/// | `assets` reserve -/// | -/// | 1. execute `InitiateReserveWithdraw(assets_and_fees_batched_together)` -/// | \--> sends `WithdrawAsset(batch), ClearOrigin, BuyExecution(fees), DepositAsset` -/// \------------------------------------------> -/// ``` +/// Test `transfer_assets` with local asset reserve and destination fee reserve. #[test] -fn reserve_transfer_assets_with_destination_asset_reserve_and_destination_fee_reserve_works() { +fn transfer_assets_with_local_asset_reserve_and_destination_fee_reserve_works() { + let expected_result = Ok(()); + local_asset_reserve_and_destination_fee_reserve_call( + XcmPallet::transfer_assets, + expected_result, + ); +} + +/// Test `limited_reserve_transfer_assets` with local asset reserve and destination fee reserve +/// disallowed. +#[test] +fn reserve_transfer_assets_with_local_asset_reserve_and_destination_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [23, 0, 0, 0], + message: Some("TooManyReserves"), + })); + local_asset_reserve_and_destination_fee_reserve_call( + XcmPallet::limited_reserve_transfer_assets, + expected_result, + ); +} + +/// Test `limited_teleport_assets` with local asset reserve and destination fee reserve disallowed. +#[test] +fn teleport_assets_with_local_asset_reserve_and_destination_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered"), + })); + local_asset_reserve_and_destination_fee_reserve_call( + XcmPallet::limited_teleport_assets, + expected_result, + ); +} + +/// Test `tested_call` with destination asset reserve and destination fee reserve. +/// +/// Verifies `expected_result` +fn destination_asset_reserve_and_destination_fee_reserve_call<Call>( + tested_call: Call, + expected_result: DispatchResult, +) where + Call: FnOnce( + OriginFor<Test>, + Box<VersionedMultiLocation>, + Box<VersionedMultiLocation>, + Box<VersionedMultiAssets>, + u32, + WeightLimit, + ) -> DispatchResult, +{ let balances = vec![(ALICE, INITIAL_BALANCE)]; - let beneficiary: MultiLocation = - Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let origin_location: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); new_test_ext_with_balances(balances).execute_with(|| { // we'll send just this foreign asset back to its reserve location and use it for fees as // well @@ -684,14 +888,19 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_destination_fee_re assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); // do the transfer - assert_ok!(XcmPallet::limited_reserve_transfer_assets( + let result = tested_call( RuntimeOrigin::signed(ALICE), Box::new(dest.into()), Box::new(beneficiary.into()), Box::new(assets.into()), fee_index, Unlimited, - )); + ); + assert_eq!(result, expected_result); + if expected_result.is_err() { + // short-circuit here for tests where we expect failure + return + } let weight = BaseXcmWeight::get() * 2; let mut last_events = last_events(3).into_iter(); @@ -702,7 +911,7 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_destination_fee_re assert_eq!( last_events.next().unwrap(), RuntimeEvent::XcmPallet(crate::Event::FeesPaid { - paying: beneficiary, + paying: origin_location, fees: MultiAssets::new(), }) ); @@ -743,18 +952,63 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_destination_fee_re }); } -/// Test `reserve_transfer_assets` with remote asset reserve and destination fee reserve is +/// Test `transfer_assets` with destination asset reserve and destination fee reserve. +#[test] +fn transfer_assets_with_destination_asset_reserve_and_destination_fee_reserve_works() { + let expected_result = Ok(()); + destination_asset_reserve_and_destination_fee_reserve_call( + XcmPallet::transfer_assets, + expected_result, + ); +} + +/// Test `limited_reserve_transfer_assets` with destination asset reserve and destination fee +/// reserve. +#[test] +fn reserve_transfer_assets_with_destination_asset_reserve_and_destination_fee_reserve_works() { + let expected_result = Ok(()); + destination_asset_reserve_and_destination_fee_reserve_call( + XcmPallet::limited_reserve_transfer_assets, + expected_result, + ); +} + +/// Test `limited_teleport_assets` with destination asset reserve and destination fee reserve /// disallowed. +#[test] +fn teleport_assets_with_destination_asset_reserve_and_destination_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered"), + })); + destination_asset_reserve_and_destination_fee_reserve_call( + XcmPallet::limited_teleport_assets, + expected_result, + ); +} + +/// Test `transfer_assets` with remote asset reserve and destination fee reserve is disallowed. /// /// Transferring foreign asset (reserve on `FOREIGN_ASSET_RESERVE_PARA_ID`) to /// `USDC_RESERVE_PARA_ID`. Using USDC (destination reserve) as fee. -#[test] -fn reserve_transfer_assets_with_remote_asset_reserve_and_destination_fee_reserve_disallowed() { +fn remote_asset_reserve_and_destination_fee_reserve_call_disallowed<Call>( + tested_call: Call, + expected_result: DispatchResult, +) where + Call: FnOnce( + OriginFor<Test>, + Box<VersionedMultiLocation>, + Box<VersionedMultiLocation>, + Box<VersionedMultiAssets>, + u32, + WeightLimit, + ) -> DispatchResult, +{ let balances = vec![(ALICE, INITIAL_BALANCE)]; - let beneficiary: MultiLocation = - Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); new_test_ext_with_balances(balances).execute_with(|| { - // create sufficient foreign asset USDC (0 total issuance) + // create sufficient foreign asset USDC let usdc_initial_local_amount = 42; let (usdc_chain, _, usdc_id_multilocation) = set_up_foreign_asset( USDC_RESERVE_PARA_ID, @@ -763,7 +1017,7 @@ fn reserve_transfer_assets_with_remote_asset_reserve_and_destination_fee_reserve true, ); - // create non-sufficient foreign asset BLA (0 total issuance) + // create non-sufficient foreign asset BLA let foreign_initial_amount = 142; let (_, _, foreign_asset_id_multilocation) = set_up_foreign_asset( FOREIGN_ASSET_RESERVE_PARA_ID, @@ -789,7 +1043,7 @@ fn reserve_transfer_assets_with_remote_asset_reserve_and_destination_fee_reserve assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); // do the transfer - let result = XcmPallet::limited_reserve_transfer_assets( + let result = tested_call( RuntimeOrigin::signed(ALICE), Box::new(dest.into()), Box::new(beneficiary.into()), @@ -797,14 +1051,7 @@ fn reserve_transfer_assets_with_remote_asset_reserve_and_destination_fee_reserve fee_index as u32, Unlimited, ); - assert_eq!( - result, - Err(DispatchError::Module(ModuleError { - index: 4, - error: [22, 0, 0, 0], - message: Some("InvalidAssetUnsupportedReserve") - })) - ); + assert_eq!(result, expected_result); // Alice native asset untouched assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); @@ -819,17 +1066,71 @@ fn reserve_transfer_assets_with_remote_asset_reserve_and_destination_fee_reserve }); } -/// Test `reserve_transfer_assets` with local asset reserve and remote fee reserve is disallowed. +/// Test `transfer_assets` with remote asset reserve and destination fee reserve is disallowed. +#[test] +fn transfer_assets_with_remote_asset_reserve_and_destination_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [22, 0, 0, 0], + message: Some("InvalidAssetUnsupportedReserve"), + })); + remote_asset_reserve_and_destination_fee_reserve_call_disallowed( + XcmPallet::transfer_assets, + expected_result, + ); +} + +/// Test `limited_reserve_transfer_assets` with remote asset reserve and destination fee reserve is +/// disallowed. +#[test] +fn reserve_transfer_assets_with_remote_asset_reserve_and_destination_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [23, 0, 0, 0], + message: Some("TooManyReserves"), + })); + remote_asset_reserve_and_destination_fee_reserve_call_disallowed( + XcmPallet::limited_reserve_transfer_assets, + expected_result, + ); +} + +/// Test `limited_teleport_assets` with remote asset reserve and destination fee reserve is +/// disallowed. +#[test] +fn teleport_assets_with_remote_asset_reserve_and_destination_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered"), + })); + remote_asset_reserve_and_destination_fee_reserve_call_disallowed( + XcmPallet::limited_teleport_assets, + expected_result, + ); +} + +/// Test `tested_call` with local asset reserve and remote fee reserve is disallowed. /// /// Transferring native asset (local reserve) to `OTHER_PARA_ID` (no teleport trust). Using foreign /// asset (`USDC_RESERVE_PARA_ID` remote reserve) for fees. -#[test] -fn reserve_transfer_assets_with_local_asset_reserve_and_remote_fee_reserve_disallowed() { +fn local_asset_reserve_and_remote_fee_reserve_call_disallowed<Call>( + tested_call: Call, + expected_result: DispatchResult, +) where + Call: FnOnce( + OriginFor<Test>, + Box<VersionedMultiLocation>, + Box<VersionedMultiLocation>, + Box<VersionedMultiAssets>, + u32, + WeightLimit, + ) -> DispatchResult, +{ let balances = vec![(ALICE, INITIAL_BALANCE)]; - let beneficiary: MultiLocation = - Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); new_test_ext_with_balances(balances).execute_with(|| { - // create sufficient foreign asset USDC (0 total issuance) + // create sufficient foreign asset USDC let usdc_initial_local_amount = 142; let (_, usdc_chain_sovereign_account, usdc_id_multilocation) = set_up_foreign_asset( USDC_RESERVE_PARA_ID, @@ -854,7 +1155,7 @@ fn reserve_transfer_assets_with_local_asset_reserve_and_remote_fee_reserve_disal assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); // do the transfer - let result = XcmPallet::limited_reserve_transfer_assets( + let result = tested_call( RuntimeOrigin::signed(ALICE), Box::new(dest.into()), Box::new(beneficiary.into()), @@ -862,14 +1163,7 @@ fn reserve_transfer_assets_with_local_asset_reserve_and_remote_fee_reserve_disal fee_index as u32, Unlimited, ); - assert_eq!( - result, - Err(DispatchError::Module(ModuleError { - index: 4, - error: [22, 0, 0, 0], - message: Some("InvalidAssetUnsupportedReserve") - })) - ); + assert_eq!(result, expected_result); assert_eq!(Assets::balance(usdc_id_multilocation, ALICE), usdc_initial_local_amount); assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); // Sovereign account of reserve parachain is unchanged @@ -882,18 +1176,70 @@ fn reserve_transfer_assets_with_local_asset_reserve_and_remote_fee_reserve_disal }); } -/// Test `reserve_transfer_assets` with destination asset reserve and remote fee reserve is +/// Test `transfer_assets` with local asset reserve and remote fee reserve is disallowed. +#[test] +fn transfer_assets_with_local_asset_reserve_and_remote_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [22, 0, 0, 0], + message: Some("InvalidAssetUnsupportedReserve"), + })); + local_asset_reserve_and_remote_fee_reserve_call_disallowed( + XcmPallet::transfer_assets, + expected_result, + ); +} + +/// Test `limited_reserve_transfer_assets` with local asset reserve and remote fee reserve is /// disallowed. +#[test] +fn reserve_transfer_assets_with_local_asset_reserve_and_remote_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [23, 0, 0, 0], + message: Some("TooManyReserves"), + })); + local_asset_reserve_and_remote_fee_reserve_call_disallowed( + XcmPallet::limited_reserve_transfer_assets, + expected_result, + ); +} + +/// Test `limited_teleport_assets` with local asset reserve and remote fee reserve is disallowed. +#[test] +fn teleport_assets_with_local_asset_reserve_and_remote_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered"), + })); + local_asset_reserve_and_remote_fee_reserve_call_disallowed( + XcmPallet::limited_teleport_assets, + expected_result, + ); +} + +/// Test `tested_call` with destination asset reserve and remote fee reserve is disallowed. /// /// Transferring native asset (local reserve) to `OTHER_PARA_ID` (no teleport trust). Using foreign /// asset (`USDC_RESERVE_PARA_ID` remote reserve) for fees. -#[test] -fn reserve_transfer_assets_with_destination_asset_reserve_and_remote_fee_reserve_disallowed() { +fn destination_asset_reserve_and_remote_fee_reserve_call_disallowed<Call>( + tested_call: Call, + expected_result: DispatchResult, +) where + Call: FnOnce( + OriginFor<Test>, + Box<VersionedMultiLocation>, + Box<VersionedMultiLocation>, + Box<VersionedMultiAssets>, + u32, + WeightLimit, + ) -> DispatchResult, +{ let balances = vec![(ALICE, INITIAL_BALANCE)]; - let beneficiary: MultiLocation = - Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); new_test_ext_with_balances(balances).execute_with(|| { - // create sufficient foreign asset USDC (0 total issuance) + // create sufficient foreign asset USDC let usdc_initial_local_amount = 42; let (_, usdc_chain_sovereign_account, usdc_id_multilocation) = set_up_foreign_asset( USDC_RESERVE_PARA_ID, @@ -902,7 +1248,7 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_remote_fee_reserve true, ); - // create non-sufficient foreign asset BLA (0 total issuance) + // create non-sufficient foreign asset BLA let foreign_initial_amount = 142; let (reserve_location, foreign_sovereign_account, foreign_asset_id_multilocation) = set_up_foreign_asset( @@ -928,7 +1274,7 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_remote_fee_reserve assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); // do the transfer - let result = XcmPallet::limited_reserve_transfer_assets( + let result = tested_call( RuntimeOrigin::signed(ALICE), Box::new(dest.into()), Box::new(beneficiary.into()), @@ -936,14 +1282,7 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_remote_fee_reserve fee_index as u32, Unlimited, ); - assert_eq!( - result, - Err(DispatchError::Module(ModuleError { - index: 4, - error: [22, 0, 0, 0], - message: Some("InvalidAssetUnsupportedReserve") - })) - ); + assert_eq!(result, expected_result); // Alice native asset untouched assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); assert_eq!(Assets::balance(usdc_id_multilocation, ALICE), usdc_initial_local_amount); @@ -961,7 +1300,51 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_remote_fee_reserve }); } -/// Test `reserve_transfer_assets` with remote asset reserve and (same) remote fee reserve. +/// Test `transfer_assets` with destination asset reserve and remote fee reserve is disallowed. +#[test] +fn transfer_assets_with_destination_asset_reserve_and_remote_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [22, 0, 0, 0], + message: Some("InvalidAssetUnsupportedReserve"), + })); + destination_asset_reserve_and_remote_fee_reserve_call_disallowed( + XcmPallet::transfer_assets, + expected_result, + ); +} + +/// Test `limited_reserve_transfer_assets` with destination asset reserve and remote fee reserve is +/// disallowed. +#[test] +fn reserve_transfer_assets_with_destination_asset_reserve_and_remote_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [23, 0, 0, 0], + message: Some("TooManyReserves"), + })); + destination_asset_reserve_and_remote_fee_reserve_call_disallowed( + XcmPallet::limited_reserve_transfer_assets, + expected_result, + ); +} + +/// Test `limited_teleport_assets` with destination asset reserve and remote fee reserve is +/// disallowed. +#[test] +fn teleport_assets_with_destination_asset_reserve_and_remote_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered"), + })); + destination_asset_reserve_and_remote_fee_reserve_call_disallowed( + XcmPallet::limited_teleport_assets, + expected_result, + ); +} + +/// Test `tested_call` with remote asset reserve and (same) remote fee reserve. /// /// Transferring native asset (local reserve) to `OTHER_PARA_ID` (no teleport trust). Using foreign /// asset (`USDC_RESERVE_PARA_ID` remote reserve) for fees. @@ -976,13 +1359,25 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_remote_fee_reserve /// | -----------------> `C` executes `DepositReserveAsset(both)` dest `B` /// | --------------------------> `DepositAsset(both)` /// ``` -#[test] -fn reserve_transfer_assets_with_remote_asset_reserve_and_remote_fee_reserve_works() { +/// +/// Verifies `expected_result` +fn remote_asset_reserve_and_remote_fee_reserve_call<Call>( + tested_call: Call, + expected_result: DispatchResult, +) where + Call: FnOnce( + OriginFor<Test>, + Box<VersionedMultiLocation>, + Box<VersionedMultiLocation>, + Box<VersionedMultiAssets>, + u32, + WeightLimit, + ) -> DispatchResult, +{ let balances = vec![(ALICE, INITIAL_BALANCE)]; - let beneficiary: MultiLocation = - Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); new_test_ext_with_balances(balances).execute_with(|| { - // create sufficient foreign asset USDC (0 total issuance) + // create sufficient foreign asset USDC let usdc_initial_local_amount = 142; let (usdc_chain, usdc_chain_sovereign_account, usdc_id_multilocation) = set_up_foreign_asset( @@ -996,12 +1391,12 @@ fn reserve_transfer_assets_with_remote_asset_reserve_and_remote_fee_reserve_work let dest = RelayLocation::get().pushed_with_interior(Parachain(OTHER_PARA_ID)).unwrap(); let assets: MultiAssets = vec![(usdc_id_multilocation, SEND_AMOUNT).into()].into(); - let fee_index = 0; + let fee_index = 0u32; // reanchor according to test-case let context = UniversalLocation::get(); let expected_dest_on_reserve = dest.reanchored(&usdc_chain, context).unwrap(); - let fees = assets.get(fee_index).unwrap().clone(); + let fees = assets.get(fee_index as usize).unwrap().clone(); let (fees_half_1, fees_half_2) = XcmPallet::halve_fees(fees).unwrap(); let mut expected_assets_on_reserve = assets.clone(); expected_assets_on_reserve.reanchor(&usdc_chain, context).unwrap(); @@ -1013,14 +1408,20 @@ fn reserve_transfer_assets_with_remote_asset_reserve_and_remote_fee_reserve_work assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); // do the transfer - assert_ok!(XcmPallet::limited_reserve_transfer_assets( + let result = tested_call( RuntimeOrigin::signed(ALICE), Box::new(dest.into()), Box::new(beneficiary.into()), Box::new(assets.into()), - fee_index as u32, + fee_index, Unlimited, - )); + ); + assert_eq!(result, expected_result); + if expected_result.is_err() { + // short-circuit here for tests where we expect failure + return + } + assert!(matches!( last_event(), RuntimeEvent::XcmPallet(crate::Event::Attempted { outcome: Outcome::Complete(_) }) @@ -1067,28 +1468,62 @@ fn reserve_transfer_assets_with_remote_asset_reserve_and_remote_fee_reserve_work }); } -/// Test `reserve_transfer_assets` with local asset reserve and teleported fee. +/// Test `transfer_assets` with remote asset reserve and (same) remote fee reserve. +#[test] +fn transfer_assets_with_remote_asset_reserve_and_remote_fee_reserve_works() { + let expected_result = Ok(()); + remote_asset_reserve_and_remote_fee_reserve_call(XcmPallet::transfer_assets, expected_result); +} + +/// Test `limited_reserve_transfer_assets` with remote asset reserve and (same) remote fee reserve. +#[test] +fn reserve_transfer_assets_with_remote_asset_reserve_and_remote_fee_reserve_works() { + let expected_result = Ok(()); + remote_asset_reserve_and_remote_fee_reserve_call( + XcmPallet::limited_reserve_transfer_assets, + expected_result, + ); +} + +/// Test `limited_teleport_assets` with remote asset reserve and (same) remote fee reserve +/// disallowed. +#[test] +fn teleport_assets_with_remote_asset_reserve_and_remote_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered"), + })); + remote_asset_reserve_and_remote_fee_reserve_call( + XcmPallet::limited_teleport_assets, + expected_result, + ); +} + +/// Test `tested_call` with local asset reserve and teleported fee. /// /// Transferring native asset (local reserve) to `USDT_PARA_ID`. Using teleport-trusted USDT for /// fees. /// -/// ```nocompile -/// Here (source) USDT_PARA_ID (destination) -/// | `assets` reserve `fees` teleport-trust -/// | -/// | 1. execute `InitiateTeleport(fees)` -/// | \--> sends `ReceiveTeleportedAsset(fees), .., DepositAsset(fees)` -/// | 2. execute `TransferReserveAsset(assets)` -/// | \-> sends `ReserveAssetDeposited(assets), ClearOrigin, BuyExecution(fees), DepositAsset` -/// \------------------------------------------> -/// ``` -#[test] -fn reserve_transfer_assets_with_local_asset_reserve_and_teleported_fee_works() { +/// Verifies `expected_result` +fn local_asset_reserve_and_teleported_fee_call<Call>( + tested_call: Call, + expected_result: DispatchResult, +) where + Call: FnOnce( + OriginFor<Test>, + Box<VersionedMultiLocation>, + Box<VersionedMultiLocation>, + Box<VersionedMultiAssets>, + u32, + WeightLimit, + ) -> DispatchResult, +{ let balances = vec![(ALICE, INITIAL_BALANCE)]; - let beneficiary: MultiLocation = - Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let origin_location: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); new_test_ext_with_balances(balances).execute_with(|| { - // create sufficient foreign asset USDT (0 total issuance) + // create sufficient foreign asset USDT let usdt_initial_local_amount = 42; let (usdt_chain, usdt_chain_sovereign_account, usdt_id_multilocation) = set_up_foreign_asset(USDT_PARA_ID, None, usdt_initial_local_amount, true); @@ -1113,14 +1548,20 @@ fn reserve_transfer_assets_with_local_asset_reserve_and_teleported_fee_works() { assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); // do the transfer - assert_ok!(XcmPallet::limited_reserve_transfer_assets( + let result = tested_call( RuntimeOrigin::signed(ALICE), Box::new(dest.into()), Box::new(beneficiary.into()), Box::new(assets.into()), fee_index as u32, Unlimited, - )); + ); + assert_eq!(result, expected_result); + if expected_result.is_err() { + // short-circuit here for tests where we expect failure + return + } + let weight = BaseXcmWeight::get() * 3; let mut last_events = last_events(3).into_iter(); assert_eq!( @@ -1130,7 +1571,7 @@ fn reserve_transfer_assets_with_local_asset_reserve_and_teleported_fee_works() { assert_eq!( last_events.next().unwrap(), RuntimeEvent::XcmPallet(crate::Event::FeesPaid { - paying: beneficiary, + paying: origin_location, fees: MultiAssets::new(), }) ); @@ -1173,34 +1614,70 @@ fn reserve_transfer_assets_with_local_asset_reserve_and_teleported_fee_works() { }); } -/// Test `reserve_transfer_assets` with destination asset reserve and teleported fee. +/// Test `transfer_assets` with local asset reserve and teleported fee. +#[test] +fn transfer_assets_with_local_asset_reserve_and_teleported_fee_works() { + let expected_result = Ok(()); + local_asset_reserve_and_teleported_fee_call(XcmPallet::transfer_assets, expected_result); +} + +/// Test `limited_reserve_transfer_assets` with local asset reserve and teleported fee disallowed. +#[test] +fn reserve_transfer_assets_with_local_asset_reserve_and_teleported_fee_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [23, 0, 0, 0], + message: Some("TooManyReserves"), + })); + local_asset_reserve_and_teleported_fee_call( + XcmPallet::limited_reserve_transfer_assets, + expected_result, + ); +} + +/// Test `limited_teleport_assets` with local asset reserve and teleported fee disallowed. +#[test] +fn teleport_assets_with_local_asset_reserve_and_teleported_fee_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered"), + })); + local_asset_reserve_and_teleported_fee_call( + XcmPallet::limited_teleport_assets, + expected_result, + ); +} + +/// Test `tested_call` with destination asset reserve and teleported fee. /// /// Transferring foreign asset (destination reserve) to `FOREIGN_ASSET_RESERVE_PARA_ID`. Using /// teleport-trusted USDT for fees. /// -/// ```nocompile -/// Here (source) FOREIGN_ASSET_RESERVE_PARA_ID (destination) -/// | `fees` (USDT) teleport-trust -/// | `assets` reserve -/// | -/// | 1. execute `InitiateTeleport(fees)` -/// | \--> sends `ReceiveTeleportedAsset(fees), .., DepositAsset(fees)` -/// | 2. execute `InitiateReserveWithdraw(assets)` -/// | \--> sends `WithdrawAsset(asset), ClearOrigin, BuyExecution(fees), DepositAsset` -/// \------------------------------------------> -/// ``` -#[test] -fn reserve_transfer_assets_with_destination_asset_reserve_and_teleported_fee_works() { +/// Verifies `expected_result` +fn destination_asset_reserve_and_teleported_fee_call<Call>( + tested_call: Call, + expected_result: DispatchResult, +) where + Call: FnOnce( + OriginFor<Test>, + Box<VersionedMultiLocation>, + Box<VersionedMultiLocation>, + Box<VersionedMultiAssets>, + u32, + WeightLimit, + ) -> DispatchResult, +{ let balances = vec![(ALICE, INITIAL_BALANCE)]; - let beneficiary: MultiLocation = - Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let origin_location: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); new_test_ext_with_balances(balances).execute_with(|| { - // create sufficient foreign asset USDT (0 total issuance) + // create sufficient foreign asset USDT let usdt_initial_local_amount = 42; let (_, usdt_chain_sovereign_account, usdt_id_multilocation) = set_up_foreign_asset(USDT_PARA_ID, None, usdt_initial_local_amount, true); - // create non-sufficient foreign asset BLA (0 total issuance) + // create non-sufficient foreign asset BLA let foreign_initial_amount = 142; let (reserve_location, foreign_sovereign_account, foreign_asset_id_multilocation) = set_up_foreign_asset( @@ -1231,14 +1708,20 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_teleported_fee_wor assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); // do the transfer - assert_ok!(XcmPallet::limited_reserve_transfer_assets( + let result = tested_call( RuntimeOrigin::signed(ALICE), Box::new(dest.into()), Box::new(beneficiary.into()), Box::new(assets.into()), fee_index as u32, Unlimited, - )); + ); + assert_eq!(result, expected_result); + if expected_result.is_err() { + // short-circuit here for tests where we expect failure + return + } + let weight = BaseXcmWeight::get() * 4; let mut last_events = last_events(3).into_iter(); assert_eq!( @@ -1248,7 +1731,7 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_teleported_fee_wor assert_eq!( last_events.next().unwrap(), RuntimeEvent::XcmPallet(crate::Event::FeesPaid { - paying: beneficiary, + paying: origin_location, fees: MultiAssets::new(), }) ); @@ -1303,22 +1786,68 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_teleported_fee_wor }); } -/// Test `reserve_transfer_assets` with remote asset reserve and teleported fee is disallowed. +/// Test `transfer_assets` with destination asset reserve and teleported fee. +#[test] +fn transfer_assets_with_destination_asset_reserve_and_teleported_fee_works() { + let expected_result = Ok(()); + destination_asset_reserve_and_teleported_fee_call(XcmPallet::transfer_assets, expected_result); +} + +/// Test `limited_reserve_transfer_assets` with destination asset reserve and teleported fee +/// disallowed. +#[test] +fn reserve_transfer_assets_with_destination_asset_reserve_and_teleported_fee_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [23, 0, 0, 0], + message: Some("TooManyReserves"), + })); + destination_asset_reserve_and_teleported_fee_call( + XcmPallet::limited_reserve_transfer_assets, + expected_result, + ); +} + +/// Test `limited_teleport_assets` with destination asset reserve and teleported fee disallowed. +#[test] +fn teleport_assets_with_destination_asset_reserve_and_teleported_fee_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered"), + })); + destination_asset_reserve_and_teleported_fee_call( + XcmPallet::limited_teleport_assets, + expected_result, + ); +} + +/// Test `tested_call` with remote asset reserve and teleported fee is disallowed. /// /// Transferring foreign asset (reserve on `FOREIGN_ASSET_RESERVE_PARA_ID`) to `USDT_PARA_ID`. /// Using teleport-trusted USDT for fees. -#[test] -fn reserve_transfer_assets_with_remote_asset_reserve_and_teleported_fee_disallowed() { +fn remote_asset_reserve_and_teleported_fee_reserve_call_disallowed<Call>( + tested_call: Call, + expected_result: DispatchResult, +) where + Call: FnOnce( + OriginFor<Test>, + Box<VersionedMultiLocation>, + Box<VersionedMultiLocation>, + Box<VersionedMultiAssets>, + u32, + WeightLimit, + ) -> DispatchResult, +{ let balances = vec![(ALICE, INITIAL_BALANCE)]; - let beneficiary: MultiLocation = - Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); new_test_ext_with_balances(balances).execute_with(|| { - // create sufficient foreign asset USDT (0 total issuance) + // create sufficient foreign asset USDT let usdt_initial_local_amount = 42; let (usdt_chain, usdt_chain_sovereign_account, usdt_id_multilocation) = set_up_foreign_asset(USDT_PARA_ID, None, usdt_initial_local_amount, true); - // create non-sufficient foreign asset BLA (0 total issuance) + // create non-sufficient foreign asset BLA let foreign_initial_amount = 142; let (_, reserve_sovereign_account, foreign_asset_id_multilocation) = set_up_foreign_asset( FOREIGN_ASSET_RESERVE_PARA_ID, @@ -1341,8 +1870,8 @@ fn reserve_transfer_assets_with_remote_asset_reserve_and_teleported_fee_disallow assert_eq!(Assets::balance(usdt_id_multilocation, ALICE), usdt_initial_local_amount); assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); - // do the transfer - let result = XcmPallet::limited_reserve_transfer_assets( + // try the transfer + let result = tested_call( RuntimeOrigin::signed(ALICE), Box::new(dest.into()), Box::new(beneficiary.into()), @@ -1350,14 +1879,7 @@ fn reserve_transfer_assets_with_remote_asset_reserve_and_teleported_fee_disallow fee_index as u32, Unlimited, ); - assert_eq!( - result, - Err(DispatchError::Module(ModuleError { - index: 4, - error: [22, 0, 0, 0], - message: Some("InvalidAssetUnsupportedReserve") - })) - ); + assert_eq!(result, expected_result); // Alice native asset untouched assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); assert_eq!(Assets::balance(usdt_id_multilocation, ALICE), usdt_initial_local_amount); @@ -1375,17 +1897,59 @@ fn reserve_transfer_assets_with_remote_asset_reserve_and_teleported_fee_disallow }); } +/// Test `transfer_assets` with remote asset reserve and teleported fee is disallowed. +#[test] +fn transfer_assets_with_remote_asset_reserve_and_teleported_fee_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [22, 0, 0, 0], + message: Some("InvalidAssetUnsupportedReserve"), + })); + remote_asset_reserve_and_teleported_fee_reserve_call_disallowed( + XcmPallet::transfer_assets, + expected_result, + ); +} + +/// Test `limited_reserve_transfer_assets` with remote asset reserve and teleported fee is +/// disallowed. +#[test] +fn reserve_transfer_assets_with_remote_asset_reserve_and_teleported_fee_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [23, 0, 0, 0], + message: Some("TooManyReserves"), + })); + remote_asset_reserve_and_teleported_fee_reserve_call_disallowed( + XcmPallet::limited_reserve_transfer_assets, + expected_result, + ); +} + +/// Test `limited_teleport_assets` with remote asset reserve and teleported fee is disallowed. +#[test] +fn teleport_assets_with_remote_asset_reserve_and_teleported_fee_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered"), + })); + remote_asset_reserve_and_teleported_fee_reserve_call_disallowed( + XcmPallet::limited_teleport_assets, + expected_result, + ); +} + /// Test `reserve_transfer_assets` single asset which is teleportable - should fail. /// /// Attempting to reserve-transfer teleport-trusted USDT to `USDT_PARA_ID` should fail. #[test] -fn reserve_transfer_assets_with_teleportable_asset_fails() { +fn reserve_transfer_assets_with_teleportable_asset_disallowed() { let balances = vec![(ALICE, INITIAL_BALANCE)]; - let beneficiary: MultiLocation = - Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); new_test_ext_with_balances(balances).execute_with(|| { - // create sufficient foreign asset USDT (0 total issuance) + // create sufficient foreign asset USDT let usdt_initial_local_amount = 42; let (usdt_chain, usdt_chain_sovereign_account, usdt_id_multilocation) = set_up_foreign_asset(USDT_PARA_ID, None, usdt_initial_local_amount, true); @@ -1428,9 +1992,9 @@ fn reserve_transfer_assets_with_teleportable_asset_fails() { }); } -/// Test `reserve_transfer_assets` with teleportable fee that is filtered - should fail. +/// Test `transfer_assets` with teleportable fee that is filtered - should fail. #[test] -fn reserve_transfer_assets_with_filtered_teleported_fee_disallowed() { +fn transfer_assets_with_filtered_teleported_fee_disallowed() { let beneficiary: MultiLocation = AccountId32 { network: None, id: BOB.into() }.into(); new_test_ext_with_balances(vec![(ALICE, INITIAL_BALANCE)]).execute_with(|| { let (assets, fee_index, _, _) = into_multiassets_checked( @@ -1439,7 +2003,7 @@ fn reserve_transfer_assets_with_filtered_teleported_fee_disallowed() { // native asset to transfer (not used for fees) - local reserve (MultiLocation::here(), SEND_AMOUNT).into(), ); - let result = XcmPallet::limited_reserve_transfer_assets( + let result = XcmPallet::transfer_assets( RuntimeOrigin::signed(ALICE), Box::new(FilteredTeleportLocation::get().into()), Box::new(beneficiary.into()), @@ -1466,10 +2030,9 @@ fn reserve_transfer_assets_with_filtered_teleported_fee_disallowed() { #[test] fn intermediary_error_reverts_side_effects() { let balances = vec![(ALICE, INITIAL_BALANCE)]; - let beneficiary: MultiLocation = - Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); new_test_ext_with_balances(balances).execute_with(|| { - // create sufficient foreign asset USDC (0 total issuance) + // create sufficient foreign asset USDC let usdc_initial_local_amount = 142; let (_, usdc_chain_sovereign_account, usdc_id_multilocation) = set_up_foreign_asset( USDC_RESERVE_PARA_ID, @@ -1515,3 +2078,329 @@ fn intermediary_error_reverts_side_effects() { assert_eq!(sent_xcm(), vec![]); }); } + +/// Test `tested_call` with teleportable asset and local fee reserve. +/// +/// Transferring USDT to `USDT_PARA_ID` (teleport trust). Using native asset (local reserve) for +/// fees. +/// +/// Verifies `expected_result` +fn teleport_asset_using_local_fee_reserve_call<Call>( + tested_call: Call, + expected_result: DispatchResult, +) where + Call: FnOnce( + OriginFor<Test>, + Box<VersionedMultiLocation>, + Box<VersionedMultiLocation>, + Box<VersionedMultiAssets>, + u32, + WeightLimit, + ) -> DispatchResult, +{ + let weight = BaseXcmWeight::get() * 3; + let balances = vec![(ALICE, INITIAL_BALANCE)]; + let origin_location: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); + new_test_ext_with_balances(balances).execute_with(|| { + // create non-sufficient foreign asset USDT + let usdt_initial_local_amount = 42; + let (usdt_chain, usdt_chain_sovereign_account, usdt_id_multilocation) = + set_up_foreign_asset(USDT_PARA_ID, None, usdt_initial_local_amount, false); + + // transfer destination is reserve location (no teleport trust) + let dest = usdt_chain; + + let (assets, fee_index, fee_asset, xfer_asset) = into_multiassets_checked( + // native asset for fee - local reserve + (MultiLocation::here(), FEE_AMOUNT).into(), + // USDT to transfer - destination reserve + (usdt_id_multilocation, SEND_AMOUNT).into(), + ); + + // reanchor according to test-case + let context = UniversalLocation::get(); + let expected_fee = fee_asset.reanchored(&dest, context).unwrap(); + let expected_asset = xfer_asset.reanchored(&dest, context).unwrap(); + + // balances checks before + assert_eq!(Assets::balance(usdt_id_multilocation, ALICE), usdt_initial_local_amount); + assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); + + // do the transfer + let result = tested_call( + RuntimeOrigin::signed(ALICE), + Box::new(dest.into()), + Box::new(beneficiary.into()), + Box::new(assets.into()), + fee_index as u32, + Unlimited, + ); + assert_eq!(result, expected_result); + if expected_result.is_err() { + // short-circuit here for tests where we expect failure + return + } + + let mut last_events = last_events(3).into_iter(); + assert_eq!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::Attempted { outcome: Outcome::Complete(weight) }) + ); + + // Alice spent (transferred) amount + assert_eq!( + Assets::balance(usdt_id_multilocation, ALICE), + usdt_initial_local_amount - SEND_AMOUNT + ); + // Alice used native asset for fees + assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE - FEE_AMOUNT); + // Destination account (parachain account) added native reserve to balances + assert_eq!(Balances::free_balance(usdt_chain_sovereign_account.clone()), FEE_AMOUNT); + assert_eq!(Assets::balance(usdt_id_multilocation, usdt_chain_sovereign_account), 0); + // Verify total and active issuance of foreign BLA have decreased (burned on + // reserve-withdraw) + let expected_issuance = usdt_initial_local_amount - SEND_AMOUNT; + assert_eq!(Assets::total_issuance(usdt_id_multilocation), expected_issuance); + assert_eq!(Assets::active_issuance(usdt_id_multilocation), expected_issuance); + + // Verify sent XCM program + assert_eq!( + sent_xcm(), + vec![( + dest, + // `fees` are being sent through local-reserve transfer because fee reserve is + // local chain; `assets` are burned on source and withdrawn from SA here + Xcm(vec![ + ReserveAssetDeposited(expected_fee.clone().into()), + buy_limited_execution(expected_fee, Unlimited), + ReceiveTeleportedAsset(expected_asset.into()), + ClearOrigin, + DepositAsset { assets: AllCounted(2).into(), beneficiary }, + ]) + )] + ); + assert_eq!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::FeesPaid { + paying: origin_location, + fees: MultiAssets::new(), + }) + ); + assert!(matches!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::Sent { .. }) + )); + }); +} + +/// Test `transfer_assets` with teleportable asset and local fee reserve. +#[test] +fn transfer_assets_with_teleportable_asset_and_local_fee_reserve_works() { + let expected_result = Ok(()); + teleport_asset_using_local_fee_reserve_call(XcmPallet::transfer_assets, expected_result); +} + +/// Test `limited_reserve_transfer_assets` with teleportable asset and local fee reserve disallowed. +#[test] +fn reserve_transfer_assets_with_teleportable_asset_and_local_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered"), + })); + teleport_asset_using_local_fee_reserve_call( + XcmPallet::limited_reserve_transfer_assets, + expected_result, + ); +} + +/// Test `limited_teleport_assets` with teleportable asset and local fee reserve disallowed. +#[test] +fn teleport_assets_with_teleportable_asset_and_local_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered"), + })); + teleport_asset_using_local_fee_reserve_call( + XcmPallet::limited_teleport_assets, + expected_result, + ); +} + +/// Test `tested_call` with teleported asset reserve and destination fee. +/// +/// Transferring USDT to `FOREIGN_ASSET_RESERVE_PARA_ID` (teleport trust). Using foreign asset +/// (destination reserve) for fees. +/// +/// Verifies `expected_result` +fn teleported_asset_using_destination_reserve_fee_call<Call>( + tested_call: Call, + expected_result: DispatchResult, +) where + Call: FnOnce( + OriginFor<Test>, + Box<VersionedMultiLocation>, + Box<VersionedMultiLocation>, + Box<VersionedMultiAssets>, + u32, + WeightLimit, + ) -> DispatchResult, +{ + let balances = vec![(ALICE, INITIAL_BALANCE)]; + let origin_location: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); + new_test_ext_with_balances(balances).execute_with(|| { + // create sufficient foreign asset BLA to be used for fees + let foreign_initial_amount = 142; + let (reserve_location, foreign_sovereign_account, foreign_asset_id_multilocation) = + set_up_foreign_asset( + FOREIGN_ASSET_RESERVE_PARA_ID, + Some(FOREIGN_ASSET_INNER_JUNCTION), + foreign_initial_amount, + true, + ); + + // create non-sufficient foreign asset USDT + let usdt_initial_local_amount = 42; + let (_, usdt_chain_sovereign_account, usdt_id_multilocation) = + set_up_foreign_asset(USDT_PARA_ID, None, usdt_initial_local_amount, false); + + // transfer destination is BLA reserve location + let dest = reserve_location; + let dest_sovereign_account = foreign_sovereign_account; + + let (assets, fee_index, fee_asset, xfer_asset) = into_multiassets_checked( + // foreign asset BLA used for fees - destination reserve + (foreign_asset_id_multilocation, FEE_AMOUNT).into(), + // USDT to transfer - teleported + (usdt_id_multilocation, SEND_AMOUNT).into(), + ); + + // reanchor according to test-case + let context = UniversalLocation::get(); + let expected_fee = fee_asset.reanchored(&dest, context).unwrap(); + let expected_asset = xfer_asset.reanchored(&dest, context).unwrap(); + + // balances checks before + assert_eq!(Assets::balance(usdt_id_multilocation, ALICE), usdt_initial_local_amount); + assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); + + // do the transfer + let result = tested_call( + RuntimeOrigin::signed(ALICE), + Box::new(dest.into()), + Box::new(beneficiary.into()), + Box::new(assets.into()), + fee_index as u32, + Unlimited, + ); + assert_eq!(result, expected_result); + if expected_result.is_err() { + // short-circuit here for tests where we expect failure + return + } + + let weight = BaseXcmWeight::get() * 4; + let mut last_events = last_events(3).into_iter(); + assert_eq!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::Attempted { outcome: Outcome::Complete(weight) }) + ); + assert_eq!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::FeesPaid { + paying: origin_location, + fees: MultiAssets::new(), + }) + ); + assert!(matches!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::Sent { .. }) + )); + // Alice native asset untouched + assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); + // Alice spent USDT for fees + assert_eq!( + Assets::balance(usdt_id_multilocation, ALICE), + usdt_initial_local_amount - SEND_AMOUNT + ); + // Alice transferred BLA + assert_eq!( + Assets::balance(foreign_asset_id_multilocation, ALICE), + foreign_initial_amount - FEE_AMOUNT + ); + // Verify balances of USDT reserve parachain + assert_eq!(Balances::free_balance(usdt_chain_sovereign_account.clone()), 0); + assert_eq!(Assets::balance(usdt_id_multilocation, usdt_chain_sovereign_account), 0); + // Verify balances of transferred-asset reserve parachain + assert_eq!(Balances::free_balance(dest_sovereign_account.clone()), 0); + assert_eq!(Assets::balance(foreign_asset_id_multilocation, dest_sovereign_account), 0); + // Verify total and active issuance of USDT have decreased (teleported) + let expected_usdt_issuance = usdt_initial_local_amount - SEND_AMOUNT; + assert_eq!(Assets::total_issuance(usdt_id_multilocation), expected_usdt_issuance); + assert_eq!(Assets::active_issuance(usdt_id_multilocation), expected_usdt_issuance); + // Verify total and active issuance of foreign BLA asset have decreased (burned on + // reserve-withdraw) + let expected_bla_issuance = foreign_initial_amount - FEE_AMOUNT; + assert_eq!(Assets::total_issuance(foreign_asset_id_multilocation), expected_bla_issuance); + assert_eq!(Assets::active_issuance(foreign_asset_id_multilocation), expected_bla_issuance); + + // Verify sent XCM program + assert_eq!( + sent_xcm(), + vec![( + dest, + Xcm(vec![ + // fees are withdrawn from origin's local SA + WithdrawAsset(expected_fee.clone().into()), + buy_limited_execution(expected_fee, Unlimited), + // assets are teleported to destination chain + ReceiveTeleportedAsset(expected_asset.into()), + ClearOrigin, + DepositAsset { assets: AllCounted(2).into(), beneficiary }, + ]) + )] + ); + }); +} + +/// Test `transfer_assets` with teleported asset reserve and destination fee. +#[test] +fn transfer_teleported_assets_using_destination_reserve_fee_works() { + let expected_result = Ok(()); + teleported_asset_using_destination_reserve_fee_call( + XcmPallet::transfer_assets, + expected_result, + ); +} + +/// Test `limited_reserve_transfer_assets` with teleported asset reserve and destination fee +/// disallowed. +#[test] +fn reserve_transfer_teleported_assets_using_destination_reserve_fee_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered"), + })); + teleported_asset_using_destination_reserve_fee_call( + XcmPallet::limited_reserve_transfer_assets, + expected_result, + ); +} + +/// Test `limited_teleport_assets` with teleported asset reserve and destination fee disallowed. +#[test] +fn teleport_assets_using_destination_reserve_fee_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered"), + })); + teleported_asset_using_destination_reserve_fee_call( + XcmPallet::limited_teleport_assets, + expected_result, + ); +} diff --git a/polkadot/xcm/pallet-xcm/src/tests/mod.rs b/polkadot/xcm/pallet-xcm/src/tests/mod.rs index 056c7dcc196..5829eb6edec 100644 --- a/polkadot/xcm/pallet-xcm/src/tests/mod.rs +++ b/polkadot/xcm/pallet-xcm/src/tests/mod.rs @@ -16,7 +16,7 @@ #![cfg(test)] -mod assets_transfer; +pub(crate) mod assets_transfer; use crate::{ mock::*, AssetTraps, CurrentMigration, Error, LatestVersionedMultiLocation, Queries, diff --git a/prdoc/pr_2388.prdoc b/prdoc/pr_2388.prdoc new file mode 100644 index 00000000000..fa560197aff --- /dev/null +++ b/prdoc/pr_2388.prdoc @@ -0,0 +1,26 @@ +# Schema: Parity PR Documentation Schema (prdoc) +# See doc at https://github.com/paritytech/prdoc + +title: Add new flexible `pallet_xcm::transfer_assets()` call/extrinsic + +doc: + - audience: Builder + description: | + For complex combinations of asset transfers where assets and fees may have different reserves or + different reserve/teleport trust configurations, users can use the newly added `transfer_assets()` + extrinsic which is more flexible in allowing more complex scenarios. + The new extrinsic enables, for example, a (non-system) parachain to teleport their `ForeignAssets` + assets to `AssetHub` while using (reserve-based) `DOT` to pay fees. + notes: + - Now `(limited_)reserve_transfer_assets()` only allow reserve-based transfers for all assets + including fees, similarly `(limited_)teleport_assets()` only allows teleports for all assets + including fees. + +migrations: + db: [] + + runtime: [] + +crates: pallet-xcm + +host_functions: [] diff --git a/substrate/frame/assets/src/benchmarking.rs b/substrate/frame/assets/src/benchmarking.rs index c9b0825542d..f8495a1c8f2 100644 --- a/substrate/frame/assets/src/benchmarking.rs +++ b/substrate/frame/assets/src/benchmarking.rs @@ -54,7 +54,7 @@ fn create_default_asset<T: Config<I>, I: 'static>( (asset_id, caller, caller_lookup) } -fn create_default_minted_asset<T: Config<I>, I: 'static>( +pub fn create_default_minted_asset<T: Config<I>, I: 'static>( is_sufficient: bool, amount: T::Balance, ) -> (T::AssetIdParameter, T::AccountId, AccountIdLookupOf<T>) { diff --git a/substrate/frame/assets/src/lib.rs b/substrate/frame/assets/src/lib.rs index 79e4fe30018..13aee138ad3 100644 --- a/substrate/frame/assets/src/lib.rs +++ b/substrate/frame/assets/src/lib.rs @@ -141,7 +141,7 @@ #![cfg_attr(not(feature = "std"), no_std)] #[cfg(feature = "runtime-benchmarks")] -mod benchmarking; +pub mod benchmarking; pub mod migration; #[cfg(test)] pub mod mock; -- GitLab