diff --git a/Cargo.lock b/Cargo.lock index d3c376952b7be..cb4cf877236b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2697,9 +2697,9 @@ dependencies = [ [[package]] name = "frame-metadata" -version = "15.1.0" +version = "15.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "878babb0b136e731cc77ec2fd883ff02745ff21e6fb662729953d44923df009c" +checksum = "f2a893ede8dde2293e94dacf9c8f5db5d0506cd909257a8f0ac2b7d610baf50c" dependencies = [ "cfg-if", "parity-scale-codec", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index e423905aa1444..ed0e8aa8fe811 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -19,8 +19,8 @@ //! The Substrate runtime. This can be compiled with `#[no_std]`, ready for Wasm. #![cfg_attr(not(feature = "std"), no_std)] -// `construct_runtime!` does a lot of recursion and requires us to increase the limit to 512. -#![recursion_limit = "512"] +// `construct_runtime!` does a lot of recursion and requires us to increase the limits. +#![recursion_limit = "1024"] use codec::{Decode, Encode, MaxEncodedLen}; use frame_election_provider_support::{ @@ -1189,6 +1189,7 @@ impl pallet_message_queue::Config for Runtime { type MessageProcessor = pallet_message_queue::mock_helpers::NoopMessageProcessor; type Size = u32; type QueueChangeHandler = (); + type QueuePausedQuery = (); type HeapSize = ConstU32<{ 64 * 1024 }>; type MaxStale = ConstU32<128>; type ServiceWeight = MessageQueueServiceWeight; diff --git a/frame/message-queue/src/integration_test.rs b/frame/message-queue/src/integration_test.rs index 172b1b4a7351f..9eaa02db69fac 100644 --- a/frame/message-queue/src/integration_test.rs +++ b/frame/message-queue/src/integration_test.rs @@ -23,7 +23,7 @@ use crate::{ mock::{ new_test_ext, CountingMessageProcessor, IntoWeight, MockedWeightInfo, NumMessagesProcessed, - SuspendedQueues, + YieldingQueues, }, mock_helpers::MessageOrigin, *, @@ -88,6 +88,7 @@ impl Config for Test { type MessageProcessor = CountingMessageProcessor; type Size = u32; type QueueChangeHandler = (); + type QueuePausedQuery = (); type HeapSize = HeapSize; type MaxStale = MaxStale; type ServiceWeight = ServiceWeight; @@ -199,7 +200,7 @@ fn stress_test_queue_suspension() { to_resume, per_queue.len() ); - SuspendedQueues::set(suspended.iter().map(|q| MessageOrigin::Everywhere(*q)).collect()); + YieldingQueues::set(suspended.iter().map(|q| MessageOrigin::Everywhere(*q)).collect()); // Pick a fraction of all messages currently in queue and process them. let resumed_messages = @@ -221,7 +222,7 @@ fn stress_test_queue_suspension() { process_all_messages(resumed_messages); msgs_remaining -= resumed_messages; - let resumed = SuspendedQueues::take(); + let resumed = YieldingQueues::take(); log::info!("Resumed all {} suspended queues", resumed.len()); log::info!("Processing all remaining {} messages", msgs_remaining); process_all_messages(msgs_remaining); diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 37fbe85fd56be..55a41643993aa 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -195,7 +195,7 @@ use frame_support::{ pallet_prelude::*, traits::{ DefensiveTruncateFrom, EnqueueMessage, ExecuteOverweightError, Footprint, ProcessMessage, - ProcessMessageError, ServiceQueues, + ProcessMessageError, QueuePausedQuery, ServiceQueues, }, BoundedSlice, CloneNoBound, DefaultNoBound, }; @@ -473,6 +473,13 @@ pub mod pallet { /// removed. type QueueChangeHandler: OnQueueChanged<::Origin>; + /// Queried by the pallet to check whether a queue can be serviced. + /// + /// This also applies to manual servicing via `execute_overweight` and `service_queues`. The + /// value of this is only polled once before servicing the queue. This means that changes to + /// it that happen *within* the servicing will not be reflected. + type QueuePausedQuery: QueuePausedQuery<::Origin>; + /// The size of the page; this implies the maximum message size which can be sent. /// /// A good value depends on the expected message sizes, their weights, the weight that is @@ -534,6 +541,10 @@ pub mod pallet { /// Such errors are expected, but not guaranteed, to resolve themselves eventually through /// retrying. TemporarilyUnprocessable, + /// The queue is paused and no message can be executed from it. + /// + /// This can change at any time and may resolve in the future by re-trying. + QueuePaused, } /// The index of the first and last (non-empty) pages. @@ -803,6 +814,8 @@ impl Pallet { weight_limit: Weight, ) -> Result> { let mut book_state = BookStateFor::::get(&origin); + ensure!(!T::QueuePausedQuery::is_paused(&origin), Error::::QueuePaused); + let mut page = Pages::::get(&origin, page_index).ok_or(Error::::NoPage)?; let (pos, is_processed, payload) = page.peek_index(index.into() as usize).ok_or(Error::::NoMessage)?; @@ -943,6 +956,10 @@ impl Pallet { let mut book_state = BookStateFor::::get(&origin); let mut total_processed = 0; + if T::QueuePausedQuery::is_paused(&origin) { + let next_ready = book_state.ready_neighbours.as_ref().map(|x| x.next.clone()); + return (false, next_ready) + } while book_state.end > book_state.begin { let (processed, status) = @@ -1284,7 +1301,11 @@ impl ServiceQueues for Pallet { Pallet::::do_execute_overweight(message_origin, page, index, weight.remaining()).map_err( |e| match e { Error::::InsufficientWeight => ExecuteOverweightError::InsufficientWeight, - _ => ExecuteOverweightError::NotFound, + Error::::AlreadyProcessed => ExecuteOverweightError::AlreadyProcessed, + Error::::QueuePaused => ExecuteOverweightError::QueuePaused, + Error::::NoPage | Error::::NoMessage | Error::::Queued => + ExecuteOverweightError::NotFound, + _ => ExecuteOverweightError::Other, }, ) } diff --git a/frame/message-queue/src/mock.rs b/frame/message-queue/src/mock.rs index 51151f6318787..05fd382480838 100644 --- a/frame/message-queue/src/mock.rs +++ b/frame/message-queue/src/mock.rs @@ -76,6 +76,7 @@ impl Config for Test { type MessageProcessor = RecordingMessageProcessor; type Size = u32; type QueueChangeHandler = RecordingQueueChangeHandler; + type QueuePausedQuery = MockedQueuePauser; type HeapSize = HeapSize; type MaxStale = MaxStale; type ServiceWeight = ServiceWeight; @@ -146,7 +147,8 @@ impl crate::weights::WeightInfo for MockedWeightInfo { parameter_types! { pub static MessagesProcessed: Vec<(Vec, MessageOrigin)> = vec![]; - pub static SuspendedQueues: Vec = vec![]; + /// Queues that should return `Yield` upon being processed. + pub static YieldingQueues: Vec = vec![]; } /// A message processor which records all processed messages into [`MessagesProcessed`]. @@ -197,7 +199,7 @@ impl ProcessMessage for RecordingMessageProcessor { /// Processed a mocked message. Messages that end with `badformat`, `corrupt`, `unsupported` or /// `yield` will fail with an error respectively. fn processing_message(msg: &[u8], origin: &MessageOrigin) -> Result<(), ProcessMessageError> { - if SuspendedQueues::get().contains(&origin) { + if YieldingQueues::get().contains(&origin) { return Err(ProcessMessageError::Yield) } @@ -262,6 +264,17 @@ impl OnQueueChanged for RecordingQueueChangeHandler { } } +parameter_types! { + pub static PausedQueues: Vec = vec![]; +} + +pub struct MockedQueuePauser; +impl QueuePausedQuery for MockedQueuePauser { + fn is_paused(id: &MessageOrigin) -> bool { + PausedQueues::get().contains(id) + } +} + /// Create new test externalities. /// /// Is generic since it is used by the unit test, integration tests and benchmarks. @@ -279,6 +292,12 @@ where ext } +/// Run this closure in test externalities. +pub fn test_closure(f: impl FnOnce() -> R) -> R { + let mut ext = new_test_ext::(); + ext.execute_with(f) +} + /// Set the weight of a specific weight function. pub fn set_weight(name: &str, w: Weight) { MockedWeightInfo::set_weight::(name, w); diff --git a/frame/message-queue/src/mock_helpers.rs b/frame/message-queue/src/mock_helpers.rs index 5a39eb0a59885..65b1e5af9099a 100644 --- a/frame/message-queue/src/mock_helpers.rs +++ b/frame/message-queue/src/mock_helpers.rs @@ -89,11 +89,11 @@ pub fn page(msg: &[u8]) -> PageOf { } pub fn single_page_book() -> BookStateOf { - BookState { begin: 0, end: 1, count: 1, ready_neighbours: None, message_count: 0, size: 0 } + BookState { begin: 0, end: 1, count: 1, ..Default::default() } } pub fn empty_book() -> BookStateOf { - BookState { begin: 0, end: 1, count: 1, ready_neighbours: None, message_count: 0, size: 0 } + BookState { begin: 0, end: 1, count: 1, ..Default::default() } } /// Returns a full page of messages with their index as payload and the number of messages. @@ -118,9 +118,9 @@ pub fn book_for(page: &PageOf) -> BookStateOf { count: 1, begin: 0, end: 1, - ready_neighbours: None, message_count: page.remaining.into() as u64, size: page.remaining_size.into() as u64, + ..Default::default() } } diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 10eff69c926e5..6587c949bde05 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -27,22 +27,22 @@ use sp_core::blake2_256; #[test] fn mocked_weight_works() { - new_test_ext::().execute_with(|| { + test_closure(|| { assert!(::WeightInfo::service_queue_base().is_zero()); }); - new_test_ext::().execute_with(|| { + test_closure(|| { set_weight("service_queue_base", Weight::MAX); assert_eq!(::WeightInfo::service_queue_base(), Weight::MAX); }); // The externalities reset it. - new_test_ext::().execute_with(|| { + test_closure(|| { assert!(::WeightInfo::service_queue_base().is_zero()); }); } #[test] fn enqueue_within_one_page_works() { - new_test_ext::().execute_with(|| { + test_closure(|| { use MessageOrigin::*; MessageQueue::enqueue_message(msg("a"), Here); MessageQueue::enqueue_message(msg("b"), Here); @@ -77,7 +77,7 @@ fn enqueue_within_one_page_works() { #[test] fn queue_priority_retains() { - new_test_ext::().execute_with(|| { + test_closure(|| { use MessageOrigin::*; assert_ring(&[]); MessageQueue::enqueue_message(msg("a"), Everywhere(1)); @@ -108,7 +108,7 @@ fn queue_priority_retains() { #[test] fn queue_priority_reset_once_serviced() { - new_test_ext::().execute_with(|| { + test_closure(|| { use MessageOrigin::*; MessageQueue::enqueue_message(msg("a"), Everywhere(1)); MessageQueue::enqueue_message(msg("b"), Everywhere(2)); @@ -135,7 +135,7 @@ fn queue_priority_reset_once_serviced() { #[test] fn service_queues_basic_works() { use MessageOrigin::*; - new_test_ext::().execute_with(|| { + test_closure(|| { MessageQueue::enqueue_messages(vec![msg("a"), msg("ab"), msg("abc")].into_iter(), Here); MessageQueue::enqueue_messages(vec![msg("x"), msg("xy"), msg("xyz")].into_iter(), There); assert_eq!(QueueChanges::take(), vec![(Here, 3, 6), (There, 3, 6)]); @@ -146,13 +146,11 @@ fn service_queues_basic_works() { assert_eq!(QueueChanges::take(), vec![(Here, 2, 5)]); // Service one message from `There`. - ServiceHead::::set(There.into()); assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); assert_eq!(MessagesProcessed::take(), vec![(vmsg("x"), There)]); assert_eq!(QueueChanges::take(), vec![(There, 2, 5)]); // Service the remaining from `Here`. - ServiceHead::::set(Here.into()); assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); assert_eq!(MessagesProcessed::take(), vec![(vmsg("ab"), Here), (vmsg("abc"), Here)]); assert_eq!(QueueChanges::take(), vec![(Here, 0, 0)]); @@ -167,7 +165,7 @@ fn service_queues_basic_works() { #[test] fn service_queues_failing_messages_works() { use MessageOrigin::*; - new_test_ext::().execute_with(|| { + test_closure(|| { set_weight("service_page_item", 1.into_weight()); MessageQueue::enqueue_message(msg("badformat"), Here); MessageQueue::enqueue_message(msg("corrupt"), Here); @@ -213,7 +211,7 @@ fn service_queues_failing_messages_works() { #[test] fn service_queues_suspension_works() { use MessageOrigin::*; - new_test_ext::().execute_with(|| { + test_closure(|| { MessageQueue::enqueue_messages(vec![msg("a"), msg("b"), msg("c")].into_iter(), Here); MessageQueue::enqueue_messages(vec![msg("x"), msg("y"), msg("z")].into_iter(), There); MessageQueue::enqueue_messages( @@ -227,8 +225,8 @@ fn service_queues_suspension_works() { assert_eq!(MessagesProcessed::take(), vec![(vmsg("a"), Here)]); assert_eq!(QueueChanges::take(), vec![(Here, 2, 2)]); - // Pause queue `Here` and `Everywhere(0)`. - SuspendedQueues::set(vec![Here, Everywhere(0)]); + // Make queue `Here` and `Everywhere(0)` yield. + YieldingQueues::set(vec![Here, Everywhere(0)]); // Service one message from `There`. assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); @@ -245,13 +243,13 @@ fn service_queues_suspension_works() { assert_eq!(MessageQueue::service_queues(Weight::MAX), Weight::zero()); // ... until we resume `Here`: - SuspendedQueues::set(vec![Everywhere(0)]); + YieldingQueues::set(vec![Everywhere(0)]); assert_eq!(MessageQueue::service_queues(Weight::MAX), 2.into_weight()); assert_eq!(MessagesProcessed::take(), vec![(vmsg("b"), Here), (vmsg("c"), Here)]); // Everywhere still won't move. assert_eq!(MessageQueue::service_queues(Weight::MAX), Weight::zero()); - SuspendedQueues::take(); + YieldingQueues::take(); // Resume `Everywhere(0)` makes it work. assert_eq!(MessageQueue::service_queues(Weight::MAX), 3.into_weight()); assert_eq!( @@ -268,7 +266,7 @@ fn service_queues_suspension_works() { #[test] fn reap_page_permanent_overweight_works() { use MessageOrigin::*; - new_test_ext::().execute_with(|| { + test_closure(|| { // Create 10 pages more than the stale limit. let n = (MaxStale::get() + 10) as usize; for _ in 0..n { @@ -308,7 +306,7 @@ fn reaping_overweight_fails_properly() { use MessageOrigin::*; assert_eq!(MaxStale::get(), 2, "The stale limit is two"); - new_test_ext::().execute_with(|| { + test_closure(|| { // page 0 MessageQueue::enqueue_message(msg("weight=4"), Here); MessageQueue::enqueue_message(msg("a"), Here); @@ -378,7 +376,7 @@ fn reaping_overweight_fails_properly() { #[test] fn service_queue_bails() { // Not enough weight for `service_queue_base`. - new_test_ext::().execute_with(|| { + test_closure(|| { set_weight("service_queue_base", 2.into_weight()); let mut meter = WeightMeter::from_limit(1.into_weight()); @@ -386,7 +384,7 @@ fn service_queue_bails() { assert!(meter.consumed.is_zero()); }); // Not enough weight for `ready_ring_unknit`. - new_test_ext::().execute_with(|| { + test_closure(|| { set_weight("ready_ring_unknit", 2.into_weight()); let mut meter = WeightMeter::from_limit(1.into_weight()); @@ -394,7 +392,7 @@ fn service_queue_bails() { assert!(meter.consumed.is_zero()); }); // Not enough weight for `service_queue_base` and `ready_ring_unknit`. - new_test_ext::().execute_with(|| { + test_closure(|| { set_weight("service_queue_base", 2.into_weight()); set_weight("ready_ring_unknit", 2.into_weight()); @@ -409,7 +407,7 @@ fn service_page_works() { use super::integration_test::Test; // Run with larger page size. use MessageOrigin::*; use PageExecutionStatus::*; - new_test_ext::().execute_with(|| { + test_closure(|| { set_weight("service_page_base_completion", 2.into_weight()); set_weight("service_page_item", 3.into_weight()); @@ -446,7 +444,7 @@ fn service_page_works() { #[test] fn service_page_bails() { // Not enough weight for `service_page_base_completion`. - new_test_ext::().execute_with(|| { + test_closure(|| { set_weight("service_page_base_completion", 2.into_weight()); let mut meter = WeightMeter::from_limit(1.into_weight()); @@ -463,7 +461,7 @@ fn service_page_bails() { assert!(meter.consumed.is_zero()); }); // Not enough weight for `service_page_base_no_completion`. - new_test_ext::().execute_with(|| { + test_closure(|| { set_weight("service_page_base_no_completion", 2.into_weight()); let mut meter = WeightMeter::from_limit(1.into_weight()); @@ -483,7 +481,7 @@ fn service_page_bails() { #[test] fn service_page_item_bails() { - new_test_ext::().execute_with(|| { + test_closure(|| { let _guard = StorageNoopGuard::default(); let (mut page, _) = full_page::(); let mut weight = WeightMeter::from_limit(10.into_weight()); @@ -510,7 +508,7 @@ fn service_page_suspension_works() { use MessageOrigin::*; use PageExecutionStatus::*; - new_test_ext::().execute_with(|| { + test_closure(|| { let (page, mut msgs) = full_page::(); assert!(msgs >= 10, "pre-condition: need at least 10 msgs per page"); let mut book = book_for::(&page); @@ -527,7 +525,7 @@ fn service_page_suspension_works() { msgs -= 5; // Then we pause the queue. - SuspendedQueues::set(vec![Here]); + YieldingQueues::set(vec![Here]); // Noting happens... for _ in 0..5 { let (_, status) = crate::Pallet::::service_page( @@ -541,7 +539,7 @@ fn service_page_suspension_works() { } // Resume and process all remaining. - SuspendedQueues::take(); + YieldingQueues::take(); let (_, status) = crate::Pallet::::service_page( &Here, &mut book, @@ -558,7 +556,7 @@ fn service_page_suspension_works() { #[test] fn bump_service_head_works() { use MessageOrigin::*; - new_test_ext::().execute_with(|| { + test_closure(|| { // Create a ready ring with three queues. BookStateFor::::insert(Here, empty_book::()); knit(&Here); @@ -581,7 +579,7 @@ fn bump_service_head_works() { /// `bump_service_head` does nothing when called with an insufficient weight limit. #[test] fn bump_service_head_bails() { - new_test_ext::().execute_with(|| { + test_closure(|| { set_weight("bump_service_head", 2.into_weight()); setup_bump_service_head::(0.into(), 10.into()); @@ -594,7 +592,7 @@ fn bump_service_head_bails() { #[test] fn bump_service_head_trivial_works() { - new_test_ext::().execute_with(|| { + test_closure(|| { set_weight("bump_service_head", 2.into_weight()); let mut meter = WeightMeter::max_limit(); @@ -615,7 +613,7 @@ fn bump_service_head_trivial_works() { #[test] fn bump_service_head_no_head_noops() { use MessageOrigin::*; - new_test_ext::().execute_with(|| { + test_closure(|| { // Create a ready ring with three queues. BookStateFor::::insert(Here, empty_book::()); knit(&Here); @@ -634,7 +632,7 @@ fn bump_service_head_no_head_noops() { #[test] fn service_page_item_consumes_correct_weight() { - new_test_ext::().execute_with(|| { + test_closure(|| { let mut page = page::(b"weight=3"); let mut weight = WeightMeter::from_limit(10.into_weight()); let overweight_limit = 0.into_weight(); @@ -658,7 +656,7 @@ fn service_page_item_consumes_correct_weight() { /// `service_page_item` skips a permanently `Overweight` message and marks it as `unprocessed`. #[test] fn service_page_item_skips_perm_overweight_message() { - new_test_ext::().execute_with(|| { + test_closure(|| { let mut page = page::(b"TooMuch"); let mut weight = WeightMeter::from_limit(2.into_weight()); let overweight_limit = 0.into_weight(); @@ -697,7 +695,7 @@ fn service_page_item_skips_perm_overweight_message() { #[test] fn peek_index_works() { use super::integration_test::Test; // Run with larger page size. - new_test_ext::().execute_with(|| { + test_closure(|| { // Fill a page with messages. let (mut page, msgs) = full_page::(); let msg_enc_len = ItemHeader::<::Size>::max_encoded_len() + 4; @@ -718,7 +716,7 @@ fn peek_index_works() { #[test] fn peek_first_and_skip_first_works() { use super::integration_test::Test; // Run with larger page size. - new_test_ext::().execute_with(|| { + test_closure(|| { // Fill a page with messages. let (mut page, msgs) = full_page::(); @@ -741,7 +739,7 @@ fn peek_first_and_skip_first_works() { #[test] fn note_processed_at_pos_works() { use super::integration_test::Test; // Run with larger page size. - new_test_ext::().execute_with(|| { + test_closure(|| { let (mut page, msgs) = full_page::(); for i in 0..msgs { @@ -777,7 +775,7 @@ fn note_processed_at_pos_idempotent() { #[test] fn is_complete_works() { use super::integration_test::Test; // Run with larger page size. - new_test_ext::().execute_with(|| { + test_closure(|| { let (mut page, msgs) = full_page::(); assert!(msgs > 3, "Boring"); let msg_enc_len = ItemHeader::<::Size>::max_encoded_len() + 4; @@ -933,7 +931,7 @@ fn page_from_message_max_len_works() { #[test] fn sweep_queue_works() { use MessageOrigin::*; - new_test_ext::().execute_with(|| { + test_closure(|| { build_triple_ring(); let book = BookStateFor::::get(Here); @@ -969,7 +967,7 @@ fn sweep_queue_works() { #[test] fn sweep_queue_wraps_works() { use MessageOrigin::*; - new_test_ext::().execute_with(|| { + test_closure(|| { BookStateFor::::insert(Here, empty_book::()); knit(&Here); @@ -982,14 +980,14 @@ fn sweep_queue_wraps_works() { #[test] fn sweep_queue_invalid_noops() { use MessageOrigin::*; - new_test_ext::().execute_with(|| { + test_closure(|| { assert_storage_noop!(MessageQueue::sweep_queue(Here)); }); } #[test] fn footprint_works() { - new_test_ext::().execute_with(|| { + test_closure(|| { let origin = MessageOrigin::Here; let (page, msgs) = full_page::(); let book = book_for::(&page); @@ -1007,7 +1005,7 @@ fn footprint_works() { /// The footprint of an invalid queue is the default footprint. #[test] fn footprint_invalid_works() { - new_test_ext::().execute_with(|| { + test_closure(|| { let origin = MessageOrigin::Here; assert_eq!(MessageQueue::footprint(origin), Default::default()); }) @@ -1017,7 +1015,7 @@ fn footprint_invalid_works() { #[test] fn footprint_on_swept_works() { use MessageOrigin::*; - new_test_ext::().execute_with(|| { + test_closure(|| { let mut book = empty_book::(); book.message_count = 3; book.size = 10; @@ -1033,7 +1031,7 @@ fn footprint_on_swept_works() { #[test] fn execute_overweight_works() { - new_test_ext::().execute_with(|| { + test_closure(|| { set_weight("bump_service_head", 1.into_weight()); set_weight("service_queue_base", 1.into_weight()); set_weight("service_page_base_completion", 1.into_weight()); @@ -1093,7 +1091,7 @@ fn execute_overweight_works() { fn permanently_overweight_book_unknits() { use MessageOrigin::*; - new_test_ext::().execute_with(|| { + test_closure(|| { set_weight("bump_service_head", 1.into_weight()); set_weight("service_queue_base", 1.into_weight()); set_weight("service_page_base_completion", 1.into_weight()); @@ -1130,7 +1128,7 @@ fn permanently_overweight_book_unknits() { fn permanently_overweight_book_unknits_multiple() { use MessageOrigin::*; - new_test_ext::().execute_with(|| { + test_closure(|| { set_weight("bump_service_head", 1.into_weight()); set_weight("service_queue_base", 1.into_weight()); set_weight("service_page_base_completion", 1.into_weight()); @@ -1169,7 +1167,7 @@ fn permanently_overweight_book_unknits_multiple() { fn ready_but_empty_does_not_panic() { use MessageOrigin::*; - new_test_ext::().execute_with(|| { + test_closure(|| { BookStateFor::::insert(Here, empty_book::()); BookStateFor::::insert(There, empty_book::()); @@ -1189,7 +1187,7 @@ fn ready_but_empty_does_not_panic() { fn ready_but_perm_overweight_does_not_panic() { use MessageOrigin::*; - new_test_ext::().execute_with(|| { + test_closure(|| { MessageQueue::enqueue_message(msg("weight=9"), Here); assert_eq!(MessageQueue::service_queues(8.into_weight()), 0.into_weight()); assert_ring(&[]); @@ -1209,7 +1207,7 @@ fn ready_but_perm_overweight_does_not_panic() { fn ready_ring_knit_basic_works() { use MessageOrigin::*; - new_test_ext::().execute_with(|| { + test_closure(|| { BookStateFor::::insert(Here, empty_book::()); for i in 0..10 { @@ -1229,12 +1227,15 @@ fn ready_ring_knit_basic_works() { fn ready_ring_knit_and_unknit_works() { use MessageOrigin::*; - new_test_ext::().execute_with(|| { + test_closure(|| { // Place three queues into the storage. BookStateFor::::insert(Here, empty_book::()); BookStateFor::::insert(There, empty_book::()); BookStateFor::::insert(Everywhere(0), empty_book::()); + // Pausing should make no difference: + PausedQueues::set(vec![Here, There, Everywhere(0)]); + // Knit them into the ready ring. assert_ring(&[]); knit(&Here); @@ -1260,7 +1261,7 @@ fn enqueue_message_works() { let max_msg_per_page = ::HeapSize::get() as u64 / (ItemHeader::<::Size>::max_encoded_len() as u64 + 1); - new_test_ext::().execute_with(|| { + test_closure(|| { // Enqueue messages which should fill three pages. let n = max_msg_per_page * 3; for i in 1..=n { @@ -1290,7 +1291,7 @@ fn enqueue_messages_works() { let max_msg_per_page = ::HeapSize::get() as u64 / (ItemHeader::<::Size>::max_encoded_len() as u64 + 1); - new_test_ext::().execute_with(|| { + test_closure(|| { // Enqueue messages which should fill three pages. let n = max_msg_per_page * 3; let msgs = vec![msg("a"); n as usize]; @@ -1315,3 +1316,144 @@ fn enqueue_messages_works() { assert_eq!(book.count as usize, Pages::::iter().count()); }); } + +#[test] +fn service_queues_suspend_works() { + use MessageOrigin::*; + test_closure(|| { + MessageQueue::enqueue_messages(vec![msg("a"), msg("ab"), msg("abc")].into_iter(), Here); + MessageQueue::enqueue_messages(vec![msg("x"), msg("xy"), msg("xyz")].into_iter(), There); + assert_eq!(QueueChanges::take(), vec![(Here, 3, 6), (There, 3, 6)]); + + // Pause `Here` - execution starts `There`. + PausedQueues::set(vec![Here]); + assert_eq!( + (true, false), + ( + ::QueuePausedQuery::is_paused(&Here), + ::QueuePausedQuery::is_paused(&There) + ) + ); + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("x"), There)]); + assert_eq!(QueueChanges::take(), vec![(There, 2, 5)]); + + // Unpause `Here` - execution continues `There`. + PausedQueues::take(); + assert_eq!( + (false, false), + ( + ::QueuePausedQuery::is_paused(&Here), + ::QueuePausedQuery::is_paused(&There) + ) + ); + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("xy"), There)]); + assert_eq!(QueueChanges::take(), vec![(There, 1, 3)]); + + // Now it swaps to `Here`. + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("a"), Here)]); + assert_eq!(QueueChanges::take(), vec![(Here, 2, 5)]); + + // Pause `There` - execution continues `Here`. + PausedQueues::set(vec![There]); + assert_eq!( + (false, true), + ( + ::QueuePausedQuery::is_paused(&Here), + ::QueuePausedQuery::is_paused(&There) + ) + ); + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("ab"), Here)]); + assert_eq!(QueueChanges::take(), vec![(Here, 1, 3)]); + + // Unpause `There` and service all remaining messages. + PausedQueues::take(); + assert_eq!( + (false, false), + ( + ::QueuePausedQuery::is_paused(&Here), + ::QueuePausedQuery::is_paused(&There) + ) + ); + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("abc"), Here), (vmsg("xyz"), There)]); + assert_eq!(QueueChanges::take(), vec![(Here, 0, 0), (There, 0, 0)]); + }); +} + +/// Tests that manual overweight execution on a suspended queue errors with `QueueSuspended`. +#[test] +fn execute_overweight_respects_suspension() { + test_closure(|| { + let origin = MessageOrigin::Here; + MessageQueue::enqueue_message(msg("weight=5"), origin); + // Mark the message as permanently overweight. + MessageQueue::service_queues(4.into_weight()); + assert_last_event::( + Event::OverweightEnqueued { + id: blake2_256(b"weight=5"), + origin, + message_index: 0, + page_index: 0, + } + .into(), + ); + PausedQueues::set(vec![origin]); + assert!(::QueuePausedQuery::is_paused(&origin)); + + // Execution should fail. + assert_eq!( + ::execute_overweight(Weight::MAX, (origin, 0, 0)), + Err(ExecuteOverweightError::QueuePaused) + ); + + PausedQueues::take(); + assert!(!::QueuePausedQuery::is_paused(&origin)); + + // Execution should work again with same args. + assert_ok!(::execute_overweight( + Weight::MAX, + (origin, 0, 0) + )); + + assert_last_event::( + Event::Processed { + id: blake2_256(b"weight=5"), + origin, + weight_used: 5.into_weight(), + success: true, + } + .into(), + ); + }); +} + +#[test] +fn service_queue_suspension_ready_ring_works() { + test_closure(|| { + let origin = MessageOrigin::Here; + PausedQueues::set(vec![origin]); + MessageQueue::enqueue_message(msg("weight=5"), origin); + + MessageQueue::service_queues(Weight::MAX); + // It did not execute but is in the ready ring. + assert!(System::events().is_empty(), "Paused"); + assert_ring(&[origin]); + + // Now when we un-pause, it will execute. + PausedQueues::take(); + MessageQueue::service_queues(Weight::MAX); + assert_last_event::( + Event::Processed { + id: blake2_256(b"weight=5"), + origin, + weight_used: 5.into_weight(), + success: true, + } + .into(), + ); + }); +} diff --git a/frame/support/Cargo.toml b/frame/support/Cargo.toml index d95c2d7a45d7b..045f1169045c8 100644 --- a/frame/support/Cargo.toml +++ b/frame/support/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] serde = { version = "1.0.163", default-features = false, features = ["alloc", "derive"] } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } -frame-metadata = { version = "15.1.0", default-features = false, features = ["v14", "v15-unstable"] } +frame-metadata = { version = "15.2.0", default-features = false, features = ["unstable"] } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../primitives/api" } sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } diff --git a/frame/support/procedural/src/construct_runtime/expand/event.rs b/frame/support/procedural/src/construct_runtime/expand/event.rs deleted file mode 100644 index fcd9b32141ab5..0000000000000 --- a/frame/support/procedural/src/construct_runtime/expand/event.rs +++ /dev/null @@ -1,168 +0,0 @@ -// This file is part of Substrate. - -// 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::construct_runtime::Pallet; -use proc_macro2::TokenStream; -use quote::quote; -use std::str::FromStr; -use syn::{Generics, Ident}; - -pub fn expand_outer_event( - runtime: &Ident, - pallet_decls: &[Pallet], - scrate: &TokenStream, -) -> syn::Result { - let mut event_variants = TokenStream::new(); - let mut event_conversions = TokenStream::new(); - let mut query_event_part_macros = Vec::new(); - - for pallet_decl in pallet_decls { - if let Some(pallet_entry) = pallet_decl.find_part("Event") { - let path = &pallet_decl.path; - let pallet_name = &pallet_decl.name; - let index = pallet_decl.index; - let instance = pallet_decl.instance.as_ref(); - let generics = &pallet_entry.generics; - - if instance.is_some() && generics.params.is_empty() { - let msg = format!( - "Instantiable pallet with no generic `Event` cannot \ - be constructed: pallet `{}` must have generic `Event`", - pallet_name, - ); - return Err(syn::Error::new(pallet_name.span(), msg)) - } - - let part_is_generic = !generics.params.is_empty(); - let pallet_event = match (instance, part_is_generic) { - (Some(inst), true) => quote!(#path::Event::<#runtime, #path::#inst>), - (Some(inst), false) => quote!(#path::Event::<#path::#inst>), - (None, true) => quote!(#path::Event::<#runtime>), - (None, false) => quote!(#path::Event), - }; - - event_variants.extend(expand_event_variant( - runtime, - pallet_decl, - index, - instance, - generics, - )); - event_conversions.extend(expand_event_conversion(scrate, pallet_decl, &pallet_event)); - query_event_part_macros.push(quote! { - #path::__substrate_event_check::is_event_part_defined!(#pallet_name); - }); - } - } - - Ok(quote! { - #( #query_event_part_macros )* - - #[derive( - Clone, PartialEq, Eq, - #scrate::codec::Encode, - #scrate::codec::Decode, - #scrate::scale_info::TypeInfo, - #scrate::RuntimeDebug, - )] - #[allow(non_camel_case_types)] - pub enum RuntimeEvent { - #event_variants - } - - #event_conversions - }) -} - -fn expand_event_variant( - runtime: &Ident, - pallet: &Pallet, - index: u8, - instance: Option<&Ident>, - generics: &Generics, -) -> TokenStream { - let path = &pallet.path; - let variant_name = &pallet.name; - let part_is_generic = !generics.params.is_empty(); - let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { - let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); - - match instance { - Some(inst) if part_is_generic => quote! { - #attr - #[codec(index = #index)] - #variant_name(#path::Event<#runtime, #path::#inst>), - }, - Some(inst) => quote! { - #attr - #[codec(index = #index)] - #variant_name(#path::Event<#path::#inst>), - }, - None if part_is_generic => quote! { - #attr - #[codec(index = #index)] - #variant_name(#path::Event<#runtime>), - }, - None => quote! { - #attr - #[codec(index = #index)] - #variant_name(#path::Event), - }, - } -} - -fn expand_event_conversion( - scrate: &TokenStream, - pallet: &Pallet, - pallet_event: &TokenStream, -) -> TokenStream { - let variant_name = &pallet.name; - let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { - let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); - - quote! { - #attr - impl From<#pallet_event> for RuntimeEvent { - fn from(x: #pallet_event) -> Self { - RuntimeEvent::#variant_name(x) - } - } - #attr - impl TryInto<#pallet_event> for RuntimeEvent { - type Error = (); - - fn try_into(self) -> #scrate::sp_std::result::Result<#pallet_event, Self::Error> { - match self { - Self::#variant_name(evt) => Ok(evt), - _ => Err(()), - } - } - } - } -} diff --git a/frame/support/procedural/src/construct_runtime/expand/metadata.rs b/frame/support/procedural/src/construct_runtime/expand/metadata.rs index e23e4d1fe75b8..8d9063e740778 100644 --- a/frame/support/procedural/src/construct_runtime/expand/metadata.rs +++ b/frame/support/procedural/src/construct_runtime/expand/metadata.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License -use crate::construct_runtime::Pallet; +use crate::construct_runtime::{parse::PalletPath, Pallet}; use proc_macro2::TokenStream; use quote::quote; use std::str::FromStr; @@ -26,6 +26,7 @@ pub fn expand_runtime_metadata( pallet_declarations: &[Pallet], scrate: &TokenStream, extrinsic: &TokenStream, + system_path: &PalletPath, ) -> TokenStream { let pallets = pallet_declarations .iter() @@ -115,6 +116,13 @@ pub fn expand_runtime_metadata( }, ty: #scrate::scale_info::meta_type::<#runtime>(), apis: (&rt).runtime_metadata(), + outer_enums: #scrate::metadata_ir::OuterEnumsIR { + call_enum_ty: #scrate::scale_info::meta_type::< + <#runtime as #system_path::Config>::RuntimeCall + >(), + event_enum_ty: #scrate::scale_info::meta_type::(), + error_enum_ty: #scrate::scale_info::meta_type::(), + } } } diff --git a/frame/support/procedural/src/construct_runtime/expand/mod.rs b/frame/support/procedural/src/construct_runtime/expand/mod.rs index 0fd98bb4dda13..830338f9265ff 100644 --- a/frame/support/procedural/src/construct_runtime/expand/mod.rs +++ b/frame/support/procedural/src/construct_runtime/expand/mod.rs @@ -17,24 +17,24 @@ mod call; mod config; -mod event; mod freeze_reason; mod hold_reason; mod inherent; mod lock_id; mod metadata; mod origin; +mod outer_enums; mod slash_reason; mod unsigned; pub use call::expand_outer_dispatch; pub use config::expand_outer_config; -pub use event::expand_outer_event; pub use freeze_reason::expand_outer_freeze_reason; pub use hold_reason::expand_outer_hold_reason; pub use inherent::expand_outer_inherent; pub use lock_id::expand_outer_lock_id; pub use metadata::expand_runtime_metadata; pub use origin::expand_outer_origin; +pub use outer_enums::{expand_outer_enum, OuterEnumType}; pub use slash_reason::expand_outer_slash_reason; pub use unsigned::expand_outer_validate_unsigned; diff --git a/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs b/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs new file mode 100644 index 0000000000000..544f63be2bfa1 --- /dev/null +++ b/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs @@ -0,0 +1,283 @@ +// This file is part of Substrate. + +// 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::construct_runtime::Pallet; +use proc_macro2::{Span, TokenStream}; +use quote::{quote, ToTokens}; +use std::str::FromStr; +use syn::{Generics, Ident}; + +/// Represents the types supported for creating an outer enum. +#[derive(Clone, Copy, PartialEq)] +pub enum OuterEnumType { + /// Collects the Event enums from all pallets. + Event, + /// Collects the Error enums from all pallets. + Error, +} + +impl OuterEnumType { + /// The name of the structure this enum represents. + fn struct_name(&self) -> &str { + match self { + OuterEnumType::Event => "RuntimeEvent", + OuterEnumType::Error => "RuntimeError", + } + } + + /// The name of the variant (ie `Event` or `Error`). + fn variant_name(&self) -> &str { + match self { + OuterEnumType::Event => "Event", + OuterEnumType::Error => "Error", + } + } +} + +impl ToTokens for OuterEnumType { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + OuterEnumType::Event => quote!(Event).to_tokens(tokens), + OuterEnumType::Error => quote!(Error).to_tokens(tokens), + } + } +} + +/// Create an outer enum that encapsulates all pallets as variants. +/// +/// Each variant represents a pallet and contains the corresponding type declared with either: +/// - #[pallet::event] for the [`OuterEnumType::Event`] variant +/// - #[pallet::error] for the [`OuterEnumType::Error`] variant +/// +/// The name of the outer enum is prefixed with Runtime, resulting in names like RuntimeEvent +/// or RuntimeError. +/// +/// This structure facilitates the decoding process by leveraging the metadata. +/// +/// # Example +/// +/// The code generate looks like the following for [`OuterEnumType::Event`]. +/// +/// ```ignore +/// enum RuntimeEvent { +/// #[codec(index = 0)] +/// System(pallet_system::Event), +/// +/// #[codec(index = 5)] +/// Balances(pallet_system::Event), +/// } +/// ``` +/// +/// Notice that the pallet index is preserved using the `#[codec(index = ..)]` attribute. +pub fn expand_outer_enum( + runtime: &Ident, + pallet_decls: &[Pallet], + scrate: &TokenStream, + enum_ty: OuterEnumType, +) -> syn::Result { + // Stores all pallet variants. + let mut enum_variants = TokenStream::new(); + // Generates the enum conversion between the `Runtime` outer enum and the pallet's enum. + let mut enum_conversions = TokenStream::new(); + // Specific for events to query via `is_event_part_defined!`. + let mut query_enum_part_macros = Vec::new(); + + let enum_name_str = enum_ty.variant_name(); + let enum_name_ident = Ident::new(enum_ty.struct_name(), Span::call_site()); + + for pallet_decl in pallet_decls { + let Some(pallet_entry) = pallet_decl.find_part(enum_name_str) else { + continue + }; + + let path = &pallet_decl.path; + let pallet_name = &pallet_decl.name; + let index = pallet_decl.index; + let instance = pallet_decl.instance.as_ref(); + let generics = &pallet_entry.generics; + + if instance.is_some() && generics.params.is_empty() { + let msg = format!( + "Instantiable pallet with no generic `{}` cannot \ + be constructed: pallet `{}` must have generic `{}`", + enum_name_str, pallet_name, enum_name_str, + ); + return Err(syn::Error::new(pallet_name.span(), msg)) + } + + let part_is_generic = !generics.params.is_empty(); + let pallet_enum = match (instance, part_is_generic) { + (Some(inst), true) => quote!(#path::#enum_ty::<#runtime, #path::#inst>), + (Some(inst), false) => quote!(#path::#enum_ty::<#path::#inst>), + (None, true) => quote!(#path::#enum_ty::<#runtime>), + (None, false) => quote!(#path::#enum_ty), + }; + + enum_variants.extend(expand_enum_variant( + runtime, + pallet_decl, + index, + instance, + generics, + enum_ty, + )); + enum_conversions.extend(expand_enum_conversion( + scrate, + pallet_decl, + &pallet_enum, + &enum_name_ident, + )); + + if enum_ty == OuterEnumType::Event { + query_enum_part_macros.push(quote! { + #path::__substrate_event_check::is_event_part_defined!(#pallet_name); + }); + } + } + + // Derives specific for the event. + let event_custom_derives = + if enum_ty == OuterEnumType::Event { quote!(Clone, PartialEq, Eq,) } else { quote!() }; + + // Implementation specific for errors. + let error_custom_impl = generate_error_impl(scrate, enum_ty); + + Ok(quote! { + #( #query_enum_part_macros )* + + #[derive( + #event_custom_derives + #scrate::codec::Encode, + #scrate::codec::Decode, + #scrate::scale_info::TypeInfo, + #scrate::RuntimeDebug, + )] + #[allow(non_camel_case_types)] + pub enum #enum_name_ident { + #enum_variants + } + + #enum_conversions + + #error_custom_impl + }) +} + +fn expand_enum_variant( + runtime: &Ident, + pallet: &Pallet, + index: u8, + instance: Option<&Ident>, + generics: &Generics, + enum_ty: OuterEnumType, +) -> TokenStream { + let path = &pallet.path; + let variant_name = &pallet.name; + let part_is_generic = !generics.params.is_empty(); + let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { + let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) + .expect("was successfully parsed before; qed"); + quote! { + #acc + #attr + } + }); + + match instance { + Some(inst) if part_is_generic => quote! { + #attr + #[codec(index = #index)] + #variant_name(#path::#enum_ty<#runtime, #path::#inst>), + }, + Some(inst) => quote! { + #attr + #[codec(index = #index)] + #variant_name(#path::#enum_ty<#path::#inst>), + }, + None if part_is_generic => quote! { + #attr + #[codec(index = #index)] + #variant_name(#path::#enum_ty<#runtime>), + }, + None => quote! { + #attr + #[codec(index = #index)] + #variant_name(#path::#enum_ty), + }, + } +} + +fn expand_enum_conversion( + scrate: &TokenStream, + pallet: &Pallet, + pallet_enum: &TokenStream, + enum_name_ident: &Ident, +) -> TokenStream { + let variant_name = &pallet.name; + let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { + let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) + .expect("was successfully parsed before; qed"); + quote! { + #acc + #attr + } + }); + + quote! { + #attr + impl From<#pallet_enum> for #enum_name_ident { + fn from(x: #pallet_enum) -> Self { + #enum_name_ident + ::#variant_name(x) + } + } + #attr + impl TryInto<#pallet_enum> for #enum_name_ident { + type Error = (); + + fn try_into(self) -> #scrate::sp_std::result::Result<#pallet_enum, Self::Error> { + match self { + Self::#variant_name(evt) => Ok(evt), + _ => Err(()), + } + } + } + } +} + +fn generate_error_impl(scrate: &TokenStream, enum_ty: OuterEnumType) -> TokenStream { + // Implementation is specific to `Error`s. + if enum_ty == OuterEnumType::Event { + return quote! {} + } + + let enum_name_ident = Ident::new(enum_ty.struct_name(), Span::call_site()); + + quote! { + impl #enum_name_ident { + /// Optionally convert the `DispatchError` into the `RuntimeError`. + /// + /// Returns `Some` if the error matches the `DispatchError::Module` variant, otherwise `None`. + pub fn from_dispatch_error(err: #scrate::sp_runtime::DispatchError) -> Option { + let #scrate::sp_runtime::DispatchError::Module(module_error) = err else { return None }; + + let bytes = #scrate::codec::Encode::encode(&module_error); + #scrate::codec::Decode::decode(&mut &bytes[..]).ok() + } + } + } +} diff --git a/frame/support/procedural/src/construct_runtime/mod.rs b/frame/support/procedural/src/construct_runtime/mod.rs index a6a1596e67c28..399934bb8311d 100644 --- a/frame/support/procedural/src/construct_runtime/mod.rs +++ b/frame/support/procedural/src/construct_runtime/mod.rs @@ -20,17 +20,69 @@ //! `construct_runtime` implementation is recursive and can generate code which will call itself in //! order to get all the pallet parts for each pallet. //! -//! Pallets define their parts (`Call`, `Storage`, ..) either explicitly with the syntax -//! `::{Call, ...}` or implicitly. +//! Pallets can define their parts: +//! - Implicitely: `System: frame_system` +//! - Explicitly: `System: frame_system::{Pallet, Call}` //! -//! In case a pallet defines its parts implicitly, then the pallet must provide the -//! `tt_default_parts` macro. `construct_runtime` will generate some code which utilizes `tt_call` -//! to call the `tt_default_parts` macro of the pallet. `tt_default_parts` will then return the -//! default pallet parts as input tokens to the `match_and_replace` macro, which ultimately -//! generates a call to `construct_runtime` again, this time with all the pallet parts explicitly -//! defined. +//! The `construct_runtime` transitions from the implicit definition to the explict one. +//! From the explicit state, Substrate expands the pallets with additional information +//! that is to be included in the runtime metadata. This expansion makes visible some extra +//! parts of the pallets, mainly the `Error` if defined. The expanded state looks like +//! `System: frame_system expanded::{Error} ::{Pallet, Call}` and concatenates the extra expanded +//! parts with the user-provided parts. For example, the `Pallet`, `Call` and `Error` parts are +//! collected. +//! +//! Pallets must provide the `tt_extra_parts` and `tt_default_parts` macros for these transitions. +//! These are automatically implemented by the `#[pallet::pallet]` macro. +//! +//! This macro also generates the following enums for ease of decoding: +//! - `enum RuntimeCall`: This type contains the information needed to decode extrinsics. +//! - `enum RuntimeEvent`: This type contains the information needed to decode events. +//! - `enum RuntimeError`: While this cannot be used directly to decode `sp_runtime::DispatchError` +//! from the chain, it contains the information needed to decode the +//! `sp_runtime::DispatchError::Module`. +//! +//! # State Transitions +//! +//! ```ignore +//! +----------+ +//! | Implicit | -----------+ +//! +----------+ | +//! | | +//! v v +//! +----------+ +------------------+ +//! | Explicit | --> | ExplicitExpanded | +//! +----------+ +------------------+ +//! ``` +//! +//! When all pallet parts are implcit, then the `construct_runtime!` macro expands to its final +//! state, the `ExplicitExpanded`. Otherwise, all implicit parts are converted to an explicit +//! expanded part allow the `construct_runtime!` to expand any remaining explicit parts to an +//! explicit expanded part. +//! +//! # Implicit to Explicit +//! +//! The `construct_runtime` macro transforms the implicit declaration of each pallet +//! `System: frame_system` to an explicit one `System: frame_system::{Pallet, Call}` using the +//! `tt_default_parts` macro. +//! +//! The `tt_default_parts` macro exposes a comma separated list of pallet parts. For example, the +//! `Event` part is exposed only if the pallet implements an event via `#[pallet::event]` macro. +//! The tokens generated by this macro are ` expanded :: { Pallet, Call }` for our example. +//! +//! The `match_and_insert` macro takes in 3 arguments: +//! - target: This is the `TokenStream` that contains the `construct_runtime!` macro. +//! - pattern: The pattern to match against in the target stream. +//! - tokens: The tokens to added after the pattern match. +//! +//! The `construct_runtime` macro uses the `tt_call` to get the default pallet parts via +//! the `tt_default_parts` macro defined by each pallet. The pallet parts are then returned as +//! input to the `match_and_replace` macro. +//! The `match_and_replace` then will modify the the `construct_runtime!` to expand the implicit +//! definition to the explicit one. +//! +//! For example, //! -//! E.g. //! ```ignore //! construct_runtime!( //! //... @@ -106,6 +158,7 @@ //! tokens = [{ ::{Pallet, Call} }] //! } //! ``` +//! //! Which will then finally expand to the following: //! ```ignore //! construct_runtime!( @@ -116,6 +169,7 @@ //! } //! ) //! ``` +//! //! This call has no implicit pallet parts, thus it will expand to the runtime construction: //! ```ignore //! pub struct Runtime { ... } @@ -140,6 +194,19 @@ //! | w/ pallet parts | //! +--------------------+ //! ``` +//! +//! # Explicit to Explicit Expanded +//! +//! Users normally do not care about this transition. +//! +//! Similarly to the previous transition, the macro expansion transforms `System: +//! frame_system::{Pallet, Call}` into `System: frame_system expanded::{Error} ::{Pallet, Call}`. +//! The `expanded` section adds extra parts that the Substrate would like to expose for each pallet +//! by default. This is done to expose the approprite types for metadata construction. +//! +//! This time, instead of calling `tt_default_parts` we are using the `tt_extra_parts` macro. +//! This macro returns the ` :: expanded { Error }` list of additional parts we would like to +//! expose. mod expand; mod parse; @@ -168,9 +235,16 @@ pub fn construct_runtime(input: TokenStream) -> TokenStream { let res = match definition { RuntimeDeclaration::Implicit(implicit_def) => check_pallet_number(input_copy.clone().into(), implicit_def.pallets.len()).and_then( - |_| construct_runtime_intermediary_expansion(input_copy.into(), implicit_def), + |_| construct_runtime_implicit_to_explicit(input_copy.into(), implicit_def), ), - RuntimeDeclaration::Explicit(explicit_decl) => + RuntimeDeclaration::Explicit(explicit_decl) => check_pallet_number( + input_copy.clone().into(), + explicit_decl.pallets.len(), + ) + .and_then(|_| { + construct_runtime_explicit_to_explicit_expanded(input_copy.into(), explicit_decl) + }), + RuntimeDeclaration::ExplicitExpanded(explicit_decl) => check_pallet_number(input_copy.into(), explicit_decl.pallets.len()) .and_then(|_| construct_runtime_final_expansion(explicit_decl)), }; @@ -186,9 +260,14 @@ pub fn construct_runtime(input: TokenStream) -> TokenStream { res.into() } -/// When some pallet have implicit parts definition then the macro will expand into a macro call to -/// `construct_runtime_args` of each pallets, see root documentation. -fn construct_runtime_intermediary_expansion( +/// All pallets that have implicit pallet parts (ie `System: frame_system`) are +/// expanded with the default parts defined by the pallet's `tt_default_parts` macro. +/// +/// This function transforms the [`RuntimeDeclaration::Implicit`] into +/// [`RuntimeDeclaration::Explicit`] that is not yet fully expanded. +/// +/// For more details, please refer to the root documentation. +fn construct_runtime_implicit_to_explicit( input: TokenStream2, definition: ImplicitRuntimeDeclaration, ) -> Result { @@ -215,6 +294,42 @@ fn construct_runtime_intermediary_expansion( Ok(expansion) } +/// All pallets that have +/// (I): explicit pallet parts (ie `System: frame_system::{Pallet, Call}`) and +/// (II): are not fully expanded (ie do not include the `Error` expansion part) +/// are fully expanded by including the parts from the pallet's `tt_extra_parts` macro. +/// +/// This function transforms the [`RuntimeDeclaration::Explicit`] that is not yet fully expanded +/// into [`RuntimeDeclaration::ExplicitExpanded`] fully expanded. +/// +/// For more details, please refer to the root documentation. +fn construct_runtime_explicit_to_explicit_expanded( + input: TokenStream2, + definition: ExplicitRuntimeDeclaration, +) -> Result { + let frame_support = generate_crate_access_2018("frame-support")?; + let mut expansion = quote::quote!( + #frame_support::construct_runtime! { #input } + ); + for pallet in definition.pallets.iter().filter(|pallet| !pallet.is_expanded) { + let pallet_path = &pallet.path; + let pallet_name = &pallet.name; + let pallet_instance = pallet.instance.as_ref().map(|instance| quote::quote!(::<#instance>)); + expansion = quote::quote!( + #frame_support::tt_call! { + macro = [{ #pallet_path::tt_extra_parts }] + frame_support = [{ #frame_support }] + ~~> #frame_support::match_and_insert! { + target = [{ #expansion }] + pattern = [{ #pallet_name: #pallet_path #pallet_instance }] + } + } + ); + } + + Ok(expansion) +} + /// All pallets have explicit definition of parts, this will expand to the runtime declaration. fn construct_runtime_final_expansion( definition: ExplicitRuntimeDeclaration, @@ -260,14 +375,23 @@ fn construct_runtime_final_expansion( let block = quote!(<#name as #frame_system::Config>::Block); let unchecked_extrinsic = quote!(<#block as #scrate::sp_runtime::traits::Block>::Extrinsic); - let outer_event = expand::expand_outer_event(&name, &pallets, &scrate)?; + let outer_event = + expand::expand_outer_enum(&name, &pallets, &scrate, expand::OuterEnumType::Event)?; + let outer_error = + expand::expand_outer_enum(&name, &pallets, &scrate, expand::OuterEnumType::Error)?; let outer_origin = expand::expand_outer_origin(&name, system_pallet, &pallets, &scrate)?; let all_pallets = decl_all_pallets(&name, pallets.iter(), &features); let pallet_to_index = decl_pallet_runtime_setup(&name, &pallets, &scrate); let dispatch = expand::expand_outer_dispatch(&name, system_pallet, &pallets, &scrate); - let metadata = expand::expand_runtime_metadata(&name, &pallets, &scrate, &unchecked_extrinsic); + let metadata = expand::expand_runtime_metadata( + &name, + &pallets, + &scrate, + &unchecked_extrinsic, + &system_pallet.path, + ); let outer_config = expand::expand_outer_config(&name, &pallets, &scrate); let inherent = expand::expand_outer_inherent(&name, &block, &unchecked_extrinsic, &pallets, &scrate); @@ -339,6 +463,8 @@ fn construct_runtime_final_expansion( #outer_event + #outer_error + #outer_origin #all_pallets diff --git a/frame/support/procedural/src/construct_runtime/parse.rs b/frame/support/procedural/src/construct_runtime/parse.rs index 6daedd542fda2..9b08e16469754 100644 --- a/frame/support/procedural/src/construct_runtime/parse.rs +++ b/frame/support/procedural/src/construct_runtime/parse.rs @@ -35,6 +35,7 @@ mod keyword { syn::custom_keyword!(Call); syn::custom_keyword!(Storage); syn::custom_keyword!(Event); + syn::custom_keyword!(Error); syn::custom_keyword!(Config); syn::custom_keyword!(Origin); syn::custom_keyword!(Inherent); @@ -45,6 +46,7 @@ mod keyword { syn::custom_keyword!(SlashReason); syn::custom_keyword!(exclude_parts); syn::custom_keyword!(use_parts); + syn::custom_keyword!(expanded); } /// Declaration of a runtime. @@ -56,6 +58,7 @@ mod keyword { pub enum RuntimeDeclaration { Implicit(ImplicitRuntimeDeclaration), Explicit(ExplicitRuntimeDeclaration), + ExplicitExpanded(ExplicitRuntimeDeclaration), } /// Declaration of a runtime with some pallet with implicit declaration of parts. @@ -106,6 +109,13 @@ impl Parse for RuntimeDeclaration { pallets, pallets_token, })), + PalletsConversion::ExplicitExpanded(pallets) => + Ok(RuntimeDeclaration::ExplicitExpanded(ExplicitRuntimeDeclaration { + name, + where_section, + pallets, + pallets_token, + })), } } } @@ -190,6 +200,8 @@ impl Parse for WhereDefinition { /// The declaration of a pallet. #[derive(Debug, Clone)] pub struct PalletDeclaration { + /// Is this pallet fully expanded? + pub is_expanded: bool, /// The name of the pallet, e.g.`System` in `System: frame_system`. pub name: Ident, /// Optional attributes tagged right above a pallet declaration. @@ -235,6 +247,7 @@ impl Parse for PalletDeclaration { let _: Token![>] = input.parse()?; res } else if !(input.peek(Token![::]) && input.peek3(token::Brace)) && + !input.peek(keyword::expanded) && !input.peek(keyword::exclude_parts) && !input.peek(keyword::use_parts) && !input.peek(Token![=]) && @@ -248,10 +261,21 @@ impl Parse for PalletDeclaration { None }; + // Check if the pallet is fully expanded. + let (is_expanded, extra_parts) = if input.peek(keyword::expanded) { + let _: keyword::expanded = input.parse()?; + let _: Token![::] = input.parse()?; + (true, parse_pallet_parts(input)?) + } else { + (false, vec![]) + }; + // Parse for explicit parts let pallet_parts = if input.peek(Token![::]) && input.peek3(token::Brace) { let _: Token![::] = input.parse()?; - Some(parse_pallet_parts(input)?) + let mut parts = parse_pallet_parts(input)?; + parts.extend(extra_parts.into_iter()); + Some(parts) } else if !input.peek(keyword::exclude_parts) && !input.peek(keyword::use_parts) && !input.peek(Token![=]) && @@ -262,7 +286,7 @@ impl Parse for PalletDeclaration { "Unexpected tokens, expected one of `::{`, `exclude_parts`, `use_parts`, `=`, `,`", )) } else { - None + is_expanded.then_some(extra_parts) }; // Parse for specified parts @@ -290,7 +314,7 @@ impl Parse for PalletDeclaration { None }; - Ok(Self { attrs, name, path, instance, pallet_parts, specified_parts, index }) + Ok(Self { is_expanded, attrs, name, path, instance, pallet_parts, specified_parts, index }) } } @@ -373,6 +397,7 @@ pub enum PalletPartKeyword { Call(keyword::Call), Storage(keyword::Storage), Event(keyword::Event), + Error(keyword::Error), Config(keyword::Config), Origin(keyword::Origin), Inherent(keyword::Inherent), @@ -395,6 +420,8 @@ impl Parse for PalletPartKeyword { Ok(Self::Storage(input.parse()?)) } else if lookahead.peek(keyword::Event) { Ok(Self::Event(input.parse()?)) + } else if lookahead.peek(keyword::Error) { + Ok(Self::Error(input.parse()?)) } else if lookahead.peek(keyword::Config) { Ok(Self::Config(input.parse()?)) } else if lookahead.peek(keyword::Origin) { @@ -425,6 +452,7 @@ impl PalletPartKeyword { Self::Call(_) => "Call", Self::Storage(_) => "Storage", Self::Event(_) => "Event", + Self::Error(_) => "Error", Self::Config(_) => "Config", Self::Origin(_) => "Origin", Self::Inherent(_) => "Inherent", @@ -443,7 +471,7 @@ impl PalletPartKeyword { /// Returns the names of all pallet parts that allow to have a generic argument. fn all_generic_arg() -> &'static [&'static str] { - &["Event", "Origin", "Config"] + &["Event", "Error", "Origin", "Config"] } } @@ -454,6 +482,7 @@ impl ToTokens for PalletPartKeyword { Self::Call(inner) => inner.to_tokens(tokens), Self::Storage(inner) => inner.to_tokens(tokens), Self::Event(inner) => inner.to_tokens(tokens), + Self::Error(inner) => inner.to_tokens(tokens), Self::Config(inner) => inner.to_tokens(tokens), Self::Origin(inner) => inner.to_tokens(tokens), Self::Inherent(inner) => inner.to_tokens(tokens), @@ -556,6 +585,8 @@ fn parse_pallet_parts_no_generic(input: ParseStream) -> Result | Explicit | -> | ExplicitExpanded | +/// +----------+ +----------+ +------------------+ +/// ``` enum PalletsConversion { + /// Pallets implicitely declare parts. + /// + /// `System: frame_system`. Implicit(Vec), + /// Pallets explicitly declare parts. + /// + /// `System: frame_system::{Pallet, Call}` + /// + /// However, for backwards compatibility with Polkadot/Kusama + /// we must propagate some other parts to the pallet by default. Explicit(Vec), + /// Pallets explicitly declare parts that are fully expanded. + /// + /// This is the end state that contains extra parts included by + /// default by Subtrate. + /// + /// `System: frame_system expanded::{Error} ::{Pallet, Call}` + /// + /// For this example, the `Pallet`, `Call` and `Error` parts are collected. + ExplicitExpanded(Vec), } /// Convert from the parsed pallet declaration to their final information. @@ -606,6 +663,7 @@ fn convert_pallets(pallets: Vec) -> syn::Result = None; let mut names = HashMap::new(); + let mut is_expanded = true; let pallets = pallets .into_iter() @@ -700,7 +758,10 @@ fn convert_pallets(pallets: Vec) -> syn::Result>>()?; + is_expanded &= pallet.is_expanded; + Ok(Pallet { + is_expanded: pallet.is_expanded, name: pallet.name, index: final_index, path: pallet.path, @@ -711,5 +772,9 @@ fn convert_pallets(pallets: Vec) -> syn::Result>>()?; - Ok(PalletsConversion::Explicit(pallets)) + if is_expanded { + Ok(PalletsConversion::ExplicitExpanded(pallets)) + } else { + Ok(PalletsConversion::Explicit(pallets)) + } } diff --git a/frame/support/procedural/src/pallet/expand/tt_default_parts.rs b/frame/support/procedural/src/pallet/expand/tt_default_parts.rs index f36c765f7beb6..356bdbf67e923 100644 --- a/frame/support/procedural/src/pallet/expand/tt_default_parts.rs +++ b/frame/support/procedural/src/pallet/expand/tt_default_parts.rs @@ -26,6 +26,8 @@ pub fn expand_tt_default_parts(def: &mut Def) -> proc_macro2::TokenStream { let count = COUNTER.with(|counter| counter.borrow_mut().inc()); let default_parts_unique_id = syn::Ident::new(&format!("__tt_default_parts_{}", count), def.item.span()); + let extra_parts_unique_id = + syn::Ident::new(&format!("__tt_extra_parts_{}", count), def.item.span()); let call_part = def.call.as_ref().map(|_| quote::quote!(Call,)); @@ -36,6 +38,8 @@ pub fn expand_tt_default_parts(def: &mut Def) -> proc_macro2::TokenStream { quote::quote!( Event #gen , ) }); + let error_part = def.error.as_ref().map(|_| quote::quote!(Error,)); + let origin_part = def.origin.as_ref().map(|origin| { let gen = origin.is_generic.then(|| quote::quote!( )); quote::quote!( Origin #gen , ) @@ -95,8 +99,8 @@ pub fn expand_tt_default_parts(def: &mut Def) -> proc_macro2::TokenStream { $($frame_support)*::tt_return! { $caller tokens = [{ - ::{ - Pallet, #call_part #storage_part #event_part #origin_part #config_part + expanded::{ + Pallet, #call_part #storage_part #event_part #error_part #origin_part #config_part #inherent_part #validate_unsigned_part #freeze_reason_part #hold_reason_part #lock_id_part #slash_reason_part } @@ -106,5 +110,33 @@ pub fn expand_tt_default_parts(def: &mut Def) -> proc_macro2::TokenStream { } pub use #default_parts_unique_id as tt_default_parts; + + + // This macro is similar to the `tt_default_parts!`. It expands the pallets thare are declared + // explicitly (`System: frame_system::{Pallet, Call}`) with extra parts. + // + // For example, after expansion an explicit pallet would look like: + // `System: expanded::{Error} ::{Pallet, Call}`. + // + // The `expanded` keyword is a marker of the final state of the `construct_runtime!`. + #[macro_export] + #[doc(hidden)] + macro_rules! #extra_parts_unique_id { + { + $caller:tt + frame_support = [{ $($frame_support:ident)::* }] + } => { + $($frame_support)*::tt_return! { + $caller + tokens = [{ + expanded::{ + #error_part + } + }] + } + }; + } + + pub use #extra_parts_unique_id as tt_extra_parts; ) } diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index e4fd04750441a..5b34a77df448a 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -113,7 +113,7 @@ pub use preimages::{Bounded, BoundedInline, FetchResult, Hash, QueryPreimage, St mod messages; pub use messages::{ EnqueueMessage, ExecuteOverweightError, Footprint, NoopServiceQueues, ProcessMessage, - ProcessMessageError, ServiceQueues, TransformOrigin, + ProcessMessageError, QueuePausedQuery, ServiceQueues, TransformOrigin, }; #[cfg(feature = "try-runtime")] diff --git a/frame/support/src/traits/messages.rs b/frame/support/src/traits/messages.rs index fe907b0c6d63e..36fa7957dff7c 100644 --- a/frame/support/src/traits/messages.rs +++ b/frame/support/src/traits/messages.rs @@ -69,8 +69,18 @@ pub trait ProcessMessage { pub enum ExecuteOverweightError { /// The referenced message was not found. NotFound, + /// The message was already processed. + /// + /// This can be treated as success condition. + AlreadyProcessed, /// The available weight was insufficient to execute the message. InsufficientWeight, + /// The queue is paused and no message can be executed from it. + /// + /// This can change at any time and may resolve in the future by re-trying. + QueuePaused, + /// An unspecified error. + Other, } /// Can service queues and execute overweight messages. @@ -220,3 +230,15 @@ where E::footprint(O::get()) } } + +/// Provides information on paused queues. +pub trait QueuePausedQuery { + /// Whether this queue is paused. + fn is_paused(origin: &Origin) -> bool; +} + +impl QueuePausedQuery for () { + fn is_paused(_: &Origin) -> bool { + false + } +} diff --git a/frame/support/test/tests/common/mod.rs b/frame/support/test/tests/common/mod.rs new file mode 100644 index 0000000000000..b02ecc1b6e1dd --- /dev/null +++ b/frame/support/test/tests/common/mod.rs @@ -0,0 +1,19 @@ +// This file is part of Substrate. + +// 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. + +///! Common functionality between tests. +pub mod outer_enums; diff --git a/frame/support/test/tests/common/outer_enums.rs b/frame/support/test/tests/common/outer_enums.rs new file mode 100644 index 0000000000000..98139b0f0d1a8 --- /dev/null +++ b/frame/support/test/tests/common/outer_enums.rs @@ -0,0 +1,146 @@ +// This file is part of Substrate. + +// 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. + +// Create 3 pallets for testing the outer error enum construction: +// +// - `pallet`: declares an error with `#[pallet::error]` +// - `pallet2`: declares an error with `#[pallet::error]` +// - `pallet3`: does not declare an error. + +#[frame_support::pallet(dev_mode)] +pub mod pallet { + use frame_support::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + } + + #[pallet::event] + pub enum Event, I: 'static = ()> { + /// Something + Something(u32), + } + + #[pallet::pallet] + pub struct Pallet(PhantomData<(T, I)>); + + #[pallet::genesis_config] + pub struct GenesisConfig, I: 'static = ()> { + phantom: PhantomData<(T, I)>, + } + + impl, I: 'static> Default for GenesisConfig { + fn default() -> Self { + GenesisConfig { phantom: Default::default() } + } + } + + #[pallet::genesis_build] + impl, I: 'static> GenesisBuild for GenesisConfig { + fn build(&self) {} + } + + #[pallet::error] + #[derive(PartialEq, Eq)] + pub enum Error { + /// doc comment put into metadata + InsufficientProposersBalance, + NonExistentStorageValue, + } +} + +#[frame_support::pallet] +pub mod pallet2 { + use frame_support::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + } + + #[pallet::event] + pub enum Event, I: 'static = ()> { + /// Something + Something(u32), + } + + #[pallet::pallet] + pub struct Pallet(PhantomData<(T, I)>); + + #[pallet::genesis_config] + pub struct GenesisConfig, I: 'static = ()> { + phantom: PhantomData<(T, I)>, + } + + impl, I: 'static> Default for GenesisConfig { + fn default() -> Self { + GenesisConfig { phantom: Default::default() } + } + } + + #[pallet::genesis_build] + impl, I: 'static> GenesisBuild for GenesisConfig { + fn build(&self) {} + } + + #[pallet::error] + #[derive(PartialEq, Eq)] + pub enum Error { + /// doc comment put into metadata + OtherInsufficientProposersBalance, + OtherNonExistentStorageValue, + } +} + +#[frame_support::pallet] +pub mod pallet3 { + use frame_support::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + } + + #[pallet::event] + pub enum Event, I: 'static = ()> { + /// Something + Something(u32), + } + + #[pallet::pallet] + pub struct Pallet(PhantomData<(T, I)>); + + #[pallet::genesis_config] + pub struct GenesisConfig, I: 'static = ()> { + phantom: PhantomData<(T, I)>, + } + + impl, I: 'static> Default for GenesisConfig { + fn default() -> Self { + GenesisConfig { phantom: Default::default() } + } + } + + #[pallet::genesis_build] + impl, I: 'static> GenesisBuild for GenesisConfig { + fn build(&self) {} + } +} diff --git a/frame/support/test/tests/construct_runtime_ui/generics_in_invalid_module.stderr b/frame/support/test/tests/construct_runtime_ui/generics_in_invalid_module.stderr index 22f38072747ee..a6adb37d04949 100644 --- a/frame/support/test/tests/construct_runtime_ui/generics_in_invalid_module.stderr +++ b/frame/support/test/tests/construct_runtime_ui/generics_in_invalid_module.stderr @@ -1,4 +1,4 @@ -error: `Call` is not allowed to have generics. Only the following pallets are allowed to have generics: `Event`, `Origin`, `Config`. +error: `Call` is not allowed to have generics. Only the following pallets are allowed to have generics: `Event`, `Error`, `Origin`, `Config`. --> tests/construct_runtime_ui/generics_in_invalid_module.rs:7:36 | 7 | Balance: balances::::{Call, Origin}, diff --git a/frame/support/test/tests/construct_runtime_ui/invalid_module_details_keyword.stderr b/frame/support/test/tests/construct_runtime_ui/invalid_module_details_keyword.stderr index d4e7b97f00b4d..dfcc9b8be42c6 100644 --- a/frame/support/test/tests/construct_runtime_ui/invalid_module_details_keyword.stderr +++ b/frame/support/test/tests/construct_runtime_ui/invalid_module_details_keyword.stderr @@ -1,4 +1,4 @@ -error: expected one of: `Pallet`, `Call`, `Storage`, `Event`, `Config`, `Origin`, `Inherent`, `ValidateUnsigned`, `FreezeReason`, `HoldReason`, `LockId`, `SlashReason` +error: expected one of: `Pallet`, `Call`, `Storage`, `Event`, `Error`, `Config`, `Origin`, `Inherent`, `ValidateUnsigned`, `FreezeReason`, `HoldReason`, `LockId`, `SlashReason` --> tests/construct_runtime_ui/invalid_module_details_keyword.rs:6:20 | 6 | system: System::{enum}, diff --git a/frame/support/test/tests/construct_runtime_ui/invalid_module_entry.rs b/frame/support/test/tests/construct_runtime_ui/invalid_module_entry.rs index f70b5b0d63a27..607741d7823d4 100644 --- a/frame/support/test/tests/construct_runtime_ui/invalid_module_entry.rs +++ b/frame/support/test/tests/construct_runtime_ui/invalid_module_entry.rs @@ -4,7 +4,7 @@ construct_runtime! { pub struct Runtime { System: system::{Pallet}, - Balance: balances::{Error}, + Balance: balances::{Unexpected}, } } diff --git a/frame/support/test/tests/construct_runtime_ui/invalid_module_entry.stderr b/frame/support/test/tests/construct_runtime_ui/invalid_module_entry.stderr index f17f04ae60aba..9dd849ff0412e 100644 --- a/frame/support/test/tests/construct_runtime_ui/invalid_module_entry.stderr +++ b/frame/support/test/tests/construct_runtime_ui/invalid_module_entry.stderr @@ -1,5 +1,5 @@ -error: expected one of: `Pallet`, `Call`, `Storage`, `Event`, `Config`, `Origin`, `Inherent`, `ValidateUnsigned`, `FreezeReason`, `HoldReason`, `LockId`, `SlashReason` +error: expected one of: `Pallet`, `Call`, `Storage`, `Event`, `Error`, `Config`, `Origin`, `Inherent`, `ValidateUnsigned`, `FreezeReason`, `HoldReason`, `LockId`, `SlashReason` --> tests/construct_runtime_ui/invalid_module_entry.rs:7:23 | -7 | Balance: balances::{Error}, - | ^^^^^ +7 | Balance: balances::{Unexpected}, + | ^^^^^^^^^^ diff --git a/frame/support/test/tests/construct_runtime_ui/missing_event_generic_on_module_with_instance.rs b/frame/support/test/tests/construct_runtime_ui/missing_event_generic_on_module_with_instance.rs index e1b2d0098d902..bc2039c4e8180 100644 --- a/frame/support/test/tests/construct_runtime_ui/missing_event_generic_on_module_with_instance.rs +++ b/frame/support/test/tests/construct_runtime_ui/missing_event_generic_on_module_with_instance.rs @@ -3,8 +3,8 @@ use frame_support::construct_runtime; construct_runtime! { pub struct Runtime { - System: system::{Pallet}, - Balance: balances::::{Event}, + System: system expanded::{}::{Pallet}, + Balance: balances:: expanded::{}::{Event}, } } diff --git a/frame/support/test/tests/construct_runtime_ui/missing_event_generic_on_module_with_instance.stderr b/frame/support/test/tests/construct_runtime_ui/missing_event_generic_on_module_with_instance.stderr index b3d9f3ac0f85f..30fcba4c710d0 100644 --- a/frame/support/test/tests/construct_runtime_ui/missing_event_generic_on_module_with_instance.stderr +++ b/frame/support/test/tests/construct_runtime_ui/missing_event_generic_on_module_with_instance.stderr @@ -1,5 +1,5 @@ error: Instantiable pallet with no generic `Event` cannot be constructed: pallet `Balance` must have generic `Event` --> tests/construct_runtime_ui/missing_event_generic_on_module_with_instance.rs:7:3 | -7 | Balance: balances::::{Event}, +7 | Balance: balances:: expanded::{}::{Event}, | ^^^^^^^ diff --git a/frame/support/test/tests/construct_runtime_ui/missing_origin_generic_on_module_with_instance.rs b/frame/support/test/tests/construct_runtime_ui/missing_origin_generic_on_module_with_instance.rs index 33e92c07ee201..42db63ae90a3a 100644 --- a/frame/support/test/tests/construct_runtime_ui/missing_origin_generic_on_module_with_instance.rs +++ b/frame/support/test/tests/construct_runtime_ui/missing_origin_generic_on_module_with_instance.rs @@ -3,8 +3,8 @@ use frame_support::construct_runtime; construct_runtime! { pub struct Runtime { - System: system::{Pallet}, - Balance: balances::::{Origin}, + System: system expanded::{}::{Pallet}, + Balance: balances:: expanded::{}::{Origin}, } } diff --git a/frame/support/test/tests/construct_runtime_ui/missing_origin_generic_on_module_with_instance.stderr b/frame/support/test/tests/construct_runtime_ui/missing_origin_generic_on_module_with_instance.stderr index 7300346191c0c..6c076d7b49fc0 100644 --- a/frame/support/test/tests/construct_runtime_ui/missing_origin_generic_on_module_with_instance.stderr +++ b/frame/support/test/tests/construct_runtime_ui/missing_origin_generic_on_module_with_instance.stderr @@ -1,5 +1,5 @@ error: Instantiable pallet with no generic `Origin` cannot be constructed: pallet `Balance` must have generic `Origin` --> tests/construct_runtime_ui/missing_origin_generic_on_module_with_instance.rs:7:3 | -7 | Balance: balances::::{Origin}, +7 | Balance: balances:: expanded::{}::{Origin}, | ^^^^^^^ diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_event_part.rs b/frame/support/test/tests/construct_runtime_ui/undefined_event_part.rs index e5bb5fb486cb3..91dfb7891c99d 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_event_part.rs +++ b/frame/support/test/tests/construct_runtime_ui/undefined_event_part.rs @@ -48,8 +48,8 @@ impl frame_system::Config for Runtime { construct_runtime! { pub struct Runtime { - System: frame_system::{Pallet, Call, Storage, Config, Event}, - Pallet: pallet::{Pallet, Event}, + System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, + Pallet: pallet expanded::{}::{Pallet, Event}, } } diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_event_part.stderr b/frame/support/test/tests/construct_runtime_ui/undefined_event_part.stderr index c9c0962d13f46..fb2f5d56bbe40 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_event_part.stderr +++ b/frame/support/test/tests/construct_runtime_ui/undefined_event_part.stderr @@ -7,8 +7,8 @@ error: `Pallet` does not have #[pallet::event] defined, perhaps you should remov 48 | / construct_runtime! { 49 | | pub struct Runtime 50 | | { -51 | | System: frame_system::{Pallet, Call, Storage, Config, Event}, -52 | | Pallet: pallet::{Pallet, Event}, +51 | | System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, +52 | | Pallet: pallet expanded::{}::{Pallet, Event}, 53 | | } 54 | | } | |_- in this macro invocation @@ -21,8 +21,8 @@ error[E0412]: cannot find type `Event` in module `pallet` 48 | / construct_runtime! { 49 | | pub struct Runtime 50 | | { -51 | | System: frame_system::{Pallet, Call, Storage, Config, Event}, -52 | | Pallet: pallet::{Pallet, Event}, +51 | | System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, +52 | | Pallet: pallet expanded::{}::{Pallet, Event}, 53 | | } 54 | | } | |_^ not found in `pallet` diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.rs b/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.rs index b6bc408043fbf..9a2a94b650b3e 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.rs +++ b/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.rs @@ -48,8 +48,8 @@ impl frame_system::Config for Runtime { construct_runtime! { pub struct Runtime { - System: frame_system::{Pallet, Call, Storage, Config, Event}, - Pallet: pallet::{Pallet, Config}, + System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, + Pallet: pallet expanded::{}::{Pallet, Config}, } } diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.stderr b/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.stderr index d5db097ccd3d0..871fd77ae465a 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.stderr +++ b/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.stderr @@ -7,8 +7,8 @@ error: `Pallet` does not have #[pallet::genesis_config] defined, perhaps you sho 48 | / construct_runtime! { 49 | | pub struct Runtime 50 | | { -51 | | System: frame_system::{Pallet, Call, Storage, Config, Event}, -52 | | Pallet: pallet::{Pallet, Config}, +51 | | System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, +52 | | Pallet: pallet expanded::{}::{Pallet, Config}, 53 | | } 54 | | } | |_- in this macro invocation @@ -21,8 +21,8 @@ error[E0412]: cannot find type `GenesisConfig` in module `pallet` 48 | / construct_runtime! { 49 | | pub struct Runtime 50 | | { -51 | | System: frame_system::{Pallet, Call, Storage, Config, Event}, -52 | | Pallet: pallet::{Pallet, Config}, +51 | | System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, +52 | | Pallet: pallet expanded::{}::{Pallet, Config}, 53 | | } 54 | | } | |_^ not found in `pallet` diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.rs b/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.rs index 4fb6382c93ffa..362623cbcbb39 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.rs +++ b/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.rs @@ -48,8 +48,8 @@ impl frame_system::Config for Runtime { construct_runtime! { pub struct Runtime { - System: frame_system::{Pallet, Call, Storage, Config, Event}, - Pallet: pallet::{Pallet, Inherent}, + System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, + Pallet: pallet expanded::{}::{Pallet, Inherent}, } } diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.stderr b/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.stderr index bce1c4d9efb87..4b208b9b13365 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.stderr +++ b/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.stderr @@ -7,8 +7,8 @@ error: `Pallet` does not have #[pallet::inherent] defined, perhaps you should re 48 | / construct_runtime! { 49 | | pub struct Runtime 50 | | { -51 | | System: frame_system::{Pallet, Call, Storage, Config, Event}, -52 | | Pallet: pallet::{Pallet, Inherent}, +51 | | System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, +52 | | Pallet: pallet expanded::{}::{Pallet, Inherent}, 53 | | } 54 | | } | |_- in this macro invocation @@ -25,8 +25,8 @@ error[E0599]: no function or associated item named `create_inherent` found for s | _^ 49 | | pub struct Runtime 50 | | { -51 | | System: frame_system::{Pallet, Call, Storage, Config, Event}, -52 | | Pallet: pallet::{Pallet, Inherent}, +51 | | System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, +52 | | Pallet: pallet expanded::{}::{Pallet, Inherent}, 53 | | } 54 | | } | |_^ function or associated item not found in `Pallet` @@ -46,8 +46,8 @@ error[E0599]: no function or associated item named `is_inherent` found for struc | _^ 49 | | pub struct Runtime 50 | | { -51 | | System: frame_system::{Pallet, Call, Storage, Config, Event}, -52 | | Pallet: pallet::{Pallet, Inherent}, +51 | | System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, +52 | | Pallet: pallet expanded::{}::{Pallet, Inherent}, 53 | | } 54 | | } | |_^ function or associated item not found in `Pallet` @@ -67,8 +67,8 @@ error[E0599]: no function or associated item named `check_inherent` found for st | _^ 49 | | pub struct Runtime 50 | | { -51 | | System: frame_system::{Pallet, Call, Storage, Config, Event}, -52 | | Pallet: pallet::{Pallet, Inherent}, +51 | | System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, +52 | | Pallet: pallet expanded::{}::{Pallet, Inherent}, 53 | | } 54 | | } | |_^ function or associated item not found in `Pallet` @@ -88,8 +88,8 @@ error[E0599]: no associated item named `INHERENT_IDENTIFIER` found for struct `p | _^ 49 | | pub struct Runtime 50 | | { -51 | | System: frame_system::{Pallet, Call, Storage, Config, Event}, -52 | | Pallet: pallet::{Pallet, Inherent}, +51 | | System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, +52 | | Pallet: pallet expanded::{}::{Pallet, Inherent}, 53 | | } 54 | | } | |_^ associated item not found in `Pallet` @@ -109,8 +109,8 @@ error[E0599]: no function or associated item named `is_inherent_required` found | _^ 49 | | pub struct Runtime 50 | | { -51 | | System: frame_system::{Pallet, Call, Storage, Config, Event}, -52 | | Pallet: pallet::{Pallet, Inherent}, +51 | | System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, +52 | | Pallet: pallet expanded::{}::{Pallet, Inherent}, 53 | | } 54 | | } | |_^ function or associated item not found in `Pallet` diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.rs b/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.rs index 997c32c95055e..8dd6fe3b726b6 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.rs +++ b/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.rs @@ -48,8 +48,8 @@ impl frame_system::Config for Runtime { construct_runtime! { pub struct Runtime { - System: frame_system::{Pallet, Call, Storage, Config, Event}, - Pallet: pallet::{Pallet, Origin}, + System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, + Pallet: pallet expanded::{}::{Pallet, Origin}, } } diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.stderr b/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.stderr index 90bf0b52cd296..bf9dff6c55dfb 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.stderr +++ b/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.stderr @@ -7,8 +7,8 @@ error: `Pallet` does not have #[pallet::origin] defined, perhaps you should remo 48 | / construct_runtime! { 49 | | pub struct Runtime 50 | | { -51 | | System: frame_system::{Pallet, Call, Storage, Config, Event}, -52 | | Pallet: pallet::{Pallet, Origin}, +51 | | System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, +52 | | Pallet: pallet expanded::{}::{Pallet, Origin}, 53 | | } 54 | | } | |_- in this macro invocation @@ -21,8 +21,8 @@ error[E0412]: cannot find type `Origin` in module `pallet` 48 | / construct_runtime! { 49 | | pub struct Runtime 50 | | { -51 | | System: frame_system::{Pallet, Call, Storage, Config, Event}, -52 | | Pallet: pallet::{Pallet, Origin}, +51 | | System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, +52 | | Pallet: pallet expanded::{}::{Pallet, Origin}, 53 | | } 54 | | } | |_^ not found in `pallet` diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_validate_unsigned_part.rs b/frame/support/test/tests/construct_runtime_ui/undefined_validate_unsigned_part.rs index f97eee993701b..3f6847665974e 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_validate_unsigned_part.rs +++ b/frame/support/test/tests/construct_runtime_ui/undefined_validate_unsigned_part.rs @@ -1,6 +1,6 @@ use frame_support::construct_runtime; -use sp_runtime::{generic, traits::BlakeTwo256}; use sp_core::sr25519; +use sp_runtime::{generic, traits::BlakeTwo256}; #[frame_support::pallet] mod pallet { diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_validate_unsigned_part.stderr b/frame/support/test/tests/construct_runtime_ui/undefined_validate_unsigned_part.stderr index a7ce8615093f9..4bd5313cb9b6f 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_validate_unsigned_part.stderr +++ b/frame/support/test/tests/construct_runtime_ui/undefined_validate_unsigned_part.stderr @@ -26,50 +26,54 @@ error[E0599]: no variant or associated item named `Pallet` found for enum `Runti | || -^^^^^^ variant or associated item not found in `RuntimeCall` | ||________| | | -53 | | } -54 | | } - | |__- variant or associated item `Pallet` not found for this enum +... | error[E0599]: no function or associated item named `pre_dispatch` found for struct `pallet::Pallet` in the current scope --> tests/construct_runtime_ui/undefined_validate_unsigned_part.rs:48:1 | -11 | pub struct Pallet(_); - | -------------------- function or associated item `pre_dispatch` not found for this struct +11 | pub struct Pallet(_); + | -------------------- function or associated item `pre_dispatch` not found for this struct ... -48 | construct_runtime! { - | _^ -49 | | pub struct Runtime -50 | | { -51 | | System: frame_system::{Pallet, Call, Storage, Config, Event}, -52 | | Pallet: pallet::{Pallet, ValidateUnsigned}, -53 | | } -54 | | } - | |_^ function or associated item not found in `Pallet` +48 | construct_runtime! { + | __^ + | | _| + | || +49 | || pub struct Runtime +50 | || { +51 | || System: frame_system::{Pallet, Call, Storage, Config, Event}, +52 | || Pallet: pallet::{Pallet, ValidateUnsigned}, +53 | || } +54 | || } + | ||_- in this macro invocation +... | | = help: items from traits can only be used if the trait is implemented and in scope = note: the following traits define an item `pre_dispatch`, perhaps you need to implement one of them: candidate #1: `SignedExtension` candidate #2: `ValidateUnsigned` - = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0599]: no function or associated item named `validate_unsigned` found for struct `pallet::Pallet` in the current scope --> tests/construct_runtime_ui/undefined_validate_unsigned_part.rs:48:1 | -11 | pub struct Pallet(_); - | -------------------- function or associated item `validate_unsigned` not found for this struct +11 | pub struct Pallet(_); + | -------------------- function or associated item `validate_unsigned` not found for this struct ... -48 | construct_runtime! { - | _^ -49 | | pub struct Runtime -50 | | { -51 | | System: frame_system::{Pallet, Call, Storage, Config, Event}, -52 | | Pallet: pallet::{Pallet, ValidateUnsigned}, -53 | | } -54 | | } - | |_^ function or associated item not found in `Pallet` +48 | construct_runtime! { + | __^ + | | _| + | || +49 | || pub struct Runtime +50 | || { +51 | || System: frame_system::{Pallet, Call, Storage, Config, Event}, +52 | || Pallet: pallet::{Pallet, ValidateUnsigned}, +53 | || } +54 | || } + | ||_- in this macro invocation +... | | = help: items from traits can only be used if the trait is implemented and in scope = note: the following traits define an item `validate_unsigned`, perhaps you need to implement one of them: candidate #1: `SignedExtension` candidate #2: `ValidateUnsigned` - = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/frame/support/test/tests/pallet_instance.rs b/frame/support/test/tests/pallet_instance.rs index d5d5982dd6d79..b8c551579f0d7 100644 --- a/frame/support/test/tests/pallet_instance.rs +++ b/frame/support/test/tests/pallet_instance.rs @@ -415,6 +415,39 @@ fn error_expand() { ); } +#[test] +fn module_error_outer_enum_expand() { + // assert that all variants of the Example pallet are included into the + // RuntimeError definition. + match RuntimeError::Example(pallet::Error::InsufficientProposersBalance) { + RuntimeError::Example(example) => match example { + pallet::Error::InsufficientProposersBalance => (), + pallet::Error::NonExistentStorageValue => (), + // Extra pattern added by `construct_runtime`. + pallet::Error::__Ignore(_, _) => (), + }, + _ => (), + }; +} + +#[test] +fn module_error_from_dispatch_error() { + let dispatch_err = DispatchError::Module(ModuleError { + index: 1, + error: [0; 4], + message: Some("InsufficientProposersBalance"), + }); + let err = RuntimeError::from_dispatch_error(dispatch_err).unwrap(); + + match err { + RuntimeError::Example(pallet::Error::InsufficientProposersBalance) => (), + _ => panic!("Module error constructed incorrectly"), + }; + + // Only `ModuleError` is converted. + assert!(RuntimeError::from_dispatch_error(DispatchError::BadOrigin).is_none()); +} + #[test] fn instance_expand() { // assert same type diff --git a/frame/support/test/tests/pallet_outer_enums_explicit.rs b/frame/support/test/tests/pallet_outer_enums_explicit.rs new file mode 100644 index 0000000000000..dc98ec43fd933 --- /dev/null +++ b/frame/support/test/tests/pallet_outer_enums_explicit.rs @@ -0,0 +1,140 @@ +// This file is part of Substrate. + +// 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 frame_support::traits::ConstU32; + +mod common; + +use common::outer_enums::{pallet, pallet2}; + +pub type Header = sp_runtime::generic::Header; +pub type Block = sp_runtime::generic::Block; +pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; + +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u32; + type RuntimeCall = RuntimeCall; + type Hash = sp_runtime::testing::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = u64; + type Lookup = sp_runtime::traits::IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU32<250>; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl common::outer_enums::pallet::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} +impl common::outer_enums::pallet::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} +impl common::outer_enums::pallet2::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} +impl common::outer_enums::pallet2::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} +impl common::outer_enums::pallet3::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} +impl common::outer_enums::pallet3::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} + +frame_support::construct_runtime!( + pub struct Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + // Exclude part `Storage` in order not to check its metadata in tests. + System: frame_system::{Pallet, Config, Call, Event }, + + // This pallet exposes the Error type explicitly. + Example: common::outer_enums::pallet::{Pallet, Config, Event, Error}, + Instance1Example: common::outer_enums::pallet::::{ Pallet, Config, Event }, + + // This pallet does not mention the Error type, but it must be propagated (similarly to the polkadot/kusama). + Example2: common::outer_enums::pallet2::{Pallet, Config, Event }, + Instance1Example2: common::outer_enums::pallet2::::{Pallet, Config, Event}, + + // This pallet does not declare any errors. + Example3: common::outer_enums::pallet3::{Pallet, Config, Event}, + Instance1Example3: common::outer_enums::pallet3::::{Pallet, Config, Event}, + } +); + +#[test] +fn module_error_outer_enum_expand_explicit() { + // The Runtime has *all* parts explicitly defined. + + // Check that all error types are propagated + match RuntimeError::Example(pallet::Error::InsufficientProposersBalance) { + // Error passed implicitely to the pallet system. + RuntimeError::System(system) => match system { + frame_system::Error::InvalidSpecName => (), + frame_system::Error::SpecVersionNeedsToIncrease => (), + frame_system::Error::FailedToExtractRuntimeVersion => (), + frame_system::Error::NonDefaultComposite => (), + frame_system::Error::NonZeroRefCount => (), + frame_system::Error::CallFiltered => (), + frame_system::Error::__Ignore(_, _) => (), + }, + + // Error declared explicitly. + RuntimeError::Example(example) => match example { + pallet::Error::InsufficientProposersBalance => (), + pallet::Error::NonExistentStorageValue => (), + pallet::Error::__Ignore(_, _) => (), + }, + // Error declared explicitly. + RuntimeError::Instance1Example(example) => match example { + pallet::Error::InsufficientProposersBalance => (), + pallet::Error::NonExistentStorageValue => (), + pallet::Error::__Ignore(_, _) => (), + }, + + // Error must propagate even if not defined explicitly as pallet part. + RuntimeError::Example2(example) => match example { + pallet2::Error::OtherInsufficientProposersBalance => (), + pallet2::Error::OtherNonExistentStorageValue => (), + pallet2::Error::__Ignore(_, _) => (), + }, + // Error must propagate even if not defined explicitly as pallet part. + RuntimeError::Instance1Example2(example) => match example { + pallet2::Error::OtherInsufficientProposersBalance => (), + pallet2::Error::OtherNonExistentStorageValue => (), + pallet2::Error::__Ignore(_, _) => (), + }, + }; +} diff --git a/frame/support/test/tests/pallet_outer_enums_implicit.rs b/frame/support/test/tests/pallet_outer_enums_implicit.rs new file mode 100644 index 0000000000000..9a14538c35930 --- /dev/null +++ b/frame/support/test/tests/pallet_outer_enums_implicit.rs @@ -0,0 +1,140 @@ +// This file is part of Substrate. + +// 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 frame_support::traits::ConstU32; + +mod common; + +use common::outer_enums::{pallet, pallet2}; + +pub type Header = sp_runtime::generic::Header; +pub type Block = sp_runtime::generic::Block; +pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; + +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u32; + type RuntimeCall = RuntimeCall; + type Hash = sp_runtime::testing::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = u64; + type Lookup = sp_runtime::traits::IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU32<250>; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl common::outer_enums::pallet::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} +impl common::outer_enums::pallet::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} +impl common::outer_enums::pallet2::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} +impl common::outer_enums::pallet2::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} +impl common::outer_enums::pallet3::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} +impl common::outer_enums::pallet3::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} + +frame_support::construct_runtime!( + pub struct Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + // Exclude part `Storage` in order not to check its metadata in tests. + System: frame_system exclude_parts { Storage }, + + // Pallet exposes `Error` implicitely. + Example: common::outer_enums::pallet, + Instance1Example: common::outer_enums::pallet::, + + // Pallet exposes `Error` implicitely. + Example2: common::outer_enums::pallet2, + Instance1Example2: common::outer_enums::pallet2::, + + // Pallet does not implement error. + Example3: common::outer_enums::pallet3, + Instance1Example3: common::outer_enums::pallet3::, + } +); + +#[test] +fn module_error_outer_enum_expand_implicit() { + // The Runtime has *all* parts implicitly defined. + + // Check that all error types are propagated + match RuntimeError::Example(pallet::Error::InsufficientProposersBalance) { + // Error passed implicitely to the pallet system. + RuntimeError::System(system) => match system { + frame_system::Error::InvalidSpecName => (), + frame_system::Error::SpecVersionNeedsToIncrease => (), + frame_system::Error::FailedToExtractRuntimeVersion => (), + frame_system::Error::NonDefaultComposite => (), + frame_system::Error::NonZeroRefCount => (), + frame_system::Error::CallFiltered => (), + frame_system::Error::__Ignore(_, _) => (), + }, + + // Error declared explicitly. + RuntimeError::Example(example) => match example { + pallet::Error::InsufficientProposersBalance => (), + pallet::Error::NonExistentStorageValue => (), + pallet::Error::__Ignore(_, _) => (), + }, + // Error declared explicitly. + RuntimeError::Instance1Example(example) => match example { + pallet::Error::InsufficientProposersBalance => (), + pallet::Error::NonExistentStorageValue => (), + pallet::Error::__Ignore(_, _) => (), + }, + + // Error must propagate even if not defined explicitly as pallet part. + RuntimeError::Example2(example) => match example { + pallet2::Error::OtherInsufficientProposersBalance => (), + pallet2::Error::OtherNonExistentStorageValue => (), + pallet2::Error::__Ignore(_, _) => (), + }, + // Error must propagate even if not defined explicitly as pallet part. + RuntimeError::Instance1Example2(example) => match example { + pallet2::Error::OtherInsufficientProposersBalance => (), + pallet2::Error::OtherNonExistentStorageValue => (), + pallet2::Error::__Ignore(_, _) => (), + }, + }; +} diff --git a/primitives/metadata-ir/Cargo.toml b/primitives/metadata-ir/Cargo.toml index 955f9673e6c27..924fc08027563 100644 --- a/primitives/metadata-ir/Cargo.toml +++ b/primitives/metadata-ir/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } -frame-metadata = { version = "15.1.0", default-features = false, features = ["v14", "v15-unstable"] } +frame-metadata = { version = "15.2.0", default-features = false, features = ["unstable"] } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } sp-std = { version = "8.0.0", default-features = false, path = "../std" } diff --git a/primitives/metadata-ir/src/lib.rs b/primitives/metadata-ir/src/lib.rs index 3ddc2911d4c93..a932585a75ca9 100644 --- a/primitives/metadata-ir/src/lib.rs +++ b/primitives/metadata-ir/src/lib.rs @@ -85,6 +85,11 @@ mod test { }, ty: meta_type::<()>(), apis: vec![], + outer_enums: OuterEnumsIR { + call_enum_ty: meta_type::<()>(), + event_enum_ty: meta_type::<()>(), + error_enum_ty: meta_type::<()>(), + }, } } diff --git a/primitives/metadata-ir/src/types.rs b/primitives/metadata-ir/src/types.rs index 93ee54891d89f..76d9385cd3cbf 100644 --- a/primitives/metadata-ir/src/types.rs +++ b/primitives/metadata-ir/src/types.rs @@ -39,6 +39,8 @@ pub struct MetadataIR { pub ty: T::Type, /// Metadata of the Runtime API. pub apis: Vec>, + /// The outer enums types as found in the runtime. + pub outer_enums: OuterEnumsIR, } /// Metadata of a runtime trait. @@ -398,3 +400,40 @@ impl From for PalletErrorMetadataIR { Self { ty } } } + +/// The type of the outer enums. +#[derive(Clone, PartialEq, Eq, Encode, Debug)] +pub struct OuterEnumsIR { + /// The type of the outer `RuntimeCall` enum. + pub call_enum_ty: T::Type, + /// The type of the outer `RuntimeEvent` enum. + pub event_enum_ty: T::Type, + /// The module error type of the + /// [`DispatchError::Module`](https://docs.rs/sp-runtime/24.0.0/sp_runtime/enum.DispatchError.html#variant.Module) variant. + /// + /// The `Module` variant will be 5 scale encoded bytes which are normally decoded into + /// an `{ index: u8, error: [u8; 4] }` struct. This type ID points to an enum type which + /// instead interprets the first `index` byte as a pallet variant, and the remaining `error` + /// bytes as the appropriate `pallet::Error` type. It is an equally valid way to decode the + /// error bytes, and can be more informative. + /// + /// # Note + /// + /// - This type cannot be used directly to decode `sp_runtime::DispatchError` from the chain. + /// It provides just the information needed to decode `sp_runtime::DispatchError::Module`. + /// - Decoding the 5 error bytes into this type will not always lead to all of the bytes being + /// consumed; many error types do not require all of the bytes to represent them fully. + pub error_enum_ty: T::Type, +} + +impl IntoPortable for OuterEnumsIR { + type Output = OuterEnumsIR; + + fn into_portable(self, registry: &mut Registry) -> Self::Output { + OuterEnumsIR { + call_enum_ty: registry.register_type(&self.call_enum_ty), + event_enum_ty: registry.register_type(&self.event_enum_ty), + error_enum_ty: registry.register_type(&self.error_enum_ty), + } + } +} diff --git a/primitives/metadata-ir/src/v15.rs b/primitives/metadata-ir/src/v15.rs index 86441228d008e..814a5ae98372f 100644 --- a/primitives/metadata-ir/src/v15.rs +++ b/primitives/metadata-ir/src/v15.rs @@ -17,20 +17,17 @@ //! Convert the IR to V15 metadata. +use crate::OuterEnumsIR; + use super::types::{ - ExtrinsicMetadataIR, MetadataIR, PalletCallMetadataIR, PalletConstantMetadataIR, - PalletErrorMetadataIR, PalletEventMetadataIR, PalletMetadataIR, PalletStorageMetadataIR, - RuntimeApiMetadataIR, RuntimeApiMethodMetadataIR, RuntimeApiMethodParamMetadataIR, - SignedExtensionMetadataIR, StorageEntryMetadataIR, StorageEntryModifierIR, StorageEntryTypeIR, - StorageHasherIR, + ExtrinsicMetadataIR, MetadataIR, PalletMetadataIR, RuntimeApiMetadataIR, + RuntimeApiMethodMetadataIR, RuntimeApiMethodParamMetadataIR, SignedExtensionMetadataIR, }; use frame_metadata::v15::{ - ExtrinsicMetadata, PalletCallMetadata, PalletConstantMetadata, PalletErrorMetadata, - PalletEventMetadata, PalletMetadata, PalletStorageMetadata, RuntimeApiMetadata, + CustomMetadata, ExtrinsicMetadata, OuterEnums, PalletMetadata, RuntimeApiMetadata, RuntimeApiMethodMetadata, RuntimeApiMethodParamMetadata, RuntimeMetadataV15, - SignedExtensionMetadata, StorageEntryMetadata, StorageEntryModifier, StorageEntryType, - StorageHasher, + SignedExtensionMetadata, }; impl From for RuntimeMetadataV15 { @@ -40,6 +37,10 @@ impl From for RuntimeMetadataV15 { ir.extrinsic.into(), ir.ty, ir.apis.into_iter().map(Into::into).collect(), + ir.outer_enums.into(), + // Substrate does not collect yet the custom metadata fields. + // This allows us to extend the V15 easily. + CustomMetadata { map: Default::default() }, ) } } @@ -86,87 +87,6 @@ impl From for PalletMetadata { } } -impl From for StorageEntryModifier { - fn from(ir: StorageEntryModifierIR) -> Self { - match ir { - StorageEntryModifierIR::Optional => StorageEntryModifier::Optional, - StorageEntryModifierIR::Default => StorageEntryModifier::Default, - } - } -} - -impl From for StorageHasher { - fn from(ir: StorageHasherIR) -> Self { - match ir { - StorageHasherIR::Blake2_128 => StorageHasher::Blake2_128, - StorageHasherIR::Blake2_256 => StorageHasher::Blake2_256, - StorageHasherIR::Blake2_128Concat => StorageHasher::Blake2_128Concat, - StorageHasherIR::Twox128 => StorageHasher::Twox128, - StorageHasherIR::Twox256 => StorageHasher::Twox256, - StorageHasherIR::Twox64Concat => StorageHasher::Twox64Concat, - StorageHasherIR::Identity => StorageHasher::Identity, - } - } -} - -impl From for StorageEntryType { - fn from(ir: StorageEntryTypeIR) -> Self { - match ir { - StorageEntryTypeIR::Plain(ty) => StorageEntryType::Plain(ty), - StorageEntryTypeIR::Map { hashers, key, value } => StorageEntryType::Map { - hashers: hashers.into_iter().map(Into::into).collect(), - key, - value, - }, - } - } -} - -impl From for StorageEntryMetadata { - fn from(ir: StorageEntryMetadataIR) -> Self { - StorageEntryMetadata { - name: ir.name, - modifier: ir.modifier.into(), - ty: ir.ty.into(), - default: ir.default, - docs: ir.docs, - } - } -} - -impl From for PalletStorageMetadata { - fn from(ir: PalletStorageMetadataIR) -> Self { - PalletStorageMetadata { - prefix: ir.prefix, - entries: ir.entries.into_iter().map(Into::into).collect(), - } - } -} - -impl From for PalletCallMetadata { - fn from(ir: PalletCallMetadataIR) -> Self { - PalletCallMetadata { ty: ir.ty } - } -} - -impl From for PalletEventMetadata { - fn from(ir: PalletEventMetadataIR) -> Self { - PalletEventMetadata { ty: ir.ty } - } -} - -impl From for PalletConstantMetadata { - fn from(ir: PalletConstantMetadataIR) -> Self { - PalletConstantMetadata { name: ir.name, ty: ir.ty, value: ir.value, docs: ir.docs } - } -} - -impl From for PalletErrorMetadata { - fn from(ir: PalletErrorMetadataIR) -> Self { - PalletErrorMetadata { ty: ir.ty } - } -} - impl From for SignedExtensionMetadata { fn from(ir: SignedExtensionMetadataIR) -> Self { SignedExtensionMetadata { @@ -180,9 +100,23 @@ impl From for SignedExtensionMetadata { impl From for ExtrinsicMetadata { fn from(ir: ExtrinsicMetadataIR) -> Self { ExtrinsicMetadata { - ty: ir.ty, version: ir.version, signed_extensions: ir.signed_extensions.into_iter().map(Into::into).collect(), + // Note: These fields are populated by complementary PR: https://github.com/paritytech/substrate/pull/14123. + address_ty: ir.ty, + call_ty: ir.ty, + signature_ty: ir.ty, + extra_ty: ir.ty, + } + } +} + +impl From for OuterEnums { + fn from(ir: OuterEnumsIR) -> Self { + OuterEnums { + call_enum_ty: ir.call_enum_ty, + event_enum_ty: ir.event_enum_ty, + error_enum_ty: ir.error_enum_ty, } } }