use crate::Config;
use codec::{Decode, Encode};
use frame_support::dispatch::{DispatchInfo, PostDispatchInfo};
use scale_info::TypeInfo;
use sp_runtime::{
traits::{
DispatchInfoOf, Dispatchable, PostDispatchInfoOf, TransactionExtension, ValidateResult,
},
transaction_validity::{TransactionSource, TransactionValidityError, ValidTransaction},
DispatchResult,
};
use sp_weights::Weight;
#[derive(Encode, Decode, Clone, Eq, PartialEq, Default, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct WeightReclaim<T: Config + Send + Sync>(core::marker::PhantomData<T>);
impl<T: Config + Send + Sync> WeightReclaim<T>
where
T::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
{
pub fn new() -> Self {
Self(Default::default())
}
}
impl<T: Config + Send + Sync> TransactionExtension<T::RuntimeCall> for WeightReclaim<T>
where
T::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
{
const IDENTIFIER: &'static str = "WeightReclaim";
type Implicit = ();
type Pre = ();
type Val = ();
fn weight(&self, _: &T::RuntimeCall) -> Weight {
<T::ExtensionsWeightInfo as super::WeightInfo>::weight_reclaim()
}
fn validate(
&self,
origin: T::RuntimeOrigin,
_call: &T::RuntimeCall,
_info: &DispatchInfoOf<T::RuntimeCall>,
_len: usize,
_self_implicit: Self::Implicit,
_inherited_implication: &impl Encode,
_source: TransactionSource,
) -> ValidateResult<Self::Val, T::RuntimeCall> {
Ok((ValidTransaction::default(), (), origin))
}
fn prepare(
self,
_val: Self::Val,
_origin: &T::RuntimeOrigin,
_call: &T::RuntimeCall,
_info: &DispatchInfoOf<T::RuntimeCall>,
_len: usize,
) -> Result<Self::Pre, TransactionValidityError> {
Ok(())
}
fn post_dispatch_details(
_pre: Self::Pre,
info: &DispatchInfoOf<T::RuntimeCall>,
post_info: &PostDispatchInfoOf<T::RuntimeCall>,
_len: usize,
_result: &DispatchResult,
) -> Result<Weight, TransactionValidityError> {
crate::Pallet::<T>::reclaim_weight(info, post_info).map(|()| Weight::zero())
}
fn bare_validate(
_call: &T::RuntimeCall,
_info: &DispatchInfoOf<T::RuntimeCall>,
_len: usize,
) -> frame_support::pallet_prelude::TransactionValidity {
Ok(ValidTransaction::default())
}
fn bare_validate_and_prepare(
_call: &T::RuntimeCall,
_info: &DispatchInfoOf<T::RuntimeCall>,
_len: usize,
) -> Result<(), TransactionValidityError> {
Ok(())
}
fn bare_post_dispatch(
info: &DispatchInfoOf<T::RuntimeCall>,
post_info: &mut PostDispatchInfoOf<T::RuntimeCall>,
_len: usize,
_result: &DispatchResult,
) -> Result<(), TransactionValidityError> {
crate::Pallet::<T>::reclaim_weight(info, post_info)
}
}
impl<T: Config + Send + Sync> core::fmt::Debug for WeightReclaim<T>
where
T::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
{
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "{}", Self::IDENTIFIER)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
mock::{new_test_ext, Test},
BlockWeight, DispatchClass,
};
use frame_support::{assert_ok, weights::Weight};
fn block_weights() -> crate::limits::BlockWeights {
<Test as crate::Config>::BlockWeights::get()
}
#[test]
fn extrinsic_already_refunded_more_precisely() {
new_test_ext().execute_with(|| {
let info =
DispatchInfo { call_weight: Weight::from_parts(512, 0), ..Default::default() };
let post_info = PostDispatchInfo {
actual_weight: Some(Weight::from_parts(128, 0)),
pays_fee: Default::default(),
};
let prior_block_weight = Weight::from_parts(64, 0);
let accurate_refund = Weight::from_parts(510, 0);
let len = 0_usize;
let base_extrinsic = block_weights().get(DispatchClass::Normal).base_extrinsic;
BlockWeight::<Test>::mutate(|current_weight| {
current_weight.set(prior_block_weight, DispatchClass::Normal);
current_weight.accrue(
base_extrinsic + info.total_weight() - accurate_refund,
DispatchClass::Normal,
);
});
crate::ExtrinsicWeightReclaimed::<Test>::put(accurate_refund);
assert_ok!(WeightReclaim::<Test>::post_dispatch_details(
(),
&info,
&post_info,
len,
&Ok(())
));
assert_eq!(crate::ExtrinsicWeightReclaimed::<Test>::get(), accurate_refund);
assert_eq!(
*BlockWeight::<Test>::get().get(DispatchClass::Normal),
info.total_weight() - accurate_refund + prior_block_weight + base_extrinsic
);
})
}
#[test]
fn extrinsic_already_refunded_less_precisely() {
new_test_ext().execute_with(|| {
let info =
DispatchInfo { call_weight: Weight::from_parts(512, 0), ..Default::default() };
let post_info = PostDispatchInfo {
actual_weight: Some(Weight::from_parts(128, 0)),
pays_fee: Default::default(),
};
let prior_block_weight = Weight::from_parts(64, 0);
let inaccurate_refund = Weight::from_parts(110, 0);
let len = 0_usize;
let base_extrinsic = block_weights().get(DispatchClass::Normal).base_extrinsic;
BlockWeight::<Test>::mutate(|current_weight| {
current_weight.set(prior_block_weight, DispatchClass::Normal);
current_weight.accrue(
base_extrinsic + info.total_weight() - inaccurate_refund,
DispatchClass::Normal,
);
});
crate::ExtrinsicWeightReclaimed::<Test>::put(inaccurate_refund);
assert_ok!(WeightReclaim::<Test>::post_dispatch_details(
(),
&info,
&post_info,
len,
&Ok(())
));
assert_eq!(
crate::ExtrinsicWeightReclaimed::<Test>::get(),
post_info.calc_unspent(&info)
);
assert_eq!(
*BlockWeight::<Test>::get().get(DispatchClass::Normal),
post_info.actual_weight.unwrap() + prior_block_weight + base_extrinsic
);
})
}
#[test]
fn extrinsic_not_refunded_before() {
new_test_ext().execute_with(|| {
let info =
DispatchInfo { call_weight: Weight::from_parts(512, 0), ..Default::default() };
let post_info = PostDispatchInfo {
actual_weight: Some(Weight::from_parts(128, 0)),
pays_fee: Default::default(),
};
let prior_block_weight = Weight::from_parts(64, 0);
let len = 0_usize;
let base_extrinsic = block_weights().get(DispatchClass::Normal).base_extrinsic;
BlockWeight::<Test>::mutate(|current_weight| {
current_weight.set(prior_block_weight, DispatchClass::Normal);
current_weight.accrue(base_extrinsic + info.total_weight(), DispatchClass::Normal);
});
assert_ok!(WeightReclaim::<Test>::post_dispatch_details(
(),
&info,
&post_info,
len,
&Ok(())
));
assert_eq!(
crate::ExtrinsicWeightReclaimed::<Test>::get(),
post_info.calc_unspent(&info)
);
assert_eq!(
*BlockWeight::<Test>::get().get(DispatchClass::Normal),
post_info.actual_weight.unwrap() + prior_block_weight + base_extrinsic
);
})
}
#[test]
fn no_actual_post_dispatch_weight() {
new_test_ext().execute_with(|| {
let info =
DispatchInfo { call_weight: Weight::from_parts(512, 0), ..Default::default() };
let post_info = PostDispatchInfo { actual_weight: None, pays_fee: Default::default() };
let prior_block_weight = Weight::from_parts(64, 0);
let len = 0_usize;
let base_extrinsic = block_weights().get(DispatchClass::Normal).base_extrinsic;
BlockWeight::<Test>::mutate(|current_weight| {
current_weight.set(prior_block_weight, DispatchClass::Normal);
current_weight.accrue(base_extrinsic + info.total_weight(), DispatchClass::Normal);
});
assert_ok!(WeightReclaim::<Test>::post_dispatch_details(
(),
&info,
&post_info,
len,
&Ok(())
));
assert_eq!(
crate::ExtrinsicWeightReclaimed::<Test>::get(),
post_info.calc_unspent(&info)
);
assert_eq!(
*BlockWeight::<Test>::get().get(DispatchClass::Normal),
info.total_weight() + prior_block_weight + base_extrinsic
);
})
}
#[test]
fn different_dispatch_class() {
new_test_ext().execute_with(|| {
let info = DispatchInfo {
call_weight: Weight::from_parts(512, 0),
class: DispatchClass::Operational,
..Default::default()
};
let post_info = PostDispatchInfo {
actual_weight: Some(Weight::from_parts(128, 0)),
pays_fee: Default::default(),
};
let prior_block_weight = Weight::from_parts(64, 0);
let len = 0_usize;
let base_extrinsic = block_weights().get(DispatchClass::Operational).base_extrinsic;
BlockWeight::<Test>::mutate(|current_weight| {
current_weight.set(prior_block_weight, DispatchClass::Operational);
current_weight
.accrue(base_extrinsic + info.total_weight(), DispatchClass::Operational);
});
assert_ok!(WeightReclaim::<Test>::post_dispatch_details(
(),
&info,
&post_info,
len,
&Ok(())
));
assert_eq!(
crate::ExtrinsicWeightReclaimed::<Test>::get(),
post_info.calc_unspent(&info)
);
assert_eq!(
*BlockWeight::<Test>::get().get(DispatchClass::Operational),
post_info.actual_weight.unwrap() + prior_block_weight + base_extrinsic
);
})
}
#[test]
fn bare_also_works() {
new_test_ext().execute_with(|| {
let info = DispatchInfo {
call_weight: Weight::from_parts(512, 0),
class: DispatchClass::Operational,
..Default::default()
};
let post_info = PostDispatchInfo {
actual_weight: Some(Weight::from_parts(128, 0)),
pays_fee: Default::default(),
};
let prior_block_weight = Weight::from_parts(64, 0);
let len = 0_usize;
let base_extrinsic = block_weights().get(DispatchClass::Operational).base_extrinsic;
BlockWeight::<Test>::mutate(|current_weight| {
current_weight.set(prior_block_weight, DispatchClass::Operational);
current_weight
.accrue(base_extrinsic + info.total_weight(), DispatchClass::Operational);
});
assert_ok!(WeightReclaim::<Test>::bare_post_dispatch(
&info,
&mut post_info.clone(),
len,
&Ok(())
));
assert_eq!(
crate::ExtrinsicWeightReclaimed::<Test>::get(),
post_info.calc_unspent(&info)
);
assert_eq!(
*BlockWeight::<Test>::get().get(DispatchClass::Operational),
post_info.actual_weight.unwrap() + prior_block_weight + base_extrinsic
);
})
}
}