moonbeam_runtime_common/tests/
xcm.rs

1// Copyright 2025 Moonbeam foundation
2// This file is part of Moonbeam.
3
4// Moonbeam is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Moonbeam is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Moonbeam.  If not, see <http://www.gnu.org/licenses/>.
16
17//! # Common XCM tests
18//!
19//! A collection of XCM tests common to all runtimes
20
21#[macro_export]
22macro_rules! generate_common_xcm_tests {
23	($runtime: ident) => {
24		#[cfg(test)]
25		pub mod common_xcm_tests {
26			use crate::common::{ExtBuilder, ALICE};
27			use cumulus_primitives_core::ExecuteXcm;
28			use frame_support::assert_ok;
29			use frame_support::traits::fungible::Inspect;
30			use frame_support::traits::EnsureOrigin;
31			use moonbeam_core_primitives::{AccountId, Balance};
32			use parity_scale_codec::Encode;
33			use sp_weights::Weight;
34			use xcm::{
35				latest::{prelude::AccountKey20, Assets as XcmAssets, Xcm},
36				VersionedAssets, VersionedLocation, VersionedXcm,
37			};
38			use $runtime::{
39				xcm_config::SelfReserve, Balances, PolkadotXcm, Runtime, RuntimeEvent,
40				RuntimeOrigin, System,
41			};
42
43			pub(crate) fn last_events(n: usize) -> Vec<RuntimeEvent> {
44				System::events()
45					.into_iter()
46					.map(|e| e.event)
47					.rev()
48					.take(n)
49					.rev()
50					.collect()
51			}
52
53			#[test]
54			fn claim_assets_works() {
55				const INITIAL_BALANCE: Balance = 10_000_000_000_000_000_000;
56				const SEND_AMOUNT: Balance = 1_000_000_000_000_000_000;
57
58				let alice = AccountId::from(ALICE);
59				let balances = vec![(alice, INITIAL_BALANCE)];
60
61				ExtBuilder::default()
62					.with_balances(balances)
63					.build()
64					.execute_with(|| {
65						let assets = XcmAssets::from((SelfReserve::get(), SEND_AMOUNT));
66						// First trap some assets.
67						let trapping_program =
68							Xcm::builder_unsafe().withdraw_asset(assets.clone()).build();
69						// Even though assets are trapped, the extrinsic returns success.
70						let origin_location =
71							<Runtime as pallet_xcm::Config>::ExecuteXcmOrigin::ensure_origin(
72								RuntimeOrigin::signed(alice),
73							)
74							.expect("qed");
75						let message = Box::new(VersionedXcm::V5(trapping_program));
76						let mut hash = message.using_encoded(sp_io::hashing::blake2_256);
77						let message = (*message).try_into().expect("qed");
78						let _ = <Runtime as pallet_xcm::Config>::XcmExecutor::prepare_and_execute(
79							origin_location,
80							message,
81							&mut hash,
82							Weight::MAX,
83							Weight::MAX,
84						);
85						assert_eq!(
86							Balances::total_balance(&alice),
87							INITIAL_BALANCE - SEND_AMOUNT
88						);
89
90						// Assets were indeed trapped.
91						assert!(last_events(2).iter().any(|evt| matches!(
92							evt,
93							RuntimeEvent::PolkadotXcm(pallet_xcm::Event::AssetsTrapped { .. })
94						)));
95
96						// Now claim them with the extrinsic.
97						assert_ok!(PolkadotXcm::claim_assets(
98							RuntimeOrigin::signed(alice),
99							Box::new(VersionedAssets::V5(assets)),
100							Box::new(VersionedLocation::V5(
101								AccountKey20 {
102									network: None,
103									key: alice.clone().into()
104								}
105								.into()
106							)),
107						));
108						// Confirm that trapped assets were claimed back
109						assert_eq!(Balances::total_balance(&alice), INITIAL_BALANCE);
110					});
111			}
112		}
113	};
114}