use super::{
mock::{mk_page, versioned_xcm, EnqueuedMessages, HRMP_PARA_ID},
*,
};
use std::collections::BTreeMap;
use XcmpMessageFormat::*;
use codec::Input;
use cumulus_primitives_core::{ParaId, XcmpMessageHandler};
use frame_support::{
assert_err, assert_noop, assert_ok, assert_storage_noop, hypothetically,
traits::{BatchFootprint, Hooks},
StorageNoopGuard,
};
use mock::{new_test_ext, FirstPagePos, ParachainSystem, RuntimeOrigin as Origin, Test, XcmpQueue};
use sp_runtime::traits::{BadOrigin, Zero};
use std::iter::{once, repeat};
use xcm::{MAX_INSTRUCTIONS_TO_DECODE, MAX_XCM_DECODE_DEPTH};
use xcm_builder::InspectMessageQueues;
fn generate_mock_xcm(idx: usize) -> VersionedXcm<Test> {
VersionedXcm::<Test>::from(Xcm::<Test>(vec![Trap(idx as u64)]))
}
fn generate_mock_xcm_batch(start_idx: usize, xcm_count: usize) -> Vec<VersionedXcm<Test>> {
let mut batch = vec![];
for i in 0..xcm_count {
batch.push(generate_mock_xcm(start_idx + i));
}
batch
}
fn encode_xcm_batch(
xcms: Vec<VersionedXcm<Test>>,
xcm_encoding: XcmEncoding,
) -> Vec<BoundedVec<u8, MaxXcmpMessageLenOf<Test>>> {
let mut data = vec![];
for xcm in xcms {
let xcm = match xcm_encoding {
XcmEncoding::Simple => xcm.encode(),
XcmEncoding::Double => xcm.encode().encode(),
};
data.push(BoundedVec::try_from(xcm).unwrap());
}
data
}
fn generate_mock_xcm_page(
start_idx: usize,
xcm_count: usize,
xcm_encoding: XcmEncoding,
) -> Vec<u8> {
let mut data: Vec<u8> = match xcm_encoding {
XcmEncoding::Simple => ConcatenatedVersionedXcm,
XcmEncoding::Double => ConcatenatedOpaqueVersionedXcm,
}
.encode();
let xcms = generate_mock_xcm_batch(start_idx, xcm_count);
let xcms = encode_xcm_batch(xcms, xcm_encoding);
for xcm in xcms {
data.extend(xcm)
}
data
}
#[test]
fn handling_signals_works() {
new_test_ext().execute_with(|| {
fn get_channel(recipient: ParaId) -> Option<OutboundChannelDetails> {
<OutboundXcmpStatus<Test>>::get()
.into_iter()
.find(|item| item.recipient == recipient)
}
let page = (XcmpMessageFormat::Signals, ChannelSignal::Suspend).encode();
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, page.as_slice())), Weight::MAX);
let channel = get_channel(1000.into()).unwrap();
assert_eq!(channel.state, OutboundState::Suspended);
let page = (XcmpMessageFormat::Signals, ChannelSignal::Resume).encode();
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, page.as_slice())), Weight::MAX);
let channel = get_channel(1000.into());
assert_eq!(channel, None);
let page = (
XcmpMessageFormat::Signals,
ChannelSignal::Suspend,
ChannelSignal::Resume,
ChannelSignal::Suspend,
ChannelSignal::Resume,
ChannelSignal::Resume,
ChannelSignal::Resume,
)
.encode();
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, page.as_slice())), Weight::MAX);
let channel = get_channel(1000.into()).unwrap();
assert_eq!(channel.state, OutboundState::Suspended);
})
}
#[test]
fn empty_concatenated_works() {
new_test_ext().execute_with(|| {
let page = generate_mock_xcm_page(0, 0, XcmEncoding::Simple);
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, page.as_slice())), Weight::MAX);
assert_eq!(EnqueuedMessages::get(), vec![]);
})
}
fn test_basic_xcm_enqueueing(xcm_encoding: XcmEncoding) {
new_test_ext().execute_with(|| {
EnqueuedMessages::set(vec![]);
let page = generate_mock_xcm_page(0, 0, xcm_encoding);
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, page.as_slice())), Weight::MAX);
assert_eq!(EnqueuedMessages::get(), vec![]);
EnqueuedMessages::set(vec![]);
let page = generate_mock_xcm_page(0, 1, xcm_encoding);
let xcms = encode_xcm_batch(generate_mock_xcm_batch(0, 1), XcmEncoding::Simple);
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, page.as_slice())), Weight::MAX);
assert_eq!(
EnqueuedMessages::get(),
xcms.into_iter().map(|xcm| (1000.into(), xcm.into_inner())).collect::<Vec<_>>()
);
EnqueuedMessages::set(vec![]);
let page = generate_mock_xcm_page(0, 10, xcm_encoding);
let xcms = encode_xcm_batch(generate_mock_xcm_batch(0, 10), XcmEncoding::Simple);
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, page.as_slice())), Weight::MAX);
assert_eq!(
EnqueuedMessages::get(),
xcms.into_iter().map(|xcm| (1000.into(), xcm.into_inner())).collect::<Vec<_>>(),
);
EnqueuedMessages::set(vec![]);
let mut xcms = vec![];
for i in 0..4 {
let page = generate_mock_xcm_page(i * 10, 10, xcm_encoding);
xcms.extend(encode_xcm_batch(generate_mock_xcm_batch(i * 10, 10), XcmEncoding::Simple));
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, page.as_slice())), Weight::MAX);
assert_eq!((i + 1) * 10, EnqueuedMessages::get().len());
}
assert_eq!(
EnqueuedMessages::get(),
xcms.into_iter().map(|xcm| (1000.into(), xcm.into_inner())).collect::<Vec<_>>()
);
});
}
#[test]
fn basic_xcm_enqueueing_works() {
test_basic_xcm_enqueueing(XcmEncoding::Simple);
test_basic_xcm_enqueueing(XcmEncoding::Double);
}
fn test_enqueueing_more_than_a_xcm_batch(xcm_encoding: XcmEncoding) {
new_test_ext().execute_with(|| {
EnqueuedMessages::set(vec![]);
<QueueConfig<Test>>::set(QueueConfigData {
suspend_threshold: 500,
drop_threshold: 500,
resume_threshold: 500,
});
let page = generate_mock_xcm_page(0, 300, xcm_encoding);
let xcms = encode_xcm_batch(generate_mock_xcm_batch(0, 300), XcmEncoding::Simple);
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, page.as_slice())), Weight::MAX);
assert_eq!(
EnqueuedMessages::get(),
xcms.into_iter().map(|xcm| (1000.into(), xcm.into_inner())).collect::<Vec<_>>()
);
})
}
#[test]
fn test_enqueueing_more_than_a_xcm_batch_works() {
test_enqueueing_more_than_a_xcm_batch(XcmEncoding::Simple);
test_enqueueing_more_than_a_xcm_batch(XcmEncoding::Double);
}
fn test_xcm_enqueueing_starts_dropping_on_overflow(xcm_encoding: XcmEncoding) {
new_test_ext().execute_with(|| {
EnqueuedMessages::set(vec![]);
<QueueConfig<Test>>::set(QueueConfigData {
suspend_threshold: 10,
drop_threshold: 10,
resume_threshold: 10,
});
XcmpQueue::handle_xcmp_messages(
once((1000.into(), 1, generate_mock_xcm_page(0, 11, xcm_encoding).as_slice())),
Weight::MAX,
);
assert_eq!(EnqueuedMessages::get().len(), 10);
EnqueuedMessages::set(vec![]);
XcmpQueue::handle_xcmp_messages(
[
(1000.into(), 1, generate_mock_xcm_page(0, 10, xcm_encoding).as_slice()),
(2000.into(), 1, generate_mock_xcm_page(0, 10, xcm_encoding).as_slice()),
]
.into_iter(),
Weight::MAX,
);
assert_eq!(EnqueuedMessages::get().len(), 20);
<QueueConfig<Test>>::set(QueueConfigData {
suspend_threshold: 300,
drop_threshold: 300,
resume_threshold: 300,
});
EnqueuedMessages::set(vec![]);
XcmpQueue::handle_xcmp_messages(
once((1000.into(), 1, generate_mock_xcm_page(0, 315, xcm_encoding).as_slice())),
Weight::MAX,
);
assert_eq!(EnqueuedMessages::get().len(), 300);
EnqueuedMessages::set(vec![]);
XcmpQueue::handle_xcmp_messages(
[
(1000.into(), 1, generate_mock_xcm_page(0, 200, xcm_encoding).as_slice()),
(1000.into(), 1, generate_mock_xcm_page(0, 150, xcm_encoding).as_slice()),
]
.into_iter(),
Weight::MAX,
);
assert_eq!(EnqueuedMessages::get().len(), 300);
})
}
#[test]
fn xcm_enqueueing_starts_dropping_on_overflow() {
test_xcm_enqueueing_starts_dropping_on_overflow(XcmEncoding::Simple);
test_xcm_enqueueing_starts_dropping_on_overflow(XcmEncoding::Double);
}
#[test]
fn xcm_enqueueing_starts_dropping_on_out_of_weight() {
new_test_ext().execute_with(|| {
<QueueConfig<Test>>::set(QueueConfigData {
suspend_threshold: 300,
drop_threshold: 300,
resume_threshold: 300,
});
let mut total_size = 0;
let xcms = encode_xcm_batch(generate_mock_xcm_batch(0, 10), XcmEncoding::Simple);
for (idx, xcm) in xcms.iter().enumerate() {
EnqueuedMessages::set(vec![]);
total_size += xcm.len();
let required_weight = <<Test as Config>::WeightInfo>::enqueue_xcmp_messages(
0,
&BatchFootprint {
msgs_count: idx + 1,
size_in_bytes: total_size,
new_pages_count: idx as u32 + 1,
},
true,
);
let mut weight_meter = WeightMeter::with_limit(required_weight);
let res = XcmpQueue::enqueue_xcmp_messages(
1000.into(),
&xcms.iter().map(|xcm| xcm.as_bounded_slice()).collect::<Vec<_>>(),
true,
&mut weight_meter,
);
if idx < xcms.len() - 1 {
assert!(res.is_err());
} else {
assert!(res.is_ok());
}
assert_eq!(
EnqueuedMessages::get(),
xcms[..idx + 1]
.iter()
.map(|xcm| (1000.into(), xcm.to_vec()))
.collect::<Vec<_>>()
);
}
})
}
#[test]
fn xcm_enqueueing_uses_correct_pov_size() {
test_xcm_enqueueing_uses_correct_pov_size(XcmEncoding::Simple);
test_xcm_enqueueing_uses_correct_pov_size(XcmEncoding::Double);
}
fn test_xcm_enqueueing_uses_correct_pov_size(xcm_encoding: XcmEncoding) {
let first_page_pos = 100_000;
new_test_ext().execute_with(|| {
<QueueConfig<Test>>::set(QueueConfigData {
suspend_threshold: 300,
drop_threshold: 300,
resume_threshold: 300,
});
FirstPagePos::set(BTreeMap::from([
(1000.into(), first_page_pos),
(2000.into(), first_page_pos),
]));
let page = generate_mock_xcm_page(0, 1, xcm_encoding);
let consumed_weight = XcmpQueue::handle_xcmp_messages(
[
(1000.into(), 1, page.as_slice()),
(1000.into(), 1, page.as_slice()),
(1000.into(), 1, page.as_slice()),
(1000.into(), 1, page.as_slice()),
(1000.into(), 1, page.as_slice()),
]
.into_iter(),
Weight::MAX,
);
assert!(consumed_weight.proof_size() > first_page_pos as u64);
assert!(consumed_weight.proof_size() < 2 * first_page_pos as u64);
let consumed_weight = XcmpQueue::handle_xcmp_messages(
[
(1000.into(), 1, page.as_slice()),
(2000.into(), 1, page.as_slice()),
(1000.into(), 1, page.as_slice()),
(2000.into(), 1, page.as_slice()),
(1000.into(), 1, page.as_slice()),
(2000.into(), 1, page.as_slice()),
]
.into_iter(),
Weight::MAX,
);
assert!(consumed_weight.proof_size() > 2 * first_page_pos as u64);
assert!(consumed_weight.proof_size() < 3 * first_page_pos as u64);
})
}
#[test]
#[cfg(not(debug_assertions))]
fn xcm_enqueueing_broken_xcm_works() {
new_test_ext().execute_with(|| {
let mut encoded_xcms = vec![];
for _ in 0..10 {
let xcm = VersionedXcm::<Test>::from(Xcm::<Test>(vec![ClearOrigin]));
encoded_xcms.push(xcm.encode());
}
let mut good = ConcatenatedVersionedXcm.encode();
good.extend(encoded_xcms.iter().flatten());
let mut bad = ConcatenatedVersionedXcm.encode();
bad.extend(vec![0u8].into_iter());
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, good.as_slice())), Weight::MAX);
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, bad.as_slice())), Weight::MAX);
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, good.as_slice())), Weight::MAX);
assert_eq!(20, EnqueuedMessages::get().len());
assert_eq!(
EnqueuedMessages::get(),
encoded_xcms
.clone()
.into_iter()
.map(|xcm| (1000.into(), xcm))
.cycle()
.take(20)
.collect::<Vec<_>>(),
);
EnqueuedMessages::take();
XcmpQueue::handle_xcmp_messages(
vec![(1000.into(), 1, good.as_slice()), (1000.into(), 1, bad.as_slice())].into_iter(),
Weight::MAX,
);
assert_eq!(10, EnqueuedMessages::get().len());
assert_eq!(
EnqueuedMessages::get(),
encoded_xcms
.into_iter()
.map(|xcm| (1000.into(), xcm))
.cycle()
.take(10)
.collect::<Vec<_>>(),
);
})
}
#[test]
#[should_panic = "Blob messages are unhandled"]
#[cfg(debug_assertions)]
fn bad_blob_message_panics() {
new_test_ext().execute_with(|| {
let data = [ConcatenatedEncodedBlob.encode(), vec![1].encode()].concat();
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, data.as_slice())), Weight::MAX);
});
}
#[test]
#[cfg(not(debug_assertions))]
fn bad_blob_message_no_panic() {
new_test_ext().execute_with(|| {
let data = [ConcatenatedEncodedBlob.encode(), vec![1].encode()].concat();
frame_support::assert_storage_noop!(XcmpQueue::handle_xcmp_messages(
once((1000.into(), 1, data.as_slice())),
Weight::MAX,
));
});
}
#[test]
#[should_panic = "HRMP inbound decode stream broke; page will be dropped."]
#[cfg(debug_assertions)]
fn handle_invalid_data_panics() {
new_test_ext().execute_with(|| {
let data = [ConcatenatedVersionedXcm.encode(), Xcm::<Test>(vec![]).encode()].concat();
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, data.as_slice())), Weight::MAX);
});
}
#[test]
#[cfg(not(debug_assertions))]
fn handle_invalid_data_no_panic() {
new_test_ext().execute_with(|| {
let data = [ConcatenatedVersionedXcm.encode(), Xcm::<Test>(vec![]).encode()].concat();
frame_support::assert_storage_noop!(XcmpQueue::handle_xcmp_messages(
once((1000.into(), 1, data.as_slice())),
Weight::MAX,
));
});
}
#[test]
fn suspend_xcm_execution_works() {
new_test_ext().execute_with(|| {
assert!(!XcmpQueue::is_paused(&2000.into()));
QueueSuspended::<Test>::put(true);
assert!(XcmpQueue::is_paused(&2000.into()));
assert!(!XcmpQueue::is_paused(&999.into()));
});
}
#[test]
fn suspend_and_resume_xcm_execution_work() {
new_test_ext().execute_with(|| {
assert_noop!(XcmpQueue::suspend_xcm_execution(Origin::signed(1)), BadOrigin);
assert_ok!(XcmpQueue::suspend_xcm_execution(Origin::root()));
assert_noop!(
XcmpQueue::suspend_xcm_execution(Origin::root()),
Error::<Test>::AlreadySuspended
);
assert!(QueueSuspended::<Test>::get());
assert_noop!(XcmpQueue::resume_xcm_execution(Origin::signed(1)), BadOrigin);
assert_ok!(XcmpQueue::resume_xcm_execution(Origin::root()));
assert_noop!(
XcmpQueue::resume_xcm_execution(Origin::root()),
Error::<Test>::AlreadyResumed
);
assert!(!QueueSuspended::<Test>::get());
});
}
#[test]
fn xcm_enqueueing_backpressure_works() {
let para: ParaId = 1000.into();
new_test_ext().execute_with(|| {
assert_ok!(XcmpQueue::update_resume_threshold(Origin::root(), 1));
assert_ok!(XcmpQueue::update_suspend_threshold(Origin::root(), 3));
assert_ok!(XcmpQueue::update_drop_threshold(Origin::root(), 5));
let page = generate_mock_xcm_page(0, 1, XcmEncoding::Simple);
XcmpQueue::handle_xcmp_messages(repeat((para, 1, page.as_slice())).take(2), Weight::MAX);
assert_eq!(EnqueuedMessages::get().len(), 2);
assert!(InboundXcmpSuspended::<Test>::get().is_empty());
XcmpQueue::handle_xcmp_messages(once((para, 1, page.as_slice())), Weight::MAX);
assert_eq!(InboundXcmpSuspended::<Test>::get().iter().collect::<Vec<_>>(), vec![¶]);
let _ = std::panic::catch_unwind(|| {
XcmpQueue::handle_xcmp_messages(
repeat((para, 1, page.as_slice())).take(10),
Weight::MAX,
)
});
assert_eq!(EnqueuedMessages::get().len(), 5);
mock::EnqueueToLocalStorage::<Pallet<Test>>::sweep_queue(para);
XcmpQueue::handle_xcmp_messages(once((para, 1, page.as_slice())), Weight::MAX);
assert!(InboundXcmpSuspended::<Test>::get().is_empty());
XcmpQueue::handle_xcmp_messages(once((para, 1, page.as_slice())), Weight::MAX);
assert!(InboundXcmpSuspended::<Test>::get().is_empty());
});
}
#[test]
fn update_suspend_threshold_works() {
new_test_ext().execute_with(|| {
assert_eq!(<QueueConfig<Test>>::get().suspend_threshold, 32);
assert_noop!(XcmpQueue::update_suspend_threshold(Origin::signed(2), 49), BadOrigin);
assert_ok!(XcmpQueue::update_suspend_threshold(Origin::root(), 33));
assert_eq!(<QueueConfig<Test>>::get().suspend_threshold, 33);
});
}
#[test]
fn update_drop_threshold_works() {
new_test_ext().execute_with(|| {
assert_eq!(<QueueConfig<Test>>::get().drop_threshold, 48);
assert_ok!(XcmpQueue::update_drop_threshold(Origin::root(), 4000));
assert_noop!(XcmpQueue::update_drop_threshold(Origin::signed(2), 7), BadOrigin);
assert_eq!(<QueueConfig<Test>>::get().drop_threshold, 4000);
});
}
#[test]
fn update_resume_threshold_works() {
new_test_ext().execute_with(|| {
assert_eq!(<QueueConfig<Test>>::get().resume_threshold, 8);
assert_noop!(
XcmpQueue::update_resume_threshold(Origin::root(), 0),
Error::<Test>::BadQueueConfig
);
assert_noop!(
XcmpQueue::update_resume_threshold(Origin::root(), 33),
Error::<Test>::BadQueueConfig
);
assert_ok!(XcmpQueue::update_resume_threshold(Origin::root(), 16));
assert_noop!(XcmpQueue::update_resume_threshold(Origin::signed(7), 3), BadOrigin);
assert_eq!(<QueueConfig<Test>>::get().resume_threshold, 16);
});
}
struct OkFixedXcmHashWithAssertingRequiredInputsSender;
impl OkFixedXcmHashWithAssertingRequiredInputsSender {
const FIXED_XCM_HASH: [u8; 32] = [9; 32];
fn fixed_delivery_asset() -> Assets {
Assets::new()
}
fn expected_delivery_result() -> Result<(XcmHash, Assets), SendError> {
Ok((Self::FIXED_XCM_HASH, Self::fixed_delivery_asset()))
}
}
impl SendXcm for OkFixedXcmHashWithAssertingRequiredInputsSender {
type Ticket = ();
fn validate(
destination: &mut Option<Location>,
message: &mut Option<Xcm<()>>,
) -> SendResult<Self::Ticket> {
assert!(destination.is_some());
assert!(message.is_some());
Ok(((), OkFixedXcmHashWithAssertingRequiredInputsSender::fixed_delivery_asset()))
}
fn deliver(_: Self::Ticket) -> Result<XcmHash, SendError> {
Ok(Self::FIXED_XCM_HASH)
}
}
#[test]
fn xcmp_queue_does_not_consume_dest_or_msg_on_not_applicable() {
let message = Xcm(vec![Trap(5)]);
let dest = (Parent, Parent, Parent);
let mut dest_wrapper = Some(dest.into());
let mut msg_wrapper = Some(message.clone());
assert_eq!(
Err(SendError::NotApplicable),
<XcmpQueue as SendXcm>::validate(&mut dest_wrapper, &mut msg_wrapper)
);
assert_eq!(Some(dest.into()), dest_wrapper.take());
assert_eq!(Some(message.clone()), msg_wrapper.take());
assert_eq!(
OkFixedXcmHashWithAssertingRequiredInputsSender::expected_delivery_result(),
send_xcm::<(XcmpQueue, OkFixedXcmHashWithAssertingRequiredInputsSender)>(
dest.into(),
message
)
);
}
#[test]
fn xcmp_queue_consumes_dest_and_msg_on_ok_validate() {
let message = Xcm(vec![Trap(5)]);
let dest: Location = (Parent, Parachain(5555)).into();
let mut dest_wrapper = Some(dest.clone());
let mut msg_wrapper = Some(message.clone());
new_test_ext().execute_with(|| {
assert!(<XcmpQueue as SendXcm>::validate(&mut dest_wrapper, &mut msg_wrapper).is_ok());
assert_eq!(None, dest_wrapper.take());
assert_eq!(None, msg_wrapper.take());
assert_eq!(
Err(SendError::Transport("NoChannel")),
send_xcm::<(XcmpQueue, OkFixedXcmHashWithAssertingRequiredInputsSender)>(
dest.into(),
message
)
);
});
}
#[test]
fn xcmp_queue_validate_nested_xcm_works() {
let dest = (Parent, Parachain(5555));
let mut good = Xcm(vec![ClearOrigin]);
for _ in 0..MAX_XCM_DECODE_DEPTH - 1 {
good = Xcm(vec![SetAppendix(good)]);
}
new_test_ext().execute_with(|| {
assert_ok!(<XcmpQueue as SendXcm>::validate(
&mut Some(dest.into()),
&mut Some(good.clone())
));
let bad = Xcm(vec![SetAppendix(good)]);
assert_eq!(
Err(SendError::ExceedsMaxMessageSize),
<XcmpQueue as SendXcm>::validate(&mut Some(dest.into()), &mut Some(bad))
);
});
}
#[test]
fn send_xcm_nested_works() {
let dest = (Parent, Parachain(HRMP_PARA_ID));
let mut good = Xcm(vec![ClearOrigin]);
for _ in 0..MAX_XCM_DECODE_DEPTH - 1 {
good = Xcm(vec![SetAppendix(good)]);
}
new_test_ext().execute_with(|| {
assert_ok!(send_xcm::<XcmpQueue>(dest.into(), good.clone()));
assert_eq!(
XcmpQueue::take_outbound_messages(usize::MAX),
vec![(
HRMP_PARA_ID.into(),
(XcmpMessageFormat::ConcatenatedVersionedXcm, VersionedXcm::from(good.clone()))
.encode(),
)]
);
});
let bad = Xcm(vec![SetAppendix(good)]);
new_test_ext().execute_with(|| {
assert_err!(send_xcm::<XcmpQueue>(dest.into(), bad), SendError::ExceedsMaxMessageSize);
assert!(XcmpQueue::take_outbound_messages(usize::MAX).is_empty());
});
}
#[test]
fn hrmp_signals_are_prioritized() {
let message = Xcm(vec![Trap(5)]);
let sibling_para_id = ParaId::from(12345);
let dest = (Parent, Parachain(sibling_para_id.into()));
let mut dest_wrapper = Some(dest.into());
let mut msg_wrapper = Some(message.clone());
new_test_ext().execute_with(|| {
frame_system::Pallet::<Test>::set_block_number(1);
<XcmpQueue as SendXcm>::validate(&mut dest_wrapper, &mut msg_wrapper).unwrap();
assert_eq!(None, dest_wrapper.take());
assert_eq!(None, msg_wrapper.take());
ParachainSystem::open_custom_outbound_hrmp_channel_for_benchmarks_or_tests(
sibling_para_id,
cumulus_primitives_core::AbridgedHrmpChannel {
max_capacity: 128,
max_total_size: 1 << 16,
max_message_size: 128,
msg_count: 0,
total_size: 0,
mqc_head: None,
},
);
let taken = XcmpQueue::take_outbound_messages(130);
assert_eq!(taken, vec![]);
let num_events = frame_system::Pallet::<Test>::events().len();
for _ in 0..256 {
assert_ok!(send_xcm::<XcmpQueue>(dest.into(), message.clone()));
}
assert_eq!(num_events + 256, frame_system::Pallet::<Test>::events().len());
let mut expected_msg = XcmpMessageFormat::ConcatenatedVersionedXcm.encode();
for _ in 0..31 {
expected_msg.extend(VersionedXcm::from(message.clone()).encode());
}
hypothetically!({
let taken = XcmpQueue::take_outbound_messages(usize::MAX);
assert_eq!(taken, vec![(sibling_para_id.into(), expected_msg,)]);
});
assert_ok!(XcmpQueue::send_signal(sibling_para_id.into(), ChannelSignal::Suspend));
let taken = XcmpQueue::take_outbound_messages(130);
assert_eq!(
taken,
vec![(
sibling_para_id.into(),
(XcmpMessageFormat::Signals, ChannelSignal::Suspend).encode()
)]
);
});
}
#[test]
fn maybe_double_encoded_versioned_xcm_works() {
assert_eq!(VersionedXcm::<()>::V3(Default::default()).encode(), &[3, 0]);
assert_eq!(VersionedXcm::<()>::V4(Default::default()).encode(), &[4, 0]);
assert_eq!(VersionedXcm::<()>::V5(Default::default()).encode(), &[5, 0]);
}
#[test]
fn maybe_double_encoded_versioned_xcm_decode_page_works() {
let page = mk_page();
let newer_xcm_version = xcm::prelude::XCM_VERSION;
let older_xcm_version = newer_xcm_version - 1;
let input = &mut &page[..];
for i in 0..100 {
match (i % 2, VersionedXcm::<()>::decode(input)) {
(0, Ok(xcm)) => {
assert_eq!(xcm, versioned_xcm(older_xcm_version));
},
(1, Ok(xcm)) => {
assert_eq!(xcm, versioned_xcm(newer_xcm_version));
},
unexpected => unreachable!("{:?}", unexpected),
}
}
assert_eq!(input.remaining_len(), Ok(Some(0)), "All data consumed");
}
#[test]
fn take_first_concatenated_xcm_works() {
let page = mk_page();
let input = &mut &page[..];
let newer_xcm_version = xcm::prelude::XCM_VERSION;
let older_xcm_version = newer_xcm_version - 1;
for i in 0..100 {
let xcm = XcmpQueue::take_first_concatenated_xcm(input, &mut WeightMeter::new()).unwrap();
match i % 2 {
0 => {
assert_eq!(xcm.to_vec(), versioned_xcm(older_xcm_version).encode());
},
1 => {
assert_eq!(xcm.to_vec(), versioned_xcm(newer_xcm_version).encode());
},
unexpected => unreachable!("{:?}", unexpected),
}
}
assert_eq!(input.remaining_len(), Ok(Some(0)), "All data consumed");
}
#[test]
fn take_first_concatenated_xcm_good_recursion_depth_works() {
let mut good = Xcm::<()>(vec![ClearOrigin]);
for _ in 0..MAX_XCM_DECODE_DEPTH - 1 {
good = Xcm(vec![SetAppendix(good)]);
}
let good = VersionedXcm::from(good);
let page = good.encode();
assert_ok!(XcmpQueue::take_first_concatenated_xcm(&mut &page[..], &mut WeightMeter::new()));
}
#[test]
fn take_first_concatenated_xcm_good_bad_depth_errors() {
let mut bad = Xcm::<()>(vec![ClearOrigin]);
for _ in 0..MAX_XCM_DECODE_DEPTH {
bad = Xcm(vec![SetAppendix(bad)]);
}
let bad = VersionedXcm::from(bad);
let page = bad.encode();
assert_eq!(
XcmpQueue::take_first_concatenated_xcm(&mut &page[..], &mut WeightMeter::new()),
Err(())
);
}
fn test_take_first_concatenated_xcms(xcm_encoding: XcmEncoding) {
let page = generate_mock_xcm_page(0, 9, xcm_encoding);
let xcms = encode_xcm_batch(generate_mock_xcm_batch(0, 9), XcmEncoding::Simple);
let data = &mut &page[1..];
assert_eq!(
XcmpQueue::take_first_concatenated_xcms(data, xcm_encoding, 5, &mut WeightMeter::new()),
Ok(xcms[0..5].iter().map(|xcm| xcm.as_bounded_slice()).collect())
);
assert_eq!(
XcmpQueue::take_first_concatenated_xcms(data, xcm_encoding, 5, &mut WeightMeter::new()),
Ok(xcms[5..9].iter().map(|xcm| xcm.as_bounded_slice()).collect())
);
let mut page = generate_mock_xcm_page(0, 5, xcm_encoding);
match xcm_encoding {
XcmEncoding::Simple => {
page.push(100);
},
XcmEncoding::Double => {
page.extend(Compact::<u32>::from(1).encode());
},
}
assert_eq!(
XcmpQueue::take_first_concatenated_xcms(
&mut &page[1..],
xcm_encoding,
10,
&mut WeightMeter::new()
),
Err(xcms[0..5].iter().map(|xcm| xcm.as_bounded_slice()).collect())
);
}
#[test]
fn take_first_concatenated_xcms_works() {
test_take_first_concatenated_xcms(XcmEncoding::Simple);
test_take_first_concatenated_xcms(XcmEncoding::Double);
}
#[test]
fn lazy_migration_works() {
use crate::migration::v3::*;
new_test_ext().execute_with(|| {
EnqueuedMessages::set(vec![]);
let _g = StorageNoopGuard::default();
let mut channels = vec![];
for i in 0..20 {
let para = ParaId::from(i);
let mut message_metadata = vec![];
for block in 0..30 {
message_metadata.push((block, XcmpMessageFormat::ConcatenatedVersionedXcm));
InboundXcmpMessages::<Test>::insert(para, block, vec![(i + block) as u8]);
}
channels.push(InboundChannelDetails {
sender: para,
state: InboundState::Ok,
message_metadata,
});
}
InboundXcmpStatus::<Test>::set(Some(channels));
for para in 0..20u32 {
assert_eq!(InboundXcmpStatus::<Test>::get().unwrap().len() as u32, 20 - para);
assert_eq!(InboundXcmpMessages::<Test>::iter_prefix(ParaId::from(para)).count(), 30);
for block in 0..30 {
XcmpQueue::on_idle(0u32.into(), Weight::MAX);
assert_eq!(
EnqueuedMessages::get(),
vec![(para.into(), vec![(29 - block + para) as u8])]
);
EnqueuedMessages::set(vec![]);
}
XcmpQueue::on_idle(0u32.into(), Weight::MAX);
assert_eq!(InboundXcmpStatus::<Test>::get().unwrap().len() as u32, 20 - para - 1);
assert_eq!(InboundXcmpMessages::<Test>::iter_prefix(ParaId::from(para)).count(), 0);
}
XcmpQueue::on_idle(0u32.into(), Weight::MAX);
assert!(!InboundXcmpStatus::<Test>::exists());
assert_eq!(InboundXcmpMessages::<Test>::iter().count(), 0);
EnqueuedMessages::set(vec![]);
});
}
#[test]
fn lazy_migration_noop_when_out_of_weight() {
use crate::migration::v3::*;
assert!(!XcmpQueue::on_idle_weight().is_zero(), "pre condition");
new_test_ext().execute_with(|| {
let _g = StorageNoopGuard::default(); let block = 5;
let para = ParaId::from(4);
let message_metadata = vec![(block, XcmpMessageFormat::ConcatenatedVersionedXcm)];
InboundXcmpMessages::<Test>::insert(para, block, vec![123u8]);
InboundXcmpStatus::<Test>::set(Some(vec![InboundChannelDetails {
sender: para,
state: InboundState::Ok,
message_metadata,
}]));
hypothetically!({
XcmpQueue::on_idle(0u32.into(), Weight::MAX);
assert_eq!(EnqueuedMessages::get(), vec![(para, vec![123u8])]);
});
assert_storage_noop!({ XcmpQueue::on_idle(0u32.into(), Weight::zero()) });
InboundXcmpMessages::<Test>::remove(para, block);
InboundXcmpStatus::<Test>::kill();
});
}
#[test]
fn xcmp_queue_send_xcm_works() {
new_test_ext().execute_with(|| {
let sibling_para_id = ParaId::from(12345);
let dest: Location = (Parent, Parachain(sibling_para_id.into())).into();
let msg = Xcm(vec![ClearOrigin]);
assert_eq!(
send_xcm::<XcmpQueue>(dest.clone(), msg.clone()),
Err(SendError::Transport("NoChannel")),
);
ParachainSystem::open_outbound_hrmp_channel_for_benchmarks_or_tests(sibling_para_id);
assert!(XcmpQueue::take_outbound_messages(usize::MAX).is_empty());
assert_ok!(send_xcm::<XcmpQueue>(dest, msg));
assert!(XcmpQueue::take_outbound_messages(usize::MAX)
.iter()
.any(|(para_id, _)| para_id == &sibling_para_id));
})
}
#[test]
fn xcmp_queue_send_too_big_xcm_fails() {
new_test_ext().execute_with(|| {
let sibling_para_id = ParaId::from(12345);
let dest = (Parent, Parachain(sibling_para_id.into())).into();
let max_message_size = 100_u32;
ParachainSystem::open_custom_outbound_hrmp_channel_for_benchmarks_or_tests(
sibling_para_id,
cumulus_primitives_core::AbridgedHrmpChannel {
max_message_size,
max_capacity: 10,
max_total_size: 10_000_000_u32,
msg_count: 0,
total_size: 0,
mqc_head: None,
},
);
let mut message = Xcm::builder_unsafe();
for _ in 0..97 {
message = message.clear_origin();
}
let message = message.build();
let encoded_message_size = message.encode().len();
let versioned_size = 1; assert_eq!(encoded_message_size, max_message_size as usize - versioned_size);
assert!(XcmpQueue::take_outbound_messages(usize::MAX).is_empty());
assert_eq!(send_xcm::<XcmpQueue>(dest, message), Err(SendError::Transport("TooBig")),);
assert!(XcmpQueue::take_outbound_messages(usize::MAX).is_empty());
});
}
#[test]
fn concatenated_opaque_version_xcm_negotiation_works() {
new_test_ext().execute_with(|| {
let sibling_para_id = ParaId::from(1000);
ParachainSystem::open_outbound_hrmp_channel_for_benchmarks_or_tests(sibling_para_id);
let dest: Location = (Parent, Parachain(sibling_para_id.into())).into();
let msg = Xcm::<()>(vec![ClearOrigin]);
assert_ok!(send_xcm::<XcmpQueue>(dest.clone(), msg.clone()));
assert_eq!(
XcmpQueue::take_outbound_messages(usize::MAX),
vec![(
sibling_para_id,
[ConcatenatedVersionedXcm.encode(), VersionedXcm::V5(msg.clone()).encode()]
.concat()
)]
);
assert_eq!(
XcmpQueue::take_outbound_messages(usize::MAX),
vec![(sibling_para_id, ConcatenatedOpaqueVersionedXcm.encode())]
);
assert!(XcmpQueue::take_outbound_messages(usize::MAX).is_empty());
let page = generate_mock_xcm_page(0, 1, XcmEncoding::Simple);
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, page.as_slice())), Weight::MAX);
assert_ok!(send_xcm::<XcmpQueue>(dest.clone(), msg.clone()));
assert_eq!(
XcmpQueue::take_outbound_messages(usize::MAX),
vec![(
sibling_para_id,
[ConcatenatedVersionedXcm.encode(), VersionedXcm::V5(msg.clone()).encode()]
.concat()
)]
);
let page = generate_mock_xcm_page(0, 0, XcmEncoding::Double);
XcmpQueue::handle_xcmp_messages(once((1000.into(), 1, page.as_slice())), Weight::MAX);
assert_ok!(send_xcm::<XcmpQueue>(dest, msg.clone()));
assert_eq!(
XcmpQueue::take_outbound_messages(usize::MAX),
vec![(
sibling_para_id,
[ConcatenatedOpaqueVersionedXcm.encode(), VersionedXcm::V5(msg).encode().encode()]
.concat()
)]
);
})
}
#[test]
fn verify_fee_factor_increase_and_decrease() {
use cumulus_primitives_core::AbridgedHrmpChannel;
use sp_runtime::FixedU128;
let sibling_para_id = ParaId::from(12345);
let destination: Location = (Parent, Parachain(sibling_para_id.into())).into();
let xcm = Xcm(vec![ClearOrigin; 100]);
let versioned_xcm = VersionedXcm::from(xcm.clone());
let mut xcmp_message = XcmpMessageFormat::ConcatenatedVersionedXcm.encode();
xcmp_message.extend(versioned_xcm.encode());
new_test_ext().execute_with(|| {
let initial = Pallet::<Test>::MIN_FEE_FACTOR;
assert_eq!(Pallet::<Test>::get_fee_factor(sibling_para_id), initial);
ParachainSystem::open_custom_outbound_hrmp_channel_for_benchmarks_or_tests(
sibling_para_id,
AbridgedHrmpChannel {
max_capacity: 10,
max_total_size: 1000,
max_message_size: 104,
msg_count: 0,
total_size: 0,
mqc_head: None,
},
);
assert_ok!(send_xcm::<XcmpQueue>(destination.clone(), xcm.clone())); assert_ok!(send_xcm::<XcmpQueue>(destination.clone(), xcm.clone())); assert_ok!(send_xcm::<XcmpQueue>(destination.clone(), xcm.clone())); assert_ok!(send_xcm::<XcmpQueue>(destination.clone(), xcm.clone())); assert_eq!(DeliveryFeeFactor::<Test>::get(sibling_para_id), initial);
let (_, delivery_fees) = validate_send::<XcmpQueue>(destination.clone(), xcm.clone())
.expect("message can be sent; qed");
let Fungible(delivery_fee_amount) = delivery_fees.inner()[0].fun else {
unreachable!("asset is fungible; qed");
};
assert_eq!(delivery_fee_amount, 402_000_000);
let smaller_xcm = Xcm(vec![ClearOrigin; 30]);
assert_ok!(send_xcm::<XcmpQueue>(destination.clone(), xcm.clone())); assert_eq!(DeliveryFeeFactor::<Test>::get(sibling_para_id), FixedU128::from_float(1.05));
for _ in 0..12 {
assert_ok!(send_xcm::<XcmpQueue>(destination.clone(), smaller_xcm.clone()));
}
assert!(DeliveryFeeFactor::<Test>::get(sibling_para_id) > FixedU128::from_float(1.88));
let (_, delivery_fees) = validate_send::<XcmpQueue>(destination.clone(), xcm.clone())
.expect("message can be sent; qed");
let Fungible(delivery_fee_amount) = delivery_fees.inner()[0].fun else {
unreachable!("asset is fungible; qed");
};
assert_eq!(delivery_fee_amount, 758_030_955);
for _ in 0..5 {
XcmpQueue::take_outbound_messages(1);
}
assert!(DeliveryFeeFactor::<Test>::get(sibling_para_id) < FixedU128::from_float(1.72));
XcmpQueue::take_outbound_messages(1);
assert!(DeliveryFeeFactor::<Test>::get(sibling_para_id) < FixedU128::from_float(1.63));
});
}
#[test]
fn get_messages_works() {
new_test_ext().execute_with(|| {
let sibling_para_id = ParaId::from(2001);
ParachainSystem::open_outbound_hrmp_channel_for_benchmarks_or_tests(sibling_para_id);
let destination: Location = (Parent, Parachain(sibling_para_id.into())).into();
let other_sibling_para_id = ParaId::from(2002);
let other_destination: Location = (Parent, Parachain(other_sibling_para_id.into())).into();
let message = Xcm(vec![ClearOrigin]);
assert_ok!(send_xcm::<XcmpQueue>(destination.clone(), message.clone()));
assert_ok!(send_xcm::<XcmpQueue>(destination.clone(), message.clone()));
assert_ok!(send_xcm::<XcmpQueue>(destination.clone(), message.clone()));
ParachainSystem::open_outbound_hrmp_channel_for_benchmarks_or_tests(other_sibling_para_id);
assert_ok!(send_xcm::<XcmpQueue>(other_destination.clone(), message.clone()));
assert_ok!(send_xcm::<XcmpQueue>(other_destination.clone(), message));
let queued_messages = XcmpQueue::get_messages();
assert_eq!(
queued_messages,
vec![
(
VersionedLocation::from(other_destination),
vec![
VersionedXcm::from(Xcm(vec![ClearOrigin])),
VersionedXcm::from(Xcm(vec![ClearOrigin])),
],
),
(
VersionedLocation::from(destination),
vec![
VersionedXcm::from(Xcm(vec![ClearOrigin])),
VersionedXcm::from(Xcm(vec![ClearOrigin])),
VersionedXcm::from(Xcm(vec![ClearOrigin])),
],
),
],
);
});
}
#[test]
fn page_not_modified_when_fragment_does_not_fit() {
new_test_ext().execute_with(|| {
let sibling = ParaId::from(2001);
ParachainSystem::open_outbound_hrmp_channel_for_benchmarks_or_tests(sibling);
let destination: Location = (Parent, Parachain(sibling.into())).into();
let message = Xcm(vec![ClearOrigin; MAX_INSTRUCTIONS_TO_DECODE as usize]);
loop {
let old_page_zero = OutboundXcmpMessages::<Test>::get(sibling, 0);
assert_ok!(send_xcm::<XcmpQueue>(destination.clone(), message.clone()));
let num_pages = OutboundXcmpMessages::<Test>::iter_prefix(sibling).count();
if num_pages == 2 {
let new_page_zero = OutboundXcmpMessages::<Test>::get(sibling, 0);
assert_eq!(old_page_zero, new_page_zero);
break;
} else if num_pages > 2 {
panic!("Too many pages created");
}
}
});
}