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::{extract_actual_weight, GetDispatchInfo, PostDispatchInfo},
65 traits::{IsSubType, OriginTrait, UnfilteredDispatchable},
66};
67use sp_core::TypeId;
68use sp_io::hashing::blake2_256;
69use sp_runtime::traits::{BadOrigin, Dispatchable, TrailingZeroInput};
70pub use weights::WeightInfo;
71
72pub use pallet::*;
73
74#[frame_support::pallet]
75pub mod pallet {
76 use super::*;
77 use frame_support::{dispatch::DispatchClass, pallet_prelude::*};
78 use frame_system::pallet_prelude::*;
79
80 #[pallet::pallet]
81 pub struct Pallet<T>(_);
82
83 #[pallet::config]
85 pub trait Config: frame_system::Config {
86 type RuntimeEvent: From<Event> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
88
89 type RuntimeCall: Parameter
91 + Dispatchable<RuntimeOrigin = Self::RuntimeOrigin, PostInfo = PostDispatchInfo>
92 + GetDispatchInfo
93 + From<frame_system::Call<Self>>
94 + UnfilteredDispatchable<RuntimeOrigin = Self::RuntimeOrigin>
95 + IsSubType<Call<Self>>
96 + IsType<<Self as frame_system::Config>::RuntimeCall>;
97
98 type PalletsOrigin: Parameter +
100 Into<<Self as frame_system::Config>::RuntimeOrigin> +
101 IsType<<<Self as frame_system::Config>::RuntimeOrigin as frame_support::traits::OriginTrait>::PalletsOrigin>;
102
103 type WeightInfo: WeightInfo;
105 }
106
107 #[pallet::event]
108 #[pallet::generate_deposit(pub(super) fn deposit_event)]
109 pub enum Event {
110 BatchInterrupted { index: u32, error: DispatchError },
113 BatchCompleted,
115 BatchCompletedWithErrors,
117 ItemCompleted,
119 ItemFailed { error: DispatchError },
121 DispatchedAs { result: DispatchResult },
123 }
124
125 const CALL_ALIGN: u32 = 1024;
130
131 #[pallet::extra_constants]
132 impl<T: Config> Pallet<T> {
133 fn batched_calls_limit() -> u32 {
135 let allocator_limit = sp_core::MAX_POSSIBLE_ALLOCATION;
136 let call_size = ((core::mem::size_of::<<T as Config>::RuntimeCall>() as u32 +
137 CALL_ALIGN - 1) /
138 CALL_ALIGN) * CALL_ALIGN;
139 let margin_factor = 3;
141
142 allocator_limit / margin_factor / call_size
143 }
144 }
145
146 #[pallet::hooks]
147 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
148 fn integrity_test() {
149 assert!(
151 core::mem::size_of::<<T as Config>::RuntimeCall>() as u32 <= CALL_ALIGN,
152 "Call enum size should be smaller than {} bytes.",
153 CALL_ALIGN,
154 );
155 }
156 }
157
158 #[pallet::error]
159 pub enum Error<T> {
160 TooManyCalls,
162 }
163
164 #[pallet::call]
165 impl<T: Config> Pallet<T> {
166 #[pallet::call_index(0)]
185 #[pallet::weight({
186 let (dispatch_weight, dispatch_class) = Pallet::<T>::weight_and_dispatch_class(&calls);
187 let dispatch_weight = dispatch_weight.saturating_add(T::WeightInfo::batch(calls.len() as u32));
188 (dispatch_weight, dispatch_class)
189 })]
190 pub fn batch(
191 origin: OriginFor<T>,
192 calls: Vec<<T as Config>::RuntimeCall>,
193 ) -> DispatchResultWithPostInfo {
194 if ensure_none(origin.clone()).is_ok() {
196 return Err(BadOrigin.into())
197 }
198
199 let is_root = ensure_root(origin.clone()).is_ok();
200 let calls_len = calls.len();
201 ensure!(calls_len <= Self::batched_calls_limit() as usize, Error::<T>::TooManyCalls);
202
203 let mut weight = Weight::zero();
205 for (index, call) in calls.into_iter().enumerate() {
206 let info = call.get_dispatch_info();
207 let result = if is_root {
209 call.dispatch_bypass_filter(origin.clone())
210 } else {
211 call.dispatch(origin.clone())
212 };
213 weight = weight.saturating_add(extract_actual_weight(&result, &info));
215 if let Err(e) = result {
216 Self::deposit_event(Event::BatchInterrupted {
217 index: index as u32,
218 error: e.error,
219 });
220 let base_weight = T::WeightInfo::batch(index.saturating_add(1) as u32);
222 return Ok(Some(base_weight.saturating_add(weight)).into())
224 }
225 Self::deposit_event(Event::ItemCompleted);
226 }
227 Self::deposit_event(Event::BatchCompleted);
228 let base_weight = T::WeightInfo::batch(calls_len as u32);
229 Ok(Some(base_weight.saturating_add(weight)).into())
230 }
231
232 #[pallet::call_index(1)]
246 #[pallet::weight({
247 let dispatch_info = call.get_dispatch_info();
248 (
249 T::WeightInfo::as_derivative()
250 .saturating_add(T::DbWeight::get().reads_writes(1, 1))
252 .saturating_add(dispatch_info.call_weight),
253 dispatch_info.class,
254 )
255 })]
256 pub fn as_derivative(
257 origin: OriginFor<T>,
258 index: u16,
259 call: Box<<T as Config>::RuntimeCall>,
260 ) -> DispatchResultWithPostInfo {
261 let mut origin = origin;
262 let who = ensure_signed(origin.clone())?;
263 let pseudonym = Self::derivative_account_id(who, index);
264 origin.set_caller_from(frame_system::RawOrigin::Signed(pseudonym));
265 let info = call.get_dispatch_info();
266 let result = call.dispatch(origin);
267 let mut weight = T::WeightInfo::as_derivative()
269 .saturating_add(T::DbWeight::get().reads_writes(1, 1));
270 weight = weight.saturating_add(extract_actual_weight(&result, &info));
272 result
273 .map_err(|mut err| {
274 err.post_info = Some(weight).into();
275 err
276 })
277 .map(|_| Some(weight).into())
278 }
279
280 #[pallet::call_index(2)]
294 #[pallet::weight({
295 let (dispatch_weight, dispatch_class) = Pallet::<T>::weight_and_dispatch_class(&calls);
296 let dispatch_weight = dispatch_weight.saturating_add(T::WeightInfo::batch_all(calls.len() as u32));
297 (dispatch_weight, dispatch_class)
298 })]
299 pub fn batch_all(
300 origin: OriginFor<T>,
301 calls: Vec<<T as Config>::RuntimeCall>,
302 ) -> DispatchResultWithPostInfo {
303 if ensure_none(origin.clone()).is_ok() {
305 return Err(BadOrigin.into())
306 }
307
308 let is_root = ensure_root(origin.clone()).is_ok();
309 let calls_len = calls.len();
310 ensure!(calls_len <= Self::batched_calls_limit() as usize, Error::<T>::TooManyCalls);
311
312 let mut weight = Weight::zero();
314 for (index, call) in calls.into_iter().enumerate() {
315 let info = call.get_dispatch_info();
316 let result = if is_root {
318 call.dispatch_bypass_filter(origin.clone())
319 } else {
320 let mut filtered_origin = origin.clone();
321 filtered_origin.add_filter(
323 move |c: &<T as frame_system::Config>::RuntimeCall| {
324 let c = <T as Config>::RuntimeCall::from_ref(c);
325 !matches!(c.is_sub_type(), Some(Call::batch_all { .. }))
326 },
327 );
328 call.dispatch(filtered_origin)
329 };
330 weight = weight.saturating_add(extract_actual_weight(&result, &info));
332 result.map_err(|mut err| {
333 let base_weight = T::WeightInfo::batch_all(index.saturating_add(1) as u32);
335 err.post_info = Some(base_weight.saturating_add(weight)).into();
337 err
338 })?;
339 Self::deposit_event(Event::ItemCompleted);
340 }
341 Self::deposit_event(Event::BatchCompleted);
342 let base_weight = T::WeightInfo::batch_all(calls_len as u32);
343 Ok(Some(base_weight.saturating_add(weight)).into())
344 }
345
346 #[pallet::call_index(3)]
353 #[pallet::weight({
354 let dispatch_info = call.get_dispatch_info();
355 (
356 T::WeightInfo::dispatch_as()
357 .saturating_add(dispatch_info.call_weight),
358 dispatch_info.class,
359 )
360 })]
361 pub fn dispatch_as(
362 origin: OriginFor<T>,
363 as_origin: Box<T::PalletsOrigin>,
364 call: Box<<T as Config>::RuntimeCall>,
365 ) -> DispatchResult {
366 ensure_root(origin)?;
367
368 let res = call.dispatch_bypass_filter((*as_origin).into());
369
370 Self::deposit_event(Event::DispatchedAs {
371 result: res.map(|_| ()).map_err(|e| e.error),
372 });
373 Ok(())
374 }
375
376 #[pallet::call_index(4)]
390 #[pallet::weight({
391 let (dispatch_weight, dispatch_class) = Pallet::<T>::weight_and_dispatch_class(&calls);
392 let dispatch_weight = dispatch_weight.saturating_add(T::WeightInfo::force_batch(calls.len() as u32));
393 (dispatch_weight, dispatch_class)
394 })]
395 pub fn force_batch(
396 origin: OriginFor<T>,
397 calls: Vec<<T as Config>::RuntimeCall>,
398 ) -> DispatchResultWithPostInfo {
399 if ensure_none(origin.clone()).is_ok() {
401 return Err(BadOrigin.into())
402 }
403
404 let is_root = ensure_root(origin.clone()).is_ok();
405 let calls_len = calls.len();
406 ensure!(calls_len <= Self::batched_calls_limit() as usize, Error::<T>::TooManyCalls);
407
408 let mut weight = Weight::zero();
410 let mut has_error: bool = false;
412 for call in calls.into_iter() {
413 let info = call.get_dispatch_info();
414 let result = if is_root {
416 call.dispatch_bypass_filter(origin.clone())
417 } else {
418 call.dispatch(origin.clone())
419 };
420 weight = weight.saturating_add(extract_actual_weight(&result, &info));
422 if let Err(e) = result {
423 has_error = true;
424 Self::deposit_event(Event::ItemFailed { error: e.error });
425 } else {
426 Self::deposit_event(Event::ItemCompleted);
427 }
428 }
429 if has_error {
430 Self::deposit_event(Event::BatchCompletedWithErrors);
431 } else {
432 Self::deposit_event(Event::BatchCompleted);
433 }
434 let base_weight = T::WeightInfo::batch(calls_len as u32);
435 Ok(Some(base_weight.saturating_add(weight)).into())
436 }
437
438 #[pallet::call_index(5)]
445 #[pallet::weight((*weight, call.get_dispatch_info().class))]
446 pub fn with_weight(
447 origin: OriginFor<T>,
448 call: Box<<T as Config>::RuntimeCall>,
449 weight: Weight,
450 ) -> DispatchResult {
451 ensure_root(origin)?;
452 let _ = weight; let res = call.dispatch_bypass_filter(frame_system::RawOrigin::Root.into());
455 res.map(|_| ()).map_err(|e| e.error)
456 }
457 }
458
459 impl<T: Config> Pallet<T> {
460 fn weight_and_dispatch_class(
462 calls: &[<T as Config>::RuntimeCall],
463 ) -> (Weight, DispatchClass) {
464 let dispatch_infos = calls.iter().map(|call| call.get_dispatch_info());
465 let (dispatch_weight, dispatch_class) = dispatch_infos.fold(
466 (Weight::zero(), DispatchClass::Operational),
467 |(total_weight, dispatch_class): (Weight, DispatchClass), di| {
468 (
469 total_weight.saturating_add(di.call_weight),
470 if di.class == DispatchClass::Normal { di.class } else { dispatch_class },
472 )
473 },
474 );
475
476 (dispatch_weight, dispatch_class)
477 }
478 }
479}
480
481#[derive(Clone, Copy, Eq, PartialEq, Encode, Decode)]
483struct IndexedUtilityPalletId(u16);
484
485impl TypeId for IndexedUtilityPalletId {
486 const TYPE_ID: [u8; 4] = *b"suba";
487}
488
489impl<T: Config> Pallet<T> {
490 pub fn derivative_account_id(who: T::AccountId, index: u16) -> T::AccountId {
492 let entropy = (b"modlpy/utilisuba", who, index).using_encoded(blake2_256);
493 Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref()))
494 .expect("infinite length input; no invalid inputs for type; qed")
495 }
496}