#![cfg_attr(not(feature = "std"), no_std)]
mod benchmarking;
mod tests;
pub mod weights;
use codec::{Decode, Encode};
use frame_support::{
dispatch::{extract_actual_weight, GetDispatchInfo, PostDispatchInfo},
traits::{IsSubType, OriginTrait, UnfilteredDispatchable},
};
use sp_core::TypeId;
use sp_io::hashing::blake2_256;
use sp_runtime::traits::{BadOrigin, Dispatchable, TrailingZeroInput};
use sp_std::prelude::*;
pub use weights::WeightInfo;
pub use pallet::*;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config: frame_system::Config {
type RuntimeEvent: From<Event> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type RuntimeCall: Parameter
+ Dispatchable<RuntimeOrigin = Self::RuntimeOrigin, PostInfo = PostDispatchInfo>
+ GetDispatchInfo
+ From<frame_system::Call<Self>>
+ UnfilteredDispatchable<RuntimeOrigin = Self::RuntimeOrigin>
+ IsSubType<Call<Self>>
+ IsType<<Self as frame_system::Config>::RuntimeCall>;
type PalletsOrigin: Parameter +
Into<<Self as frame_system::Config>::RuntimeOrigin> +
IsType<<<Self as frame_system::Config>::RuntimeOrigin as frame_support::traits::OriginTrait>::PalletsOrigin>;
type WeightInfo: WeightInfo;
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event {
BatchInterrupted { index: u32, error: DispatchError },
BatchCompleted,
BatchCompletedWithErrors,
ItemCompleted,
ItemFailed { error: DispatchError },
DispatchedAs { result: DispatchResult },
}
const CALL_ALIGN: u32 = 1024;
#[pallet::extra_constants]
impl<T: Config> Pallet<T> {
fn batched_calls_limit() -> u32 {
let allocator_limit = sp_core::MAX_POSSIBLE_ALLOCATION;
let call_size = ((sp_std::mem::size_of::<<T as Config>::RuntimeCall>() as u32 +
CALL_ALIGN - 1) / CALL_ALIGN) *
CALL_ALIGN;
let margin_factor = 3;
allocator_limit / margin_factor / call_size
}
}
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn integrity_test() {
assert!(
sp_std::mem::size_of::<<T as Config>::RuntimeCall>() as u32 <= CALL_ALIGN,
"Call enum size should be smaller than {} bytes.",
CALL_ALIGN,
);
}
}
#[pallet::error]
pub enum Error<T> {
TooManyCalls,
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight({
let dispatch_infos = calls.iter().map(|call| call.get_dispatch_info()).collect::<Vec<_>>();
let dispatch_weight = dispatch_infos.iter()
.map(|di| di.weight)
.fold(Weight::zero(), |total: Weight, weight: Weight| total.saturating_add(weight))
.saturating_add(T::WeightInfo::batch(calls.len() as u32));
let dispatch_class = {
let all_operational = dispatch_infos.iter()
.map(|di| di.class)
.all(|class| class == DispatchClass::Operational);
if all_operational {
DispatchClass::Operational
} else {
DispatchClass::Normal
}
};
(dispatch_weight, dispatch_class)
})]
pub fn batch(
origin: OriginFor<T>,
calls: Vec<<T as Config>::RuntimeCall>,
) -> DispatchResultWithPostInfo {
if ensure_none(origin.clone()).is_ok() {
return Err(BadOrigin.into())
}
let is_root = ensure_root(origin.clone()).is_ok();
let calls_len = calls.len();
ensure!(calls_len <= Self::batched_calls_limit() as usize, Error::<T>::TooManyCalls);
let mut weight = Weight::zero();
for (index, call) in calls.into_iter().enumerate() {
let info = call.get_dispatch_info();
let result = if is_root {
call.dispatch_bypass_filter(origin.clone())
} else {
call.dispatch(origin.clone())
};
weight = weight.saturating_add(extract_actual_weight(&result, &info));
if let Err(e) = result {
Self::deposit_event(Event::BatchInterrupted {
index: index as u32,
error: e.error,
});
let base_weight = T::WeightInfo::batch(index.saturating_add(1) as u32);
return Ok(Some(base_weight + weight).into())
}
Self::deposit_event(Event::ItemCompleted);
}
Self::deposit_event(Event::BatchCompleted);
let base_weight = T::WeightInfo::batch(calls_len as u32);
Ok(Some(base_weight + weight).into())
}
#[pallet::call_index(1)]
#[pallet::weight({
let dispatch_info = call.get_dispatch_info();
(
T::WeightInfo::as_derivative()
// AccountData for inner call origin accountdata.
.saturating_add(T::DbWeight::get().reads_writes(1, 1))
.saturating_add(dispatch_info.weight),
dispatch_info.class,
)
})]
pub fn as_derivative(
origin: OriginFor<T>,
index: u16,
call: Box<<T as Config>::RuntimeCall>,
) -> DispatchResultWithPostInfo {
let mut origin = origin;
let who = ensure_signed(origin.clone())?;
let pseudonym = Self::derivative_account_id(who, index);
origin.set_caller_from(frame_system::RawOrigin::Signed(pseudonym));
let info = call.get_dispatch_info();
let result = call.dispatch(origin);
let mut weight = T::WeightInfo::as_derivative()
.saturating_add(T::DbWeight::get().reads_writes(1, 1));
weight = weight.saturating_add(extract_actual_weight(&result, &info));
result
.map_err(|mut err| {
err.post_info = Some(weight).into();
err
})
.map(|_| Some(weight).into())
}
#[pallet::call_index(2)]
#[pallet::weight({
let dispatch_infos = calls.iter().map(|call| call.get_dispatch_info()).collect::<Vec<_>>();
let dispatch_weight = dispatch_infos.iter()
.map(|di| di.weight)
.fold(Weight::zero(), |total: Weight, weight: Weight| total.saturating_add(weight))
.saturating_add(T::WeightInfo::batch_all(calls.len() as u32));
let dispatch_class = {
let all_operational = dispatch_infos.iter()
.map(|di| di.class)
.all(|class| class == DispatchClass::Operational);
if all_operational {
DispatchClass::Operational
} else {
DispatchClass::Normal
}
};
(dispatch_weight, dispatch_class)
})]
pub fn batch_all(
origin: OriginFor<T>,
calls: Vec<<T as Config>::RuntimeCall>,
) -> DispatchResultWithPostInfo {
if ensure_none(origin.clone()).is_ok() {
return Err(BadOrigin.into())
}
let is_root = ensure_root(origin.clone()).is_ok();
let calls_len = calls.len();
ensure!(calls_len <= Self::batched_calls_limit() as usize, Error::<T>::TooManyCalls);
let mut weight = Weight::zero();
for (index, call) in calls.into_iter().enumerate() {
let info = call.get_dispatch_info();
let result = if is_root {
call.dispatch_bypass_filter(origin.clone())
} else {
let mut filtered_origin = origin.clone();
filtered_origin.add_filter(
move |c: &<T as frame_system::Config>::RuntimeCall| {
let c = <T as Config>::RuntimeCall::from_ref(c);
!matches!(c.is_sub_type(), Some(Call::batch_all { .. }))
},
);
call.dispatch(filtered_origin)
};
weight = weight.saturating_add(extract_actual_weight(&result, &info));
result.map_err(|mut err| {
let base_weight = T::WeightInfo::batch_all(index.saturating_add(1) as u32);
err.post_info = Some(base_weight + weight).into();
err
})?;
Self::deposit_event(Event::ItemCompleted);
}
Self::deposit_event(Event::BatchCompleted);
let base_weight = T::WeightInfo::batch_all(calls_len as u32);
Ok(Some(base_weight.saturating_add(weight)).into())
}
#[pallet::call_index(3)]
#[pallet::weight({
let dispatch_info = call.get_dispatch_info();
(
T::WeightInfo::dispatch_as()
.saturating_add(dispatch_info.weight),
dispatch_info.class,
)
})]
pub fn dispatch_as(
origin: OriginFor<T>,
as_origin: Box<T::PalletsOrigin>,
call: Box<<T as Config>::RuntimeCall>,
) -> DispatchResult {
ensure_root(origin)?;
let res = call.dispatch_bypass_filter((*as_origin).into());
Self::deposit_event(Event::DispatchedAs {
result: res.map(|_| ()).map_err(|e| e.error),
});
Ok(())
}
#[pallet::call_index(4)]
#[pallet::weight({
let dispatch_infos = calls.iter().map(|call| call.get_dispatch_info()).collect::<Vec<_>>();
let dispatch_weight = dispatch_infos.iter()
.map(|di| di.weight)
.fold(Weight::zero(), |total: Weight, weight: Weight| total.saturating_add(weight))
.saturating_add(T::WeightInfo::force_batch(calls.len() as u32));
let dispatch_class = {
let all_operational = dispatch_infos.iter()
.map(|di| di.class)
.all(|class| class == DispatchClass::Operational);
if all_operational {
DispatchClass::Operational
} else {
DispatchClass::Normal
}
};
(dispatch_weight, dispatch_class)
})]
pub fn force_batch(
origin: OriginFor<T>,
calls: Vec<<T as Config>::RuntimeCall>,
) -> DispatchResultWithPostInfo {
if ensure_none(origin.clone()).is_ok() {
return Err(BadOrigin.into())
}
let is_root = ensure_root(origin.clone()).is_ok();
let calls_len = calls.len();
ensure!(calls_len <= Self::batched_calls_limit() as usize, Error::<T>::TooManyCalls);
let mut weight = Weight::zero();
let mut has_error: bool = false;
for call in calls.into_iter() {
let info = call.get_dispatch_info();
let result = if is_root {
call.dispatch_bypass_filter(origin.clone())
} else {
call.dispatch(origin.clone())
};
weight = weight.saturating_add(extract_actual_weight(&result, &info));
if let Err(e) = result {
has_error = true;
Self::deposit_event(Event::ItemFailed { error: e.error });
} else {
Self::deposit_event(Event::ItemCompleted);
}
}
if has_error {
Self::deposit_event(Event::BatchCompletedWithErrors);
} else {
Self::deposit_event(Event::BatchCompleted);
}
let base_weight = T::WeightInfo::batch(calls_len as u32);
Ok(Some(base_weight.saturating_add(weight)).into())
}
#[pallet::call_index(5)]
#[pallet::weight((*weight, call.get_dispatch_info().class))]
pub fn with_weight(
origin: OriginFor<T>,
call: Box<<T as Config>::RuntimeCall>,
weight: Weight,
) -> DispatchResult {
ensure_root(origin)?;
let _ = weight;
let res = call.dispatch_bypass_filter(frame_system::RawOrigin::Root.into());
res.map(|_| ()).map_err(|e| e.error)
}
}
}
#[derive(Clone, Copy, Eq, PartialEq, Encode, Decode)]
struct IndexedUtilityPalletId(u16);
impl TypeId for IndexedUtilityPalletId {
const TYPE_ID: [u8; 4] = *b"suba";
}
impl<T: Config> Pallet<T> {
pub fn derivative_account_id(who: T::AccountId, index: u16) -> T::AccountId {
let entropy = (b"modlpy/utilisuba", who, index).using_encoded(blake2_256);
Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref()))
.expect("infinite length input; no invalid inputs for type; qed")
}
}