Newer
Older
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot 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.
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
Adrian Catangiu
committed
#![cfg(test)]
pub(crate) mod assets_transfer;
Adrian Catangiu
committed
Branislav Kontur
committed
mock::*, pallet::SupportedVersion, AssetTraps, Config, CurrentMigration, Error,
ExecuteControllerWeightInfo, LatestVersionedLocation, Pallet, Queries, QueryStatus,
VersionDiscoveryQueue, VersionMigrationStage, VersionNotifiers, VersionNotifyTargets,
WeightInfo,
assert_err_ignore_postinfo, assert_noop, assert_ok,
use polkadot_parachain_primitives::primitives::Id as ParaId;
use sp_runtime::traits::{AccountIdConversion, BlakeTwo256, Hash};
use xcm_builder::AllowKnownQueryResponses;
traits::{Properties, QueryHandler, QueryResponseStatus, ShouldExecute},
const ALICE: AccountId = AccountId::new([0u8; 32]);
const BOB: AccountId = AccountId::new([1u8; 32]);
const INITIAL_BALANCE: u128 = 100;
const SEND_AMOUNT: u128 = 10;
Adrian Catangiu
committed
const FEE_AMOUNT: u128 = 2;
Gavin Wood
committed
#[test]
fn report_outcome_notify_works() {
let balances = vec![
(ALICE, INITIAL_BALANCE),
Adrian Catangiu
committed
(ParaId::from(OTHER_PARA_ID).into_account_truncating(), INITIAL_BALANCE),
let sender: Location = AccountId32 { network: None, id: ALICE.into() }.into();
let mut message = Xcm(vec![TransferAsset {
assets: (Here, SEND_AMOUNT).into(),
beneficiary: sender.clone(),
}]);
let call = pallet_test_notifier::Call::notification_received {
query_id: 0,
response: Default::default(),
};
let notify = RuntimeCall::TestNotifier(call);
Gavin Wood
committed
new_test_ext_with_balances(balances).execute_with(|| {
Adrian Catangiu
committed
Parachain(OTHER_PARA_ID).into_location(),
Gavin Wood
committed
assert_eq!(
message,
Xcm(vec![
SetAppendix(Xcm(vec![ReportError(QueryResponseInfo {
destination: Parent.into(),
Gavin Wood
committed
query_id: 0,
max_weight: Weight::from_parts(1_000_000, 1_000_000),
})])),
TransferAsset { assets: (Here, SEND_AMOUNT).into(), beneficiary: sender },
Gavin Wood
committed
])
);
Gavin Wood
committed
let status = QueryStatus::Pending {
responder: Location::from(Parachain(OTHER_PARA_ID)).into(),
Adrian Catangiu
committed
maybe_notify: Some((5, 2)),
Gavin Wood
committed
timeout: 100,
Gavin Wood
committed
};
assert_eq!(crate::Queries::<Test>::iter().collect::<Vec<_>>(), vec![(0, status)]);
let message = Xcm(vec![QueryResponse {
query_id: 0,
response: Response::ExecutionResult(None),
max_weight: Weight::from_parts(1_000_000, 1_000_000),
querier: Some(querier),
}]);
let mut hash = fake_message_hash(&message);
let r = XcmExecutor::<XcmConfig>::prepare_and_execute(
Adrian Catangiu
committed
Parachain(OTHER_PARA_ID),
Gavin Wood
committed
);
assert_eq!(r, Outcome::Complete { used: Weight::from_parts(1_000, 1_000) });
Gavin Wood
committed
assert_eq!(
last_events(2),
vec![
RuntimeEvent::TestNotifier(pallet_test_notifier::Event::ResponseReceived(
Adrian Catangiu
committed
Parachain(OTHER_PARA_ID).into(),
Gavin Wood
committed
0,
Response::ExecutionResult(None),
Gavin Wood
committed
)),
RuntimeEvent::XcmPallet(crate::Event::Notified {
query_id: 0,
Adrian Catangiu
committed
pallet_index: 5,
Gavin Wood
committed
]
);
assert_eq!(crate::Queries::<Test>::iter().collect::<Vec<_>>(), vec![]);
});
}
#[test]
fn report_outcome_works() {
let balances = vec![
(ALICE, INITIAL_BALANCE),
Adrian Catangiu
committed
(ParaId::from(OTHER_PARA_ID).into_account_truncating(), INITIAL_BALANCE),
let sender: Location = AccountId32 { network: None, id: ALICE.into() }.into();
let mut message = Xcm(vec![TransferAsset {
assets: (Here, SEND_AMOUNT).into(),
beneficiary: sender.clone(),
}]);
Gavin Wood
committed
new_test_ext_with_balances(balances).execute_with(|| {
Adrian Catangiu
committed
XcmPallet::report_outcome(&mut message, Parachain(OTHER_PARA_ID).into_location(), 100)
.unwrap();
Gavin Wood
committed
assert_eq!(
message,
Xcm(vec![
SetAppendix(Xcm(vec![ReportError(QueryResponseInfo {
destination: Parent.into(),
Gavin Wood
committed
query_id: 0,
TransferAsset { assets: (Here, SEND_AMOUNT).into(), beneficiary: sender },
Gavin Wood
committed
])
);
Gavin Wood
committed
let status = QueryStatus::Pending {
responder: Location::from(Parachain(OTHER_PARA_ID)).into(),
Gavin Wood
committed
maybe_notify: None,
timeout: 100,
Gavin Wood
committed
};
assert_eq!(crate::Queries::<Test>::iter().collect::<Vec<_>>(), vec![(0, status)]);
let message = Xcm(vec![QueryResponse {
query_id: 0,
response: Response::ExecutionResult(None),
max_weight: Weight::zero(),
querier: Some(querier),
}]);
let mut hash = fake_message_hash(&message);
let r = XcmExecutor::<XcmConfig>::prepare_and_execute(
Adrian Catangiu
committed
Parachain(OTHER_PARA_ID),
Gavin Wood
committed
);
assert_eq!(r, Outcome::Complete { used: Weight::from_parts(1_000, 1_000) });
RuntimeEvent::XcmPallet(crate::Event::ResponseReady {
query_id: 0,
response: Response::ExecutionResult(None),
})
let response =
QueryResponseStatus::Ready { response: Response::ExecutionResult(None), at: 1 };
assert_eq!(XcmPallet::take_response(0), response);
});
}
#[test]
fn custom_querier_works() {
let balances = vec![
(ALICE, INITIAL_BALANCE),
Adrian Catangiu
committed
(ParaId::from(OTHER_PARA_ID).into_account_truncating(), INITIAL_BALANCE),
];
new_test_ext_with_balances(balances).execute_with(|| {
let querier: Location = (Parent, AccountId32 { network: None, id: ALICE.into() }).into();
let r = TestNotifier::prepare_new_query(RuntimeOrigin::signed(ALICE), querier.clone());
assert_eq!(r, Ok(()));
let status = QueryStatus::Pending {
responder: Location::from(AccountId32 { network: None, id: ALICE.into() }).into(),
};
assert_eq!(crate::Queries::<Test>::iter().collect::<Vec<_>>(), vec![(0, status)]);
// Supplying no querier when one is expected will fail
let message = Xcm(vec![QueryResponse {
query_id: 0,
response: Response::ExecutionResult(None),
max_weight: Weight::zero(),
querier: None,
}]);
let mut hash = fake_message_hash(&message);
let r = XcmExecutor::<XcmConfig>::prepare_and_execute(
AccountId32 { network: None, id: ALICE.into() },
message,
Weight::from_parts(1_000_000_000, 1_000_000_000),
Weight::from_parts(1_000, 1_000),
);
assert_eq!(r, Outcome::Complete { used: Weight::from_parts(1_000, 1_000) });
RuntimeEvent::XcmPallet(crate::Event::InvalidQuerier {
origin: AccountId32 { network: None, id: ALICE.into() }.into(),
query_id: 0,
maybe_actual_querier: None,
}),
);
// Supplying the wrong querier will also fail
let message = Xcm(vec![QueryResponse {
query_id: 0,
response: Response::ExecutionResult(None),
max_weight: Weight::zero(),
let mut hash = fake_message_hash(&message);
let r = XcmExecutor::<XcmConfig>::prepare_and_execute(
AccountId32 { network: None, id: ALICE.into() },
message,
Weight::from_parts(1_000_000_000, 1_000_000_000),
Weight::from_parts(1_000, 1_000),
);
assert_eq!(r, Outcome::Complete { used: Weight::from_parts(1_000, 1_000) });
RuntimeEvent::XcmPallet(crate::Event::InvalidQuerier {
origin: AccountId32 { network: None, id: ALICE.into() }.into(),
query_id: 0,
expected_querier: querier.clone(),
maybe_actual_querier: Some(Location::here()),
);
// Multiple failures should not have changed the query state
let message = Xcm(vec![QueryResponse {
query_id: 0,
response: Response::ExecutionResult(None),
max_weight: Weight::zero(),
querier: Some(querier),
}]);
let mut hash = fake_message_hash(&message);
let r = XcmExecutor::<XcmConfig>::prepare_and_execute(
AccountId32 { network: None, id: ALICE.into() },
message,
assert_eq!(r, Outcome::Complete { used: Weight::from_parts(1_000, 1_000) });
Gavin Wood
committed
assert_eq!(
last_event(),
RuntimeEvent::XcmPallet(crate::Event::ResponseReady {
query_id: 0,
response: Response::ExecutionResult(None),
})
Gavin Wood
committed
);
let response =
QueryResponseStatus::Ready { response: Response::ExecutionResult(None), at: 1 };
Gavin Wood
committed
assert_eq!(XcmPallet::take_response(0), response);
});
}
/// Test sending an `XCM` message (`XCM::ReserveAssetDeposit`)
///
/// Asserts that the expected message is sent and the event is emitted
#[test]
fn send_works() {
let balances = vec![
(ALICE, INITIAL_BALANCE),
Adrian Catangiu
committed
(ParaId::from(OTHER_PARA_ID).into_account_truncating(), INITIAL_BALANCE),
new_test_ext_with_balances(balances).execute_with(|| {
let sender: Location = AccountId32 { network: None, id: ALICE.into() }.into();
Gavin Wood
committed
let message = Xcm(vec![
ReserveAssetDeposited((Parent, SEND_AMOUNT).into()),
ClearOrigin,
buy_execution((Parent, SEND_AMOUNT)),
DepositAsset { assets: AllCounted(1).into(), beneficiary: sender.clone() },
Gavin Wood
committed
]);
Gavin Wood
committed
let versioned_dest = Box::new(RelayLocation::get().into());
let versioned_message = Box::new(VersionedXcm::from(message.clone()));
assert_ok!(XcmPallet::send(
RuntimeOrigin::signed(ALICE),
versioned_dest,
versioned_message
));
let sent_message = Xcm(Some(DescendOrigin(sender.clone().try_into().unwrap()))
.into_iter()
.chain(message.0.clone().into_iter())
.collect());
let id = fake_message_hash(&sent_message);
assert_eq!(sent_xcm(), vec![(Here.into(), sent_message)]);
RuntimeEvent::XcmPallet(crate::Event::Sent {
origin: sender,
destination: RelayLocation::get(),
message,
message_id: id,
})
);
});
}
/// Test that sending an `XCM` message fails when the `XcmRouter` blocks the
/// matching message format
///
/// Asserts that `send` fails with `Error::SendFailure`
#[test]
fn send_fails_when_xcm_router_blocks() {
let balances = vec![
(ALICE, INITIAL_BALANCE),
Adrian Catangiu
committed
(ParaId::from(OTHER_PARA_ID).into_account_truncating(), INITIAL_BALANCE),
new_test_ext_with_balances(balances).execute_with(|| {
let sender: Location = Junction::AccountId32 { network: None, id: ALICE.into() }.into();
Gavin Wood
committed
let message = Xcm(vec![
ReserveAssetDeposited((Parent, SEND_AMOUNT).into()),
buy_execution((Parent, SEND_AMOUNT)),
DepositAsset { assets: AllCounted(1).into(), beneficiary: sender },
Gavin Wood
committed
]);
Box::new(VersionedXcm::from(message.clone())),
),
crate::Error::<Test>::SendFailure
);
});
}
/// Test local execution of XCM
///
/// Asserts that the sender's balance is decreased and the beneficiary's balance
/// is increased. Verifies the expected event is emitted.
#[test]
fn execute_withdraw_to_deposit_works() {
let balances = vec![
(ALICE, INITIAL_BALANCE),
Adrian Catangiu
committed
(ParaId::from(OTHER_PARA_ID).into_account_truncating(), INITIAL_BALANCE),
new_test_ext_with_balances(balances).execute_with(|| {
let dest: Location = Junction::AccountId32 { network: None, id: BOB.into() }.into();
assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE);
assert_ok!(XcmPallet::execute(
Gavin Wood
committed
Box::new(VersionedXcm::from(Xcm(vec![
WithdrawAsset((Here, SEND_AMOUNT).into()),
buy_execution((Here, SEND_AMOUNT)),
DepositAsset { assets: AllCounted(1).into(), beneficiary: dest },
Gavin Wood
committed
]))),
));
assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE - SEND_AMOUNT);
assert_eq!(Balances::total_balance(&BOB), SEND_AMOUNT);
assert_eq!(
last_event(),
RuntimeEvent::XcmPallet(crate::Event::Attempted {
outcome: Outcome::Complete { used: weight }
})
/// Test drop/claim assets.
#[test]
fn trapped_assets_can_be_claimed() {
let balances = vec![(ALICE, INITIAL_BALANCE), (BOB, INITIAL_BALANCE)];
new_test_ext_with_balances(balances).execute_with(|| {
let dest: Location = Junction::AccountId32 { network: None, id: BOB.into() }.into();
assert_ok!(XcmPallet::execute(
Box::new(VersionedXcm::from(Xcm(vec![
WithdrawAsset((Here, SEND_AMOUNT).into()),
buy_execution((Here, SEND_AMOUNT)),
// Don't propagated the error into the result.
SetErrorHandler(Xcm(vec![ClearError])),
// This will make an error.
Trap(0),
// This would succeed, but we never get to it.
DepositAsset { assets: AllCounted(1).into(), beneficiary: dest.clone() },
let source: Location = Junction::AccountId32 { network: None, id: ALICE.into() }.into();
let trapped = AssetTraps::<Test>::iter().collect::<Vec<_>>();
let vma = VersionedAssets::from(Assets::from((Here, SEND_AMOUNT)));
let hash = BlakeTwo256::hash_of(&(source.clone(), vma.clone()));
assert_eq!(
last_events(2),
vec![
RuntimeEvent::XcmPallet(crate::Event::AssetsTrapped {
origin: source,
assets: vma
}),
RuntimeEvent::XcmPallet(crate::Event::Attempted {
outcome: Outcome::Complete { used: BaseXcmWeight::get() * 5 }
]
);
assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE - SEND_AMOUNT);
assert_eq!(Balances::total_balance(&BOB), INITIAL_BALANCE);
let expected = vec![(hash, 1u32)];
assert_eq!(trapped, expected);
assert_ok!(XcmPallet::execute(
Box::new(VersionedXcm::from(Xcm(vec![
ClaimAsset { assets: (Here, SEND_AMOUNT).into(), ticket: Here.into() },
buy_execution((Here, SEND_AMOUNT)),
DepositAsset { assets: AllCounted(1).into(), beneficiary: dest.clone() },
));
assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE - SEND_AMOUNT);
assert_eq!(Balances::total_balance(&BOB), INITIAL_BALANCE + SEND_AMOUNT);
assert_eq!(AssetTraps::<Test>::iter().collect::<Vec<_>>(), vec![]);
// Can't claim twice.
assert_err_ignore_postinfo!(
XcmPallet::execute(
RuntimeOrigin::signed(ALICE),
Box::new(VersionedXcm::from(Xcm(vec![
ClaimAsset { assets: (Here, SEND_AMOUNT).into(), ticket: Here.into() },
buy_execution((Here, SEND_AMOUNT)),
DepositAsset { assets: AllCounted(1).into(), beneficiary: dest },
]))),
weight
),
Error::<Test>::LocalExecutionIncomplete
);
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
// Like `trapped_assets_can_be_claimed` but using the `claim_assets` extrinsic.
#[test]
fn claim_assets_works() {
let balances = vec![(ALICE, INITIAL_BALANCE)];
new_test_ext_with_balances(balances).execute_with(|| {
// First trap some assets.
let trapping_program =
Xcm::builder_unsafe().withdraw_asset((Here, SEND_AMOUNT).into()).build();
// Even though assets are trapped, the extrinsic returns success.
assert_ok!(XcmPallet::execute(
RuntimeOrigin::signed(ALICE),
Box::new(VersionedXcm::V4(trapping_program)),
BaseXcmWeight::get() * 2,
));
assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE - SEND_AMOUNT);
// Expected `AssetsTrapped` event info.
let source: Location = Junction::AccountId32 { network: None, id: ALICE.into() }.into();
let versioned_assets = VersionedAssets::V4(Assets::from((Here, SEND_AMOUNT)));
let hash = BlakeTwo256::hash_of(&(source.clone(), versioned_assets.clone()));
// Assets were indeed trapped.
assert_eq!(
last_events(2),
vec![
RuntimeEvent::XcmPallet(crate::Event::AssetsTrapped {
hash,
origin: source,
assets: versioned_assets
}),
RuntimeEvent::XcmPallet(crate::Event::Attempted {
outcome: Outcome::Complete { used: BaseXcmWeight::get() * 1 }
})
],
);
let trapped = AssetTraps::<Test>::iter().collect::<Vec<_>>();
assert_eq!(trapped, vec![(hash, 1)]);
// Now claim them with the extrinsic.
assert_ok!(XcmPallet::claim_assets(
RuntimeOrigin::signed(ALICE),
Box::new(VersionedAssets::V4((Here, SEND_AMOUNT).into())),
Box::new(VersionedLocation::V4(
AccountId32 { network: None, id: ALICE.clone().into() }.into()
)),
));
assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE);
assert_eq!(AssetTraps::<Test>::iter().collect::<Vec<_>>(), vec![]);
});
}
Xiliang Chen
committed
/// Test failure to complete execution reverts intermediate side-effects.
///
/// XCM program will withdraw and deposit some assets, then fail execution of a further withdraw.
/// Assert that the previous instructions effects are reverted.
#[test]
fn incomplete_execute_reverts_side_effects() {
let balances = vec![(ALICE, INITIAL_BALANCE), (BOB, INITIAL_BALANCE)];
new_test_ext_with_balances(balances).execute_with(|| {
let weight = BaseXcmWeight::get() * 4;
let dest: Location = Junction::AccountId32 { network: None, id: BOB.into() }.into();
Xiliang Chen
committed
assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE);
let amount_to_send = INITIAL_BALANCE - ExistentialDeposit::get();
Xiliang Chen
committed
let result = XcmPallet::execute(
RuntimeOrigin::signed(ALICE),
Box::new(VersionedXcm::from(Xcm(vec![
// Withdraw + BuyExec + Deposit should work
WithdrawAsset(assets.clone()),
buy_execution(assets.inner()[0].clone()),
DepositAsset { assets: assets.clone().into(), beneficiary: dest },
// Withdrawing once more will fail because of InsufficientBalance, and we expect to
// revert the effects of the above instructions as well
WithdrawAsset(assets),
]))),
weight,
);
// all effects are reverted and balances unchanged for either sender or receiver
assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE);
assert_eq!(Balances::total_balance(&BOB), INITIAL_BALANCE);
Xiliang Chen
committed
assert_eq!(
result,
Err(sp_runtime::DispatchErrorWithPostInfo {
post_info: frame_support::dispatch::PostDispatchInfo {
actual_weight: Some(
<Pallet<Test> as ExecuteControllerWeightInfo>::execute() + weight
),
Xiliang Chen
committed
pays_fee: frame_support::dispatch::Pays::Yes,
},
error: sp_runtime::DispatchError::Module(sp_runtime::ModuleError {
index: 4,
error: [24, 0, 0, 0,],
message: Some("LocalExecutionIncomplete")
})
})
);
});
}
let remote: Location = Parachain(1000).into();
let versioned_remote = LatestVersionedLocation(&remote);
assert_eq!(versioned_remote.encode(), remote.into_versioned().encode());
}
#[test]
fn basic_subscription_works() {
new_test_ext_with_balances(vec![]).execute_with(|| {
assert_ok!(XcmPallet::force_subscribe_version_notify(
));
assert_eq!(
Queries::<Test>::iter().collect::<Vec<_>>(),
vec![(
0,
QueryStatus::VersionNotifier { origin: remote.clone().into(), is_active: false }
)]
);
assert_eq!(
VersionNotifiers::<Test>::iter().collect::<Vec<_>>(),
);
assert_eq!(
take_sent_xcm(),
vec![(
Xcm(vec![SubscribeVersion { query_id: 0, max_response_weight: Weight::zero() }]),
),]
);
let weight = BaseXcmWeight::get();
let mut message = Xcm::<()>(vec![
// Remote supports XCM v2
QueryResponse {
query_id: 0,
max_weight: Weight::zero(),
response: Response::Version(1),
querier: None,
},
]);
assert_ok!(AllowKnownQueryResponses::<XcmPallet>::should_execute(
&remote,
&mut Properties { weight_credit: Weight::zero(), message_id: None },
));
});
}
#[test]
fn subscriptions_increment_id() {
new_test_ext_with_balances(vec![]).execute_with(|| {
assert_ok!(XcmPallet::force_subscribe_version_notify(
assert_ok!(XcmPallet::force_subscribe_version_notify(
));
assert_eq!(
take_sent_xcm(),
vec![
(
Xcm(vec![SubscribeVersion {
query_id: 0,
max_response_weight: Weight::zero()
}]),
Xcm(vec![SubscribeVersion {
query_id: 1,
max_response_weight: Weight::zero()
}]),
),
]
);
});
}
#[test]
fn double_subscription_fails() {
new_test_ext_with_balances(vec![]).execute_with(|| {
assert_ok!(XcmPallet::force_subscribe_version_notify(
));
assert_noop!(
XcmPallet::force_subscribe_version_notify(
),
Error::<Test>::AlreadySubscribed,
);
})
}
#[test]
fn unsubscribe_works() {
new_test_ext_with_balances(vec![]).execute_with(|| {
assert_ok!(XcmPallet::force_subscribe_version_notify(
));
assert_ok!(XcmPallet::force_unsubscribe_version_notify(
));
assert_noop!(
XcmPallet::force_unsubscribe_version_notify(
),
Error::<Test>::NoSubscription,
);
assert_eq!(
take_sent_xcm(),
vec![
(
Xcm(vec![SubscribeVersion {
query_id: 0,
max_response_weight: Weight::zero()
}]),
]
);
});
}
/// Parachain 1000 is asking us for a version subscription.
#[test]
fn subscription_side_works() {
new_test_ext_with_balances(vec![]).execute_with(|| {
AdvertisedXcmVersion::set(1);
let weight = BaseXcmWeight::get();
let message =
Xcm(vec![SubscribeVersion { query_id: 0, max_response_weight: Weight::zero() }]);
let mut hash = fake_message_hash(&message);
let r = XcmExecutor::<XcmConfig>::prepare_and_execute(
remote.clone(),
message,
&mut hash,
weight,
Weight::zero(),
);
assert_eq!(r, Outcome::Complete { used: weight });
let instr = QueryResponse {
query_id: 0,
max_weight: Weight::zero(),
response: Response::Version(1),
querier: None,
};
assert_eq!(take_sent_xcm(), vec![(remote.clone(), Xcm(vec![instr]))]);
// A runtime upgrade which doesn't alter the version sends no notifications.
CurrentMigration::<Test>::put(VersionMigrationStage::default());
XcmPallet::on_initialize(1);
assert_eq!(take_sent_xcm(), vec![]);
// New version.
AdvertisedXcmVersion::set(2);
// A runtime upgrade which alters the version does send notifications.
CurrentMigration::<Test>::put(VersionMigrationStage::default());
let instr = QueryResponse {
query_id: 0,
max_weight: Weight::zero(),
response: Response::Version(2),
querier: None,
};
assert_eq!(take_sent_xcm(), vec![(remote, Xcm(vec![instr]))]);
});
}
#[test]
fn subscription_side_upgrades_work_with_notify() {
new_test_ext_with_balances(vec![]).execute_with(|| {
AdvertisedXcmVersion::set(1);
let v2_location = VersionedLocation::V2(xcm::v2::Junction::Parachain(1001).into());
VersionNotifyTargets::<Test>::insert(1, v2_location, (70, Weight::zero(), 2));
let v3_location = Parachain(1003).into_versioned();
VersionNotifyTargets::<Test>::insert(3, v3_location, (72, Weight::zero(), 2));
// A runtime upgrade which alters the version does send notifications.
CurrentMigration::<Test>::put(VersionMigrationStage::default());
XcmPallet::on_initialize(1);
let instr1 = QueryResponse {
query_id: 70,
max_weight: Weight::zero(),
response: Response::Version(3),
querier: None,
};
let instr3 = QueryResponse {
query_id: 72,
max_weight: Weight::zero(),
response: Response::Version(3),
querier: None,
};
let mut sent = take_sent_xcm();
sent.sort_by_key(|k| match (k.1).0[0] {
QueryResponse { query_id: q, .. } => q,
_ => 0,
});
assert_eq!(
sent,
vec![
(Parachain(1001).into(), Xcm(vec![instr1])),
]
);
let mut contents = VersionNotifyTargets::<Test>::iter().collect::<Vec<_>>();
assert_eq!(
contents,
vec![
(XCM_VERSION, Parachain(1001).into_versioned(), (70, Weight::zero(), 3)),
(XCM_VERSION, Parachain(1003).into_versioned(), (72, Weight::zero(), 3)),
]
);
});
}
#[test]
fn subscription_side_upgrades_work_without_notify() {
new_test_ext_with_balances(vec![]).execute_with(|| {
let v2_location = VersionedLocation::V2(xcm::v2::Junction::Parachain(1001).into());
VersionNotifyTargets::<Test>::insert(1, v2_location, (70, Weight::zero(), 2));
let v3_location = Parachain(1003).into_versioned();
VersionNotifyTargets::<Test>::insert(3, v3_location, (72, Weight::zero(), 2));
// A runtime upgrade which alters the version does send notifications.
CurrentMigration::<Test>::put(VersionMigrationStage::default());
XcmPallet::on_initialize(1);
let mut contents = VersionNotifyTargets::<Test>::iter().collect::<Vec<_>>();
assert_eq!(
contents,
vec![
(XCM_VERSION, Parachain(1001).into_versioned(), (70, Weight::zero(), 4)),
(XCM_VERSION, Parachain(1003).into_versioned(), (72, Weight::zero(), 4)),
]
);
});
}
#[test]
fn subscriber_side_subscription_works() {
new_test_ext_with_balances_and_xcm_version(vec![], Some(XCM_VERSION)).execute_with(|| {
assert_ok!(XcmPallet::force_subscribe_version_notify(
assert_eq!(XcmPallet::get_version_for(&remote), None);
take_sent_xcm();
// Assume subscription target is working ok.
let weight = BaseXcmWeight::get();
let message = Xcm(vec![
// Remote supports XCM v2
QueryResponse {
query_id: 0,
max_weight: Weight::zero(),
response: Response::Version(1),
querier: None,
},
let mut hash = fake_message_hash(&message);
let r = XcmExecutor::<XcmConfig>::prepare_and_execute(
remote.clone(),
message,
&mut hash,
weight,
Weight::zero(),
);
assert_eq!(r, Outcome::Complete { used: weight });
assert_eq!(take_sent_xcm(), vec![]);
assert_eq!(XcmPallet::get_version_for(&remote), Some(1));
// This message cannot be sent to a v2 remote.
let v2_msg = xcm::v2::Xcm::<()>(vec![xcm::v2::Instruction::Trap(0)]);
assert_eq!(XcmPallet::wrap_version(&remote, v2_msg.clone()), Err(()));
let message = Xcm(vec![
// Remote upgraded to XCM v2
QueryResponse {
query_id: 0,
max_weight: Weight::zero(),
response: Response::Version(2),
querier: None,
},
let mut hash = fake_message_hash(&message);
let r = XcmExecutor::<XcmConfig>::prepare_and_execute(
remote.clone(),
message,
&mut hash,
weight,
Weight::zero(),
);
assert_eq!(r, Outcome::Complete { used: weight });
assert_eq!(take_sent_xcm(), vec![]);
assert_eq!(XcmPallet::get_version_for(&remote), Some(2));
// This message can now be sent to remote as it's v2.
assert_eq!(
XcmPallet::wrap_version(&remote, v2_msg.clone()),
Ok(VersionedXcm::from(v2_msg))
);
});
}
/// We should auto-subscribe when we don't know the remote's version.
#[test]
fn auto_subscription_works() {
new_test_ext_with_balances_and_xcm_version(vec![], None).execute_with(|| {
let remote_v2: Location = Parachain(1000).into();
let remote_v4: Location = Parachain(1001).into();
assert_ok!(XcmPallet::force_default_xcm_version(RuntimeOrigin::root(), Some(2)));
// Wrapping a version for a destination we don't know elicits a subscription.
let msg_v2 = xcm::v2::Xcm::<()>(vec![xcm::v2::Instruction::Trap(0)]);
let msg_v4 = xcm::v4::Xcm::<()>(vec![xcm::v4::Instruction::ClearTopic]);
XcmPallet::wrap_version(&remote_v2, msg_v2.clone()),
Ok(VersionedXcm::from(msg_v2.clone())),
assert_eq!(XcmPallet::wrap_version(&remote_v2, msg_v4.clone()), Err(()));
assert_eq!(VersionDiscoveryQueue::<Test>::get().into_inner(), expected);
assert_eq!(XcmPallet::wrap_version(&remote_v4, msg_v4.clone()), Err(()));
let expected = vec![(remote_v2.clone().into(), 2), (remote_v4.clone().into(), 2)];
assert_eq!(VersionDiscoveryQueue::<Test>::get().into_inner(), expected);
XcmPallet::on_initialize(1);
assert_eq!(
take_sent_xcm(),
vec![(
Xcm(vec![SubscribeVersion { query_id: 0, max_response_weight: Weight::zero() }]),
let weight = BaseXcmWeight::get();
let message = Xcm(vec![
QueryResponse {
query_id: 0,
max_weight: Weight::zero(),
let mut hash = fake_message_hash(&message);
let r = XcmExecutor::<XcmConfig>::prepare_and_execute(
remote_v4.clone(),
message,
&mut hash,
weight,
Weight::zero(),
);
assert_eq!(r, Outcome::Complete { used: weight });
XcmPallet::wrap_version(&remote_v4, msg_v2.clone()),
Ok(VersionedXcm::from(msg_v2.clone()).into_version(4).unwrap()),
// This message can now be sent to remote_v4 as it's v4.
XcmPallet::wrap_version(&remote_v4, msg_v4.clone()),
Ok(VersionedXcm::from(msg_v4.clone()))
);
XcmPallet::on_initialize(2);
assert_eq!(
take_sent_xcm(),
vec![(
Xcm(vec![SubscribeVersion { query_id: 1, max_response_weight: Weight::zero() }]),
let weight = BaseXcmWeight::get();
let message = Xcm(vec![