1#![cfg_attr(not(feature = "std"), no_std)]
54
55mod benchmarking;
56mod tests;
57pub mod weights;
58
59extern crate alloc;
60
61use alloc::{boxed::Box, vec::Vec};
62use codec::{Decode, Encode};
63use frame_support::{
64 dispatch::{
65 extract_actual_weight,
66 DispatchClass::{Normal, Operational},
67 GetDispatchInfo, PostDispatchInfo,
68 },
69 traits::{IsSubType, OriginTrait, UnfilteredDispatchable},
70};
71use sp_core::TypeId;
72use sp_io::hashing::blake2_256;
73use sp_runtime::traits::{BadOrigin, Dispatchable, TrailingZeroInput};
74pub use weights::WeightInfo;
75
76pub use pallet::*;
77
78#[frame_support::pallet]
79pub mod pallet {
80 use super::*;
81 use frame_support::{dispatch::DispatchClass, pallet_prelude::*};
82 use frame_system::pallet_prelude::*;
83
84 #[pallet::pallet]
85 pub struct Pallet<T>(_);
86
87 #[pallet::config]
89 pub trait Config: frame_system::Config {
90 #[allow(deprecated)]
92 type RuntimeEvent: From<Event> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
93
94 type RuntimeCall: Parameter
96 + Dispatchable<RuntimeOrigin = Self::RuntimeOrigin, PostInfo = PostDispatchInfo>
97 + GetDispatchInfo
98 + From<frame_system::Call<Self>>
99 + UnfilteredDispatchable<RuntimeOrigin = Self::RuntimeOrigin>
100 + IsSubType<Call<Self>>
101 + IsType<<Self as frame_system::Config>::RuntimeCall>;
102
103 type PalletsOrigin: Parameter +
105 Into<<Self as frame_system::Config>::RuntimeOrigin> +
106 IsType<<<Self as frame_system::Config>::RuntimeOrigin as frame_support::traits::OriginTrait>::PalletsOrigin>;
107
108 type WeightInfo: WeightInfo;
110 }
111
112 #[pallet::event]
113 #[pallet::generate_deposit(pub(super) fn deposit_event)]
114 pub enum Event {
115 BatchInterrupted { index: u32, error: DispatchError },
118 BatchCompleted,
120 BatchCompletedWithErrors,
122 ItemCompleted,
124 ItemFailed { error: DispatchError },
126 DispatchedAs { result: DispatchResult },
128 IfElseMainSuccess,
130 IfElseFallbackCalled { main_error: DispatchError },
132 }
133
134 const CALL_ALIGN: u32 = 1024;
139
140 #[pallet::extra_constants]
141 impl<T: Config> Pallet<T> {
142 fn batched_calls_limit() -> u32 {
144 let allocator_limit = sp_core::MAX_POSSIBLE_ALLOCATION;
145 let call_size = (core::mem::size_of::<<T as Config>::RuntimeCall>() as u32)
146 .div_ceil(CALL_ALIGN) *
147 CALL_ALIGN;
148 let margin_factor = 3;
150
151 allocator_limit / margin_factor / call_size
152 }
153 }
154
155 #[pallet::hooks]
156 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
157 fn integrity_test() {
158 assert!(
160 core::mem::size_of::<<T as Config>::RuntimeCall>() as u32 <= CALL_ALIGN,
161 "Call enum size should be smaller than {} bytes.",
162 CALL_ALIGN,
163 );
164 }
165 }
166
167 #[pallet::error]
168 pub enum Error<T> {
169 TooManyCalls,
171 }
172
173 #[pallet::call]
174 impl<T: Config> Pallet<T> {
175 #[pallet::call_index(0)]
194 #[pallet::weight({
195 let (dispatch_weight, dispatch_class) = Pallet::<T>::weight_and_dispatch_class(&calls);
196 let dispatch_weight = dispatch_weight.saturating_add(T::WeightInfo::batch(calls.len() as u32));
197 (dispatch_weight, dispatch_class)
198 })]
199 pub fn batch(
200 origin: OriginFor<T>,
201 calls: Vec<<T as Config>::RuntimeCall>,
202 ) -> DispatchResultWithPostInfo {
203 if ensure_none(origin.clone()).is_ok() {
205 return Err(BadOrigin.into())
206 }
207
208 let is_root = ensure_root(origin.clone()).is_ok();
209 let calls_len = calls.len();
210 ensure!(calls_len <= Self::batched_calls_limit() as usize, Error::<T>::TooManyCalls);
211
212 let mut weight = Weight::zero();
214 for (index, call) in calls.into_iter().enumerate() {
215 let info = call.get_dispatch_info();
216 let result = if is_root {
218 call.dispatch_bypass_filter(origin.clone())
219 } else {
220 call.dispatch(origin.clone())
221 };
222 weight = weight.saturating_add(extract_actual_weight(&result, &info));
224 if let Err(e) = result {
225 Self::deposit_event(Event::BatchInterrupted {
226 index: index as u32,
227 error: e.error,
228 });
229 let base_weight = T::WeightInfo::batch(index.saturating_add(1) as u32);
231 return Ok(Some(base_weight.saturating_add(weight)).into())
233 }
234 Self::deposit_event(Event::ItemCompleted);
235 }
236 Self::deposit_event(Event::BatchCompleted);
237 let base_weight = T::WeightInfo::batch(calls_len as u32);
238 Ok(Some(base_weight.saturating_add(weight)).into())
239 }
240
241 #[pallet::call_index(1)]
255 #[pallet::weight({
256 let dispatch_info = call.get_dispatch_info();
257 (
258 T::WeightInfo::as_derivative()
259 .saturating_add(T::DbWeight::get().reads_writes(1, 1))
261 .saturating_add(dispatch_info.call_weight),
262 dispatch_info.class,
263 )
264 })]
265 pub fn as_derivative(
266 origin: OriginFor<T>,
267 index: u16,
268 call: Box<<T as Config>::RuntimeCall>,
269 ) -> DispatchResultWithPostInfo {
270 let mut origin = origin;
271 let who = ensure_signed(origin.clone())?;
272 let pseudonym = derivative_account_id(who, index);
273 origin.set_caller_from(frame_system::RawOrigin::Signed(pseudonym));
274 let info = call.get_dispatch_info();
275 let result = call.dispatch(origin);
276 let mut weight = T::WeightInfo::as_derivative()
278 .saturating_add(T::DbWeight::get().reads_writes(1, 1));
279 weight = weight.saturating_add(extract_actual_weight(&result, &info));
281 result
282 .map_err(|mut err| {
283 err.post_info = Some(weight).into();
284 err
285 })
286 .map(|_| Some(weight).into())
287 }
288
289 #[pallet::call_index(2)]
303 #[pallet::weight({
304 let (dispatch_weight, dispatch_class) = Pallet::<T>::weight_and_dispatch_class(&calls);
305 let dispatch_weight = dispatch_weight.saturating_add(T::WeightInfo::batch_all(calls.len() as u32));
306 (dispatch_weight, dispatch_class)
307 })]
308 pub fn batch_all(
309 origin: OriginFor<T>,
310 calls: Vec<<T as Config>::RuntimeCall>,
311 ) -> DispatchResultWithPostInfo {
312 if ensure_none(origin.clone()).is_ok() {
314 return Err(BadOrigin.into())
315 }
316
317 let is_root = ensure_root(origin.clone()).is_ok();
318 let calls_len = calls.len();
319 ensure!(calls_len <= Self::batched_calls_limit() as usize, Error::<T>::TooManyCalls);
320
321 let mut weight = Weight::zero();
323 for (index, call) in calls.into_iter().enumerate() {
324 let info = call.get_dispatch_info();
325 let result = if is_root {
327 call.dispatch_bypass_filter(origin.clone())
328 } else {
329 let mut filtered_origin = origin.clone();
330 filtered_origin.add_filter(
332 move |c: &<T as frame_system::Config>::RuntimeCall| {
333 let c = <T as Config>::RuntimeCall::from_ref(c);
334 !matches!(c.is_sub_type(), Some(Call::batch_all { .. }))
335 },
336 );
337 call.dispatch(filtered_origin)
338 };
339 weight = weight.saturating_add(extract_actual_weight(&result, &info));
341 result.map_err(|mut err| {
342 let base_weight = T::WeightInfo::batch_all(index.saturating_add(1) as u32);
344 err.post_info = Some(base_weight.saturating_add(weight)).into();
346 err
347 })?;
348 Self::deposit_event(Event::ItemCompleted);
349 }
350 Self::deposit_event(Event::BatchCompleted);
351 let base_weight = T::WeightInfo::batch_all(calls_len as u32);
352 Ok(Some(base_weight.saturating_add(weight)).into())
353 }
354
355 #[pallet::call_index(3)]
362 #[pallet::weight({
363 let dispatch_info = call.get_dispatch_info();
364 (
365 T::WeightInfo::dispatch_as()
366 .saturating_add(dispatch_info.call_weight),
367 dispatch_info.class,
368 )
369 })]
370 pub fn dispatch_as(
371 origin: OriginFor<T>,
372 as_origin: Box<T::PalletsOrigin>,
373 call: Box<<T as Config>::RuntimeCall>,
374 ) -> DispatchResult {
375 ensure_root(origin)?;
376
377 let res = call.dispatch_bypass_filter((*as_origin).into());
378
379 Self::deposit_event(Event::DispatchedAs {
380 result: res.map(|_| ()).map_err(|e| e.error),
381 });
382 Ok(())
383 }
384
385 #[pallet::call_index(4)]
399 #[pallet::weight({
400 let (dispatch_weight, dispatch_class) = Pallet::<T>::weight_and_dispatch_class(&calls);
401 let dispatch_weight = dispatch_weight.saturating_add(T::WeightInfo::force_batch(calls.len() as u32));
402 (dispatch_weight, dispatch_class)
403 })]
404 pub fn force_batch(
405 origin: OriginFor<T>,
406 calls: Vec<<T as Config>::RuntimeCall>,
407 ) -> DispatchResultWithPostInfo {
408 if ensure_none(origin.clone()).is_ok() {
410 return Err(BadOrigin.into())
411 }
412
413 let is_root = ensure_root(origin.clone()).is_ok();
414 let calls_len = calls.len();
415 ensure!(calls_len <= Self::batched_calls_limit() as usize, Error::<T>::TooManyCalls);
416
417 let mut weight = Weight::zero();
419 let mut has_error: bool = false;
421 for call in calls.into_iter() {
422 let info = call.get_dispatch_info();
423 let result = if is_root {
425 call.dispatch_bypass_filter(origin.clone())
426 } else {
427 call.dispatch(origin.clone())
428 };
429 weight = weight.saturating_add(extract_actual_weight(&result, &info));
431 if let Err(e) = result {
432 has_error = true;
433 Self::deposit_event(Event::ItemFailed { error: e.error });
434 } else {
435 Self::deposit_event(Event::ItemCompleted);
436 }
437 }
438 if has_error {
439 Self::deposit_event(Event::BatchCompletedWithErrors);
440 } else {
441 Self::deposit_event(Event::BatchCompleted);
442 }
443 let base_weight = T::WeightInfo::batch(calls_len as u32);
444 Ok(Some(base_weight.saturating_add(weight)).into())
445 }
446
447 #[pallet::call_index(5)]
454 #[pallet::weight((*weight, call.get_dispatch_info().class))]
455 pub fn with_weight(
456 origin: OriginFor<T>,
457 call: Box<<T as Config>::RuntimeCall>,
458 weight: Weight,
459 ) -> DispatchResult {
460 ensure_root(origin)?;
461 let _ = weight; let res = call.dispatch_bypass_filter(frame_system::RawOrigin::Root.into());
464 res.map(|_| ()).map_err(|e| e.error)
465 }
466
467 #[pallet::call_index(6)]
491 #[pallet::weight({
492 let main = main.get_dispatch_info();
493 let fallback = fallback.get_dispatch_info();
494 (
495 T::WeightInfo::if_else()
496 .saturating_add(main.call_weight)
497 .saturating_add(fallback.call_weight),
498 if main.class == Operational && fallback.class == Operational { Operational } else { Normal },
499 )
500 })]
501 pub fn if_else(
502 origin: OriginFor<T>,
503 main: Box<<T as Config>::RuntimeCall>,
504 fallback: Box<<T as Config>::RuntimeCall>,
505 ) -> DispatchResultWithPostInfo {
506 if ensure_none(origin.clone()).is_ok() {
508 return Err(BadOrigin.into());
509 }
510
511 let is_root = ensure_root(origin.clone()).is_ok();
512
513 let mut weight = T::WeightInfo::if_else();
515
516 let main_info = main.get_dispatch_info();
517
518 let main_result = if is_root {
520 main.dispatch_bypass_filter(origin.clone())
521 } else {
522 main.dispatch(origin.clone())
523 };
524
525 weight = weight.saturating_add(extract_actual_weight(&main_result, &main_info));
527
528 let Err(main_error) = main_result else {
529 Self::deposit_event(Event::IfElseMainSuccess);
531 return Ok(Some(weight).into());
532 };
533
534 let fallback_info = fallback.get_dispatch_info();
536
537 let fallback_result = if is_root {
538 fallback.dispatch_bypass_filter(origin.clone())
539 } else {
540 fallback.dispatch(origin)
541 };
542
543 weight = weight.saturating_add(extract_actual_weight(&fallback_result, &fallback_info));
545
546 let Err(fallback_error) = fallback_result else {
547 Self::deposit_event(Event::IfElseFallbackCalled { main_error: main_error.error });
549 return Ok(Some(weight).into());
550 };
551
552 Err(sp_runtime::DispatchErrorWithPostInfo {
554 error: fallback_error.error,
555 post_info: Some(weight).into(),
556 })
557 }
558
559 #[pallet::call_index(7)]
565 #[pallet::weight({
566 let dispatch_info = call.get_dispatch_info();
567 (
568 T::WeightInfo::dispatch_as_fallible()
569 .saturating_add(dispatch_info.call_weight),
570 dispatch_info.class,
571 )
572 })]
573 pub fn dispatch_as_fallible(
574 origin: OriginFor<T>,
575 as_origin: Box<T::PalletsOrigin>,
576 call: Box<<T as Config>::RuntimeCall>,
577 ) -> DispatchResult {
578 ensure_root(origin)?;
579
580 call.dispatch_bypass_filter((*as_origin).into()).map_err(|e| e.error)?;
581
582 Self::deposit_event(Event::DispatchedAs { result: Ok(()) });
583
584 Ok(())
585 }
586 }
587
588 impl<T: Config> Pallet<T> {
589 fn weight_and_dispatch_class(
591 calls: &[<T as Config>::RuntimeCall],
592 ) -> (Weight, DispatchClass) {
593 let dispatch_infos = calls.iter().map(|call| call.get_dispatch_info());
594 let (dispatch_weight, dispatch_class) = dispatch_infos.fold(
595 (Weight::zero(), DispatchClass::Operational),
596 |(total_weight, dispatch_class): (Weight, DispatchClass), di| {
597 (
598 total_weight.saturating_add(di.call_weight),
599 if di.class == DispatchClass::Normal { di.class } else { dispatch_class },
601 )
602 },
603 );
604
605 (dispatch_weight, dispatch_class)
606 }
607 }
608}
609
610#[derive(Clone, Copy, Eq, PartialEq, Encode, Decode)]
612struct IndexedUtilityPalletId(u16);
613
614impl TypeId for IndexedUtilityPalletId {
615 const TYPE_ID: [u8; 4] = *b"suba";
616}
617
618impl<T: Config> Pallet<T> {
619 #[deprecated(
620 note = "`Pallet::derivative_account_id` will be removed after August 2025. Please instead use the freestanding module function `derivative_account_id`."
621 )]
622 pub fn derivative_account_id(who: T::AccountId, index: u16) -> T::AccountId {
623 derivative_account_id(who, index)
624 }
625}
626
627pub fn derivative_account_id<AccountId: Encode + Decode>(who: AccountId, index: u16) -> AccountId {
636 let entropy = (b"modlpy/utilisuba", who, index).using_encoded(blake2_256);
637 Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref()))
638 .expect("infinite length input; no invalid inputs for type; qed")
639}