diff --git a/Cargo.lock b/Cargo.lock index 6de8fcfb1afd..05386203452c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -824,6 +824,7 @@ dependencies = [ "sp-runtime", "staging-xcm", "staging-xcm-executor", + "xcm-runtime-apis", ] [[package]] @@ -2507,9 +2508,9 @@ checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" [[package]] name = "cc" -version = "1.1.6" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" +checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7" dependencies = [ "jobserver", "libc", @@ -2751,12 +2752,12 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.10" +version = "4.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f6b81fb3c84f5563d509c59b5a48d935f689e993afa90fe39047f05adef9142" +checksum = "35723e6a11662c2afb578bcf0b88bf6ea8e21282a953428f240574fcc3a2b5b3" dependencies = [ "clap_builder", - "clap_derive 4.5.8", + "clap_derive 4.5.11", ] [[package]] @@ -2770,9 +2771,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.10" +version = "4.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca6706fd5224857d9ac5eb9355f6683563cc0541c7cd9d014043b57cbec78ac" +checksum = "49eb96cbfa7cfa35017b7cd548c75b14c3118c98b423041d70562665e07fb0fa" dependencies = [ "anstream", "anstyle", @@ -2787,7 +2788,7 @@ version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "586a385f7ef2f8b4d86bddaa0c094794e7ccbfe5ffef1f434fe928143fc783a5" dependencies = [ - "clap 4.5.10", + "clap 4.5.11", ] [[package]] @@ -2805,9 +2806,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.8" +version = "4.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" +checksum = "5d029b67f89d30bbb547c89fd5161293c0aec155fc691d7924b64550662db93e" dependencies = [ "heck 0.5.0", "proc-macro2 1.0.82", @@ -3613,7 +3614,7 @@ dependencies = [ "anes", "cast", "ciborium", - "clap 4.5.10", + "clap 4.5.11", "criterion-plot", "futures", "is-terminal", @@ -3747,7 +3748,7 @@ dependencies = [ name = "cumulus-client-cli" version = "0.7.0" dependencies = [ - "clap 4.5.10", + "clap 4.5.11", "parity-scale-codec", "sc-chain-spec", "sc-cli", @@ -4210,7 +4211,7 @@ name = "cumulus-pov-validator" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.5.10", + "clap 4.5.11", "parity-scale-codec", "polkadot-node-primitives", "polkadot-parachain-primitives", @@ -4537,7 +4538,7 @@ name = "cumulus-test-service" version = "0.1.0" dependencies = [ "async-trait", - "clap 4.5.10", + "clap 4.5.11", "criterion", "cumulus-client-cli", "cumulus-client-collator", @@ -5802,7 +5803,7 @@ dependencies = [ "Inflector", "array-bytes", "chrono", - "clap 4.5.10", + "clap 4.5.11", "comfy-table", "frame-benchmarking", "frame-support", @@ -5894,7 +5895,7 @@ dependencies = [ name = "frame-election-solution-type-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 4.5.10", + "clap 4.5.11", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-support", @@ -5966,7 +5967,7 @@ dependencies = [ name = "frame-omni-bencher" version = "0.1.0" dependencies = [ - "clap 4.5.10", + "clap 4.5.11", "cumulus-primitives-proof-size-hostfunction", "env_logger 0.11.3", "frame-benchmarking-cli", @@ -7363,9 +7364,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.31" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" dependencies = [ "libc", ] @@ -8728,7 +8729,7 @@ dependencies = [ name = "minimal-template-node" version = "0.0.0" dependencies = [ - "clap 4.5.10", + "clap 4.5.11", "docify", "futures", "futures-timer", @@ -9221,7 +9222,7 @@ name = "node-bench" version = "0.9.0-dev" dependencies = [ "array-bytes", - "clap 4.5.10", + "clap 4.5.11", "derive_more", "fs_extra", "futures", @@ -9300,7 +9301,7 @@ dependencies = [ name = "node-runtime-generate-bags" version = "3.0.0" dependencies = [ - "clap 4.5.10", + "clap 4.5.11", "generate-bags", "kitchensink-runtime", ] @@ -9309,7 +9310,7 @@ dependencies = [ name = "node-template-release" version = "3.0.0" dependencies = [ - "clap 4.5.10", + "clap 4.5.11", "flate2", "fs_extra", "glob", @@ -9611,9 +9612,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.66" +version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ "bitflags 2.6.0", "cfg-if", @@ -9652,9 +9653,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.103" +version = "0.9.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" dependencies = [ "cc", "libc", @@ -12031,7 +12032,7 @@ dependencies = [ name = "parachain-template-node" version = "0.0.0" dependencies = [ - "clap 4.5.10", + "clap 4.5.11", "color-print", "cumulus-client-cli", "cumulus-client-collator", @@ -12991,7 +12992,7 @@ name = "polkadot-cli" version = "7.0.0" dependencies = [ "cfg-if", - "clap 4.5.10", + "clap 4.5.11", "frame-benchmarking-cli", "futures", "log", @@ -13851,7 +13852,7 @@ dependencies = [ "async-trait", "bridge-hub-rococo-runtime", "bridge-hub-westend-runtime", - "clap 4.5.10", + "clap 4.5.11", "collectives-westend-runtime", "color-print", "contracts-rococo-runtime", @@ -14835,7 +14836,7 @@ dependencies = [ "async-trait", "bincode", "bitvec", - "clap 4.5.10", + "clap 4.5.11", "clap-num", "color-eyre", "colored", @@ -14933,7 +14934,7 @@ version = "1.0.0" dependencies = [ "assert_matches", "async-trait", - "clap 4.5.10", + "clap 4.5.11", "color-eyre", "futures", "futures-timer", @@ -15075,7 +15076,7 @@ dependencies = [ name = "polkadot-voter-bags" version = "7.0.0" dependencies = [ - "clap 4.5.10", + "clap 4.5.11", "generate-bags", "sp-io", "westend-runtime", @@ -16230,7 +16231,7 @@ dependencies = [ name = "remote-ext-tests-bags-list" version = "1.0.0" dependencies = [ - "clap 4.5.10", + "clap 4.5.11", "frame-system", "log", "pallet-bags-list-remote-tests", @@ -17080,7 +17081,7 @@ name = "sc-chain-spec" version = "28.0.0" dependencies = [ "array-bytes", - "clap 4.5.10", + "clap 4.5.11", "docify", "log", "memmap2 0.9.3", @@ -17123,7 +17124,7 @@ version = "0.36.0" dependencies = [ "array-bytes", "chrono", - "clap 4.5.10", + "clap 4.5.11", "fdlimit", "futures", "futures-timer", @@ -18307,7 +18308,7 @@ dependencies = [ name = "sc-storage-monitor" version = "0.16.0" dependencies = [ - "clap 4.5.10", + "clap 4.5.11", "fs4", "log", "sp-core", @@ -18681,18 +18682,18 @@ dependencies = [ [[package]] name = "secp256k1" -version = "0.28.0" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acea373acb8c21ecb5a23741452acd2593ed44ee3d343e72baaa143bc89d0d5" +checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" dependencies = [ "secp256k1-sys", ] [[package]] name = "secp256k1-sys" -version = "0.9.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09e67c467c38fd24bd5499dc9a18183b31575c12ee549197e3e20d57aa4fe3b7" +checksum = "e5d1746aae42c19d583c3c1a8c646bfad910498e2051c551a7f2e3c0c9fbb7eb" dependencies = [ "cc", ] @@ -19191,9 +19192,9 @@ dependencies = [ [[package]] name = "slotmap" -version = "1.0.7" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" dependencies = [ "version_check", ] @@ -19729,7 +19730,7 @@ dependencies = [ name = "solochain-template-node" version = "0.0.0" dependencies = [ - "clap 4.5.10", + "clap 4.5.11", "frame-benchmarking-cli", "frame-metadata-hash-extension", "frame-system", @@ -20405,7 +20406,7 @@ dependencies = [ name = "sp-npos-elections-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 4.5.10", + "clap 4.5.11", "honggfuzz", "rand", "sp-npos-elections", @@ -20918,7 +20919,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" name = "staging-chain-spec-builder" version = "1.6.1" dependencies = [ - "clap 4.5.10", + "clap 4.5.11", "log", "sc-chain-spec", "serde_json", @@ -20931,7 +20932,7 @@ version = "3.0.0-dev" dependencies = [ "array-bytes", "assert_cmd", - "clap 4.5.10", + "clap 4.5.11", "clap_complete", "criterion", "futures", @@ -20966,7 +20967,7 @@ dependencies = [ name = "staging-node-inspect" version = "0.12.0" dependencies = [ - "clap 4.5.10", + "clap 4.5.11", "parity-scale-codec", "sc-cli", "sc-client-api", @@ -21239,7 +21240,7 @@ dependencies = [ name = "subkey" version = "9.0.0" dependencies = [ - "clap 4.5.10", + "clap 4.5.11", "sc-cli", ] @@ -21865,7 +21866,7 @@ dependencies = [ name = "test-parachain-adder-collator" version = "1.0.0" dependencies = [ - "clap 4.5.10", + "clap 4.5.11", "futures", "futures-timer", "log", @@ -21912,7 +21913,7 @@ dependencies = [ name = "test-parachain-undying-collator" version = "1.0.0" dependencies = [ - "clap 4.5.10", + "clap 4.5.11", "futures", "futures-timer", "log", 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 25e1cffad543..76179c6d82c6 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/xcm_helpers.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/xcm_helpers.rs @@ -69,3 +69,11 @@ pub fn non_fee_asset(assets: &Assets, fee_idx: usize) -> Option<(Location, u128) }; Some((asset.id.0, asset_amount)) } + +pub fn get_amount_from_versioned_assets(assets: VersionedAssets) -> u128 { + let latest_assets: Assets = assets.try_into().unwrap(); + let Fungible(amount) = latest_assets.inner()[0].fun else { + unreachable!("asset is non-fungible"); + }; + 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 b4579da94cbf..f66a5f1d5fe7 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 @@ -28,6 +28,7 @@ pallet-utility = { workspace = true } xcm = { workspace = true } pallet-xcm = { workspace = true } xcm-executor = { workspace = true } +xcm-runtime-apis = { workspace = true, default-features = true } polkadot-runtime-common = { workspace = true, default-features = true } rococo-runtime-constants = { workspace = true, default-features = true } diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs index eca358317054..6309c0584107 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs @@ -40,7 +40,9 @@ mod imports { assert_expected_events, bx, Chain, Parachain as Para, RelayChain as Relay, Test, TestArgs, TestContext, TestExt, }, - xcm_helpers::{non_fee_asset, xcm_transact_paid_execution}, + xcm_helpers::{ + get_amount_from_versioned_assets, non_fee_asset, xcm_transact_paid_execution, + }, ASSETS_PALLET_ID, RESERVABLE_ASSET_ID, XCM_V3, }; pub use parachains_common::Balance; 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 8fffec23d73a..88fa379c4072 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 @@ -21,3 +21,4 @@ mod set_xcm_versions; mod swap; mod teleport; mod treasury; +mod xcm_fee_estimation; diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/xcm_fee_estimation.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/xcm_fee_estimation.rs new file mode 100644 index 000000000000..aa0e183ecdda --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/xcm_fee_estimation.rs @@ -0,0 +1,286 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Tests for XCM fee estimation in the runtime. + +use crate::imports::*; +use frame_support::{ + dispatch::RawOrigin, + sp_runtime::{traits::Dispatchable, DispatchResult}, +}; +use xcm_runtime_apis::{ + dry_run::runtime_decl_for_dry_run_api::DryRunApiV1, + fees::runtime_decl_for_xcm_payment_api::XcmPaymentApiV1, +}; + +fn sender_assertions(test: ParaToParaThroughAHTest) { + type RuntimeEvent = ::RuntimeEvent; + PenpalA::assert_xcm_pallet_attempted_complete(None); + + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Burned { asset_id, owner, balance } + ) => { + asset_id: *asset_id == Location::new(1, []), + owner: *owner == test.sender.account_id, + balance: *balance == test.args.amount, + }, + ] + ); +} + +fn hop_assertions(test: ParaToParaThroughAHTest) { + type RuntimeEvent = ::RuntimeEvent; + AssetHubRococo::assert_xcmp_queue_success(None); + + assert_expected_events!( + AssetHubRococo, + vec![ + RuntimeEvent::Balances( + pallet_balances::Event::Burned { amount, .. } + ) => { + amount: *amount == test.args.amount, + }, + ] + ); +} + +fn receiver_assertions(test: ParaToParaThroughAHTest) { + type RuntimeEvent = ::RuntimeEvent; + PenpalB::assert_xcmp_queue_success(None); + + assert_expected_events!( + PenpalB, + vec![ + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Issued { asset_id, owner, .. } + ) => { + asset_id: *asset_id == Location::new(1, []), + owner: *owner == test.receiver.account_id, + }, + ] + ); +} + +fn transfer_assets_para_to_para_through_ah_dispatchable( + test: ParaToParaThroughAHTest, +) -> DispatchResult { + let call = transfer_assets_para_to_para_through_ah_call(test.clone()); + match call.dispatch(test.signed_origin) { + Ok(_) => Ok(()), + Err(error_with_post_info) => Err(error_with_post_info.error), + } +} + +fn transfer_assets_para_to_para_through_ah_call( + test: ParaToParaThroughAHTest, +) -> ::RuntimeCall { + type RuntimeCall = ::RuntimeCall; + + let asset_hub_location: Location = PenpalB::sibling_location_of(AssetHubRococo::para_id()); + let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(test.args.assets.len() as u32)), + beneficiary: test.args.beneficiary, + }]); + RuntimeCall::PolkadotXcm(pallet_xcm::Call::transfer_assets_using_type_and_then { + dest: bx!(test.args.dest.into()), + assets: bx!(test.args.assets.clone().into()), + assets_transfer_type: bx!(TransferType::RemoteReserve(asset_hub_location.clone().into())), + remote_fees_id: bx!(VersionedAssetId::V4(AssetId(Location::new(1, [])))), + fees_transfer_type: bx!(TransferType::RemoteReserve(asset_hub_location.into())), + custom_xcm_on_dest: bx!(VersionedXcm::from(custom_xcm_on_dest)), + weight_limit: test.args.weight_limit, + }) +} + +/// We are able to dry-run and estimate the fees for a multi-hop XCM journey. +/// Scenario: Alice on PenpalA has some DOTs and wants to send them to PenpalB. +/// We want to know the fees using the `DryRunApi` and `XcmPaymentApi`. +#[test] +fn multi_hop_works() { + let destination = PenpalA::sibling_location_of(PenpalB::para_id()); + let sender = PenpalASender::get(); + let amount_to_send = 1_000_000_000_000; + let asset_owner = PenpalAssetOwner::get(); + let assets: Assets = (Parent, amount_to_send).into(); + let relay_native_asset_location = Location::parent(); + let sender_as_seen_by_ah = AssetHubRococo::sibling_location_of(PenpalA::para_id()); + let sov_of_sender_on_ah = AssetHubRococo::sovereign_account_id_of(sender_as_seen_by_ah.clone()); + + // fund Parachain's sender account + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(asset_owner.clone()), + relay_native_asset_location.clone(), + sender.clone(), + amount_to_send * 2, + ); + + // fund the Parachain Origin's SA on AssetHub with the native tokens held in reserve. + AssetHubRococo::fund_accounts(vec![(sov_of_sender_on_ah.clone(), amount_to_send * 2)]); + + // Init values for Parachain Destination + let beneficiary_id = PenpalBReceiver::get(); + + let test_args = TestContext { + sender: PenpalASender::get(), // Bob in PenpalB. + receiver: PenpalBReceiver::get(), // Alice. + args: TestArgs::new_para( + destination, + beneficiary_id.clone(), + amount_to_send, + assets, + None, + 0, + ), + }; + let mut test = ParaToParaThroughAHTest::new(test_args); + + // We get them from the PenpalA closure. + let mut delivery_fees_amount = 0; + let mut remote_message = VersionedXcm::V4(Xcm(Vec::new())); + ::execute_with(|| { + type Runtime = ::Runtime; + type OriginCaller = ::OriginCaller; + + let call = transfer_assets_para_to_para_through_ah_call(test.clone()); + let origin = OriginCaller::system(RawOrigin::Signed(sender.clone())); + let result = Runtime::dry_run_call(origin, call).unwrap(); + // We filter the result to get only the messages we are interested in. + let (destination_to_query, messages_to_query) = &result + .forwarded_xcms + .iter() + .find(|(destination, _)| { + *destination == VersionedLocation::V4(Location::new(1, [Parachain(1000)])) + }) + .unwrap(); + assert_eq!(messages_to_query.len(), 1); + remote_message = messages_to_query[0].clone(); + let delivery_fees = + Runtime::query_delivery_fees(destination_to_query.clone(), remote_message.clone()) + .unwrap(); + delivery_fees_amount = get_amount_from_versioned_assets(delivery_fees); + }); + + // These are set in the AssetHub closure. + let mut intermediate_execution_fees = 0; + let mut intermediate_delivery_fees_amount = 0; + let mut intermediate_remote_message = VersionedXcm::V4(Xcm::<()>(Vec::new())); + ::execute_with(|| { + type Runtime = ::Runtime; + type RuntimeCall = ::RuntimeCall; + + // First we get the execution fees. + let weight = Runtime::query_xcm_weight(remote_message.clone()).unwrap(); + intermediate_execution_fees = Runtime::query_weight_to_asset_fee( + weight, + VersionedAssetId::V4(Location::new(1, []).into()), + ) + .unwrap(); + + // We have to do this to turn `VersionedXcm<()>` into `VersionedXcm`. + let xcm_program = + VersionedXcm::V4(Xcm::::from(remote_message.clone().try_into().unwrap())); + + // Now we get the delivery fees to the final destination. + let result = + Runtime::dry_run_xcm(sender_as_seen_by_ah.clone().into(), xcm_program).unwrap(); + let (destination_to_query, messages_to_query) = &result + .forwarded_xcms + .iter() + .find(|(destination, _)| { + *destination == VersionedLocation::V4(Location::new(1, [Parachain(2001)])) + }) + .unwrap(); + // There's actually two messages here. + // One created when the message we sent from PenpalA arrived and was executed. + // The second one when we dry-run the xcm. + // We could've gotten the message from the queue without having to dry-run, but + // offchain applications would have to dry-run, so we do it here as well. + intermediate_remote_message = messages_to_query[0].clone(); + let delivery_fees = Runtime::query_delivery_fees( + destination_to_query.clone(), + intermediate_remote_message.clone(), + ) + .unwrap(); + intermediate_delivery_fees_amount = get_amount_from_versioned_assets(delivery_fees); + }); + + // Get the final execution fees in the destination. + let mut final_execution_fees = 0; + ::execute_with(|| { + type Runtime = ::Runtime; + + let weight = Runtime::query_xcm_weight(intermediate_remote_message.clone()).unwrap(); + final_execution_fees = + Runtime::query_weight_to_asset_fee(weight, VersionedAssetId::V4(Parent.into())) + .unwrap(); + }); + + // Dry-running is done. + PenpalA::reset_ext(); + AssetHubRococo::reset_ext(); + PenpalB::reset_ext(); + + // Fund accounts again. + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(asset_owner), + relay_native_asset_location.clone(), + sender.clone(), + amount_to_send * 2, + ); + AssetHubRococo::fund_accounts(vec![(sov_of_sender_on_ah, amount_to_send * 2)]); + + // Actually run the extrinsic. + let sender_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location.clone(), &sender) + }); + let receiver_assets_before = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location.clone(), &beneficiary_id) + }); + + test.set_assertion::(sender_assertions); + test.set_assertion::(hop_assertions); + test.set_assertion::(receiver_assertions); + test.set_dispatchable::(transfer_assets_para_to_para_through_ah_dispatchable); + test.assert(); + + let sender_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location.clone(), &sender) + }); + let receiver_assets_after = PenpalB::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location, &beneficiary_id) + }); + + // We know the exact fees on every hop. + assert_eq!( + sender_assets_after, + sender_assets_before - amount_to_send - delivery_fees_amount /* This is charged directly + * from the sender's + * account. */ + ); + assert_eq!( + receiver_assets_after, + receiver_assets_before + amount_to_send - + intermediate_execution_fees - + intermediate_delivery_fees_amount - + final_execution_fees + ); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs index 9401621c5ba3..060c3fb39254 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs @@ -40,7 +40,9 @@ mod imports { assert_expected_events, bx, Chain, Parachain as Para, RelayChain as Relay, Test, TestArgs, TestContext, TestExt, }, - xcm_helpers::{non_fee_asset, xcm_transact_paid_execution}, + xcm_helpers::{ + get_amount_from_versioned_assets, non_fee_asset, xcm_transact_paid_execution, + }, ASSETS_PALLET_ID, RESERVABLE_ASSET_ID, XCM_V3, }; pub use parachains_common::{AccountId, Balance}; diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/xcm_fee_estimation.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/xcm_fee_estimation.rs index c01aa7825336..037d6604ea4d 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/xcm_fee_estimation.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/xcm_fee_estimation.rs @@ -17,89 +17,95 @@ use crate::imports::*; -use frame_system::RawOrigin; +use frame_support::{ + dispatch::RawOrigin, + sp_runtime::{traits::Dispatchable, DispatchResult}, +}; use xcm_runtime_apis::{ dry_run::runtime_decl_for_dry_run_api::DryRunApiV1, fees::runtime_decl_for_xcm_payment_api::XcmPaymentApiV1, }; -/// We are able to dry-run and estimate the fees for a teleport between relay and system para. -/// Scenario: Alice on Westend relay chain wants to teleport WND to Asset Hub. -/// We want to know the fees using the `DryRunApi` and `XcmPaymentApi`. -#[test] -fn teleport_relay_system_para_works() { - let destination: Location = Parachain(1000).into(); // Asset Hub. - let beneficiary_id = AssetHubWestendReceiver::get(); - let beneficiary: Location = AccountId32 { id: beneficiary_id.clone().into(), network: None } // Test doesn't allow specifying a network here. - .into(); // Beneficiary in Asset Hub. - let teleport_amount = 1_000_000_000_000; // One WND (12 decimals). - let assets: Assets = vec![(Here, teleport_amount).into()].into(); - - // We get them from the Westend closure. - let mut delivery_fees_amount = 0; - let mut remote_message = VersionedXcm::V4(Xcm(Vec::new())); - ::new_ext().execute_with(|| { - type Runtime = ::Runtime; - type RuntimeCall = ::RuntimeCall; - type OriginCaller = ::OriginCaller; - - let call = RuntimeCall::XcmPallet(pallet_xcm::Call::transfer_assets { - dest: Box::new(VersionedLocation::V4(destination.clone())), - beneficiary: Box::new(VersionedLocation::V4(beneficiary)), - assets: Box::new(VersionedAssets::V4(assets)), - fee_asset_item: 0, - weight_limit: Unlimited, - }); - let origin = OriginCaller::system(RawOrigin::Signed(WestendSender::get())); - let result = Runtime::dry_run_call(origin, call).unwrap(); - assert_eq!(result.forwarded_xcms.len(), 1); - let (destination_to_query, messages_to_query) = &result.forwarded_xcms[0]; - assert_eq!(messages_to_query.len(), 1); - remote_message = messages_to_query[0].clone(); - let delivery_fees = - Runtime::query_delivery_fees(destination_to_query.clone(), remote_message.clone()) - .unwrap(); - delivery_fees_amount = get_amount_from_versioned_assets(delivery_fees); - }); - - // This is set in the AssetHubWestend closure. - let mut remote_execution_fees = 0; - ::execute_with(|| { - type Runtime = ::Runtime; - - let weight = Runtime::query_xcm_weight(remote_message.clone()).unwrap(); - remote_execution_fees = - Runtime::query_weight_to_asset_fee(weight, VersionedAssetId::V4(Parent.into())) - .unwrap(); - }); - - let test_args = TestContext { - sender: WestendSender::get(), // Alice. - receiver: AssetHubWestendReceiver::get(), // Bob in Asset Hub. - args: TestArgs::new_relay(destination, beneficiary_id, teleport_amount), - }; - let mut test = RelayToSystemParaTest::new(test_args); +fn sender_assertions(test: ParaToParaThroughAHTest) { + type RuntimeEvent = ::RuntimeEvent; + PenpalA::assert_xcm_pallet_attempted_complete(None); + + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Burned { asset_id, owner, balance } + ) => { + asset_id: *asset_id == Location::new(1, []), + owner: *owner == test.sender.account_id, + balance: *balance == test.args.amount, + }, + ] + ); +} - let sender_balance_before = test.sender.balance; - let receiver_balance_before = test.receiver.balance; - assert_eq!(sender_balance_before, 1_000_000_000_000_000_000); - assert_eq!(receiver_balance_before, 4_096_000_000_000); +fn hop_assertions(test: ParaToParaThroughAHTest) { + type RuntimeEvent = ::RuntimeEvent; + AssetHubWestend::assert_xcmp_queue_success(None); + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::Balances( + pallet_balances::Event::Burned { amount, .. } + ) => { + amount: *amount == test.args.amount, + }, + ] + ); +} - test.set_dispatchable::(transfer_assets); - test.assert(); +fn receiver_assertions(test: ParaToParaThroughAHTest) { + type RuntimeEvent = ::RuntimeEvent; + PenpalB::assert_xcmp_queue_success(None); + + assert_expected_events!( + PenpalB, + vec![ + RuntimeEvent::ForeignAssets( + pallet_assets::Event::Issued { asset_id, owner, .. } + ) => { + asset_id: *asset_id == Location::new(1, []), + owner: *owner == test.receiver.account_id, + }, + ] + ); +} - let sender_balance_after = test.sender.balance; - let receiver_balance_after = test.receiver.balance; +fn transfer_assets_para_to_para_through_ah_dispatchable( + test: ParaToParaThroughAHTest, +) -> DispatchResult { + let call = transfer_assets_para_to_para_through_ah_call(test.clone()); + match call.dispatch(test.signed_origin) { + Ok(_) => Ok(()), + Err(error_with_post_info) => Err(error_with_post_info.error), + } +} - // We now know the exact fees. - assert_eq!( - sender_balance_after, - sender_balance_before - delivery_fees_amount - teleport_amount - ); - assert_eq!( - receiver_balance_after, - receiver_balance_before + teleport_amount - remote_execution_fees - ); +fn transfer_assets_para_to_para_through_ah_call( + test: ParaToParaThroughAHTest, +) -> ::RuntimeCall { + type RuntimeCall = ::RuntimeCall; + + let asset_hub_location: Location = PenpalB::sibling_location_of(AssetHubWestend::para_id()); + let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(test.args.assets.len() as u32)), + beneficiary: test.args.beneficiary, + }]); + RuntimeCall::PolkadotXcm(pallet_xcm::Call::transfer_assets_using_type_and_then { + dest: bx!(test.args.dest.into()), + assets: bx!(test.args.assets.clone().into()), + assets_transfer_type: bx!(TransferType::RemoteReserve(asset_hub_location.clone().into())), + remote_fees_id: bx!(VersionedAssetId::V4(AssetId(Location::new(1, [])))), + fees_transfer_type: bx!(TransferType::RemoteReserve(asset_hub_location.into())), + custom_xcm_on_dest: bx!(VersionedXcm::from(custom_xcm_on_dest)), + weight_limit: test.args.weight_limit, + }) } /// We are able to dry-run and estimate the fees for a multi-hop XCM journey. @@ -109,12 +115,13 @@ fn teleport_relay_system_para_works() { fn multi_hop_works() { let destination = PenpalA::sibling_location_of(PenpalB::para_id()); let sender = PenpalASender::get(); - let amount_to_send = 1_000_000_000_000; // One WND (12 decimals). + let amount_to_send = 1_000_000_000_000; let asset_owner = PenpalAssetOwner::get(); let assets: Assets = (Parent, amount_to_send).into(); - let relay_native_asset_location = RelayLocation::get(); - let sender_as_seen_by_relay = Westend::child_location_of(PenpalA::para_id()); - let sov_of_sender_on_relay = Westend::sovereign_account_id_of(sender_as_seen_by_relay.clone()); + let relay_native_asset_location = Location::parent(); + let sender_as_seen_by_ah = AssetHubWestend::sibling_location_of(PenpalA::para_id()); + let sov_of_sender_on_ah = + AssetHubWestend::sovereign_account_id_of(sender_as_seen_by_ah.clone()); // fund Parachain's sender account PenpalA::mint_foreign_asset( @@ -124,36 +131,44 @@ fn multi_hop_works() { amount_to_send * 2, ); - // fund the Parachain Origin's SA on Relay Chain with the native tokens held in reserve - Westend::fund_accounts(vec![(sov_of_sender_on_relay.clone().into(), amount_to_send * 2)]); + // fund the Parachain Origin's SA on AssetHub with the native tokens held in reserve. + AssetHubWestend::fund_accounts(vec![(sov_of_sender_on_ah.clone(), amount_to_send * 2)]); // Init values for Parachain Destination let beneficiary_id = PenpalBReceiver::get(); - let beneficiary: Location = AccountId32 { - id: beneficiary_id.clone().into(), - network: None, // Test doesn't allow specifying a network here. - } - .into(); + + let test_args = TestContext { + sender: PenpalASender::get(), // Bob in PenpalB. + receiver: PenpalBReceiver::get(), // Alice. + args: TestArgs::new_para( + destination, + beneficiary_id.clone(), + amount_to_send, + assets, + None, + 0, + ), + }; + let mut test = ParaToParaThroughAHTest::new(test_args); // We get them from the PenpalA closure. let mut delivery_fees_amount = 0; let mut remote_message = VersionedXcm::V4(Xcm(Vec::new())); ::execute_with(|| { type Runtime = ::Runtime; - type RuntimeCall = ::RuntimeCall; type OriginCaller = ::OriginCaller; - let call = RuntimeCall::PolkadotXcm(pallet_xcm::Call::transfer_assets { - dest: Box::new(VersionedLocation::V4(destination.clone())), - beneficiary: Box::new(VersionedLocation::V4(beneficiary)), - assets: Box::new(VersionedAssets::V4(assets.clone())), - fee_asset_item: 0, - weight_limit: Unlimited, - }); - let origin = OriginCaller::system(RawOrigin::Signed(PenpalASender::get())); + let call = transfer_assets_para_to_para_through_ah_call(test.clone()); + let origin = OriginCaller::system(RawOrigin::Signed(sender.clone())); let result = Runtime::dry_run_call(origin, call).unwrap(); - assert_eq!(result.forwarded_xcms.len(), 1); - let (destination_to_query, messages_to_query) = &result.forwarded_xcms[0]; + // We filter the result to get only the messages we are interested in. + let (destination_to_query, messages_to_query) = &result + .forwarded_xcms + .iter() + .find(|(destination, _)| { + *destination == VersionedLocation::V4(Location::new(1, [Parachain(1000)])) + }) + .unwrap(); assert_eq!(messages_to_query.len(), 1); remote_message = messages_to_query[0].clone(); let delivery_fees = @@ -162,18 +177,21 @@ fn multi_hop_works() { delivery_fees_amount = get_amount_from_versioned_assets(delivery_fees); }); - // This is set in the Westend closure. + // These are set in the AssetHub closure. let mut intermediate_execution_fees = 0; let mut intermediate_delivery_fees_amount = 0; let mut intermediate_remote_message = VersionedXcm::V4(Xcm::<()>(Vec::new())); - ::execute_with(|| { - type Runtime = ::Runtime; - type RuntimeCall = ::RuntimeCall; + ::execute_with(|| { + type Runtime = ::Runtime; + type RuntimeCall = ::RuntimeCall; // First we get the execution fees. let weight = Runtime::query_xcm_weight(remote_message.clone()).unwrap(); - intermediate_execution_fees = - Runtime::query_weight_to_asset_fee(weight, VersionedAssetId::V4(Here.into())).unwrap(); + intermediate_execution_fees = Runtime::query_weight_to_asset_fee( + weight, + VersionedAssetId::V4(Location::new(1, []).into()), + ) + .unwrap(); // We have to do this to turn `VersionedXcm<()>` into `VersionedXcm`. let xcm_program = @@ -181,8 +199,14 @@ fn multi_hop_works() { // Now we get the delivery fees to the final destination. let result = - Runtime::dry_run_xcm(sender_as_seen_by_relay.clone().into(), xcm_program).unwrap(); - let (destination_to_query, messages_to_query) = &result.forwarded_xcms[0]; + Runtime::dry_run_xcm(sender_as_seen_by_ah.clone().into(), xcm_program).unwrap(); + let (destination_to_query, messages_to_query) = &result + .forwarded_xcms + .iter() + .find(|(destination, _)| { + *destination == VersionedLocation::V4(Location::new(1, [Parachain(2001)])) + }) + .unwrap(); // There's actually two messages here. // One created when the message we sent from PenpalA arrived and was executed. // The second one when we dry-run the xcm. @@ -200,7 +224,7 @@ fn multi_hop_works() { // Get the final execution fees in the destination. let mut final_execution_fees = 0; ::execute_with(|| { - type Runtime = ::Runtime; + type Runtime = ::Runtime; let weight = Runtime::query_xcm_weight(intermediate_remote_message.clone()).unwrap(); final_execution_fees = @@ -210,7 +234,7 @@ fn multi_hop_works() { // Dry-running is done. PenpalA::reset_ext(); - Westend::reset_ext(); + AssetHubWestend::reset_ext(); PenpalB::reset_ext(); // Fund accounts again. @@ -220,23 +244,9 @@ fn multi_hop_works() { sender.clone(), amount_to_send * 2, ); - Westend::fund_accounts(vec![(sov_of_sender_on_relay.into(), amount_to_send * 2)]); + AssetHubWestend::fund_accounts(vec![(sov_of_sender_on_ah, amount_to_send * 2)]); // Actually run the extrinsic. - let test_args = TestContext { - sender: PenpalASender::get(), // Alice. - receiver: PenpalBReceiver::get(), // Bob in PenpalB. - args: TestArgs::new_para( - destination, - beneficiary_id.clone(), - amount_to_send, - assets, - None, - 0, - ), - }; - let mut test = ParaToParaThroughRelayTest::new(test_args); - let sender_assets_before = PenpalA::execute_with(|| { type ForeignAssets = ::ForeignAssets; >::balance(relay_native_asset_location.clone(), &sender) @@ -246,7 +256,10 @@ fn multi_hop_works() { >::balance(relay_native_asset_location.clone(), &beneficiary_id) }); - test.set_dispatchable::(transfer_assets_para_to_para); + test.set_assertion::(sender_assertions); + test.set_assertion::(hop_assertions); + test.set_assertion::(receiver_assertions); + test.set_dispatchable::(transfer_assets_para_to_para_through_ah_dispatchable); test.assert(); let sender_assets_after = PenpalA::execute_with(|| { @@ -273,33 +286,3 @@ fn multi_hop_works() { final_execution_fees ); } - -fn get_amount_from_versioned_assets(assets: VersionedAssets) -> u128 { - let latest_assets: Assets = assets.try_into().unwrap(); - let Fungible(amount) = latest_assets.inner()[0].fun else { - unreachable!("asset is fungible"); - }; - amount -} - -fn transfer_assets(test: RelayToSystemParaTest) -> DispatchResult { - ::XcmPallet::transfer_assets( - test.signed_origin, - bx!(test.args.dest.into()), - bx!(test.args.beneficiary.into()), - bx!(test.args.assets.into()), - test.args.fee_asset_item, - test.args.weight_limit, - ) -} - -fn transfer_assets_para_to_para(test: ParaToParaThroughRelayTest) -> DispatchResult { - ::PolkadotXcm::transfer_assets( - test.signed_origin, - bx!(test.args.dest.into()), - bx!(test.args.beneficiary.into()), - bx!(test.args.assets.into()), - test.args.fee_asset_item, - test.args.weight_limit, - ) -}