Lines
100 %
Functions
Branches
// Copyright 2019-2025 PureStake Inc.
// This file is part of Moonbeam.
// Moonbeam 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.
// Moonbeam 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 Moonbeam. If not, see <http://www.gnu.org/licenses/>.
use crate::mock::*;
use crate::*;
use cumulus_primitives_core::relay_chain::HrmpChannelId;
use frame_support::weights::Weight;
use frame_support::{assert_noop, assert_ok, weights::constants::WEIGHT_REF_TIME_PER_SECOND};
use sp_runtime::traits::Convert;
use sp_runtime::DispatchError;
use sp_std::boxed::Box;
use xcm::latest::prelude::*;
use xcm_primitives::{UtilityAvailableCalls, UtilityEncodeCall};
#[test]
fn test_register_address() {
ExtBuilder::default()
.with_balances(vec![])
.build()
.execute_with(|| {
// Only root can do this, as specified in runtime
assert_noop!(
XcmTransactor::register(RuntimeOrigin::signed(1u64), 1u64, 1),
DispatchError::BadOrigin
);
// Root can register
assert_ok!(XcmTransactor::register(RuntimeOrigin::root(), 1u64, 1));
assert_eq!(XcmTransactor::index_to_account(&1).unwrap(), 1u64);
let expected = vec![crate::Event::RegisteredDerivative {
account_id: 1u64,
index: 1,
}];
assert_eq!(events(), expected);
})
}
fn test_transact_through_derivative_errors() {
// Non-claimed index so cannot transfer
XcmTransactor::transact_through_derivative(
RuntimeOrigin::signed(1u64),
Transactors::Relay,
1,
CurrencyPayment {
currency: Currency::AsMultiLocation(Box::new(
xcm::VersionedLocation::from(Location::parent())
)),
fee_amount: None
},
vec![0u8],
TransactWeights {
transact_required_weight_at_most: 100u64.into(),
overall_weight: None
false
),
Error::<Test>::UnclaimedIndex
// TransactInfo not yet set
Error::<Test>::TransactorInfoNotSet
// Root can set transact info
assert_ok!(XcmTransactor::set_transact_info(
RuntimeOrigin::root(),
Box::new(xcm::VersionedLocation::from(Location::parent())),
0.into(),
10000.into(),
None
));
// TransactInfo present, but FeePerSecond not set
Error::<Test>::FeePerSecondNotSet
// Set fee per second
assert_ok!(XcmTransactor::set_fee_per_second(
Box::new(xcm::VersionedLocation::from(Location::new(
[Junction::Parachain(1000)]
))),
1
// TransactInfo present, but the asset is not a reserve of dest
xcm::VersionedLocation::from(Location::new(
))
Error::<Test>::AssetIsNotReserveInDestination
// Cannot exceed the max weight
transact_required_weight_at_most: 10001u64.into(),
Error::<Test>::MaxWeightTransactReached
fn test_transact_through_signed_errors() {
XcmTransactor::transact_through_signed(
// Root can set transact info without extra_signed being None
Error::<Test>::SignedTransactNotAllowedForDestination
// Root can set transact info, with extra signed
15000.into(),
Some(12000.into())
fn test_transact_through_derivative_multilocation_success() {
// fee as destination are the same, this time it should work
assert_ok!(XcmTransactor::transact_through_derivative(
currency: Currency::AsMultiLocation(Box::new(xcm::VersionedLocation::from(
Location::parent()
vec![1u8],
let expected = vec![
crate::Event::RegisteredDerivative {
crate::Event::TransactInfoChanged {
location: Location::parent(),
remote_info: RemoteTransactInfoWithMaxWeight {
transact_extra_weight: 0.into(),
max_weight: 10000.into(),
transact_extra_weight_signed: None,
crate::Event::DestFeePerSecondChanged {
fee_per_second: 1,
crate::Event::TransactedDerivative {
dest: Location::parent(),
call: Transactors::Relay
.encode_call(UtilityAvailableCalls::AsDerivative(1, vec![1u8])),
];
fn test_transact_through_derivative_success() {
currency: Currency::AsCurrencyId(CurrencyId::OtherReserve(0)),
let sent_messages = mock::sent_xcm();
let (_, sent_message) = sent_messages.first().unwrap();
// Check message doesn't contain the appendix
assert!(!sent_message.0.contains(&SetAppendix(Xcm(vec![
RefundSurplus,
DepositAsset {
assets: Wild(AllCounted(1u32)),
beneficiary: Location {
parents: 0,
interior: [Junction::Parachain(100)].into()
]))));
fn test_root_can_transact_through_sovereign() {
// Only root can do this
XcmTransactor::transact_through_sovereign(
RuntimeOrigin::signed(1),
Some(1u64),
OriginKind::SovereignAccount,
assert_ok!(XcmTransactor::transact_through_sovereign(
crate::Event::TransactedSovereign {
fee_payer: Some(1u64),
call: vec![1u8],
fn test_fee_calculation_works() {
assert_eq!(
XcmTransactor::calculate_fee_per_second(
1000000000.into(),
8 * WEIGHT_REF_TIME_PER_SECOND as u128
8000000000
// Kusama case
fn test_fee_calculation_works_kusama_0_9_20_case() {
// 38620923000 * 319324000/1e12 = 12332587.6161
// integer arithmetic would round this to 12332587
// we test here that it rounds up to 12332588 instead
XcmTransactor::calculate_fee_per_second(319324000.into(), 38620923000),
12332588
fn de_registering_works() {
assert_ok!(XcmTransactor::deregister(RuntimeOrigin::root(), 1));
assert!(XcmTransactor::index_to_account(&1).is_none());
crate::Event::DeRegisteredDerivative { index: 1 },
fn removing_transact_info_works() {
// Root can remove transact info
assert_ok!(XcmTransactor::remove_transact_info(
assert!(XcmTransactor::transact_info(Location::parent()).is_none());
crate::Event::TransactInfoRemoved {
fn test_transact_through_signed_fails_if_transact_info_not_set_at_all() {
fn test_transact_through_signed_fails_if_weight_is_not_set() {
// weight value not set for signed transact, fails
fn test_transact_through_signed_fails_if_weight_overflows() {
Some(Weight::MAX)
// weight should overflow
transact_required_weight_at_most: 10064u64.into(),
Error::<Test>::WeightOverflow
fn test_transact_through_signed_fails_if_weight_is_bigger_than_max_weight() {
Some(1.into())
// 10000 + 1 > 10000 (max weight permitted by dest chain)
transact_required_weight_at_most: 100000u64.into(),
fn test_transact_through_signed_fails_if_fee_per_second_not_set() {
// fee per second not set, fails
fn test_transact_through_signed_works() {
// transact info and fee per second set
// this time it should work
assert_ok!(XcmTransactor::transact_through_signed(
transact_extra_weight_signed: Some(1.into()),
crate::Event::TransactedSigned {
fee_payer: 1u64,
fn test_send_through_derivative_with_custom_weight_and_fee() {
// We are gonna use a total weight of 10_100, a tx weight of 100,
// and a total fee of 100
let total_weight: Weight = 10_100u64.into();
let tx_weight: Weight = 100_u64.into();
let total_fee = 100u128;
// By specifying total fee and total weight, we ensure
// that even if the transact_info is not populated,
// the message is forged with our parameters
fee_amount: Some(total_fee)
transact_required_weight_at_most: tx_weight,
overall_weight: Some(Limited(total_weight))
// Lets make sure the message is as expected
assert!(sent_message
.0
.contains(&WithdrawAsset((Location::here(), total_fee).into())));
assert!(sent_message.0.contains(&BuyExecution {
fees: (Location::here(), total_fee).into(),
weight_limit: Limited(total_weight),
}));
assert!(sent_message.0.contains(&Transact {
origin_kind: OriginKind::SovereignAccount,
fallback_max_weight: Some(tx_weight),
.encode_call(UtilityAvailableCalls::AsDerivative(1, vec![1u8]))
.into(),
fn test_send_through_sovereign_with_custom_weight_and_fee() {
call: vec![1u8].into(),
fn test_transact_through_sovereign_with_fee_payer_none() {
// We don't specify any fee_payer, instead we pay fees with the
// sovereign account funds directly on the destination.
None,
fee_payer: None,
// Lets make sure the message is as expected even if we haven't indicated a
// fee_payer.
fn test_send_through_signed_with_custom_weight_and_fee() {
let expected = vec![crate::Event::TransactedSigned {
fn test_hrmp_manipulator_init() {
assert_ok!(XcmTransactor::hrmp_manage(
HrmpOperation::InitOpen(HrmpInitParams {
para_id: 1u32.into(),
proposed_max_capacity: 1,
proposed_max_message_size: 1
}),
origin_kind: OriginKind::Native,
call: vec![0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0].into(),
fn test_hrmp_manipulator_init_v3_convert_works() {
// Change xcm version
CustomVersionWrapper::set_version(3);
// Check message contains the new appendix
assert!(sent_message.0.contains(&SetAppendix(Xcm(vec![
assets: Wild(AllCounted(1)),
fn test_hrmp_manipulator_init_v5_convert_works() {
CustomVersionWrapper::set_version(5);
fn test_hrmp_manipulator_init_v6_convert_fails() {
CustomVersionWrapper::set_version(6);
XcmTransactor::hrmp_manage(
Error::<Test>::ErrorValidating
fn test_hrmp_max_fee_errors() {
let total_fee = 10_000_000_000_000u128;
Error::<Test>::TooMuchFeeUsed
fn test_hrmp_manipulator_accept() {
HrmpOperation::Accept {
para_id: 1u32.into()
call: vec![0, 0, 1, 0, 0, 0].into(),
fn test_hrmp_manipulator_cancel() {
let channel_id = HrmpChannelId {
sender: 1u32.into(),
recipient: 1u32.into(),
};
let open_requests: u32 = 1;
HrmpOperation::Cancel {
channel_id,
open_requests
fn test_hrmp_manipulator_close() {
HrmpOperation::Close(HrmpChannelId {
recipient: 1u32.into()
call: vec![0, 0, 1, 0, 0, 0, 1, 0, 0, 0].into(),
fn test_transact_through_derivative_with_refund_works() {
overall_weight: Some(Limited(1000.into()))
true
fn test_transact_through_derivative_with_refund_fails_overall_weight_not_set() {
Error::<Test>::RefundNotSupportedWithTransactInfo
fn test_transact_through_signed_with_refund_works() {
// Overall weight to use
interior: [
Junction::Parachain(100),
AccountIdToLocation::convert(1)
.interior
.take_first()
.unwrap()
]
.into()