diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2672bc2b00..6ec7604957 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -55,7 +55,6 @@ jobs: run: cargo test --workspace --release --locked -q --features=runtime-metrics,try-runtime env: RUSTFLAGS: "-C debug-assertions -D warnings" - SKIP_WASM_BUILD: 1 - name: Test all features run: cargo test --workspace --release --locked -q --features=runtime-benchmarks,runtime-metrics,try-runtime diff --git a/Cargo.lock b/Cargo.lock index 90c6aff873..e59cb4ac97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -524,6 +524,29 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" +[[package]] +name = "asset-hub-kusama-integration-tests" +version = "1.0.0" +dependencies = [ + "assert_matches", + "asset-hub-kusama-runtime", + "frame-support", + "frame-system", + "integration-tests-common", + "pallet-asset-conversion", + "pallet-assets", + "pallet-balances", + "pallet-xcm", + "parachains-common", + "parity-scale-codec", + "polkadot-core-primitives", + "polkadot-parachain-primitives", + "polkadot-runtime-parachains", + "sp-runtime", + "staging-xcm", + "xcm-emulator", +] + [[package]] name = "asset-hub-kusama-runtime" version = "1.0.0" @@ -598,6 +621,27 @@ dependencies = [ "substrate-wasm-builder", ] +[[package]] +name = "asset-hub-polkadot-integration-tests" +version = "1.0.0" +dependencies = [ + "frame-support", + "frame-system", + "integration-tests-common", + "pallet-asset-conversion", + "pallet-assets", + "pallet-balances", + "pallet-xcm", + "parachains-common", + "parity-scale-codec", + "polkadot-core-primitives", + "polkadot-parachain-primitives", + "polkadot-runtime-parachains", + "sp-runtime", + "staging-xcm", + "xcm-emulator", +] + [[package]] name = "asset-hub-polkadot-runtime" version = "1.0.0" @@ -4452,6 +4496,53 @@ dependencies = [ "num-traits", ] +[[package]] +name = "integration-tests-common" +version = "1.0.0" +dependencies = [ + "asset-hub-kusama-runtime", + "asset-hub-polkadot-runtime", + "asset-test-utils", + "bp-messages", + "bridge-hub-kusama-runtime", + "bridge-hub-polkadot-runtime", + "bridge-runtime-common", + "collectives-polkadot-runtime", + "cumulus-pallet-dmp-queue", + "cumulus-pallet-parachain-system", + "cumulus-pallet-xcmp-queue", + "cumulus-primitives-core", + "frame-support", + "kusama-runtime-constants", + "pallet-assets", + "pallet-balances", + "pallet-bridge-messages", + "pallet-im-online", + "pallet-message-queue", + "pallet-staking", + "pallet-xcm", + "parachains-common", + "parity-scale-codec", + "paste", + "penpal-runtime", + "polkadot-core-primitives", + "polkadot-parachain-primitives", + "polkadot-primitives", + "polkadot-runtime", + "polkadot-runtime-constants", + "polkadot-runtime-parachains", + "serde_json", + "sp-authority-discovery", + "sp-consensus-babe", + "sp-consensus-beefy", + "sp-consensus-grandpa", + "sp-core", + "sp-runtime", + "staging-kusama-runtime", + "staging-xcm", + "xcm-emulator", +] + [[package]] name = "interceptor" version = "0.8.2" @@ -7797,6 +7888,68 @@ dependencies = [ "base64ct", ] +[[package]] +name = "penpal-runtime" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63d1c2aa2cbc5012e6af2781ff7fb0bf7188ff4d25643a0f36a44f327b779fb4" +dependencies = [ + "cumulus-pallet-aura-ext", + "cumulus-pallet-dmp-queue", + "cumulus-pallet-parachain-system", + "cumulus-pallet-session-benchmarking", + "cumulus-pallet-xcm", + "cumulus-pallet-xcmp-queue", + "cumulus-primitives-core", + "cumulus-primitives-utility", + "frame-benchmarking", + "frame-executive", + "frame-support", + "frame-system", + "frame-system-benchmarking", + "frame-system-rpc-runtime-api", + "frame-try-runtime", + "hex-literal", + "log", + "pallet-asset-tx-payment", + "pallet-assets", + "pallet-aura", + "pallet-authorship", + "pallet-balances", + "pallet-collator-selection", + "pallet-session", + "pallet-sudo", + "pallet-timestamp", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", + "pallet-xcm", + "parachains-common", + "parity-scale-codec", + "polkadot-parachain-primitives", + "polkadot-primitives", + "polkadot-runtime-common", + "scale-info", + "smallvec", + "sp-api", + "sp-block-builder", + "sp-consensus-aura", + "sp-core", + "sp-genesis-builder", + "sp-inherents", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-std", + "sp-storage", + "sp-transaction-pool", + "sp-version", + "staging-parachain-info", + "staging-xcm", + "staging-xcm-builder", + "staging-xcm-executor", + "substrate-wasm-builder", +] + [[package]] name = "percent-encoding" version = "2.3.0" @@ -13585,6 +13738,39 @@ dependencies = [ "time 0.3.27", ] +[[package]] +name = "xcm-emulator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd344ca12e6217fbbc6ee27bedb01113357ea31bea198e74b76f618102b6f06c" +dependencies = [ + "cumulus-pallet-parachain-system", + "cumulus-primitives-core", + "cumulus-primitives-parachain-inherent", + "cumulus-test-relay-sproof-builder", + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "lazy_static", + "log", + "pallet-balances", + "pallet-message-queue", + "parachains-common", + "parity-scale-codec", + "paste", + "polkadot-parachain-primitives", + "polkadot-primitives", + "polkadot-runtime-parachains", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "sp-tracing", + "staging-xcm", + "staging-xcm-executor", +] + [[package]] name = "xcm-procedural" version = "4.0.0" diff --git a/Cargo.toml b/Cargo.toml index ae60da4073..1062ab77cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,8 @@ members = [ "system-parachains/bridge-hubs/bridge-hub-polkadot", "system-parachains/collectives/collectives-polkadot", "system-parachains/gluttons/glutton-kusama", + "integration-tests/emulated/assets/asset-hub-kusama", + "integration-tests/emulated/assets/asset-hub-polkadot", ] [profile.release] diff --git a/integration-tests/emulated/assets/asset-hub-kusama/Cargo.toml b/integration-tests/emulated/assets/asset-hub-kusama/Cargo.toml new file mode 100644 index 0000000000..4b61aa5b12 --- /dev/null +++ b/integration-tests/emulated/assets/asset-hub-kusama/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "asset-hub-kusama-integration-tests" +version = "1.0.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +description = "Asset Hub Kusama runtime integration tests with xcm-emulator" +publish = false + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.4.0" } +assert_matches = "1.5.0" + +# Substrate +sp-runtime = { version = "28.0.0" } +frame-support = { version = "25.0.0" } +frame-system = { version = "25.0.0" } +pallet-assets = { version = "26.0.0" } +pallet-balances = { version = "25.0.0" } +pallet-asset-conversion = { version = "7.0.0" } + +# Polkadot +polkadot-core-primitives = { version = "4.0.0" } +polkadot-parachain-primitives = { version = "3.0.0" } +polkadot-runtime-parachains = { version = "4.0.0" } +xcm = { package = "staging-xcm", version = "4.0.0" } +pallet-xcm = { version = "4.0.0" } + +# Cumulus +parachains-common = { version = "4.0.0" } +xcm-emulator = { version = "0.2.0" } +asset-hub-kusama-runtime = { path = "../../../../system-parachains/asset-hubs/asset-hub-kusama" } + +# Local +integration-tests-common = { path = "../../common" } + +[features] +runtime-benchmarks = [ + "integration-tests-common/runtime-benchmarks" +] diff --git a/integration-tests/emulated/assets/asset-hub-kusama/src/lib.rs b/integration-tests/emulated/assets/asset-hub-kusama/src/lib.rs new file mode 100644 index 0000000000..60c4551459 --- /dev/null +++ b/integration-tests/emulated/assets/asset-hub-kusama/src/lib.rs @@ -0,0 +1,92 @@ +// 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. + +#![cfg(not(feature = "runtime-benchmarks"))] + +pub use codec::Encode; +pub use frame_support::{ + assert_err, assert_ok, + pallet_prelude::Weight, + sp_runtime::{AccountId32, DispatchError, DispatchResult}, + traits::fungibles::Inspect, +}; +pub use integration_tests_common::{ + constants::{ + asset_hub_kusama::ED as ASSET_HUB_KUSAMA_ED, kusama::ED as KUSAMA_ED, PROOF_SIZE_THRESHOLD, + REF_TIME_THRESHOLD, XCM_V3, + }, + xcm_helpers::{xcm_transact_paid_execution, xcm_transact_unpaid_execution}, + AssetHubKusama, AssetHubKusamaPallet, AssetHubKusamaReceiver, AssetHubKusamaSender, Kusama, + KusamaPallet, KusamaReceiver, KusamaSender, PenpalKusamaA, PenpalKusamaAPallet, + PenpalKusamaAReceiver, PenpalKusamaASender, PenpalKusamaB, PenpalKusamaBPallet, +}; +pub use parachains_common::{AccountId, Balance}; +pub use xcm::{ + prelude::{AccountId32 as AccountId32Junction, *}, + v3::{Error, NetworkId::Kusama as KusamaId}, +}; +pub use xcm_emulator::{ + assert_expected_events, bx, helpers::weight_within_threshold, Chain, Parachain as Para, + RelayChain as Relay, Test, TestArgs, TestContext, TestExt, +}; + +pub const ASSET_ID: u32 = 1; +pub const ASSET_MIN_BALANCE: u128 = 1000; +// `Assets` pallet index +pub const ASSETS_PALLET_ID: u8 = 50; + +pub type RelayToSystemParaTest = Test; +pub type SystemParaToRelayTest = Test; +pub type SystemParaToParaTest = Test; + +/// Returns a `TestArgs` instance to be used for the Relay Chain across integration tests +pub fn relay_test_args(amount: Balance) -> TestArgs { + TestArgs { + dest: Kusama::child_location_of(AssetHubKusama::para_id()), + beneficiary: AccountId32Junction { + network: None, + id: AssetHubKusamaReceiver::get().into(), + } + .into(), + amount, + assets: (Here, amount).into(), + asset_id: None, + fee_asset_item: 0, + weight_limit: WeightLimit::Unlimited, + } +} + +/// Returns a `TestArgs` instance to be used for the System Parachain across integration tests +pub fn system_para_test_args( + dest: MultiLocation, + beneficiary_id: AccountId32, + amount: Balance, + assets: MultiAssets, + asset_id: Option, +) -> TestArgs { + TestArgs { + dest, + beneficiary: AccountId32Junction { network: None, id: beneficiary_id.into() }.into(), + amount, + assets, + asset_id, + fee_asset_item: 0, + weight_limit: WeightLimit::Unlimited, + } +} + +#[cfg(test)] +#[cfg(not(feature = "runtime-benchmarks"))] +mod tests; diff --git a/integration-tests/emulated/assets/asset-hub-kusama/src/tests/hrmp_channels.rs b/integration-tests/emulated/assets/asset-hub-kusama/src/tests/hrmp_channels.rs new file mode 100644 index 0000000000..f930e3182c --- /dev/null +++ b/integration-tests/emulated/assets/asset-hub-kusama/src/tests/hrmp_channels.rs @@ -0,0 +1,196 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::*; + +const MAX_CAPACITY: u32 = 8; +const MAX_MESSAGE_SIZE: u32 = 8192; + +/// Opening HRMP channels between Parachains should work +#[test] +fn open_hrmp_channel_between_paras_works() { + // Parchain A init values + let para_a_id = PenpalKusamaA::para_id(); + let para_a_root_origin = ::RuntimeOrigin::root(); + + // Parachain B init values + let para_b_id = PenpalKusamaB::para_id(); + let para_b_root_origin = ::RuntimeOrigin::root(); + + let fee_amount = KUSAMA_ED * 1000; + let fund_amount = KUSAMA_ED * 1000_000_000; + + // Fund Parachain's Sovereign accounts to be able to reserve the deposit + let para_a_sovereign_account = Kusama::fund_para_sovereign(fund_amount, para_a_id); + let para_b_sovereign_account = Kusama::fund_para_sovereign(fund_amount, para_b_id); + + let relay_destination: VersionedMultiLocation = PenpalKusamaA::parent_location().into(); + + // ---- Init Open channel from Parachain to System Parachain + let mut call = Kusama::init_open_channel_call(para_b_id, MAX_CAPACITY, MAX_MESSAGE_SIZE); + let origin_kind = OriginKind::Native; + let native_asset: MultiAsset = (Here, fee_amount).into(); + let beneficiary = Kusama::sovereign_account_id_of_child_para(para_a_id); + + let mut xcm = xcm_transact_paid_execution(call, origin_kind, native_asset.clone(), beneficiary); + + PenpalKusamaA::execute_with(|| { + assert_ok!(::PolkadotXcm::send( + para_a_root_origin, + bx!(relay_destination.clone()), + bx!(xcm), + )); + + PenpalKusamaA::assert_xcm_pallet_sent(); + }); + + Kusama::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + Kusama::assert_ump_queue_processed( + true, + Some(para_a_id), + Some(Weight::from_parts(1_300_000_000, 200000)), + ); + + assert_expected_events!( + Kusama, + vec![ + // Parachain's Sovereign account balance is withdrawn to pay XCM fees + RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + who: *who == para_a_sovereign_account.clone(), + amount: *amount == fee_amount, + }, + // Sender deposit is reserved for Parachain's Sovereign account + RuntimeEvent::Balances(pallet_balances::Event::Reserved { who, .. }) =>{ + who: *who == para_a_sovereign_account, + }, + // Open channel requested from Para A to Para B + RuntimeEvent::Hrmp( + polkadot_runtime_parachains::hrmp::Event::OpenChannelRequested { + sender, + recipient, + proposed_max_capacity: max_capacity, + proposed_max_message_size: max_message_size + } + ) => { + sender: *sender == para_a_id.into(), + recipient: *recipient == para_b_id.into(), + max_capacity: *max_capacity == MAX_CAPACITY, + max_message_size: *max_message_size == MAX_MESSAGE_SIZE, + }, + ] + ); + }); + + // ---- Accept Open channel from Parachain to System Parachain + call = Kusama::accept_open_channel_call(para_a_id); + let beneficiary = Kusama::sovereign_account_id_of_child_para(para_b_id); + + xcm = xcm_transact_paid_execution(call, origin_kind, native_asset, beneficiary); + + PenpalKusamaB::execute_with(|| { + assert_ok!(::PolkadotXcm::send( + para_b_root_origin, + bx!(relay_destination), + bx!(xcm), + )); + + PenpalKusamaB::assert_xcm_pallet_sent(); + }); + + PenpalKusamaB::execute_with(|| {}); + + Kusama::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + Kusama::assert_ump_queue_processed( + true, + Some(para_b_id), + Some(Weight::from_parts(1_300_000_000, 200_000)), + ); + + assert_expected_events!( + Kusama, + vec![ + // Parachain's Sovereign account balance is withdrawn to pay XCM fees + RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + who: *who == para_b_sovereign_account.clone(), + amount: *amount == fee_amount, + }, + // Sender deposit is reserved for Parachain's Sovereign account + RuntimeEvent::Balances(pallet_balances::Event::Reserved { who, .. }) =>{ + who: *who == para_b_sovereign_account, + }, + // Open channel accepted for Para A to Para B + RuntimeEvent::Hrmp( + polkadot_runtime_parachains::hrmp::Event::OpenChannelAccepted { + sender, recipient + } + ) => { + sender: *sender == para_a_id.into(), + recipient: *recipient == para_b_id.into(), + }, + ] + ); + }); + + Kusama::force_process_hrmp_open(para_a_id, para_b_id); +} + +/// Opening HRMP channels between System Parachains and Parachains should work +#[test] +fn force_open_hrmp_channel_for_system_para_works() { + // Relay Chain init values + let relay_root_origin = ::RuntimeOrigin::root(); + + // System Para init values + let system_para_id = AssetHubKusama::para_id(); + + // Parachain A init values + let para_a_id = PenpalKusamaA::para_id(); + + Kusama::execute_with(|| { + assert_ok!(::Hrmp::force_open_hrmp_channel( + relay_root_origin, + system_para_id, + para_a_id, + MAX_CAPACITY, + MAX_MESSAGE_SIZE + )); + + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + Kusama, + vec![ + // HRMP channel forced opened + RuntimeEvent::Hrmp( + polkadot_runtime_parachains::hrmp::Event::HrmpChannelForceOpened { + sender, + recipient, + proposed_max_capacity: max_capacity, + proposed_max_message_size: max_message_size + } + ) => { + sender: *sender == system_para_id.into(), + recipient: *recipient == para_a_id.into(), + max_capacity: *max_capacity == MAX_CAPACITY, + max_message_size: *max_message_size == MAX_MESSAGE_SIZE, + }, + ] + ); + }); +} diff --git a/integration-tests/emulated/assets/asset-hub-kusama/src/tests/mod.rs b/integration-tests/emulated/assets/asset-hub-kusama/src/tests/mod.rs new file mode 100644 index 0000000000..ac0b92004e --- /dev/null +++ b/integration-tests/emulated/assets/asset-hub-kusama/src/tests/mod.rs @@ -0,0 +1,23 @@ +// 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. + +#![cfg(not(feature = "runtime-benchmarks"))] + +mod hrmp_channels; +mod reserve_transfer; +mod send; +mod set_xcm_versions; +mod swap; +mod teleport; diff --git a/integration-tests/emulated/assets/asset-hub-kusama/src/tests/reserve_transfer.rs b/integration-tests/emulated/assets/asset-hub-kusama/src/tests/reserve_transfer.rs new file mode 100644 index 0000000000..cc2e7a66b3 --- /dev/null +++ b/integration-tests/emulated/assets/asset-hub-kusama/src/tests/reserve_transfer.rs @@ -0,0 +1,255 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::*; + +fn relay_origin_assertions(t: RelayToSystemParaTest) { + type RuntimeEvent = ::RuntimeEvent; + + Kusama::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(630_000_000, 6_000))); + + assert_expected_events!( + Kusama, + vec![ + // Amount to reserve transfer is transferred to System Parachain's Sovereign account + RuntimeEvent::Balances(pallet_balances::Event::Transfer { from, to, amount }) => { + from: *from == t.sender.account_id, + to: *to == Kusama::sovereign_account_id_of( + t.args.dest + ), + amount: *amount == t.args.amount, + }, + ] + ); +} + +fn system_para_dest_assertions_incomplete(_t: RelayToSystemParaTest) { + AssetHubKusama::assert_dmp_queue_incomplete(None, Some(Error::UntrustedReserveLocation)); +} + +fn system_para_to_relay_assertions(_t: SystemParaToRelayTest) { + AssetHubKusama::assert_xcm_pallet_attempted_error(Some(XcmError::Barrier)) +} + +fn system_para_to_para_assertions(t: SystemParaToParaTest) { + type RuntimeEvent = ::RuntimeEvent; + + AssetHubKusama::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts( + 630_000_000, + 6_000, + ))); + + assert_expected_events!( + AssetHubKusama, + vec![ + // Amount to reserve transfer is transferred to Parachain's Sovereing account + RuntimeEvent::Balances( + pallet_balances::Event::Transfer { from, to, amount } + ) => { + from: *from == t.sender.account_id, + to: *to == AssetHubKusama::sovereign_account_id_of( + t.args.dest + ), + amount: *amount == t.args.amount, + }, + ] + ); +} + +fn system_para_to_para_assets_assertions(t: SystemParaToParaTest) { + type RuntimeEvent = ::RuntimeEvent; + + AssetHubKusama::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts( + 680_000_000, + 6000, + ))); + + assert_expected_events!( + AssetHubKusama, + vec![ + // Amount to reserve transfer is transferred to Parachain's Sovereing account + RuntimeEvent::Assets( + pallet_assets::Event::Transferred { asset_id, from, to, amount } + ) => { + asset_id: *asset_id == ASSET_ID, + from: *from == t.sender.account_id, + to: *to == AssetHubKusama::sovereign_account_id_of( + t.args.dest + ), + amount: *amount == t.args.amount, + }, + ] + ); +} + +fn relay_limited_reserve_transfer_assets(t: RelayToSystemParaTest) -> DispatchResult { + ::XcmPallet::limited_reserve_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_limited_reserve_transfer_assets(t: SystemParaToRelayTest) -> DispatchResult { + ::PolkadotXcm::limited_reserve_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_limited_reserve_transfer_assets(t: SystemParaToParaTest) -> DispatchResult { + ::PolkadotXcm::limited_reserve_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 Reserve Transfers of native asset from Relay Chain to the System Parachain shouldn't +/// work +#[test] +fn limited_reserve_transfer_native_asset_from_relay_to_system_para_fails() { + // Init values for Relay Chain + let amount_to_send: Balance = KUSAMA_ED * 1000; + let test_args = TestContext { + sender: KusamaSender::get(), + receiver: AssetHubKusamaReceiver::get(), + args: relay_test_args(amount_to_send), + }; + + let mut test = RelayToSystemParaTest::new(test_args); + + let sender_balance_before = test.sender.balance; + let receiver_balance_before = test.receiver.balance; + + test.set_assertion::(relay_origin_assertions); + test.set_assertion::(system_para_dest_assertions_incomplete); + test.set_dispatchable::(relay_limited_reserve_transfer_assets); + test.assert(); + + let sender_balance_after = test.sender.balance; + let receiver_balance_after = test.receiver.balance; + + assert!(sender_balance_before - amount_to_send >= sender_balance_after); + assert_eq!(receiver_balance_before, receiver_balance_after); +} + +/// Limited Reserve Transfers of native asset from System Parachain to Relay Chain shoudln't work +#[test] +fn limited_reserve_transfer_native_asset_from_system_para_to_relay_fails() { + // Init values for System Parachain + let destination = AssetHubKusama::parent_location(); + let beneficiary_id = KusamaReceiver::get(); + let amount_to_send: Balance = ASSET_HUB_KUSAMA_ED * 1000; + let assets = (Parent, amount_to_send).into(); + + let test_args = TestContext { + sender: AssetHubKusamaSender::get(), + receiver: KusamaReceiver::get(), + args: system_para_test_args(destination, beneficiary_id, amount_to_send, assets, None), + }; + + let mut test = SystemParaToRelayTest::new(test_args); + + let sender_balance_before = test.sender.balance; + let receiver_balance_before = test.receiver.balance; + + test.set_assertion::(system_para_to_relay_assertions); + test.set_dispatchable::(system_para_limited_reserve_transfer_assets); + test.assert(); + + let sender_balance_after = test.sender.balance; + let receiver_balance_after = test.receiver.balance; + + assert_eq!(sender_balance_before, sender_balance_after); + assert_eq!(receiver_balance_before, receiver_balance_after); +} + +/// Limited Reserve Transfers of native asset from System Parachain to Parachain should work +#[test] +fn limited_reserve_transfer_native_asset_from_system_para_to_para() { + // Init values for System Parachain + let destination = AssetHubKusama::sibling_location_of(PenpalKusamaA::para_id()); + let beneficiary_id = PenpalKusamaAReceiver::get(); + let amount_to_send: Balance = ASSET_HUB_KUSAMA_ED * 1000; + let assets = (Parent, amount_to_send).into(); + + let test_args = TestContext { + sender: AssetHubKusamaSender::get(), + receiver: PenpalKusamaAReceiver::get(), + args: system_para_test_args(destination, beneficiary_id, amount_to_send, assets, None), + }; + + let mut test = SystemParaToParaTest::new(test_args); + + let sender_balance_before = test.sender.balance; + + test.set_assertion::(system_para_to_para_assertions); + // TODO: Add assertion for Penpal runtime. Right now message is failing with + // `UntrustedReserveLocation` + test.set_dispatchable::(system_para_to_para_limited_reserve_transfer_assets); + test.assert(); + + let sender_balance_after = test.sender.balance; + + assert!(sender_balance_before - amount_to_send >= sender_balance_after); + // TODO: Check receiver balance when Penpal runtime is improved to propery handle reserve + // transfers +} + +/// Limited Reserve Transfers of a local asset from System Parachain to Parachain should work +#[test] +fn limited_reserve_transfer_asset_from_system_para_to_para() { + // Force create asset from Relay Chain and mint assets for System Parachain's sender account + AssetHubKusama::force_create_and_mint_asset( + ASSET_ID, + ASSET_MIN_BALANCE, + true, + AssetHubKusamaSender::get(), + ASSET_MIN_BALANCE * 1000000, + ); + + // Init values for System Parachain + let destination = AssetHubKusama::sibling_location_of(PenpalKusamaA::para_id()); + let beneficiary_id = PenpalKusamaAReceiver::get(); + let amount_to_send = ASSET_MIN_BALANCE * 1000; + let assets = + (X2(PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())), amount_to_send) + .into(); + + let system_para_test_args = TestContext { + sender: AssetHubKusamaSender::get(), + receiver: PenpalKusamaAReceiver::get(), + args: system_para_test_args(destination, beneficiary_id, amount_to_send, assets, None), + }; + + let mut system_para_test = SystemParaToParaTest::new(system_para_test_args); + + system_para_test.set_assertion::(system_para_to_para_assets_assertions); + // TODO: Add assertions when Penpal is able to manage assets + system_para_test + .set_dispatchable::(system_para_to_para_limited_reserve_transfer_assets); + system_para_test.assert(); +} diff --git a/integration-tests/emulated/assets/asset-hub-kusama/src/tests/send.rs b/integration-tests/emulated/assets/asset-hub-kusama/src/tests/send.rs new file mode 100644 index 0000000000..face60a3a5 --- /dev/null +++ b/integration-tests/emulated/assets/asset-hub-kusama/src/tests/send.rs @@ -0,0 +1,195 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::*; + +/// Relay Chain should be able to execute `Transact` instructions in System Parachain +/// when `OriginKind::Superuser` and signer is `sudo` +#[test] +fn send_transact_sudo_from_relay_to_system_para_works() { + // Init tests variables + let root_origin = ::RuntimeOrigin::root(); + let system_para_destination = Kusama::child_location_of(AssetHubKusama::para_id()).into(); + let asset_owner: AccountId = AssetHubKusamaSender::get().into(); + let xcm = AssetHubKusama::force_create_asset_xcm( + OriginKind::Superuser, + ASSET_ID, + asset_owner.clone(), + true, + 1000, + ); + // Send XCM message from Relay Chain + Kusama::execute_with(|| { + assert_ok!(::XcmPallet::send( + root_origin, + bx!(system_para_destination), + bx!(xcm), + )); + + Kusama::assert_xcm_pallet_sent(); + }); + + // Receive XCM message in Assets Parachain + AssetHubKusama::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + AssetHubKusama::assert_dmp_queue_complete(Some(Weight::from_parts(1_020_000_000, 200_000))); + + assert_expected_events!( + AssetHubKusama, + vec![ + RuntimeEvent::Assets(pallet_assets::Event::ForceCreated { asset_id, owner }) => { + asset_id: *asset_id == ASSET_ID, + owner: *owner == asset_owner, + }, + ] + ); + + assert!(::Assets::asset_exists(ASSET_ID)); + }); +} + +/// Relay Chain shouldn't be able to execute `Transact` instructions in System Parachain +/// when `OriginKind::Native` +#[test] +fn send_transact_native_from_relay_to_system_para_fails() { + // Init tests variables + let signed_origin = ::RuntimeOrigin::signed(KusamaSender::get().into()); + let system_para_destination = Kusama::child_location_of(AssetHubKusama::para_id()).into(); + let asset_owner = AssetHubKusamaSender::get().into(); + let xcm = AssetHubKusama::force_create_asset_xcm( + OriginKind::Native, + ASSET_ID, + asset_owner, + true, + 1000, + ); + + // Send XCM message from Relay Chain + Kusama::execute_with(|| { + assert_err!( + ::XcmPallet::send( + signed_origin, + bx!(system_para_destination), + bx!(xcm) + ), + DispatchError::BadOrigin + ); + }); +} + +/// System Parachain shouldn't be able to execute `Transact` instructions in Relay Chain +/// when `OriginKind::Native` +#[test] +fn send_transact_native_from_system_para_to_relay_fails() { + // Init tests variables + let signed_origin = + ::RuntimeOrigin::signed(AssetHubKusamaSender::get().into()); + let relay_destination = AssetHubKusama::parent_location().into(); + let call = ::RuntimeCall::System(frame_system::Call::< + ::Runtime, + >::remark_with_event { + remark: vec![0, 1, 2, 3], + }) + .encode() + .into(); + let origin_kind = OriginKind::Native; + + let xcm = xcm_transact_unpaid_execution(call, origin_kind); + + // Send XCM message from Relay Chain + AssetHubKusama::execute_with(|| { + assert_err!( + ::PolkadotXcm::send( + signed_origin, + bx!(relay_destination), + bx!(xcm) + ), + DispatchError::BadOrigin + ); + }); +} + +/// Parachain should be able to send XCM paying its fee with sufficient asset +/// in the System Parachain +#[test] +fn send_xcm_from_para_to_system_para_paying_fee_with_assets_works() { + let para_sovereign_account = AssetHubKusama::sovereign_account_id_of( + AssetHubKusama::sibling_location_of(PenpalKusamaA::para_id()), + ); + + // Force create and mint assets for Parachain's sovereign account + AssetHubKusama::force_create_and_mint_asset( + ASSET_ID, + ASSET_MIN_BALANCE, + true, + para_sovereign_account.clone(), + ASSET_MIN_BALANCE * 1000000000, + ); + + // We just need a call that can pass the `SafeCallFilter` + // Call values are not relevant + let call = AssetHubKusama::force_create_asset_call( + ASSET_ID, + para_sovereign_account.clone(), + true, + ASSET_MIN_BALANCE, + ); + + let origin_kind = OriginKind::SovereignAccount; + let fee_amount = ASSET_MIN_BALANCE * 1000000; + let native_asset = + (X2(PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())), fee_amount).into(); + + let root_origin = ::RuntimeOrigin::root(); + let system_para_destination = + PenpalKusamaA::sibling_location_of(AssetHubKusama::para_id()).into(); + let xcm = xcm_transact_paid_execution( + call, + origin_kind, + native_asset, + para_sovereign_account.clone(), + ); + + PenpalKusamaA::execute_with(|| { + assert_ok!(::PolkadotXcm::send( + root_origin, + bx!(system_para_destination), + bx!(xcm), + )); + + PenpalKusamaA::assert_xcm_pallet_sent(); + }); + + AssetHubKusama::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + AssetHubKusama::assert_xcmp_queue_success(None); + + assert_expected_events!( + AssetHubKusama, + vec![ + RuntimeEvent::Assets(pallet_assets::Event::Burned { asset_id, owner, balance }) => { + asset_id: *asset_id == ASSET_ID, + owner: *owner == para_sovereign_account, + balance: *balance == fee_amount, + }, + RuntimeEvent::Assets(pallet_assets::Event::Issued { asset_id, .. }) => { + asset_id: *asset_id == ASSET_ID, + }, + ] + ); + }); +} diff --git a/integration-tests/emulated/assets/asset-hub-kusama/src/tests/set_xcm_versions.rs b/integration-tests/emulated/assets/asset-hub-kusama/src/tests/set_xcm_versions.rs new file mode 100644 index 0000000000..4b2716e90f --- /dev/null +++ b/integration-tests/emulated/assets/asset-hub-kusama/src/tests/set_xcm_versions.rs @@ -0,0 +1,93 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::*; + +#[test] +fn relay_sets_system_para_xcm_supported_version() { + // Init tests variables + let sudo_origin = ::RuntimeOrigin::root(); + let system_para_destination: MultiLocation = + Kusama::child_location_of(AssetHubKusama::para_id()); + + // Relay Chain sets supported version for Asset Parachain + Kusama::execute_with(|| { + assert_ok!(::XcmPallet::force_xcm_version( + sudo_origin, + bx!(system_para_destination), + XCM_V3 + )); + + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + Kusama, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::SupportedVersionChanged { + location, + version: XCM_V3 + }) => { location: *location == system_para_destination, }, + ] + ); + }); +} + +#[test] +fn system_para_sets_relay_xcm_supported_version() { + // Init test variables + let sudo_origin = ::RuntimeOrigin::root(); + let parent_location = AssetHubKusama::parent_location(); + let system_para_destination: VersionedMultiLocation = + Kusama::child_location_of(AssetHubKusama::para_id()).into(); + let call = ::RuntimeCall::PolkadotXcm(pallet_xcm::Call::< + ::Runtime, + >::force_xcm_version { + location: bx!(parent_location), + version: XCM_V3, + }) + .encode() + .into(); + let origin_kind = OriginKind::Superuser; + + let xcm = xcm_transact_unpaid_execution(call, origin_kind); + + // System Parachain sets supported version for Relay Chain throught it + Kusama::execute_with(|| { + assert_ok!(::XcmPallet::send( + sudo_origin, + bx!(system_para_destination), + bx!(xcm), + )); + + Kusama::assert_xcm_pallet_sent(); + }); + + // System Parachain receive the XCM message + AssetHubKusama::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + AssetHubKusama::assert_dmp_queue_complete(Some(Weight::from_parts(1_020_000_000, 200_000))); + + assert_expected_events!( + AssetHubKusama, + vec![ + RuntimeEvent::PolkadotXcm(pallet_xcm::Event::SupportedVersionChanged { + location, + version: XCM_V3 + }) => { location: *location == parent_location, }, + ] + ); + }); +} diff --git a/integration-tests/emulated/assets/asset-hub-kusama/src/tests/swap.rs b/integration-tests/emulated/assets/asset-hub-kusama/src/tests/swap.rs new file mode 100644 index 0000000000..3a67b54358 --- /dev/null +++ b/integration-tests/emulated/assets/asset-hub-kusama/src/tests/swap.rs @@ -0,0 +1,364 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::*; +use frame_support::{instances::Instance2, BoundedVec}; +use parachains_common::kusama::currency::EXISTENTIAL_DEPOSIT; +use sp_runtime::{DispatchError, ModuleError}; + +#[test] +fn swap_locally_on_chain_using_local_assets() { + let asset_native = Box::new(asset_hub_kusama_runtime::xcm_config::KsmLocation::get()); + let asset_one = Box::new(MultiLocation { + parents: 0, + interior: X2(PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())), + }); + + AssetHubKusama::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_ok!(::Assets::create( + ::RuntimeOrigin::signed(AssetHubKusamaSender::get()), + ASSET_ID.into(), + AssetHubKusamaSender::get().into(), + 1000, + )); + assert!(::Assets::asset_exists(ASSET_ID)); + + assert_ok!(::Assets::mint( + ::RuntimeOrigin::signed(AssetHubKusamaSender::get()), + ASSET_ID.into(), + AssetHubKusamaSender::get().into(), + 100_000_000_000_000, + )); + + assert_ok!(::Balances::force_set_balance( + ::RuntimeOrigin::root(), + AssetHubKusamaSender::get().into(), + 100_000_000_000_000, + )); + + assert_ok!(::AssetConversion::create_pool( + ::RuntimeOrigin::signed(AssetHubKusamaSender::get()), + asset_native.clone(), + asset_one.clone(), + )); + + assert_expected_events!( + AssetHubKusama, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + + assert_ok!(::AssetConversion::add_liquidity( + ::RuntimeOrigin::signed(AssetHubKusamaSender::get()), + asset_native.clone(), + asset_one.clone(), + 1_000_000_000_000, + 2_000_000_000_000, + 0, + 0, + AssetHubKusamaSender::get().into() + )); + + assert_expected_events!( + AssetHubKusama, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {lp_token_minted, .. }) => { lp_token_minted: *lp_token_minted == 1414213562273, }, + ] + ); + + let path = BoundedVec::<_, _>::truncate_from(vec![asset_native.clone(), asset_one.clone()]); + + assert_ok!( + ::AssetConversion::swap_exact_tokens_for_tokens( + ::RuntimeOrigin::signed(AssetHubKusamaSender::get()), + path, + 100, + 1, + AssetHubKusamaSender::get().into(), + true + ) + ); + + assert_expected_events!( + AssetHubKusama, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::SwapExecuted { amount_in, amount_out, .. }) => { + amount_in: *amount_in == 100, + amount_out: *amount_out == 199, + }, + ] + ); + + assert_ok!(::AssetConversion::remove_liquidity( + ::RuntimeOrigin::signed(AssetHubKusamaSender::get()), + asset_native, + asset_one, + 1414213562273 - EXISTENTIAL_DEPOSIT * 2, // all but the 2 EDs can't be retrieved. + 0, + 0, + AssetHubKusamaSender::get().into(), + )); + }); +} + +#[test] +fn swap_locally_on_chain_using_foreign_assets() { + use frame_support::weights::WeightToFee; + + let asset_native = Box::new(asset_hub_kusama_runtime::xcm_config::KsmLocation::get()); + + let foreign_asset1_at_asset_hub_kusama = Box::new(MultiLocation { + parents: 1, + interior: X3( + Parachain(PenpalKusamaA::para_id().into()), + PalletInstance(ASSETS_PALLET_ID), + GeneralIndex(ASSET_ID.into()), + ), + }); + + let assets_para_destination: VersionedMultiLocation = + MultiLocation { parents: 1, interior: X1(Parachain(AssetHubKusama::para_id().into())) } + .into(); + + let penpal_location = + MultiLocation { parents: 1, interior: X1(Parachain(PenpalKusamaA::para_id().into())) }; + + // 1. Create asset on penpal: + PenpalKusamaA::execute_with(|| { + assert_ok!(::Assets::create( + ::RuntimeOrigin::signed(PenpalKusamaASender::get()), + ASSET_ID.into(), + PenpalKusamaASender::get().into(), + 1000, + )); + + assert!(::Assets::asset_exists(ASSET_ID)); + }); + + // 2. Create foreign asset on asset_hub_kusama: + + 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_kusama = AssetHubKusama::sovereign_account_id_of(penpal_location); + + AssetHubKusama::fund_accounts(vec![ + (AssetHubKusamaSender::get().into(), 5_000_000 * KUSAMA_ED), /* An account to swap dot + * for something else. */ + (sov_penpal_on_asset_hub_kusama.clone().into(), 1000_000_000_000_000_000 * KUSAMA_ED), + ]); + + let sov_penpal_on_asset_hub_kusama_as_location: MultiLocation = MultiLocation { + parents: 0, + interior: X1(AccountId32Junction { + network: None, + id: sov_penpal_on_asset_hub_kusama.clone().into(), + }), + }; + + let call_foreign_assets_create = + ::RuntimeCall::ForeignAssets(pallet_assets::Call::< + ::Runtime, + Instance2, + >::create { + id: *foreign_asset1_at_asset_hub_kusama, + min_balance: 1000, + admin: sov_penpal_on_asset_hub_kusama.clone().into(), + }) + .encode() + .into(); + + let buy_execution_fee_amount = parachains_common::kusama::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_kusama_as_location, + }, + ])); + + // Send XCM message from penpal => asset_hub_kusama + let sudo_penpal_origin = ::RuntimeOrigin::root(); + PenpalKusamaA::execute_with(|| { + assert_ok!(::PolkadotXcm::send( + sudo_penpal_origin.clone(), + bx!(assets_para_destination.clone()), + bx!(xcm), + )); + + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + PenpalKusamaA, + vec![ + RuntimeEvent::PolkadotXcm(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + // Receive XCM message in Assets Parachain + AssetHubKusama::execute_with(|| { + assert!(::ForeignAssets::asset_exists( + *foreign_asset1_at_asset_hub_kusama + )); + + // 3: Mint foreign asset on asset_hub_kusama: + // + // (While it might be nice to use batch, + // currently that's disabled due to safe call filters.) + + type RuntimeEvent = ::RuntimeEvent; + // 3. Mint foreign asset (in reality this should be a teleport or some such) + assert_ok!(::ForeignAssets::mint( + ::RuntimeOrigin::signed( + sov_penpal_on_asset_hub_kusama.clone().into() + ), + *foreign_asset1_at_asset_hub_kusama, + sov_penpal_on_asset_hub_kusama.clone().into(), + 3_000_000_000_000, + )); + + assert_expected_events!( + AssetHubKusama, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + ] + ); + + // 4. Create pool: + assert_ok!(::AssetConversion::create_pool( + ::RuntimeOrigin::signed(AssetHubKusamaSender::get()), + asset_native.clone(), + foreign_asset1_at_asset_hub_kusama.clone(), + )); + + assert_expected_events!( + AssetHubKusama, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + + // 5. Add liquidity: + assert_ok!(::AssetConversion::add_liquidity( + ::RuntimeOrigin::signed( + sov_penpal_on_asset_hub_kusama.clone() + ), + asset_native.clone(), + foreign_asset1_at_asset_hub_kusama.clone(), + 1_000_000_000_000, + 2_000_000_000_000, + 0, + 0, + sov_penpal_on_asset_hub_kusama.clone().into() + )); + + assert_expected_events!( + AssetHubKusama, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {lp_token_minted, .. }) => { + lp_token_minted: *lp_token_minted == 1414213562273, + }, + ] + ); + + // 6. Swap! + let path = BoundedVec::<_, _>::truncate_from(vec![ + asset_native.clone(), + foreign_asset1_at_asset_hub_kusama.clone(), + ]); + + assert_ok!( + ::AssetConversion::swap_exact_tokens_for_tokens( + ::RuntimeOrigin::signed(AssetHubKusamaSender::get()), + path, + 100000, + 1000, + AssetHubKusamaSender::get().into(), + true + ) + ); + + assert_expected_events!( + AssetHubKusama, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::SwapExecuted { amount_in, amount_out, .. },) => { + amount_in: *amount_in == 100000, + amount_out: *amount_out == 199399, + }, + ] + ); + + // 7. Remove liquidity + assert_ok!(::AssetConversion::remove_liquidity( + ::RuntimeOrigin::signed( + sov_penpal_on_asset_hub_kusama.clone() + ), + asset_native, + foreign_asset1_at_asset_hub_kusama, + 1414213562273 - 2_000_000_000, // all but the 2 EDs can't be retrieved. + 0, + 0, + sov_penpal_on_asset_hub_kusama.clone().into(), + )); + }); +} + +#[test] +fn cannot_create_pool_from_pool_assets() { + let asset_native = Box::new(asset_hub_kusama_runtime::xcm_config::KsmLocation::get()); + let mut asset_one = asset_hub_kusama_runtime::xcm_config::PoolAssetsPalletLocation::get(); + asset_one.append_with(GeneralIndex(ASSET_ID.into())).expect("pool assets"); + + AssetHubKusama::execute_with(|| { + let pool_owner_account_id = asset_hub_kusama_runtime::AssetConversionOrigin::get(); + + assert_ok!(::PoolAssets::create( + ::RuntimeOrigin::signed(pool_owner_account_id.clone()), + ASSET_ID.into(), + pool_owner_account_id.clone().into(), + 1000, + )); + assert!(::PoolAssets::asset_exists(ASSET_ID)); + + assert_ok!(::PoolAssets::mint( + ::RuntimeOrigin::signed(pool_owner_account_id), + ASSET_ID.into(), + AssetHubKusamaSender::get().into(), + 3_000_000_000_000, + )); + + assert_matches::assert_matches!( + ::AssetConversion::create_pool( + ::RuntimeOrigin::signed(AssetHubKusamaSender::get()), + asset_native.clone(), + Box::new(asset_one), + ), + Err(DispatchError::Module(ModuleError{index: _, error: _, message})) => assert_eq!(message, Some("UnsupportedAsset")) + ); + }); +} diff --git a/integration-tests/emulated/assets/asset-hub-kusama/src/tests/teleport.rs b/integration-tests/emulated/assets/asset-hub-kusama/src/tests/teleport.rs new file mode 100644 index 0000000000..b0d2712046 --- /dev/null +++ b/integration-tests/emulated/assets/asset-hub-kusama/src/tests/teleport.rs @@ -0,0 +1,236 @@ +// 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. + +#![allow(dead_code)] // + +use crate::*; + +fn relay_origin_assertions(t: RelayToSystemParaTest) { + type RuntimeEvent = ::RuntimeEvent; + + Kusama::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(630_000_000, 7_000))); + + assert_expected_events!( + Kusama, + vec![ + // Amount to teleport is withdrawn from Sender + RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + who: *who == t.sender.account_id, + amount: *amount == t.args.amount, + }, + // Amount to teleport is deposited in Relay's `CheckAccount` + RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, amount }) => { + who: *who == ::XcmPallet::check_account(), + amount: *amount == t.args.amount, + }, + ] + ); +} + +fn relay_dest_assertions(t: SystemParaToRelayTest) { + type RuntimeEvent = ::RuntimeEvent; + + Kusama::assert_ump_queue_processed( + true, + Some(AssetHubKusama::para_id()), + Some(Weight::from_parts(310_000_000, 7_000)), + ); + + assert_expected_events!( + Kusama, + vec![ + // Amount is witdrawn from Relay Chain's `CheckAccount` + RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + who: *who == ::XcmPallet::check_account(), + amount: *amount == t.args.amount, + }, + // Amount minus fees are deposited in Receiver's account + RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + who: *who == t.receiver.account_id, + }, + ] + ); +} + +fn relay_dest_assertions_fail(_t: SystemParaToRelayTest) { + Kusama::assert_ump_queue_processed( + false, + Some(AssetHubKusama::para_id()), + Some(Weight::from_parts(150_000_000, 3_500)), + ); +} + +fn para_origin_assertions(t: SystemParaToRelayTest) { + type RuntimeEvent = ::RuntimeEvent; + + AssetHubKusama::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts( + 535_000_000, + 7_000, + ))); + + AssetHubKusama::assert_parachain_system_ump_sent(); + + assert_expected_events!( + AssetHubKusama, + vec![ + // Amount is withdrawn from Sender's account + RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + who: *who == t.sender.account_id, + amount: *amount == t.args.amount, + }, + ] + ); +} + +fn para_dest_assertions(t: RelayToSystemParaTest) { + type RuntimeEvent = ::RuntimeEvent; + + AssetHubKusama::assert_dmp_queue_complete(None); + + assert_expected_events!( + AssetHubKusama, + vec![ + // Amount minus fees are deposited in Receiver's account + RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + who: *who == t.receiver.account_id, + }, + ] + ); +} + +fn relay_limited_teleport_assets(t: RelayToSystemParaTest) -> DispatchResult { + ::XcmPallet::limited_teleport_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_limited_teleport_assets(t: SystemParaToRelayTest) -> DispatchResult { + ::PolkadotXcm::limited_teleport_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() { + // Init values for Relay Chain + let amount_to_send: Balance = KUSAMA_ED * 1000; + let test_args = TestContext { + sender: KusamaSender::get(), + receiver: AssetHubKusamaReceiver::get(), + args: relay_test_args(amount_to_send), + }; + + let mut test = RelayToSystemParaTest::new(test_args); + + let sender_balance_before = test.sender.balance; + let receiver_balance_before = test.receiver.balance; + + test.set_assertion::(relay_origin_assertions); + test.set_assertion::(para_dest_assertions); + test.set_dispatchable::(relay_limited_teleport_assets); + test.assert(); + + let sender_balance_after = test.sender.balance; + let receiver_balance_after = test.receiver.balance; + + // Sender's balance is reduced + assert!(sender_balance_before - amount_to_send >= sender_balance_after); + // Receiver's balance is increased + assert!(receiver_balance_after > receiver_balance_before); +} + +/// Limited Teleport of native asset from System Parachain to Relay Chain +/// should work when there is enough balance in Relay Chain's `CheckAccount` +#[test] +fn limited_teleport_native_assets_back_from_system_para_to_relay_works() { + // Dependency - Relay Chain's `CheckAccount` should have enough balance + limited_teleport_native_assets_from_relay_to_system_para_works(); + + // Init values for Relay Chain + let amount_to_send: Balance = ASSET_HUB_KUSAMA_ED * 1000; + let destination = AssetHubKusama::parent_location(); + let beneficiary_id = KusamaReceiver::get(); + let assets = (Parent, amount_to_send).into(); + + let test_args = TestContext { + sender: AssetHubKusamaSender::get(), + receiver: KusamaReceiver::get(), + args: system_para_test_args(destination, beneficiary_id, amount_to_send, assets, None), + }; + + let mut test = SystemParaToRelayTest::new(test_args); + + let sender_balance_before = test.sender.balance; + let receiver_balance_before = test.receiver.balance; + + test.set_assertion::(para_origin_assertions); + test.set_assertion::(relay_dest_assertions); + test.set_dispatchable::(system_para_limited_teleport_assets); + test.assert(); + + let sender_balance_after = test.sender.balance; + let receiver_balance_after = test.receiver.balance; + + // Sender's balance is reduced + assert!(sender_balance_before - amount_to_send >= sender_balance_after); + // Receiver's balance is increased + assert!(receiver_balance_after > receiver_balance_before); +} + +/// Limited Teleport of native asset from System Parachain to Relay Chain +/// shouldn't work when there is not enough balance in Relay Chain's `CheckAccount` +#[test] +fn limited_teleport_native_assets_from_system_para_to_relay_fails() { + // Init values for Relay Chain + let amount_to_send: Balance = ASSET_HUB_KUSAMA_ED * 1000; + let destination = AssetHubKusama::parent_location().into(); + let beneficiary_id = KusamaReceiver::get().into(); + let assets = (Parent, amount_to_send).into(); + + let test_args = TestContext { + sender: AssetHubKusamaSender::get(), + receiver: KusamaReceiver::get(), + args: system_para_test_args(destination, beneficiary_id, amount_to_send, assets, None), + }; + + let mut test = SystemParaToRelayTest::new(test_args); + + let sender_balance_before = test.sender.balance; + let receiver_balance_before = test.receiver.balance; + + test.set_assertion::(para_origin_assertions); + test.set_assertion::(relay_dest_assertions_fail); + test.set_dispatchable::(system_para_limited_teleport_assets); + test.assert(); + + let sender_balance_after = test.sender.balance; + let receiver_balance_after = test.receiver.balance; + + // Sender's balance is reduced by amount to send (+ delivery fees) + assert!(sender_balance_before - amount_to_send >= sender_balance_after); + // Receiver's balance does not change + assert_eq!(receiver_balance_after, receiver_balance_before); +} diff --git a/integration-tests/emulated/assets/asset-hub-polkadot/Cargo.toml b/integration-tests/emulated/assets/asset-hub-polkadot/Cargo.toml new file mode 100644 index 0000000000..eb01b282b8 --- /dev/null +++ b/integration-tests/emulated/assets/asset-hub-polkadot/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "asset-hub-polkadot-integration-tests" +version = "1.0.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +description = "Asset Hub Polkadot runtime integration tests with xcm-emulator" +publish = false + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.4.0" } + +# Substrate +sp-runtime = { version = "28.0.0" } +frame-support = { version = "25.0.0" } +frame-system = { version = "25.0.0" } +pallet-assets = { version = "26.0.0" } +pallet-balances = { version = "25.0.0" } +pallet-asset-conversion = { version = "7.0.0" } + +# Polkadot +polkadot-core-primitives = { version = "4.0.0" } +polkadot-parachain-primitives = { version = "3.0.0" } +polkadot-runtime-parachains = { version = "4.0.0" } +xcm = { package = "staging-xcm", version = "4.0.0" } +pallet-xcm = { version = "4.0.0" } + +# Cumulus +parachains-common = { version = "4.0.0" } +xcm-emulator = { version = "0.2.0" } + +# Local +integration-tests-common = { path = "../../common" } + +[features] +runtime-benchmarks = [ + "integration-tests-common/runtime-benchmarks" +] diff --git a/integration-tests/emulated/assets/asset-hub-polkadot/src/lib.rs b/integration-tests/emulated/assets/asset-hub-polkadot/src/lib.rs new file mode 100644 index 0000000000..8daf8c81e7 --- /dev/null +++ b/integration-tests/emulated/assets/asset-hub-polkadot/src/lib.rs @@ -0,0 +1,92 @@ +// 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. + +#![cfg(not(feature = "runtime-benchmarks"))] + +pub use codec::Encode; +pub use frame_support::{ + assert_err, assert_ok, + pallet_prelude::Weight, + sp_runtime::{AccountId32, DispatchError, DispatchResult}, + traits::fungibles::Inspect, +}; +pub use integration_tests_common::{ + constants::{ + asset_hub_polkadot::ED as ASSET_HUB_POLKADOT_ED, polkadot::ED as POLKADOT_ED, + PROOF_SIZE_THRESHOLD, REF_TIME_THRESHOLD, XCM_V3, + }, + xcm_helpers::{xcm_transact_paid_execution, xcm_transact_unpaid_execution}, + AssetHubPolkadot, AssetHubPolkadotPallet, AssetHubPolkadotReceiver, AssetHubPolkadotSender, + PenpalPolkadotA, PenpalPolkadotAPallet, PenpalPolkadotAReceiver, PenpalPolkadotB, + PenpalPolkadotBPallet, Polkadot, PolkadotPallet, PolkadotReceiver, PolkadotSender, +}; +pub use parachains_common::{AccountId, Balance}; +pub use xcm::{ + prelude::{AccountId32 as AccountId32Junction, *}, + v3::{Error, NetworkId::Polkadot as PolkadotId}, +}; +pub use xcm_emulator::{ + assert_expected_events, bx, helpers::weight_within_threshold, Chain, Parachain as Para, + RelayChain as Relay, Test, TestArgs, TestContext, TestExt, +}; + +pub const ASSET_ID: u32 = 1; +pub const ASSET_MIN_BALANCE: u128 = 1000; +// `Assets` pallet index +pub const ASSETS_PALLET_ID: u8 = 50; + +pub type RelayToSystemParaTest = Test; +pub type SystemParaToRelayTest = Test; +pub type SystemParaToParaTest = Test; + +/// Returns a `TestArgs` instance to be used for the Relay Chain across integration tests +pub fn relay_test_args(amount: Balance) -> TestArgs { + TestArgs { + dest: Polkadot::child_location_of(AssetHubPolkadot::para_id()), + beneficiary: AccountId32Junction { + network: None, + id: AssetHubPolkadotReceiver::get().into(), + } + .into(), + amount, + assets: (Here, amount).into(), + asset_id: None, + fee_asset_item: 0, + weight_limit: WeightLimit::Unlimited, + } +} + +/// Returns a `TestArgs` instance to be used for the System Parachain across integration tests +pub fn system_para_test_args( + dest: MultiLocation, + beneficiary_id: AccountId32, + amount: Balance, + assets: MultiAssets, + asset_id: Option, +) -> TestArgs { + TestArgs { + dest, + beneficiary: AccountId32Junction { network: None, id: beneficiary_id.into() }.into(), + amount, + assets, + asset_id, + fee_asset_item: 0, + weight_limit: WeightLimit::Unlimited, + } +} + +#[cfg(test)] +#[cfg(not(feature = "runtime-benchmarks"))] +mod tests; diff --git a/integration-tests/emulated/assets/asset-hub-polkadot/src/tests/hrmp_channels.rs b/integration-tests/emulated/assets/asset-hub-polkadot/src/tests/hrmp_channels.rs new file mode 100644 index 0000000000..731e601aea --- /dev/null +++ b/integration-tests/emulated/assets/asset-hub-polkadot/src/tests/hrmp_channels.rs @@ -0,0 +1,190 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::*; + +const MAX_CAPACITY: u32 = 8; +const MAX_MESSAGE_SIZE: u32 = 8192; + +/// Opening HRMP channels between Parachains should work +#[test] +fn open_hrmp_channel_between_paras_works() { + // Parchain A init values + let para_a_id = PenpalPolkadotA::para_id(); + let para_a_root_origin = ::RuntimeOrigin::root(); + + // Parachain B init values + let para_b_id = PenpalPolkadotB::para_id(); + let para_b_root_origin = ::RuntimeOrigin::root(); + + let fee_amount = POLKADOT_ED * 1000; + let fund_amount = POLKADOT_ED * 1000_000_000; + + // Fund Parachain's Sovereign accounts to be able to reserve the deposit + let para_a_sovereign_account = Polkadot::fund_para_sovereign(fund_amount, para_a_id); + let para_b_sovereign_account = Polkadot::fund_para_sovereign(fund_amount, para_b_id); + + let relay_destination: VersionedMultiLocation = PenpalPolkadotA::parent_location().into(); + + // ---- Init Open channel from Parachain to System Parachain + let mut call = Polkadot::init_open_channel_call(para_b_id, MAX_CAPACITY, MAX_MESSAGE_SIZE); + let origin_kind = OriginKind::Native; + let native_asset: MultiAsset = (Here, fee_amount).into(); + let beneficiary = Polkadot::sovereign_account_id_of_child_para(para_a_id); + + let mut xcm = xcm_transact_paid_execution(call, origin_kind, native_asset.clone(), beneficiary); + + PenpalPolkadotA::execute_with(|| { + assert_ok!(::PolkadotXcm::send( + para_a_root_origin, + bx!(relay_destination.clone()), + bx!(xcm), + )); + + PenpalPolkadotA::assert_xcm_pallet_sent(); + }); + + Polkadot::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + Polkadot::assert_ump_queue_processed( + true, + Some(para_a_id), + Some(Weight::from_parts(1_300_000_000, 207_000)), + ); + + assert_expected_events!( + Polkadot, + vec![ + // Parachain's Sovereign account balance is withdrawn to pay XCM fees + RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + who: *who == para_a_sovereign_account.clone(), + amount: *amount == fee_amount, + }, + // Sender deposit is reserved for Parachain's Sovereign account + RuntimeEvent::Balances(pallet_balances::Event::Reserved { who, .. }) =>{ + who: *who == para_a_sovereign_account, + }, + // Open channel requested from Para A to Para B + RuntimeEvent::Hrmp( + polkadot_runtime_parachains::hrmp::Event::OpenChannelRequested { + sender, recipient, proposed_max_capacity: max_capacity, proposed_max_message_size: max_message_size + } + ) => { + sender: *sender == para_a_id.into(), + recipient: *recipient == para_b_id.into(), + max_capacity: *max_capacity == MAX_CAPACITY, + max_message_size: *max_message_size == MAX_MESSAGE_SIZE, + }, + ] + ); + }); + + // ---- Accept Open channel from Parachain to System Parachain + call = Polkadot::accept_open_channel_call(para_a_id); + let beneficiary = Polkadot::sovereign_account_id_of_child_para(para_b_id); + + xcm = xcm_transact_paid_execution(call, origin_kind, native_asset, beneficiary); + + PenpalPolkadotB::execute_with(|| { + assert_ok!(::PolkadotXcm::send( + para_b_root_origin, + bx!(relay_destination), + bx!(xcm), + )); + + PenpalPolkadotB::assert_xcm_pallet_sent(); + }); + + PenpalPolkadotB::execute_with(|| {}); + + Polkadot::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + Polkadot::assert_ump_queue_processed( + true, + Some(para_b_id), + Some(Weight::from_parts(1_300_000_000, 207_000)), + ); + + assert_expected_events!( + Polkadot, + vec![ + // Parachain's Sovereign account balance is withdrawn to pay XCM fees + RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + who: *who == para_b_sovereign_account.clone(), + amount: *amount == fee_amount, + }, + // Sender deposit is reserved for Parachain's Sovereign account + RuntimeEvent::Balances(pallet_balances::Event::Reserved { who, .. }) =>{ + who: *who == para_b_sovereign_account, + }, + // Open channel accepted for Para A to Para B + RuntimeEvent::Hrmp( + polkadot_runtime_parachains::hrmp::Event::OpenChannelAccepted { + sender, recipient + } + ) => { + sender: *sender == para_a_id.into(), + recipient: *recipient == para_b_id.into(), + }, + ] + ); + }); + + Polkadot::force_process_hrmp_open(para_a_id, para_b_id); +} + +/// Opening HRMP channels between System Parachains and Parachains should work +#[test] +fn force_open_hrmp_channel_for_system_para_works() { + // Relay Chain init values + let relay_root_origin = ::RuntimeOrigin::root(); + + // System Para init values + let system_para_id = AssetHubPolkadot::para_id(); + + // Parachain A init values + let para_a_id = PenpalPolkadotA::para_id(); + + Polkadot::execute_with(|| { + assert_ok!(::Hrmp::force_open_hrmp_channel( + relay_root_origin, + system_para_id, + para_a_id, + MAX_CAPACITY, + MAX_MESSAGE_SIZE + )); + + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + Polkadot, + vec![ + // HRMP channel forced opened + RuntimeEvent::Hrmp( + polkadot_runtime_parachains::hrmp::Event::HrmpChannelForceOpened { + sender, recipient, proposed_max_capacity: max_capacity, proposed_max_message_size: max_message_size + } + ) => { + sender: *sender == system_para_id.into(), + recipient: *recipient == para_a_id.into(), + max_capacity: *max_capacity == MAX_CAPACITY, + max_message_size: *max_message_size == MAX_MESSAGE_SIZE, + }, + ] + ); + }); +} diff --git a/integration-tests/emulated/assets/asset-hub-polkadot/src/tests/mod.rs b/integration-tests/emulated/assets/asset-hub-polkadot/src/tests/mod.rs new file mode 100644 index 0000000000..27c92c543b --- /dev/null +++ b/integration-tests/emulated/assets/asset-hub-polkadot/src/tests/mod.rs @@ -0,0 +1,22 @@ +// 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. + +#![cfg(not(feature = "runtime-benchmarks"))] + +mod hrmp_channels; +mod reserve_transfer; +mod send; +mod set_xcm_versions; +mod teleport; diff --git a/integration-tests/emulated/assets/asset-hub-polkadot/src/tests/reserve_transfer.rs b/integration-tests/emulated/assets/asset-hub-polkadot/src/tests/reserve_transfer.rs new file mode 100644 index 0000000000..96c4358abd --- /dev/null +++ b/integration-tests/emulated/assets/asset-hub-polkadot/src/tests/reserve_transfer.rs @@ -0,0 +1,255 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::*; + +fn relay_origin_assertions(t: RelayToSystemParaTest) { + type RuntimeEvent = ::RuntimeEvent; + + Polkadot::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(630_000_000, 6_000))); + + assert_expected_events!( + Polkadot, + vec![ + // Amount to reserve transfer is transferred to System Parachain's Sovereign account + RuntimeEvent::Balances(pallet_balances::Event::Transfer { from, to, amount }) => { + from: *from == t.sender.account_id, + to: *to == Polkadot::sovereign_account_id_of( + t.args.dest + ), + amount: *amount == t.args.amount, + }, + ] + ); +} + +fn system_para_dest_assertions_incomplete(_t: RelayToSystemParaTest) { + AssetHubPolkadot::assert_dmp_queue_incomplete(None, Some(Error::UntrustedReserveLocation)); +} + +fn system_para_to_relay_assertions(_t: SystemParaToRelayTest) { + AssetHubPolkadot::assert_xcm_pallet_attempted_error(Some(XcmError::Barrier)) +} + +fn system_para_to_para_assertions(t: SystemParaToParaTest) { + type RuntimeEvent = ::RuntimeEvent; + + AssetHubPolkadot::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts( + 680_000_000, + 6000, + ))); + + assert_expected_events!( + AssetHubPolkadot, + vec![ + // Amount to reserve transfer is transferred to Parachain's Sovereing account + RuntimeEvent::Balances( + pallet_balances::Event::Transfer { from, to, amount } + ) => { + from: *from == t.sender.account_id, + to: *to == AssetHubPolkadot::sovereign_account_id_of( + t.args.dest + ), + amount: *amount == t.args.amount, + }, + ] + ); +} + +fn system_para_to_para_assets_assertions(t: SystemParaToParaTest) { + type RuntimeEvent = ::RuntimeEvent; + + AssetHubPolkadot::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts( + 680_000_000, + 6000, + ))); + + assert_expected_events!( + AssetHubPolkadot, + vec![ + // Amount to reserve transfer is transferred to Parachain's Sovereing account + RuntimeEvent::Assets( + pallet_assets::Event::Transferred { asset_id, from, to, amount } + ) => { + asset_id: *asset_id == ASSET_ID, + from: *from == t.sender.account_id, + to: *to == AssetHubPolkadot::sovereign_account_id_of( + t.args.dest + ), + amount: *amount == t.args.amount, + }, + ] + ); +} + +fn relay_limited_reserve_transfer_assets(t: RelayToSystemParaTest) -> DispatchResult { + ::XcmPallet::limited_reserve_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_limited_reserve_transfer_assets(t: SystemParaToRelayTest) -> DispatchResult { + ::PolkadotXcm::limited_reserve_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_limited_reserve_transfer_assets(t: SystemParaToParaTest) -> DispatchResult { + ::PolkadotXcm::limited_reserve_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 Reserve Transfers of native asset from Relay Chain to the System Parachain shouldn't +/// work +#[test] +fn limited_reserve_transfer_native_asset_from_relay_to_system_para_fails() { + // Init values for Relay Chain + let amount_to_send: Balance = POLKADOT_ED * 1000; + let test_args = TestContext { + sender: PolkadotSender::get(), + receiver: AssetHubPolkadotReceiver::get(), + args: relay_test_args(amount_to_send), + }; + + let mut test = RelayToSystemParaTest::new(test_args); + + let sender_balance_before = test.sender.balance; + let receiver_balance_before = test.receiver.balance; + + test.set_assertion::(relay_origin_assertions); + test.set_assertion::(system_para_dest_assertions_incomplete); + test.set_dispatchable::(relay_limited_reserve_transfer_assets); + test.assert(); + + let sender_balance_after = test.sender.balance; + let receiver_balance_after = test.receiver.balance; + + assert!(sender_balance_before - amount_to_send >= sender_balance_after); + assert_eq!(receiver_balance_before, receiver_balance_after); +} + +/// Limited Reserve Transfers of native asset from System Parachain to Relay Chain shoudln't work +#[test] +fn limited_reserve_transfer_native_asset_from_system_para_to_relay_fails() { + // Init values for System Parachain + let destination = AssetHubPolkadot::parent_location(); + let beneficiary_id = PolkadotReceiver::get(); + let amount_to_send: Balance = ASSET_HUB_POLKADOT_ED * 1000; + let assets = (Parent, amount_to_send).into(); + + let test_args = TestContext { + sender: AssetHubPolkadotSender::get(), + receiver: PolkadotReceiver::get(), + args: system_para_test_args(destination, beneficiary_id, amount_to_send, assets, None), + }; + + let mut test = SystemParaToRelayTest::new(test_args); + + let sender_balance_before = test.sender.balance; + let receiver_balance_before = test.receiver.balance; + + test.set_assertion::(system_para_to_relay_assertions); + test.set_dispatchable::(system_para_limited_reserve_transfer_assets); + test.assert(); + + let sender_balance_after = test.sender.balance; + let receiver_balance_after = test.receiver.balance; + + assert_eq!(sender_balance_before, sender_balance_after); + assert_eq!(receiver_balance_before, receiver_balance_after); +} + +/// Limited Reserve Transfers of native asset from System Parachain to Parachain should work +#[test] +fn limited_reserve_transfer_native_asset_from_system_para_to_para() { + // Init values for System Parachain + let destination = AssetHubPolkadot::sibling_location_of(PenpalPolkadotA::para_id()); + let beneficiary_id = PenpalPolkadotAReceiver::get(); + let amount_to_send: Balance = ASSET_HUB_POLKADOT_ED * 1000; + let assets = (Parent, amount_to_send).into(); + + let test_args = TestContext { + sender: AssetHubPolkadotSender::get(), + receiver: PenpalPolkadotAReceiver::get(), + args: system_para_test_args(destination, beneficiary_id, amount_to_send, assets, None), + }; + + let mut test = SystemParaToParaTest::new(test_args); + + let sender_balance_before = test.sender.balance; + + test.set_assertion::(system_para_to_para_assertions); + // TODO: Add assertion for Penpal runtime. Right now message is failing with + // `UntrustedReserveLocation` + test.set_dispatchable::(system_para_to_para_limited_reserve_transfer_assets); + test.assert(); + + let sender_balance_after = test.sender.balance; + + assert!(sender_balance_before - amount_to_send >= sender_balance_after); + // TODO: Check receiver balance when Penpal runtime is improved to propery handle reserve + // transfers +} + +/// Limited Reserve Transfers of a local asset from System Parachain to Parachain should work +#[test] +fn limited_reserve_transfer_asset_from_system_para_to_para() { + // Force create asset from Relay Chain and mint assets for System Parachain's sender account + AssetHubPolkadot::force_create_and_mint_asset( + ASSET_ID, + ASSET_MIN_BALANCE, + true, + AssetHubPolkadotSender::get(), + ASSET_MIN_BALANCE * 1000000, + ); + + // Init values for System Parachain + let destination = AssetHubPolkadot::sibling_location_of(PenpalPolkadotA::para_id()); + let beneficiary_id = PenpalPolkadotAReceiver::get(); + let amount_to_send = ASSET_MIN_BALANCE * 1000; + let assets = + (X2(PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())), amount_to_send) + .into(); + + let system_para_test_args = TestContext { + sender: AssetHubPolkadotSender::get(), + receiver: PenpalPolkadotAReceiver::get(), + args: system_para_test_args(destination, beneficiary_id, amount_to_send, assets, None), + }; + + let mut system_para_test = SystemParaToParaTest::new(system_para_test_args); + + system_para_test.set_assertion::(system_para_to_para_assets_assertions); + // TODO: Add assertions when Penpal is able to manage assets + system_para_test + .set_dispatchable::(system_para_to_para_limited_reserve_transfer_assets); + system_para_test.assert(); +} diff --git a/integration-tests/emulated/assets/asset-hub-polkadot/src/tests/send.rs b/integration-tests/emulated/assets/asset-hub-polkadot/src/tests/send.rs new file mode 100644 index 0000000000..86ee5838b4 --- /dev/null +++ b/integration-tests/emulated/assets/asset-hub-polkadot/src/tests/send.rs @@ -0,0 +1,198 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::*; + +/// Relay Chain should be able to execute `Transact` instructions in System Parachain +/// when `OriginKind::Superuser` and signer is `sudo` +#[test] +fn send_transact_sudo_from_relay_to_system_para_works() { + // Init tests variables + let root_origin = ::RuntimeOrigin::root(); + let system_para_destination = Polkadot::child_location_of(AssetHubPolkadot::para_id()).into(); + let asset_owner: AccountId = AssetHubPolkadotSender::get().into(); + let xcm = AssetHubPolkadot::force_create_asset_xcm( + OriginKind::Superuser, + ASSET_ID, + asset_owner.clone(), + true, + 1000, + ); + // Send XCM message from Relay Chain + Polkadot::execute_with(|| { + assert_ok!(::XcmPallet::send( + root_origin, + bx!(system_para_destination), + bx!(xcm), + )); + + Polkadot::assert_xcm_pallet_sent(); + }); + + // Receive XCM message in Assets Parachain + AssetHubPolkadot::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + AssetHubPolkadot::assert_dmp_queue_complete(Some(Weight::from_parts( + 1_020_000_000, + 200_000, + ))); + + assert_expected_events!( + AssetHubPolkadot, + vec![ + RuntimeEvent::Assets(pallet_assets::Event::ForceCreated { asset_id, owner }) => { + asset_id: *asset_id == ASSET_ID, + owner: *owner == asset_owner, + }, + ] + ); + + assert!(::Assets::asset_exists(ASSET_ID)); + }); +} + +/// Relay Chain shouldn't be able to execute `Transact` instructions in System Parachain +/// when `OriginKind::Native` +#[test] +fn send_transact_native_from_relay_to_system_para_fails() { + // Init tests variables + let signed_origin = ::RuntimeOrigin::signed(PolkadotSender::get().into()); + let system_para_destination = Polkadot::child_location_of(AssetHubPolkadot::para_id()).into(); + let asset_owner = AssetHubPolkadotSender::get().into(); + let xcm = AssetHubPolkadot::force_create_asset_xcm( + OriginKind::Native, + ASSET_ID, + asset_owner, + true, + 1000, + ); + + // Send XCM message from Relay Chain + Polkadot::execute_with(|| { + assert_err!( + ::XcmPallet::send( + signed_origin, + bx!(system_para_destination), + bx!(xcm) + ), + DispatchError::BadOrigin + ); + }); +} + +/// System Parachain shouldn't be able to execute `Transact` instructions in Relay Chain +/// when `OriginKind::Native` +#[test] +fn send_transact_native_from_system_para_to_relay_fails() { + // Init tests variables + let signed_origin = + ::RuntimeOrigin::signed(AssetHubPolkadotSender::get().into()); + let relay_destination = AssetHubPolkadot::parent_location().into(); + let call = ::RuntimeCall::System(frame_system::Call::< + ::Runtime, + >::remark_with_event { + remark: vec![0, 1, 2, 3], + }) + .encode() + .into(); + let origin_kind = OriginKind::Native; + + let xcm = xcm_transact_unpaid_execution(call, origin_kind); + + // Send XCM message from Relay Chain + AssetHubPolkadot::execute_with(|| { + assert_err!( + ::PolkadotXcm::send( + signed_origin, + bx!(relay_destination), + bx!(xcm) + ), + DispatchError::BadOrigin + ); + }); +} + +/// Parachain should be able to send XCM paying its fee with sufficient asset +/// in the System Parachain +#[test] +fn send_xcm_from_para_to_system_para_paying_fee_with_assets_works() { + let para_sovereign_account = AssetHubPolkadot::sovereign_account_id_of( + AssetHubPolkadot::sibling_location_of(PenpalPolkadotA::para_id()), + ); + + // Force create and mint assets for Parachain's sovereign account + AssetHubPolkadot::force_create_and_mint_asset( + ASSET_ID, + ASSET_MIN_BALANCE, + true, + para_sovereign_account.clone(), + ASSET_MIN_BALANCE * 1000000000, + ); + + // We just need a call that can pass the `SafeCallFilter` + // Call values are not relevant + let call = AssetHubPolkadot::force_create_asset_call( + ASSET_ID, + para_sovereign_account.clone(), + true, + ASSET_MIN_BALANCE, + ); + + let origin_kind = OriginKind::SovereignAccount; + let fee_amount = ASSET_MIN_BALANCE * 1000000; + let native_asset = + (X2(PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())), fee_amount).into(); + + let root_origin = ::RuntimeOrigin::root(); + let system_para_destination = + PenpalPolkadotA::sibling_location_of(AssetHubPolkadot::para_id()).into(); + let xcm = xcm_transact_paid_execution( + call, + origin_kind, + native_asset, + para_sovereign_account.clone(), + ); + + PenpalPolkadotA::execute_with(|| { + assert_ok!(::PolkadotXcm::send( + root_origin, + bx!(system_para_destination), + bx!(xcm), + )); + + PenpalPolkadotA::assert_xcm_pallet_sent(); + }); + + AssetHubPolkadot::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + AssetHubPolkadot::assert_xcmp_queue_success(None); + + assert_expected_events!( + AssetHubPolkadot, + vec![ + RuntimeEvent::Assets(pallet_assets::Event::Burned { asset_id, owner, balance }) => { + asset_id: *asset_id == ASSET_ID, + owner: *owner == para_sovereign_account, + balance: *balance == fee_amount, + }, + RuntimeEvent::Assets(pallet_assets::Event::Issued { asset_id, .. }) => { + asset_id: *asset_id == ASSET_ID, + }, + ] + ); + }); +} diff --git a/integration-tests/emulated/assets/asset-hub-polkadot/src/tests/set_xcm_versions.rs b/integration-tests/emulated/assets/asset-hub-polkadot/src/tests/set_xcm_versions.rs new file mode 100644 index 0000000000..8ec3ab4426 --- /dev/null +++ b/integration-tests/emulated/assets/asset-hub-polkadot/src/tests/set_xcm_versions.rs @@ -0,0 +1,96 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::*; + +#[test] +fn relay_sets_system_para_xcm_supported_version() { + // Init tests variables + let sudo_origin = ::RuntimeOrigin::root(); + let system_para_destination: MultiLocation = + Polkadot::child_location_of(AssetHubPolkadot::para_id()); + + // Relay Chain sets supported version for Asset Parachain + Polkadot::execute_with(|| { + assert_ok!(::XcmPallet::force_xcm_version( + sudo_origin, + bx!(system_para_destination), + XCM_V3 + )); + + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + Polkadot, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::SupportedVersionChanged { + location, + version: XCM_V3 + }) => { location: *location == system_para_destination, }, + ] + ); + }); +} + +#[test] +fn system_para_sets_relay_xcm_supported_version() { + // Init test variables + let sudo_origin = ::RuntimeOrigin::root(); + let parent_location = AssetHubPolkadot::parent_location(); + let system_para_destination: VersionedMultiLocation = + Polkadot::child_location_of(AssetHubPolkadot::para_id()).into(); + let call = ::RuntimeCall::PolkadotXcm(pallet_xcm::Call::< + ::Runtime, + >::force_xcm_version { + location: bx!(parent_location), + version: XCM_V3, + }) + .encode() + .into(); + let origin_kind = OriginKind::Superuser; + + let xcm = xcm_transact_unpaid_execution(call, origin_kind); + + // System Parachain sets supported version for Relay Chain throught it + Polkadot::execute_with(|| { + assert_ok!(::XcmPallet::send( + sudo_origin, + bx!(system_para_destination), + bx!(xcm), + )); + + Polkadot::assert_xcm_pallet_sent(); + }); + + // System Parachain receive the XCM message + AssetHubPolkadot::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + AssetHubPolkadot::assert_dmp_queue_complete(Some(Weight::from_parts( + 1_020_000_000, + 200_000, + ))); + + assert_expected_events!( + AssetHubPolkadot, + vec![ + RuntimeEvent::PolkadotXcm(pallet_xcm::Event::SupportedVersionChanged { + location, + version: XCM_V3 + }) => { location: *location == parent_location, }, + ] + ); + }); +} diff --git a/integration-tests/emulated/assets/asset-hub-polkadot/src/tests/teleport.rs b/integration-tests/emulated/assets/asset-hub-polkadot/src/tests/teleport.rs new file mode 100644 index 0000000000..8d2aec13bd --- /dev/null +++ b/integration-tests/emulated/assets/asset-hub-polkadot/src/tests/teleport.rs @@ -0,0 +1,236 @@ +// 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. + +#![allow(dead_code)] // + +use crate::*; + +fn relay_origin_assertions(t: RelayToSystemParaTest) { + type RuntimeEvent = ::RuntimeEvent; + + Polkadot::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(635_000_000, 7_000))); + + assert_expected_events!( + Polkadot, + vec![ + // Amount to teleport is withdrawn from Sender + RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + who: *who == t.sender.account_id, + amount: *amount == t.args.amount, + }, + // Amount to teleport is deposited in Relay's `CheckAccount` + RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, amount }) => { + who: *who == ::XcmPallet::check_account(), + amount: *amount == t.args.amount, + }, + ] + ); +} + +fn relay_dest_assertions(t: SystemParaToRelayTest) { + type RuntimeEvent = ::RuntimeEvent; + + Polkadot::assert_ump_queue_processed( + true, + Some(AssetHubPolkadot::para_id()), + Some(Weight::from_parts(370_000_000, 7_000)), + ); + + assert_expected_events!( + Polkadot, + vec![ + // Amount is witdrawn from Relay Chain's `CheckAccount` + RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + who: *who == ::XcmPallet::check_account(), + amount: *amount == t.args.amount, + }, + // Amount minus fees are deposited in Receiver's account + RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + who: *who == t.receiver.account_id, + }, + ] + ); +} + +fn relay_dest_assertions_fail(_t: SystemParaToRelayTest) { + Polkadot::assert_ump_queue_processed( + false, + Some(AssetHubPolkadot::para_id()), + Some(Weight::from_parts(235_000_000, 3_500)), + ); +} + +fn para_origin_assertions(t: SystemParaToRelayTest) { + type RuntimeEvent = ::RuntimeEvent; + + AssetHubPolkadot::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts( + 635_000_000, + 7_000, + ))); + + AssetHubPolkadot::assert_parachain_system_ump_sent(); + + assert_expected_events!( + AssetHubPolkadot, + vec![ + // Amount is withdrawn from Sender's account + RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + who: *who == t.sender.account_id, + amount: *amount == t.args.amount, + }, + ] + ); +} + +fn para_dest_assertions(t: RelayToSystemParaTest) { + type RuntimeEvent = ::RuntimeEvent; + + AssetHubPolkadot::assert_dmp_queue_complete(None); + + assert_expected_events!( + AssetHubPolkadot, + vec![ + // Amount minus fees are deposited in Receiver's account + RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + who: *who == t.receiver.account_id, + }, + ] + ); +} + +fn relay_limited_teleport_assets(t: RelayToSystemParaTest) -> DispatchResult { + ::XcmPallet::limited_teleport_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_limited_teleport_assets(t: SystemParaToRelayTest) -> DispatchResult { + ::PolkadotXcm::limited_teleport_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() { + // Init values for Relay Chain + let amount_to_send: Balance = POLKADOT_ED * 1000; + let test_args = TestContext { + sender: PolkadotSender::get(), + receiver: AssetHubPolkadotReceiver::get(), + args: relay_test_args(amount_to_send), + }; + + let mut test = RelayToSystemParaTest::new(test_args); + + let sender_balance_before = test.sender.balance; + let receiver_balance_before = test.receiver.balance; + + test.set_assertion::(relay_origin_assertions); + test.set_assertion::(para_dest_assertions); + test.set_dispatchable::(relay_limited_teleport_assets); + test.assert(); + + let sender_balance_after = test.sender.balance; + let receiver_balance_after = test.receiver.balance; + + // Sender's balance is reduced + assert!(sender_balance_before - amount_to_send >= sender_balance_after); + // Receiver's balance is increased + assert!(receiver_balance_after > receiver_balance_before); +} + +/// Limited Teleport of native asset from System Parachain to Relay Chain +/// should work when there is enough balance in Relay Chain's `CheckAccount` +#[test] +fn limited_teleport_native_assets_back_from_system_para_to_relay_works() { + // Dependency - Relay Chain's `CheckAccount` should have enough balance + limited_teleport_native_assets_from_relay_to_system_para_works(); + + // Init values for Relay Chain + let amount_to_send: Balance = ASSET_HUB_POLKADOT_ED * 1000; + let destination = AssetHubPolkadot::parent_location(); + let beneficiary_id = PolkadotReceiver::get(); + let assets = (Parent, amount_to_send).into(); + + let test_args = TestContext { + sender: AssetHubPolkadotSender::get(), + receiver: PolkadotReceiver::get(), + args: system_para_test_args(destination, beneficiary_id, amount_to_send, assets, None), + }; + + let mut test = SystemParaToRelayTest::new(test_args); + + let sender_balance_before = test.sender.balance; + let receiver_balance_before = test.receiver.balance; + + test.set_assertion::(para_origin_assertions); + test.set_assertion::(relay_dest_assertions); + test.set_dispatchable::(system_para_limited_teleport_assets); + test.assert(); + + let sender_balance_after = test.sender.balance; + let receiver_balance_after = test.receiver.balance; + + // Sender's balance is reduced + assert!(sender_balance_before - amount_to_send >= sender_balance_after); + // Receiver's balance is increased + assert!(receiver_balance_after > receiver_balance_before); +} + +/// Limited Teleport of native asset from System Parachain to Relay Chain +/// should't work when there is not enough balance in Relay Chain's `CheckAccount` +#[test] +fn limited_teleport_native_assets_from_system_para_to_relay_fails() { + // Init values for Relay Chain + let amount_to_send: Balance = ASSET_HUB_POLKADOT_ED * 1000; + let destination = AssetHubPolkadot::parent_location().into(); + let beneficiary_id = PolkadotReceiver::get().into(); + let assets = (Parent, amount_to_send).into(); + + let test_args = TestContext { + sender: AssetHubPolkadotSender::get(), + receiver: PolkadotReceiver::get(), + args: system_para_test_args(destination, beneficiary_id, amount_to_send, assets, None), + }; + + let mut test = SystemParaToRelayTest::new(test_args); + + let sender_balance_before = test.sender.balance; + let receiver_balance_before = test.receiver.balance; + + test.set_assertion::(para_origin_assertions); + test.set_assertion::(relay_dest_assertions_fail); + test.set_dispatchable::(system_para_limited_teleport_assets); + test.assert(); + + let sender_balance_after = test.sender.balance; + let receiver_balance_after = test.receiver.balance; + + // Sender's balance is reduced + assert!(sender_balance_before - amount_to_send >= sender_balance_after); + // Receiver's balance does not change + assert_eq!(receiver_balance_after, receiver_balance_before); +} diff --git a/integration-tests/emulated/common/Cargo.toml b/integration-tests/emulated/common/Cargo.toml new file mode 100644 index 0000000000..bd52994ceb --- /dev/null +++ b/integration-tests/emulated/common/Cargo.toml @@ -0,0 +1,65 @@ +[package] +name = "integration-tests-common" +version = "1.0.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +description = "Common resources for integration testing with xcm-emulator" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.4.0" } +paste = "1.0.14" +serde_json = "1.0.108" + +# Substrate +grandpa-primitives = { package = "sp-consensus-grandpa", version = "10.0.0" } +sp-authority-discovery = { version = "23.0.0" } +sp-runtime = { version = "28.0.0" } +frame-support = { version = "25.0.0" } +sp-core = { version = "25.0.0" } +sp-consensus-babe = { version = "0.29.0" } +pallet-assets = { version = "26.0.0" } +pallet-balances = { version = "25.0.0" } +pallet-message-queue = { version = "28.0.0" } +pallet-im-online = { version = "24.0.0" } +pallet-staking = { version = "25.0.0" } +beefy-primitives = { package = "sp-consensus-beefy", version = "10.0.0" } + +# Polkadot +polkadot-primitives = { version = "4.0.0" } +polkadot-runtime-parachains = { version = "4.0.0" } +xcm = { package = "staging-xcm", version = "4.0.0" } +pallet-xcm = { version = "4.0.0" } +polkadot-core-primitives = { version = "4.0.0" } +polkadot-parachain-primitives = { version = "3.0.0" } + +# Cumulus +parachains-common = { version = "4.0.0" } +cumulus-primitives-core = { version = "0.4.0" } +xcm-emulator = { version = "0.2.0" } +cumulus-pallet-xcmp-queue = { version = "0.4.0" } +cumulus-pallet-parachain-system = { features = ["parameterized-consensus-hook",] , version = "0.4.0" } +asset-test-utils = { version = "4.0.0" } +cumulus-pallet-dmp-queue = { version = "0.4.0" } +penpal-runtime = { version = "0.11.0" } + +# Local runtimes +kusama-runtime = { package = "staging-kusama-runtime", path = "../../../relay/kusama" } +kusama-runtime-constants = { path = "../../../relay/kusama/constants" } +polkadot-runtime = { path = "../../../relay/polkadot" } +polkadot-runtime-constants = { path = "../../../relay/polkadot/constants" } +asset-hub-polkadot-runtime = { path = "../../../system-parachains/asset-hubs/asset-hub-polkadot" } +asset-hub-kusama-runtime = { path = "../../../system-parachains/asset-hubs/asset-hub-kusama" } +collectives-polkadot-runtime = { path = "../../../system-parachains/collectives/collectives-polkadot" } +bridge-hub-polkadot-runtime = { path = "../../../system-parachains/bridge-hubs/bridge-hub-polkadot" } +bridge-hub-kusama-runtime = { path = "../../../system-parachains/bridge-hubs/bridge-hub-kusama" } + +# Bridges +bp-messages = { version = "0.4.0" } +pallet-bridge-messages = { version = "0.4.0" } +bridge-runtime-common = { version = "0.4.0" } + +[features] +runtime-benchmarks = [ + "penpal-runtime/runtime-benchmarks" +] diff --git a/integration-tests/emulated/common/src/constants.rs b/integration-tests/emulated/common/src/constants.rs new file mode 100644 index 0000000000..e0cfb9f658 --- /dev/null +++ b/integration-tests/emulated/common/src/constants.rs @@ -0,0 +1,802 @@ +// 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. + +// Substrate +use beefy_primitives::ecdsa_crypto::AuthorityId as BeefyId; +use grandpa_primitives::AuthorityId as GrandpaId; +use pallet_im_online::sr25519::AuthorityId as ImOnlineId; +use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; +use sp_consensus_babe::AuthorityId as BabeId; +use sp_core::{sr25519, storage::Storage, Pair, Public}; +use sp_runtime::{ + traits::{IdentifyAccount, Verify}, + BuildStorage, MultiSignature, Perbill, +}; + +// Cumulus +use parachains_common::{AccountId, AssetHubPolkadotAuraId, AuraId, Balance, BlockNumber}; +use polkadot_parachain_primitives::primitives::{HeadData, ValidationCode}; +use polkadot_primitives::{AssignmentId, ValidatorId}; +use polkadot_runtime_parachains::{ + configuration::HostConfiguration, + paras::{ParaGenesisArgs, ParaKind}, +}; +use xcm; + +pub const XCM_V2: u32 = 3; +pub const XCM_V3: u32 = 2; +pub const REF_TIME_THRESHOLD: u64 = 33; +pub const PROOF_SIZE_THRESHOLD: u64 = 33; + +type AccountPublic = ::Signer; + +/// Helper function to generate a crypto pair from seed +fn get_from_seed(seed: &str) -> ::Public { + TPublic::Pair::from_string(&format!("//{}", seed), None) + .expect("static values are valid; qed") + .public() +} + +/// Helper function to generate an account ID from seed. +fn get_account_id_from_seed(seed: &str) -> AccountId +where + AccountPublic: From<::Public>, +{ + AccountPublic::from(get_from_seed::(seed)).into_account() +} + +pub mod accounts { + use super::*; + pub const ALICE: &str = "Alice"; + pub const BOB: &str = "Bob"; + pub const CHARLIE: &str = "Charlie"; + pub const DAVE: &str = "Dave"; + pub const EVE: &str = "Eve"; + pub const FERDIE: &str = "Ferdei"; + pub const ALICE_STASH: &str = "Alice//stash"; + pub const BOB_STASH: &str = "Bob//stash"; + pub const CHARLIE_STASH: &str = "Charlie//stash"; + pub const DAVE_STASH: &str = "Dave//stash"; + pub const EVE_STASH: &str = "Eve//stash"; + pub const FERDIE_STASH: &str = "Ferdie//stash"; + pub const FERDIE_BEEFY: &str = "Ferdie//stash"; + + pub fn init_balances() -> Vec { + vec![ + get_account_id_from_seed::(ALICE), + get_account_id_from_seed::(BOB), + get_account_id_from_seed::(CHARLIE), + get_account_id_from_seed::(DAVE), + get_account_id_from_seed::(EVE), + get_account_id_from_seed::(FERDIE), + get_account_id_from_seed::(ALICE_STASH), + get_account_id_from_seed::(BOB_STASH), + get_account_id_from_seed::(CHARLIE_STASH), + get_account_id_from_seed::(DAVE_STASH), + get_account_id_from_seed::(EVE_STASH), + get_account_id_from_seed::(FERDIE_STASH), + ] + } +} + +pub mod collators { + use super::*; + + pub fn invulnerables_asset_hub_polkadot() -> Vec<(AccountId, AssetHubPolkadotAuraId)> { + vec![ + ( + get_account_id_from_seed::("Alice"), + get_from_seed::("Alice"), + ), + ( + get_account_id_from_seed::("Bob"), + get_from_seed::("Bob"), + ), + ] + } + + pub fn invulnerables() -> Vec<(AccountId, AuraId)> { + vec![ + ( + get_account_id_from_seed::("Alice"), + get_from_seed::("Alice"), + ), + (get_account_id_from_seed::("Bob"), get_from_seed::("Bob")), + ] + } +} + +pub mod validators { + use super::*; + + pub fn initial_authorities() -> Vec<( + AccountId, + AccountId, + BabeId, + GrandpaId, + ImOnlineId, + ValidatorId, + AssignmentId, + AuthorityDiscoveryId, + BeefyId, + )> { + let seed = "Alice"; + vec![( + get_account_id_from_seed::(&format!("{}//stash", seed)), + get_account_id_from_seed::(seed), + get_from_seed::(seed), + get_from_seed::(seed), + get_from_seed::(seed), + get_from_seed::(seed), + get_from_seed::(seed), + get_from_seed::(seed), + get_from_seed::(seed), + )] + } +} + +/// The default XCM version to set in genesis config. +const SAFE_XCM_VERSION: u32 = xcm::prelude::XCM_VERSION; + +// Polkadot +pub mod polkadot { + use super::*; + pub const ED: Balance = polkadot_runtime_constants::currency::EXISTENTIAL_DEPOSIT; + const STASH: u128 = 100 * polkadot_runtime_constants::currency::UNITS; + + pub fn get_host_config() -> HostConfiguration { + HostConfiguration { + max_upward_queue_count: 10, + max_upward_queue_size: 51200, + max_upward_message_size: 51200, + max_upward_message_num_per_candidate: 10, + max_downward_message_size: 51200, + hrmp_sender_deposit: 100_000_000_000, + hrmp_recipient_deposit: 100_000_000_000, + hrmp_channel_max_capacity: 1000, + hrmp_channel_max_message_size: 102400, + hrmp_channel_max_total_size: 102400, + hrmp_max_parachain_outbound_channels: 30, + hrmp_max_parachain_inbound_channels: 30, + ..Default::default() + } + } + + fn session_keys( + babe: BabeId, + grandpa: GrandpaId, + im_online: ImOnlineId, + para_validator: ValidatorId, + para_assignment: AssignmentId, + authority_discovery: AuthorityDiscoveryId, + beefy: BeefyId, + ) -> polkadot_runtime::SessionKeys { + polkadot_runtime::SessionKeys { + babe, + grandpa, + im_online, + para_validator, + para_assignment, + authority_discovery, + beefy, + } + } + + pub fn genesis() -> Storage { + let genesis_config = polkadot_runtime::RuntimeGenesisConfig { + system: polkadot_runtime::SystemConfig { + code: polkadot_runtime::WASM_BINARY.unwrap().to_vec(), + ..Default::default() + }, + balances: polkadot_runtime::BalancesConfig { + balances: accounts::init_balances() + .iter() + .cloned() + .map(|k| (k, ED * 4096)) + .collect(), + }, + session: polkadot_runtime::SessionConfig { + keys: validators::initial_authorities() + .iter() + .map(|x| { + ( + x.0.clone(), + x.0.clone(), + polkadot::session_keys( + x.2.clone(), + x.3.clone(), + x.4.clone(), + x.5.clone(), + x.6.clone(), + x.7.clone(), + x.8.clone(), + ), + ) + }) + .collect::>(), + }, + staking: polkadot_runtime::StakingConfig { + validator_count: validators::initial_authorities().len() as u32, + minimum_validator_count: 1, + stakers: validators::initial_authorities() + .iter() + .map(|x| { + (x.0.clone(), x.1.clone(), STASH, polkadot_runtime::StakerStatus::Validator) + }) + .collect(), + invulnerables: validators::initial_authorities() + .iter() + .map(|x| x.0.clone()) + .collect(), + force_era: pallet_staking::Forcing::ForceNone, + slash_reward_fraction: Perbill::from_percent(10), + ..Default::default() + }, + babe: polkadot_runtime::BabeConfig { + authorities: Default::default(), + epoch_config: Some(polkadot_runtime::BABE_GENESIS_EPOCH_CONFIG), + ..Default::default() + }, + configuration: polkadot_runtime::ConfigurationConfig { config: get_host_config() }, + paras: polkadot_runtime::ParasConfig { + paras: vec![ + ( + asset_hub_polkadot::PARA_ID.into(), + ParaGenesisArgs { + genesis_head: HeadData::default(), + validation_code: ValidationCode( + asset_hub_polkadot_runtime::WASM_BINARY.unwrap().to_vec(), + ), + para_kind: ParaKind::Parachain, + }, + ), + ( + bridge_hub_polkadot::PARA_ID.into(), + ParaGenesisArgs { + genesis_head: HeadData::default(), + validation_code: ValidationCode( + bridge_hub_polkadot_runtime::WASM_BINARY.unwrap().to_vec(), + ), + para_kind: ParaKind::Parachain, + }, + ), + ( + penpal::PARA_ID_A.into(), + ParaGenesisArgs { + genesis_head: HeadData::default(), + validation_code: ValidationCode( + penpal_runtime::WASM_BINARY.unwrap().to_vec(), + ), + para_kind: ParaKind::Parachain, + }, + ), + ( + penpal::PARA_ID_B.into(), + ParaGenesisArgs { + genesis_head: HeadData::default(), + validation_code: ValidationCode( + penpal_runtime::WASM_BINARY.unwrap().to_vec(), + ), + para_kind: ParaKind::Parachain, + }, + ), + ], + ..Default::default() + }, + ..Default::default() + }; + + genesis_config.build_storage().unwrap() + } +} + +// Kusama +pub mod kusama { + use super::*; + pub const ED: Balance = kusama_runtime_constants::currency::EXISTENTIAL_DEPOSIT; + use kusama_runtime_constants::currency::UNITS as KSM; + const ENDOWMENT: u128 = 1_000_000 * KSM; + const STASH: u128 = 100 * KSM; + + pub fn get_host_config() -> HostConfiguration { + HostConfiguration { + max_upward_queue_count: 10, + max_upward_queue_size: 51200, + max_upward_message_size: 51200, + max_upward_message_num_per_candidate: 10, + max_downward_message_size: 51200, + hrmp_sender_deposit: 5_000_000_000_000, + hrmp_recipient_deposit: 5_000_000_000_000, + hrmp_channel_max_capacity: 1000, + hrmp_channel_max_message_size: 102400, + hrmp_channel_max_total_size: 102400, + hrmp_max_parachain_outbound_channels: 30, + hrmp_max_parachain_inbound_channels: 30, + ..Default::default() + } + } + + fn session_keys( + babe: BabeId, + grandpa: GrandpaId, + im_online: ImOnlineId, + para_validator: ValidatorId, + para_assignment: AssignmentId, + authority_discovery: AuthorityDiscoveryId, + beefy: BeefyId, + ) -> kusama_runtime::SessionKeys { + kusama_runtime::SessionKeys { + babe, + grandpa, + im_online, + para_validator, + para_assignment, + authority_discovery, + beefy, + } + } + + pub fn genesis() -> Storage { + let genesis_config = kusama_runtime::RuntimeGenesisConfig { + system: kusama_runtime::SystemConfig { + code: kusama_runtime::WASM_BINARY.unwrap().to_vec(), + ..Default::default() + }, + balances: kusama_runtime::BalancesConfig { + balances: accounts::init_balances() + .iter() + .map(|k: &AccountId| (k.clone(), ENDOWMENT)) + .collect(), + }, + session: kusama_runtime::SessionConfig { + keys: validators::initial_authorities() + .iter() + .map(|x| { + ( + x.0.clone(), + x.0.clone(), + kusama::session_keys( + x.2.clone(), + x.3.clone(), + x.4.clone(), + x.5.clone(), + x.6.clone(), + x.7.clone(), + x.8.clone(), + ), + ) + }) + .collect::>(), + }, + staking: kusama_runtime::StakingConfig { + validator_count: validators::initial_authorities().len() as u32, + minimum_validator_count: 1, + stakers: validators::initial_authorities() + .iter() + .map(|x| { + (x.0.clone(), x.1.clone(), STASH, kusama_runtime::StakerStatus::Validator) + }) + .collect(), + invulnerables: validators::initial_authorities() + .iter() + .map(|x| x.0.clone()) + .collect(), + force_era: pallet_staking::Forcing::NotForcing, + slash_reward_fraction: Perbill::from_percent(10), + ..Default::default() + }, + babe: kusama_runtime::BabeConfig { + authorities: Default::default(), + epoch_config: Some(kusama_runtime::BABE_GENESIS_EPOCH_CONFIG), + ..Default::default() + }, + configuration: kusama_runtime::ConfigurationConfig { config: get_host_config() }, + paras: kusama_runtime::ParasConfig { + paras: vec![ + ( + asset_hub_kusama::PARA_ID.into(), + ParaGenesisArgs { + genesis_head: HeadData::default(), + validation_code: ValidationCode( + asset_hub_kusama_runtime::WASM_BINARY.unwrap().to_vec(), + ), + para_kind: ParaKind::Parachain, + }, + ), + ( + bridge_hub_kusama::PARA_ID.into(), + ParaGenesisArgs { + genesis_head: HeadData::default(), + validation_code: ValidationCode( + bridge_hub_kusama_runtime::WASM_BINARY.unwrap().to_vec(), + ), + para_kind: ParaKind::Parachain, + }, + ), + ( + penpal::PARA_ID_A.into(), + ParaGenesisArgs { + genesis_head: HeadData::default(), + validation_code: ValidationCode( + penpal_runtime::WASM_BINARY.unwrap().to_vec(), + ), + para_kind: ParaKind::Parachain, + }, + ), + ( + penpal::PARA_ID_B.into(), + ParaGenesisArgs { + genesis_head: HeadData::default(), + validation_code: ValidationCode( + penpal_runtime::WASM_BINARY.unwrap().to_vec(), + ), + para_kind: ParaKind::Parachain, + }, + ), + ], + ..Default::default() + }, + ..Default::default() + }; + + genesis_config.build_storage().unwrap() + } +} + +// Asset Hub Polkadot +pub mod asset_hub_polkadot { + use super::*; + pub const PARA_ID: u32 = 1000; + pub const ED: Balance = parachains_common::polkadot::currency::EXISTENTIAL_DEPOSIT; + + pub fn genesis() -> Storage { + let genesis_config = asset_hub_polkadot_runtime::RuntimeGenesisConfig { + system: asset_hub_polkadot_runtime::SystemConfig { + code: asset_hub_polkadot_runtime::WASM_BINARY + .expect("WASM binary was not build, please build it!") + .to_vec(), + ..Default::default() + }, + balances: asset_hub_polkadot_runtime::BalancesConfig { + balances: accounts::init_balances() + .iter() + .cloned() + .map(|k| (k, ED * 4096)) + .collect(), + }, + parachain_info: asset_hub_polkadot_runtime::ParachainInfoConfig { + parachain_id: PARA_ID.into(), + ..Default::default() + }, + collator_selection: asset_hub_polkadot_runtime::CollatorSelectionConfig { + invulnerables: collators::invulnerables_asset_hub_polkadot() + .iter() + .cloned() + .map(|(acc, _)| acc) + .collect(), + candidacy_bond: ED * 16, + ..Default::default() + }, + session: asset_hub_polkadot_runtime::SessionConfig { + keys: collators::invulnerables_asset_hub_polkadot() + .into_iter() + .map(|(acc, aura)| { + ( + acc.clone(), // account id + acc, // validator id + asset_hub_polkadot_runtime::SessionKeys { aura }, // session keys + ) + }) + .collect(), + }, + polkadot_xcm: asset_hub_polkadot_runtime::PolkadotXcmConfig { + safe_xcm_version: Some(SAFE_XCM_VERSION), + ..Default::default() + }, + ..Default::default() + }; + + genesis_config.build_storage().unwrap() + } +} + +// Asset Hub Kusama +pub mod asset_hub_kusama { + use super::*; + pub const PARA_ID: u32 = 1000; + pub const ED: Balance = parachains_common::kusama::currency::EXISTENTIAL_DEPOSIT; + + pub fn genesis() -> Storage { + let genesis_config = asset_hub_kusama_runtime::RuntimeGenesisConfig { + system: asset_hub_kusama_runtime::SystemConfig { + code: asset_hub_kusama_runtime::WASM_BINARY + .expect("WASM binary was not build, please build it!") + .to_vec(), + ..Default::default() + }, + balances: asset_hub_kusama_runtime::BalancesConfig { + balances: accounts::init_balances() + .iter() + .cloned() + .map(|k| (k, ED * 4096)) + .collect(), + }, + parachain_info: asset_hub_kusama_runtime::ParachainInfoConfig { + parachain_id: PARA_ID.into(), + ..Default::default() + }, + collator_selection: asset_hub_kusama_runtime::CollatorSelectionConfig { + invulnerables: collators::invulnerables() + .iter() + .cloned() + .map(|(acc, _)| acc) + .collect(), + candidacy_bond: ED * 16, + ..Default::default() + }, + session: asset_hub_kusama_runtime::SessionConfig { + keys: collators::invulnerables() + .into_iter() + .map(|(acc, aura)| { + ( + acc.clone(), // account id + acc, // validator id + asset_hub_kusama_runtime::SessionKeys { aura }, // session keys + ) + }) + .collect(), + }, + polkadot_xcm: asset_hub_kusama_runtime::PolkadotXcmConfig { + safe_xcm_version: Some(SAFE_XCM_VERSION), + ..Default::default() + }, + ..Default::default() + }; + + genesis_config.build_storage().unwrap() + } +} + +// Bridge Hub Polkadot +pub mod bridge_hub_polkadot { + use super::*; + pub const PARA_ID: u32 = 1002; + pub const ED: Balance = parachains_common::polkadot::currency::EXISTENTIAL_DEPOSIT; + + pub fn genesis() -> Storage { + let genesis_config = bridge_hub_polkadot_runtime::RuntimeGenesisConfig { + system: bridge_hub_polkadot_runtime::SystemConfig { + code: bridge_hub_polkadot_runtime::WASM_BINARY + .expect("WASM binary was not build, please build it!") + .to_vec(), + ..Default::default() + }, + balances: bridge_hub_polkadot_runtime::BalancesConfig { + balances: accounts::init_balances() + .iter() + .cloned() + .map(|k| (k, ED * 4096)) + .collect(), + }, + parachain_info: bridge_hub_polkadot_runtime::ParachainInfoConfig { + parachain_id: PARA_ID.into(), + ..Default::default() + }, + collator_selection: bridge_hub_polkadot_runtime::CollatorSelectionConfig { + invulnerables: collators::invulnerables() + .iter() + .cloned() + .map(|(acc, _)| acc) + .collect(), + candidacy_bond: ED * 16, + ..Default::default() + }, + session: bridge_hub_polkadot_runtime::SessionConfig { + keys: collators::invulnerables() + .into_iter() + .map(|(acc, aura)| { + ( + acc.clone(), // account id + acc, // validator id + bridge_hub_polkadot_runtime::SessionKeys { aura }, // session keys + ) + }) + .collect(), + }, + polkadot_xcm: bridge_hub_polkadot_runtime::PolkadotXcmConfig { + safe_xcm_version: Some(SAFE_XCM_VERSION), + ..Default::default() + }, + ..Default::default() + }; + + genesis_config.build_storage().unwrap() + } +} + +// Bridge Hub Kusama +pub mod bridge_hub_kusama { + use super::*; + pub const PARA_ID: u32 = 1002; + pub const ED: Balance = parachains_common::kusama::currency::EXISTENTIAL_DEPOSIT; + + pub fn genesis() -> Storage { + let genesis_config = bridge_hub_kusama_runtime::RuntimeGenesisConfig { + system: bridge_hub_kusama_runtime::SystemConfig { + code: bridge_hub_kusama_runtime::WASM_BINARY + .expect("WASM binary was not build, please build it!") + .to_vec(), + ..Default::default() + }, + balances: bridge_hub_kusama_runtime::BalancesConfig { + balances: accounts::init_balances() + .iter() + .cloned() + .map(|k| (k, ED * 4096)) + .collect(), + }, + parachain_info: bridge_hub_kusama_runtime::ParachainInfoConfig { + parachain_id: PARA_ID.into(), + ..Default::default() + }, + collator_selection: bridge_hub_kusama_runtime::CollatorSelectionConfig { + invulnerables: collators::invulnerables() + .iter() + .cloned() + .map(|(acc, _)| acc) + .collect(), + candidacy_bond: ED * 16, + ..Default::default() + }, + session: bridge_hub_kusama_runtime::SessionConfig { + keys: collators::invulnerables() + .into_iter() + .map(|(acc, aura)| { + ( + acc.clone(), // account id + acc, // validator id + bridge_hub_kusama_runtime::SessionKeys { aura }, // session keys + ) + }) + .collect(), + }, + polkadot_xcm: bridge_hub_kusama_runtime::PolkadotXcmConfig { + safe_xcm_version: Some(SAFE_XCM_VERSION), + ..Default::default() + }, + ..Default::default() + }; + + genesis_config.build_storage().unwrap() + } +} + +// Collectives +pub mod collectives { + use super::*; + pub const PARA_ID: u32 = 1001; + pub const ED: Balance = parachains_common::polkadot::currency::EXISTENTIAL_DEPOSIT; + + pub fn genesis() -> Storage { + let genesis_config = collectives_polkadot_runtime::RuntimeGenesisConfig { + system: collectives_polkadot_runtime::SystemConfig { + code: collectives_polkadot_runtime::WASM_BINARY + .expect("WASM binary was not build, please build it!") + .to_vec(), + ..Default::default() + }, + balances: collectives_polkadot_runtime::BalancesConfig { + balances: accounts::init_balances() + .iter() + .cloned() + .map(|k| (k, ED * 4096)) + .collect(), + }, + parachain_info: collectives_polkadot_runtime::ParachainInfoConfig { + parachain_id: PARA_ID.into(), + ..Default::default() + }, + collator_selection: collectives_polkadot_runtime::CollatorSelectionConfig { + invulnerables: collators::invulnerables() + .iter() + .cloned() + .map(|(acc, _)| acc) + .collect(), + candidacy_bond: ED * 16, + ..Default::default() + }, + session: collectives_polkadot_runtime::SessionConfig { + keys: collators::invulnerables() + .into_iter() + .map(|(acc, aura)| { + ( + acc.clone(), // account id + acc, // validator id + collectives_polkadot_runtime::SessionKeys { aura }, // session keys + ) + }) + .collect(), + }, + polkadot_xcm: collectives_polkadot_runtime::PolkadotXcmConfig { + safe_xcm_version: Some(SAFE_XCM_VERSION), + ..Default::default() + }, + ..Default::default() + }; + + genesis_config.build_storage().unwrap() + } +} + +// Penpal +pub mod penpal { + use super::*; + pub const PARA_ID_A: u32 = 2000; + pub const PARA_ID_B: u32 = 2001; + pub const ED: Balance = penpal_runtime::EXISTENTIAL_DEPOSIT; + + pub fn genesis(para_id: u32) -> Storage { + let genesis_config = penpal_runtime::RuntimeGenesisConfig { + system: penpal_runtime::SystemConfig { + code: penpal_runtime::WASM_BINARY + .expect("WASM binary was not build, please build it!") + .to_vec(), + ..Default::default() + }, + balances: penpal_runtime::BalancesConfig { + balances: accounts::init_balances() + .iter() + .cloned() + .map(|k| (k, ED * 4096)) + .collect(), + }, + parachain_info: penpal_runtime::ParachainInfoConfig { + parachain_id: para_id.into(), + ..Default::default() + }, + collator_selection: penpal_runtime::CollatorSelectionConfig { + invulnerables: collators::invulnerables() + .iter() + .cloned() + .map(|(acc, _)| acc) + .collect(), + candidacy_bond: ED * 16, + ..Default::default() + }, + session: penpal_runtime::SessionConfig { + keys: collators::invulnerables() + .into_iter() + .map(|(acc, aura)| { + ( + acc.clone(), // account id + acc, // validator id + penpal_runtime::SessionKeys { aura }, // session keys + ) + }) + .collect(), + }, + polkadot_xcm: penpal_runtime::PolkadotXcmConfig { + safe_xcm_version: Some(SAFE_XCM_VERSION), + ..Default::default() + }, + sudo: penpal_runtime::SudoConfig { + key: Some(get_account_id_from_seed::("Alice")), + }, + ..Default::default() + }; + + genesis_config.build_storage().unwrap() + } +} diff --git a/integration-tests/emulated/common/src/impls.rs b/integration-tests/emulated/common/src/impls.rs new file mode 100644 index 0000000000..5969b8ac84 --- /dev/null +++ b/integration-tests/emulated/common/src/impls.rs @@ -0,0 +1,645 @@ +// 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. + +pub use codec::{Decode, Encode}; +pub use paste; + +pub use crate::{ + constants::{PROOF_SIZE_THRESHOLD, REF_TIME_THRESHOLD}, + xcm_helpers::xcm_transact_unpaid_execution, +}; + +// Substrate +pub use frame_support::{assert_ok, traits::fungibles::Inspect}; +pub use pallet_assets; +pub use pallet_message_queue; +use sp_core::Get; + +// Cumulus +use bp_messages::{ + target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch}, + LaneId, MessageKey, OutboundLaneData, +}; +use bridge_runtime_common::messages_xcm_extension::XcmBlobMessageDispatchResult; +pub use cumulus_pallet_dmp_queue; +pub use cumulus_pallet_parachain_system; +pub use cumulus_pallet_xcmp_queue; +pub use cumulus_primitives_core::{ + relay_chain::HrmpChannelId, DmpMessageHandler, ParaId, XcmpMessageHandler, +}; +use pallet_bridge_messages::{Config, Instance1, OutboundLanes, Pallet}; +pub use parachains_common::{AccountId, Balance}; +pub use xcm_emulator::{ + assert_expected_events, bx, helpers::weight_within_threshold, BridgeMessage, + BridgeMessageDispatchError, BridgeMessageHandler, Chain, Parachain, RelayChain, TestExt, +}; + +// Polkadot +pub use pallet_xcm; +pub use polkadot_runtime_parachains::{ + dmp, hrmp, + inclusion::{AggregateMessageOrigin, UmpQueueId}, +}; +pub use xcm::{ + prelude::{OriginKind, Outcome, VersionedXcm, Weight}, + v3::Error, + DoubleEncoded, +}; + +pub struct BridgeHubMessageHandler { + _marker: std::marker::PhantomData<(S, T, I)>, +} + +struct LaneIdWrapper(LaneId); + +impl From for u32 { + fn from(lane_id: LaneIdWrapper) -> u32 { + u32::from_be_bytes(lane_id.0 .0) + } +} + +impl From for LaneIdWrapper { + fn from(id: u32) -> LaneIdWrapper { + LaneIdWrapper(LaneId(id.to_be_bytes())) + } +} + +// TODO: uncomment when https://github.com/polkadot-fellows/runtimes/pull/108 is merged +// type BridgeHubPolkadotRuntime = ::Runtime; +// type BridgeHubKusamaRuntime = ::Runtime; + +// TODO: uncomment when https://github.com/polkadot-fellows/runtimes/pull/108 is merged +// pub type PolkadotKusamaMessageHandler +// = BridgeHubMessageHandler; +// pub type KusamaPolkadotMessageHandler +// = BridgeHubMessageHandler; + +impl BridgeMessageHandler for BridgeHubMessageHandler +where + S: Config, + T: Config, + I: 'static, + >::InboundPayload: From>, + >::MessageDispatch: + MessageDispatch, +{ + fn get_source_outbound_messages() -> Vec { + // get the source active outbound lanes + let active_lanes = S::ActiveOutboundLanes::get(); + + let mut messages: Vec = Default::default(); + + // collect messages from `OutboundMessages` for each active outbound lane in the source + for lane in active_lanes { + let latest_generated_nonce = + OutboundLanes::::get(lane).latest_generated_nonce; + let latest_received_nonce = + OutboundLanes::::get(lane).latest_received_nonce; + + (latest_received_nonce + 1..=latest_generated_nonce).for_each(|nonce| { + let encoded_payload: Vec = + Pallet::::outbound_message_data(*lane, nonce) + .expect("Bridge message does not exist") + .into(); + let payload = Vec::::decode(&mut &encoded_payload[..]) + .expect("Decodign XCM message failed"); + let id: u32 = LaneIdWrapper(*lane).into(); + let message = BridgeMessage { id, nonce, payload }; + + messages.push(message); + }); + } + messages + } + + fn dispatch_target_inbound_message( + message: BridgeMessage, + ) -> Result<(), BridgeMessageDispatchError> { + type TargetMessageDispatch = >::MessageDispatch; + type InboundPayload = >::InboundPayload; + + let lane_id = LaneIdWrapper::from(message.id).0; + let nonce = message.nonce; + let payload = Ok(From::from(message.payload)); + + // Directly dispatch outbound messages assuming everything is correct + // and bypassing the `Relayers` and `InboundLane` logic + let dispatch_result = TargetMessageDispatch::::dispatch(DispatchMessage { + key: MessageKey { lane_id, nonce }, + data: DispatchMessageData::> { payload }, + }); + + let result = match dispatch_result.dispatch_level_result { + XcmBlobMessageDispatchResult::Dispatched => Ok(()), + XcmBlobMessageDispatchResult::InvalidPayload => Err(BridgeMessageDispatchError( + Box::new(XcmBlobMessageDispatchResult::InvalidPayload), + )), + XcmBlobMessageDispatchResult::NotDispatched(e) => Err(BridgeMessageDispatchError( + Box::new(XcmBlobMessageDispatchResult::NotDispatched(e)), + )), + }; + result + } + + fn notify_source_message_delivery(lane_id: u32) { + let data = OutboundLanes::::get(LaneIdWrapper::from(lane_id).0); + let new_data = OutboundLaneData { + oldest_unpruned_nonce: data.oldest_unpruned_nonce + 1, + latest_received_nonce: data.latest_received_nonce + 1, + ..data + }; + + OutboundLanes::::insert(LaneIdWrapper::from(lane_id).0, new_data); + } +} + +#[macro_export] +macro_rules! impl_accounts_helpers_for_relay_chain { + ( $chain:ident ) => { + $crate::impls::paste::paste! { + impl $chain { + /// Fund a set of accounts with a balance + pub fn fund_accounts(accounts: Vec<($crate::impls::AccountId, $crate::impls::Balance)>) { + ::execute_with(|| { + for account in accounts { + $crate::impls::assert_ok!(]>::Balances::force_set_balance( + ::RuntimeOrigin::root(), + account.0.into(), + account.1, + )); + } + }); + } + /// Fund a sovereign account based on its Parachain Id + pub fn fund_para_sovereign(amount: $crate::impls::Balance, para_id: $crate::impls::ParaId) -> sp_runtime::AccountId32 { + let sovereign_account = ::sovereign_account_id_of_child_para(para_id); + Self::fund_accounts(vec![(sovereign_account.clone(), amount)]); + sovereign_account + } + } + } + }; +} + +#[macro_export] +macro_rules! impl_assert_events_helpers_for_relay_chain { + ( $chain:ident ) => { + $crate::impls::paste::paste! { + type [<$chain RuntimeEvent>] = <$chain as $crate::impls::Chain>::RuntimeEvent; + + impl $chain { + /// Asserts a dispatchable is completely executed and XCM sent + pub fn assert_xcm_pallet_attempted_complete(expected_weight: Option<$crate::impls::Weight>) { + $crate::impls::assert_expected_events!( + Self, + vec![ + [<$chain RuntimeEvent>]::XcmPallet( + $crate::impls::pallet_xcm::Event::Attempted { outcome: $crate::impls::Outcome::Complete(weight) } + ) => { + weight: $crate::impls::weight_within_threshold( + ($crate::impls::REF_TIME_THRESHOLD, $crate::impls::PROOF_SIZE_THRESHOLD), + expected_weight.unwrap_or(*weight), + *weight + ), + }, + ] + ); + } + + /// Asserts a dispatchable is incompletely executed and XCM sent + pub fn assert_xcm_pallet_attempted_incomplete( + expected_weight: Option<$crate::impls::Weight>, + expected_error: Option<$crate::impls::Error>, + ) { + $crate::impls::assert_expected_events!( + Self, + vec![ + // Dispatchable is properly executed and XCM message sent + [<$chain RuntimeEvent>]::XcmPallet( + $crate::impls::pallet_xcm::Event::Attempted { outcome: $crate::impls::Outcome::Incomplete(weight, error) } + ) => { + weight: $crate::impls::weight_within_threshold( + ($crate::impls::REF_TIME_THRESHOLD, $crate::impls::PROOF_SIZE_THRESHOLD), + expected_weight.unwrap_or(*weight), + *weight + ), + error: *error == expected_error.unwrap_or(*error), + }, + ] + ); + } + + /// Asserts a XCM message is sent + pub fn assert_xcm_pallet_sent() { + $crate::impls::assert_expected_events!( + Self, + vec![ + [<$chain RuntimeEvent>]::XcmPallet($crate::impls::pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + } + + /// Asserts a XCM from System Parachain is succesfully received and proccessed + pub fn assert_ump_queue_processed( + expected_success: bool, + expected_id: Option<$crate::impls::ParaId>, + expected_weight: Option<$crate::impls::Weight>, + ) { + $crate::impls::assert_expected_events!( + Self, + vec![ + // XCM is succesfully received and proccessed + [<$chain RuntimeEvent>]::MessageQueue($crate::impls::pallet_message_queue::Event::Processed { + origin: $crate::impls::AggregateMessageOrigin::Ump($crate::impls::UmpQueueId::Para(id)), + weight_used, + success, + .. + }) => { + id: *id == expected_id.unwrap_or(*id), + weight_used: $crate::impls::weight_within_threshold( + ($crate::impls::REF_TIME_THRESHOLD, $crate::impls::PROOF_SIZE_THRESHOLD), + expected_weight.unwrap_or(*weight_used), + *weight_used + ), + success: *success == expected_success, + }, + ] + ); + } + } + } + }; +} + +#[macro_export] +macro_rules! impl_hrmp_channels_helpers_for_relay_chain { + ( $chain:ident ) => { + $crate::impls::paste::paste! { + impl $chain { + /// Init open channel request with another Parachain + pub fn init_open_channel_call( + recipient_para_id: $crate::impls::ParaId, + max_capacity: u32, + max_message_size: u32, + ) -> $crate::impls::DoubleEncoded<()> { + use $crate::impls::Encode; + + ::RuntimeCall::Hrmp($crate::impls::hrmp::Call::< + ::Runtime, + >::hrmp_init_open_channel { + recipient: recipient_para_id, + proposed_max_capacity: max_capacity, + proposed_max_message_size: max_message_size, + }) + .encode() + .into() + } + /// Recipient Parachain accept the open request from another Parachain + pub fn accept_open_channel_call(sender_para_id: $crate::impls::ParaId) -> $crate::impls::DoubleEncoded<()> { + use $crate::impls::Encode; + + ::RuntimeCall::Hrmp($crate::impls::hrmp::Call::< + ::Runtime, + >::hrmp_accept_open_channel { + sender: sender_para_id, + }) + .encode() + .into() + } + + /// A root origin force to open a channel between two Parachains + pub fn force_process_hrmp_open(sender: $crate::impls::ParaId, recipient: $crate::impls::ParaId) { + use $crate::impls::Chain; + + ::execute_with(|| { + let relay_root_origin = ::RuntimeOrigin::root(); + + // Force process HRMP open channel requests without waiting for the next session + $crate::impls::assert_ok!(]>::Hrmp::force_process_hrmp_open( + relay_root_origin, + u32::MAX, + )); + + let channel_id = $crate::impls::HrmpChannelId { sender, recipient }; + + let hrmp_channel_exist = $crate::impls::hrmp::HrmpChannels::< + ::Runtime, + >::contains_key(&channel_id); + + // Check the HRMP channel has been successfully registered + assert!(hrmp_channel_exist) + }); + } + } + } + }; +} + +#[macro_export] +macro_rules! impl_accounts_helpers_for_parachain { + ( $chain:ident ) => { + $crate::impls::paste::paste! { + impl $chain { + /// Fund a set of accounts with a balance + pub fn fund_accounts(accounts: Vec<($crate::impls::AccountId, $crate::impls::Balance)>) { + ::execute_with(|| { + for account in accounts { + $crate::impls::assert_ok!(]>::Balances::force_set_balance( + ::RuntimeOrigin::root(), + account.0.into(), + account.1, + )); + } + }); + } + } + } + }; +} + +#[macro_export] +macro_rules! impl_assert_events_helpers_for_parachain { + ( $chain:ident ) => { + $crate::impls::paste::paste! { + type [<$chain RuntimeEvent>] = <$chain as $crate::impls::Chain>::RuntimeEvent; + + impl $chain { + /// Asserts a dispatchable is completely executed and XCM sent + pub fn assert_xcm_pallet_attempted_complete(expected_weight: Option<$crate::impls::Weight>) { + $crate::impls::assert_expected_events!( + Self, + vec![ + [<$chain RuntimeEvent>]::PolkadotXcm( + $crate::impls::pallet_xcm::Event::Attempted { outcome: $crate::impls::Outcome::Complete(weight) } + ) => { + weight: $crate::impls::weight_within_threshold( + ($crate::impls::REF_TIME_THRESHOLD, $crate::impls::PROOF_SIZE_THRESHOLD), + expected_weight.unwrap_or(*weight), + *weight + ), + }, + ] + ); + } + + /// Asserts a dispatchable is incompletely executed and XCM sent + pub fn assert_xcm_pallet_attempted_incomplete( + expected_weight: Option<$crate::impls::Weight>, + expected_error: Option<$crate::impls::Error>, + ) { + $crate::impls::assert_expected_events!( + Self, + vec![ + // Dispatchable is properly executed and XCM message sent + [<$chain RuntimeEvent>]::PolkadotXcm( + $crate::impls::pallet_xcm::Event::Attempted { outcome: $crate::impls::Outcome::Incomplete(weight, error) } + ) => { + weight: $crate::impls::weight_within_threshold( + ($crate::impls::REF_TIME_THRESHOLD, $crate::impls::PROOF_SIZE_THRESHOLD), + expected_weight.unwrap_or(*weight), + *weight + ), + error: *error == expected_error.unwrap_or(*error), + }, + ] + ); + } + + /// Asserts a dispatchable throws and error when trying to be sent + pub fn assert_xcm_pallet_attempted_error(expected_error: Option<$crate::impls::Error>) { + $crate::impls::assert_expected_events!( + Self, + vec![ + // Execution fails in the origin with `Barrier` + [<$chain RuntimeEvent>]::PolkadotXcm( + $crate::impls::pallet_xcm::Event::Attempted { outcome: $crate::impls::Outcome::Error(error) } + ) => { + error: *error == expected_error.unwrap_or(*error), + }, + ] + ); + } + + /// Asserts a XCM message is sent + pub fn assert_xcm_pallet_sent() { + $crate::impls::assert_expected_events!( + Self, + vec![ + [<$chain RuntimeEvent>]::PolkadotXcm($crate::impls::pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + } + + /// Asserts a XCM message is sent to Relay Chain + pub fn assert_parachain_system_ump_sent() { + $crate::impls::assert_expected_events!( + Self, + vec![ + [<$chain RuntimeEvent>]::ParachainSystem( + $crate::impls::cumulus_pallet_parachain_system::Event::UpwardMessageSent { .. } + ) => {}, + ] + ); + } + + /// Asserts a XCM from Relay Chain is completely executed + pub fn assert_dmp_queue_complete(expected_weight: Option<$crate::impls::Weight>) { + $crate::impls::assert_expected_events!( + Self, + vec![ + [<$chain RuntimeEvent>]::DmpQueue($crate::impls::cumulus_pallet_dmp_queue::Event::ExecutedDownward { + outcome: $crate::impls::Outcome::Complete(weight), .. + }) => { + weight: $crate::impls::weight_within_threshold( + ($crate::impls::REF_TIME_THRESHOLD, $crate::impls::PROOF_SIZE_THRESHOLD), + expected_weight.unwrap_or(*weight), + *weight + ), + }, + ] + ); + } + + /// Asserts a XCM from Relay Chain is incompletely executed + pub fn assert_dmp_queue_incomplete( + expected_weight: Option<$crate::impls::Weight>, + expected_error: Option<$crate::impls::Error>, + ) { + $crate::impls::assert_expected_events!( + Self, + vec![ + [<$chain RuntimeEvent>]::DmpQueue($crate::impls::cumulus_pallet_dmp_queue::Event::ExecutedDownward { + outcome: $crate::impls::Outcome::Incomplete(weight, error), .. + }) => { + weight: $crate::impls::weight_within_threshold( + ($crate::impls::REF_TIME_THRESHOLD, $crate::impls::PROOF_SIZE_THRESHOLD), + expected_weight.unwrap_or(*weight), + *weight + ), + error: *error == expected_error.unwrap_or(*error), + }, + ] + ); + } + + /// Asserts a XCM from another Parachain is completely executed + pub fn assert_xcmp_queue_success(expected_weight: Option<$crate::impls::Weight>) { + $crate::impls::assert_expected_events!( + Self, + vec![ + [<$chain RuntimeEvent>]::XcmpQueue( + $crate::impls::cumulus_pallet_xcmp_queue::Event::Success { weight, .. } + ) => { + weight: $crate::impls::weight_within_threshold( + ($crate::impls::REF_TIME_THRESHOLD, $crate::impls::PROOF_SIZE_THRESHOLD), + expected_weight.unwrap_or(*weight), + *weight + ), + }, + ] + ); + } + } + } + }; +} + +#[macro_export] +macro_rules! impl_assets_helpers_for_parachain { + ( $chain:ident, $relay_chain:ident ) => { + $crate::impls::paste::paste! { + impl $chain { + /// Returns the encoded call for `force_create` from the assets pallet + pub fn force_create_asset_call( + asset_id: u32, + owner: $crate::impls::AccountId, + is_sufficient: bool, + min_balance: $crate::impls::Balance, + ) -> $crate::impls::DoubleEncoded<()> { + use $crate::impls::{Chain, Encode}; + + ::RuntimeCall::Assets($crate::impls::pallet_assets::Call::< + ::Runtime, + $crate::impls::pallet_assets::Instance1, + >::force_create { + id: asset_id.into(), + owner: owner.into(), + is_sufficient, + min_balance, + }) + .encode() + .into() + } + + /// Returns a `VersionedXcm` for `force_create` from the assets pallet + pub fn force_create_asset_xcm( + origin_kind: $crate::impls::OriginKind, + asset_id: u32, + owner: $crate::impls::AccountId, + is_sufficient: bool, + min_balance: $crate::impls::Balance, + ) -> $crate::impls::VersionedXcm<()> { + let call = Self::force_create_asset_call(asset_id, owner, is_sufficient, min_balance); + $crate::impls::xcm_transact_unpaid_execution(call, origin_kind) + } + + /// Mint assets making use of the assets pallet + pub fn mint_asset( + signed_origin: ::RuntimeOrigin, + id: u32, + beneficiary: $crate::impls::AccountId, + amount_to_mint: u128, + ) { + ::execute_with(|| { + $crate::impls::assert_ok!(]>::Assets::mint( + signed_origin, + id.into(), + beneficiary.clone().into(), + amount_to_mint + )); + + type RuntimeEvent = <$chain as $crate::impls::Chain>::RuntimeEvent; + + $crate::impls::assert_expected_events!( + Self, + vec![ + RuntimeEvent::Assets($crate::impls::pallet_assets::Event::Issued { asset_id, owner, amount }) => { + asset_id: *asset_id == id, + owner: *owner == beneficiary.clone().into(), + amount: *amount == amount_to_mint, + }, + ] + ); + }); + } + + /// Force create and mint assets making use of the assets pallet + pub fn force_create_and_mint_asset( + id: u32, + min_balance: u128, + is_sufficient: bool, + asset_owner: $crate::impls::AccountId, + amount_to_mint: u128, + ) { + use $crate::impls::{bx, Chain, RelayChain, Parachain, Inspect, TestExt}; + // Init values for Relay Chain + let root_origin = <$relay_chain as Chain>::RuntimeOrigin::root(); + let destination = <$relay_chain>::child_location_of(<$chain>::para_id()); + let xcm = Self::force_create_asset_xcm( + $crate::impls::OriginKind::Superuser, + id, + asset_owner.clone(), + is_sufficient, + min_balance, + ); + + <$relay_chain>::execute_with(|| { + $crate::impls::assert_ok!(<$relay_chain as [<$relay_chain Pallet>]>::XcmPallet::send( + root_origin, + bx!(destination.into()), + bx!(xcm), + )); + + <$relay_chain>::assert_xcm_pallet_sent(); + }); + + Self::execute_with(|| { + Self::assert_dmp_queue_complete(Some($crate::impls::Weight::from_parts(1_020_000_000, 200_000))); + + type RuntimeEvent = <$chain as $crate::impls::Chain>::RuntimeEvent; + + $crate::impls::assert_expected_events!( + Self, + vec![ + // Asset has been created + RuntimeEvent::Assets($crate::impls::pallet_assets::Event::ForceCreated { asset_id, owner }) => { + asset_id: *asset_id == id, + owner: *owner == asset_owner.clone(), + }, + ] + ); + + assert!(]>::Assets::asset_exists(id.into())); + }); + + let signed_origin = ::RuntimeOrigin::signed(asset_owner.clone()); + + // Mint asset for System Parachain's sender + Self::mint_asset(signed_origin, id, asset_owner, amount_to_mint); + } + } + } + }; +} diff --git a/integration-tests/emulated/common/src/lib.rs b/integration-tests/emulated/common/src/lib.rs new file mode 100644 index 0000000000..d2eb63090f --- /dev/null +++ b/integration-tests/emulated/common/src/lib.rs @@ -0,0 +1,328 @@ +// 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. + +#![cfg(not(feature = "runtime-benchmarks"))] + +pub mod constants; +pub mod impls; +pub mod xcm_helpers; + +use constants::{ + accounts::{ALICE, BOB}, + asset_hub_kusama, asset_hub_polkadot, bridge_hub_kusama, bridge_hub_polkadot, collectives, + kusama, penpal, polkadot, +}; + +// Substrate +use frame_support::traits::OnInitialize; + +// Cumulus +use xcm_emulator::{ + // decl_test_bridges, + decl_test_networks, + decl_test_parachains, + decl_test_relay_chains, + decl_test_sender_receiver_accounts_parameter_types, + DefaultMessageProcessor, +}; + +use polkadot_primitives::runtime_api::runtime_decl_for_parachain_host::ParachainHostV7; + +decl_test_relay_chains! { + #[api_version(5)] + pub struct Polkadot { + genesis = polkadot::genesis(), + on_init = (), + runtime = polkadot_runtime, + core = { + MessageProcessor: DefaultMessageProcessor, + SovereignAccountOf: polkadot_runtime::xcm_config::SovereignAccountOf, + }, + pallets = { + XcmPallet: polkadot_runtime::XcmPallet, + Balances: polkadot_runtime::Balances, + Hrmp: polkadot_runtime::Hrmp, + } + }, + #[api_version(5)] + pub struct Kusama { + genesis = kusama::genesis(), + on_init = (), + runtime = kusama_runtime, + core = { + MessageProcessor: DefaultMessageProcessor, + SovereignAccountOf: kusama_runtime::xcm_config::SovereignAccountOf, + }, + pallets = { + XcmPallet: kusama_runtime::XcmPallet, + Balances: kusama_runtime::Balances, + Hrmp: kusama_runtime::Hrmp, + } + }, +} + +decl_test_parachains! { + // Polkadot Parachains + pub struct AssetHubPolkadot { + genesis = asset_hub_polkadot::genesis(), + on_init = { + asset_hub_polkadot_runtime::AuraExt::on_initialize(1); + }, + runtime = asset_hub_polkadot_runtime, + core = { + XcmpMessageHandler: asset_hub_polkadot_runtime::XcmpQueue, + DmpMessageHandler: asset_hub_polkadot_runtime::DmpQueue, + LocationToAccountId: asset_hub_polkadot_runtime::xcm_config::LocationToAccountId, + ParachainInfo: asset_hub_polkadot_runtime::ParachainInfo, + }, + pallets = { + PolkadotXcm: asset_hub_polkadot_runtime::PolkadotXcm, + Assets: asset_hub_polkadot_runtime::Assets, + Balances: asset_hub_polkadot_runtime::Balances, + } + }, + pub struct Collectives { + genesis = collectives::genesis(), + on_init = { + collectives_polkadot_runtime::AuraExt::on_initialize(1); + }, + runtime = collectives_polkadot_runtime, + core = { + XcmpMessageHandler: collectives_polkadot_runtime::XcmpQueue, + DmpMessageHandler: collectives_polkadot_runtime::DmpQueue, + LocationToAccountId: collectives_polkadot_runtime::xcm_config::LocationToAccountId, + ParachainInfo: collectives_polkadot_runtime::ParachainInfo, + }, + pallets = { + PolkadotXcm: collectives_polkadot_runtime::PolkadotXcm, + Balances: collectives_polkadot_runtime::Balances, + } + }, + pub struct BridgeHubPolkadot { + genesis = bridge_hub_polkadot::genesis(), + on_init = { + bridge_hub_polkadot_runtime::AuraExt::on_initialize(1); + }, + runtime = bridge_hub_polkadot_runtime, + core = { + XcmpMessageHandler: bridge_hub_polkadot_runtime::XcmpQueue, + DmpMessageHandler: bridge_hub_polkadot_runtime::DmpQueue, + LocationToAccountId: bridge_hub_polkadot_runtime::xcm_config::LocationToAccountId, + ParachainInfo: bridge_hub_polkadot_runtime::ParachainInfo, + }, + pallets = { + PolkadotXcm: bridge_hub_polkadot_runtime::PolkadotXcm, + } + }, + pub struct PenpalPolkadotA { + genesis = penpal::genesis(penpal::PARA_ID_A), + on_init = { + penpal_runtime::AuraExt::on_initialize(1); + }, + runtime = penpal_runtime, + core = { + XcmpMessageHandler: penpal_runtime::XcmpQueue, + DmpMessageHandler: penpal_runtime::DmpQueue, + LocationToAccountId: penpal_runtime::xcm_config::LocationToAccountId, + ParachainInfo: penpal_runtime::ParachainInfo, + }, + pallets = { + PolkadotXcm: penpal_runtime::PolkadotXcm, + Assets: penpal_runtime::Assets, + } + }, + pub struct PenpalPolkadotB { + genesis = penpal::genesis(penpal::PARA_ID_B), + on_init = { + penpal_runtime::AuraExt::on_initialize(1); + }, + runtime = penpal_runtime, + core = { + XcmpMessageHandler: penpal_runtime::XcmpQueue, + DmpMessageHandler: penpal_runtime::DmpQueue, + LocationToAccountId: penpal_runtime::xcm_config::LocationToAccountId, + ParachainInfo: penpal_runtime::ParachainInfo, + }, + pallets = { + PolkadotXcm: penpal_runtime::PolkadotXcm, + Assets: penpal_runtime::Assets, + } + }, + // Kusama Parachains + pub struct AssetHubKusama { + genesis = asset_hub_kusama::genesis(), + on_init = { + asset_hub_kusama_runtime::AuraExt::on_initialize(1); + }, + runtime = asset_hub_kusama_runtime, + core = { + XcmpMessageHandler: asset_hub_kusama_runtime::XcmpQueue, + DmpMessageHandler: asset_hub_kusama_runtime::DmpQueue, + LocationToAccountId: asset_hub_kusama_runtime::xcm_config::LocationToAccountId, + ParachainInfo: asset_hub_kusama_runtime::ParachainInfo, + }, + pallets = { + PolkadotXcm: asset_hub_kusama_runtime::PolkadotXcm, + Assets: asset_hub_kusama_runtime::Assets, + ForeignAssets: asset_hub_kusama_runtime::ForeignAssets, + PoolAssets: asset_hub_kusama_runtime::PoolAssets, + AssetConversion: asset_hub_kusama_runtime::AssetConversion, + Balances: asset_hub_kusama_runtime::Balances, + } + }, + pub struct BridgeHubKusama { + genesis = bridge_hub_kusama::genesis(), + on_init = { + bridge_hub_kusama_runtime::AuraExt::on_initialize(1); + }, + runtime = bridge_hub_kusama_runtime, + core = { + XcmpMessageHandler: bridge_hub_kusama_runtime::XcmpQueue, + DmpMessageHandler: bridge_hub_kusama_runtime::DmpQueue, + LocationToAccountId: bridge_hub_kusama_runtime::xcm_config::LocationToAccountId, + ParachainInfo: bridge_hub_kusama_runtime::ParachainInfo, + }, + pallets = { + PolkadotXcm: bridge_hub_kusama_runtime::PolkadotXcm, + } + }, + pub struct PenpalKusamaA { + genesis = penpal::genesis(penpal::PARA_ID_A), + on_init = { + penpal_runtime::AuraExt::on_initialize(1); + }, + runtime = penpal_runtime, + core = { + XcmpMessageHandler: penpal_runtime::XcmpQueue, + DmpMessageHandler: penpal_runtime::DmpQueue, + LocationToAccountId: penpal_runtime::xcm_config::LocationToAccountId, + ParachainInfo: penpal_runtime::ParachainInfo, + }, + pallets = { + PolkadotXcm: penpal_runtime::PolkadotXcm, + Assets: penpal_runtime::Assets, + } + }, + pub struct PenpalKusamaB { + genesis = penpal::genesis(penpal::PARA_ID_B), + on_init = { + penpal_runtime::AuraExt::on_initialize(1); + }, + runtime = penpal_runtime, + core = { + XcmpMessageHandler: penpal_runtime::XcmpQueue, + DmpMessageHandler: penpal_runtime::DmpQueue, + LocationToAccountId: penpal_runtime::xcm_config::LocationToAccountId, + ParachainInfo: penpal_runtime::ParachainInfo, + }, + pallets = { + PolkadotXcm: penpal_runtime::PolkadotXcm, + Assets: penpal_runtime::Assets, + } + }, +} + +decl_test_networks! { + pub struct PolkadotMockNet { + relay_chain = Polkadot, + parachains = vec![ + AssetHubPolkadot, + Collectives, + BridgeHubPolkadot, + PenpalPolkadotA, + PenpalPolkadotB, + ], + // TODO: uncomment when https://github.com/polkadot-fellows/runtimes/pull/108 is merged + // bridge = PolkadotKusamaMockBridge + bridge = () + }, + pub struct KusamaMockNet { + relay_chain = Kusama, + parachains = vec![ + AssetHubKusama, + BridgeHubKusama, + PenpalKusamaA, + PenpalKusamaB, + ], + // TODO: uncomment when https://github.com/polkadot-fellows/runtimes/pull/108 is merged + // bridge = KusamaPolkadotMockBridge + bridge = () + }, +} + +// TODO: uncomment when https://github.com/polkadot-fellows/runtimes/pull/108 is merged +// decl_test_bridges! { +// pub struct PolkadotKusamaMockBridge { +// source = BridgeHubPolkadot, +// target = BridgeHubKusama, +// handler = PolkadotKusamaMessageHandler +// }, +// pub struct KusamaPolkadotMockBridge { +// source = BridgeHubKusama, +// target = BridgeHubPolkadot, +// handler = KusamaPolkadotMessageHandler +// } +// } + +// Polkadot implementation +impl_accounts_helpers_for_relay_chain!(Polkadot); +impl_assert_events_helpers_for_relay_chain!(Polkadot); +impl_hrmp_channels_helpers_for_relay_chain!(Polkadot); + +// Kusama implementation +impl_accounts_helpers_for_relay_chain!(Kusama); +impl_assert_events_helpers_for_relay_chain!(Kusama); +impl_hrmp_channels_helpers_for_relay_chain!(Kusama); + +// AssetHubPolkadot implementation +impl_accounts_helpers_for_parachain!(AssetHubPolkadot); +impl_assets_helpers_for_parachain!(AssetHubPolkadot, Polkadot); +impl_assert_events_helpers_for_parachain!(AssetHubPolkadot); + +// AssetHubKusama implementation +impl_accounts_helpers_for_parachain!(AssetHubKusama); +impl_assets_helpers_for_parachain!(AssetHubKusama, Kusama); +impl_assert_events_helpers_for_parachain!(AssetHubKusama); + +// PenpalPolkadot implementations +impl_assert_events_helpers_for_parachain!(PenpalPolkadotA); +impl_assert_events_helpers_for_parachain!(PenpalPolkadotB); + +// PenpalKusama implementations +impl_assert_events_helpers_for_parachain!(PenpalKusamaA); +impl_assert_events_helpers_for_parachain!(PenpalKusamaB); + +// Collectives implementation +impl_accounts_helpers_for_parachain!(Collectives); +impl_assert_events_helpers_for_parachain!(Collectives); + +decl_test_sender_receiver_accounts_parameter_types! { + // Relays + Polkadot { sender: ALICE, receiver: BOB }, + Kusama { sender: ALICE, receiver: BOB }, + // Asset Hubs + AssetHubPolkadot { sender: ALICE, receiver: BOB }, + AssetHubKusama { sender: ALICE, receiver: BOB }, + // Collectives + Collectives { sender: ALICE, receiver: BOB }, + // Bridged Hubs + BridgeHubPolkadot { sender: ALICE, receiver: BOB }, + BridgeHubKusama { sender: ALICE, receiver: BOB }, + // Penpals + PenpalPolkadotA { sender: ALICE, receiver: BOB }, + PenpalPolkadotB { sender: ALICE, receiver: BOB }, + PenpalKusamaA { sender: ALICE, receiver: BOB }, + PenpalKusamaB { sender: ALICE, receiver: BOB } +} diff --git a/integration-tests/emulated/common/src/macros.rs b/integration-tests/emulated/common/src/macros.rs new file mode 100644 index 0000000000..a65b2057af --- /dev/null +++ b/integration-tests/emulated/common/src/macros.rs @@ -0,0 +1,107 @@ +// 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. + +#[macro_export] +macro_rules! test_parachain_is_trusted_teleporter { + ( $sender_para:ty, $sender_xcm_config:ty, vec![$( $receiver_para:ty ),+], ($assets:expr, $amount:expr) ) => { + $crate::paste::paste! { + // init Origin variables + let sender = [<$sender_para Sender>]::get(); + let mut para_sender_balance_before = + <$sender_para as $crate::Chain>::account_data_of(sender.clone()).free; + let origin = <$sender_para as $crate::Chain>::RuntimeOrigin::signed(sender.clone()); + let fee_asset_item = 0; + let weight_limit = $crate::WeightLimit::Unlimited; + + $( + { + // init Destination variables + let receiver = [<$receiver_para Receiver>]::get(); + let para_receiver_balance_before = + <$receiver_para as $crate::Chain>::account_data_of(receiver.clone()).free; + let para_destination = + <$sender_para>::sibling_location_of(<$receiver_para>::para_id()); + let beneficiary: MultiLocation = + $crate::AccountId32 { network: None, id: receiver.clone().into() }.into(); + + // Send XCM message from Origin Parachain + // We are only testing the limited teleport version, which should be ok since success will + // depend only on a proper `XcmConfig` at destination. + <$sender_para>::execute_with(|| { + assert_ok!(<$sender_para as [<$sender_para Pallet>]>::PolkadotXcm::limited_teleport_assets( + origin.clone(), + bx!(para_destination.into()), + bx!(beneficiary.into()), + bx!($assets.clone().into()), + fee_asset_item, + weight_limit.clone(), + )); + + type RuntimeEvent = <$sender_para as $crate::Chain>::RuntimeEvent; + + assert_expected_events!( + $sender_para, + vec![ + RuntimeEvent::PolkadotXcm( + $crate::pallet_xcm::Event::Attempted { outcome: Outcome::Complete { .. } } + ) => {}, + RuntimeEvent::XcmpQueue( + $crate::cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. } + ) => {}, + RuntimeEvent::Balances( + $crate::pallet_balances::Event::Withdraw { who: sender, amount } + ) => {}, + ] + ); + }); + + // Receive XCM message in Destination Parachain + <$receiver_para>::execute_with(|| { + type RuntimeEvent = <$receiver_para as $crate::Chain>::RuntimeEvent; + + assert_expected_events!( + $receiver_para, + vec![ + RuntimeEvent::Balances( + $crate::pallet_balances::Event::Deposit { who: receiver, .. } + ) => {}, + RuntimeEvent::XcmpQueue( + $crate::cumulus_pallet_xcmp_queue::Event::Success { .. } + ) => {}, + ] + ); + }); + + // Check if balances are updated accordingly in Origin and Destination Parachains + let para_sender_balance_after = + <$sender_para as $crate::Chain>::account_data_of(sender.clone()).free; + let para_receiver_balance_after = + <$receiver_para as $crate::Chain>::account_data_of(receiver.clone()).free; + let delivery_fees = <$sender_para>::execute_with(|| { + asset_test_utils::xcm_helpers::transfer_assets_delivery_fees::< + <$sender_xcm_config as xcm_executor::Config>::XcmSender, + >($assets.clone(), fee_asset_item, weight_limit.clone(), beneficiary, para_destination) + }); + + assert_eq!(para_sender_balance_before - $amount - delivery_fees, para_sender_balance_after); + assert!(para_receiver_balance_after > para_receiver_balance_before); + + // Update sender balance + para_sender_balance_before = <$sender_para as $crate::Chain>::account_data_of(sender.clone()).free; + } + )+ + } + }; +} diff --git a/integration-tests/emulated/common/src/xcm_helpers.rs b/integration-tests/emulated/common/src/xcm_helpers.rs new file mode 100644 index 0000000000..4096cdbba0 --- /dev/null +++ b/integration-tests/emulated/common/src/xcm_helpers.rs @@ -0,0 +1,66 @@ +// Copyright Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +use parachains_common::AccountId; +use xcm::{ + prelude::{ + AccountId32, All, BuyExecution, DepositAsset, MultiAsset, MultiAssets, MultiLocation, + OriginKind, RefundSurplus, Transact, UnpaidExecution, VersionedXcm, Weight, WeightLimit, + WithdrawAsset, Xcm, X1, + }, + DoubleEncoded, +}; + +/// Helper method to build a XCM with a `Transact` instruction and paying for its execution +pub fn xcm_transact_paid_execution( + call: DoubleEncoded<()>, + origin_kind: OriginKind, + native_asset: MultiAsset, + beneficiary: AccountId, +) -> VersionedXcm<()> { + let weight_limit = WeightLimit::Unlimited; + let require_weight_at_most = Weight::from_parts(1000000000, 200000); + let native_assets: MultiAssets = native_asset.clone().into(); + + VersionedXcm::from(Xcm(vec![ + WithdrawAsset(native_assets), + BuyExecution { fees: native_asset, weight_limit }, + Transact { require_weight_at_most, origin_kind, call }, + RefundSurplus, + DepositAsset { + assets: All.into(), + beneficiary: MultiLocation { + parents: 0, + interior: X1(AccountId32 { network: None, id: beneficiary.into() }), + }, + }, + ])) +} + +/// Helper method to build a XCM with a `Transact` instruction without paying for its execution +pub fn xcm_transact_unpaid_execution( + call: DoubleEncoded<()>, + origin_kind: OriginKind, +) -> VersionedXcm<()> { + let weight_limit = WeightLimit::Unlimited; + let require_weight_at_most = Weight::from_parts(1000000000, 200000); + let check_origin = None; + + VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit, check_origin }, + Transact { require_weight_at_most, origin_kind, call }, + ])) +}