#![cfg_attr(not(feature = "std"), no_std)]
use frame_support::pallet_prelude::{
ensure, Decode, Encode, MaxEncodedLen, PhantomData, RuntimeDebug, StorageVersion, TypeInfo,
};
use frame_support::{
dispatch::{DispatchInfo, DispatchResult, GetDispatchInfo, Pays, PostDispatchInfo},
traits::{
Currency,
ExistenceRequirement::{AllowDeath, KeepAlive},
InstanceFilter, IsSubType, IsType, OriginTrait, ReservableCurrency,
},
};
use pallet_transaction_payment::OnChargeTransaction;
use sp_io::hashing::blake2_256;
use sp_runtime::{
traits::{DispatchInfoOf, Dispatchable, One, PostDispatchInfoOf, SignedExtension, TrailingZeroInput, Zero},
transaction_validity::{InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction},
FixedPointOperand, Saturating,
};
use sp_std::{
fmt::{Debug, Formatter, Result as FmtResult},
prelude::*,
};
use support::LimitedBalance;
pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
pub use pallet::*;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod weights;
pub use weights::*;
type BalanceOf<T> = <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
type OnChargeTransactionBalanceOf<T> =
<<T as pallet_transaction_payment::Config>::OnChargeTransaction as OnChargeTransaction<T>>::Balance;
type LiquidityInfoOf<T> =
<<T as pallet_transaction_payment::Config>::OnChargeTransaction as OnChargeTransaction<T>>::LiquidityInfo;
type PotDetailsOf<T> = PotDetails<<T as frame_system::Config>::AccountId, <T as Config>::SponsorshipType, BalanceOf<T>>;
type UserDetailsOf<T> = UserDetails<<T as frame_system::Config>::AccountId, BalanceOf<T>>;
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)]
pub struct PotDetails<AccountId, SponsorshipType, Balance: frame_support::traits::tokens::Balance> {
sponsor: AccountId,
sponsorship_type: SponsorshipType,
fee_quota: LimitedBalance<Balance>,
reserve_quota: LimitedBalance<Balance>,
deposit: Balance,
}
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)]
pub struct UserDetails<AccountId, Balance: frame_support::traits::tokens::Balance> {
proxy: AccountId,
fee_quota: LimitedBalance<Balance>,
reserve_quota: LimitedBalance<Balance>,
deposit: Balance,
}
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use scale_info::prelude::boxed::Box;
use sp_runtime::traits::Dispatchable;
#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
#[pallet::without_storage_info]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config: frame_system::Config + pallet_transaction_payment::Config {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type RuntimeCall: Parameter
+ Dispatchable<RuntimeOrigin = Self::RuntimeOrigin>
+ GetDispatchInfo
+ From<frame_system::Call<Self>>
+ IsSubType<Call<Self>>
+ IsType<<Self as frame_system::Config>::RuntimeCall>;
type Currency: ReservableCurrency<Self::AccountId>;
type PotId: Member + Parameter + MaxEncodedLen + Copy + From<u32>;
type SponsorshipType: Parameter
+ Member
+ Ord
+ PartialOrd
+ InstanceFilter<<Self as Config>::RuntimeCall>
+ MaxEncodedLen
+ Default;
#[pallet::constant]
type UserDeposit: Get<BalanceOf<Self>>;
#[pallet::constant]
type PotDeposit: Get<BalanceOf<Self>>;
type WeightInfo: WeightInfo;
}
#[pallet::storage]
pub(super) type Pot<T: Config> = StorageMap<_, Blake2_128Concat, T::PotId, PotDetailsOf<T>, OptionQuery>;
#[pallet::storage]
pub(super) type User<T: Config> =
StorageDoubleMap<_, Blake2_128Concat, T::PotId, Blake2_128Concat, T::AccountId, UserDetailsOf<T>, OptionQuery>;
#[pallet::storage]
pub(super) type UserRegistrationCount<T: Config> = StorageMap<_, Blake2_128Concat, T::AccountId, u32, ValueQuery>;
#[pallet::storage]
pub(super) type PotUserMigrationPerBlock<T: Config> = StorageValue<_, (u32, u32), OptionQuery>;
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
PotCreated {
pot: T::PotId,
sponsor: T::AccountId,
sponsorship_type: T::SponsorshipType,
fee_quota: BalanceOf<T>,
reserve_quota: BalanceOf<T>,
},
PotRemoved { pot: T::PotId },
PotUpdated {
pot: T::PotId,
fee_quota: BalanceOf<T>,
reserve_quota: BalanceOf<T>,
},
PotSponsorshipTypeUpdated {
pot: T::PotId,
sponsorship_type: T::SponsorshipType,
},
UsersRegistered {
pot: T::PotId,
users: Vec<T::AccountId>,
fee_quota: BalanceOf<T>,
reserve_quota: BalanceOf<T>,
},
UsersRemoved { pot: T::PotId, users: Vec<T::AccountId> },
UsersLimitsUpdated {
pot: T::PotId,
users: Vec<T::AccountId>,
fee_quota: BalanceOf<T>,
reserve_quota: BalanceOf<T>,
},
Sponsored { paid: BalanceOf<T>, repaid: BalanceOf<T> },
TransactionFeePaid { sponsor: T::AccountId, fee: BalanceOf<T> },
}
#[pallet::error]
pub enum Error<T> {
InUse,
NoPermission,
PotNotExist,
UserNotRegistered,
UserAlreadyRegistered,
CannotRemoveProxy,
CannotCreateProxy,
BalanceLeak,
CannotUpdateFeeLimit,
CannotUpdateReserveLimit,
MigrationInProgress,
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::create_pot())]
pub fn create_pot(
origin: OriginFor<T>,
pot: T::PotId,
sponsorship_type: T::SponsorshipType,
fee_quota: BalanceOf<T>,
reserve_quota: BalanceOf<T>,
) -> DispatchResult {
let who = ensure_signed(origin)?;
ensure!(!Pot::<T>::contains_key(pot), Error::<T>::InUse);
T::Currency::reserve(&who, T::PotDeposit::get())?;
<Pot<T>>::insert(
pot,
PotDetailsOf::<T> {
sponsor: who.clone(),
sponsorship_type: sponsorship_type.clone(),
fee_quota: LimitedBalance::with_limit(fee_quota),
reserve_quota: LimitedBalance::with_limit(reserve_quota),
deposit: T::PotDeposit::get(),
},
);
Self::deposit_event(Event::PotCreated {
pot,
sponsor: who,
sponsorship_type,
fee_quota,
reserve_quota,
});
Ok(())
}
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::remove_pot())]
pub fn remove_pot(origin: OriginFor<T>, pot: T::PotId) -> DispatchResult {
let who = ensure_signed(origin)?;
Pot::<T>::try_mutate(pot, |maybe_pot_details| -> DispatchResult {
let pot_details = maybe_pot_details.as_mut().ok_or(Error::<T>::InUse)?;
ensure!(pot_details.sponsor == who, Error::<T>::NoPermission);
let users = User::<T>::iter_prefix(pot).count();
ensure!(users == 0, Error::<T>::InUse);
T::Currency::unreserve(&who, pot_details.deposit);
*maybe_pot_details = None;
Ok(())
})?;
Self::deposit_event(Event::PotRemoved { pot });
Ok(())
}
#[pallet::call_index(2)]
#[pallet::weight(T::WeightInfo::register_users(users.len() as u32))]
pub fn register_users(
origin: OriginFor<T>,
pot: T::PotId,
users: Vec<T::AccountId>,
common_fee_quota: BalanceOf<T>,
common_reserve_quota: BalanceOf<T>,
) -> DispatchResult {
let who = ensure_signed(origin)?;
let pot_details = Pot::<T>::get(pot).ok_or(Error::<T>::PotNotExist)?;
ensure!(pot_details.sponsor == who, Error::<T>::NoPermission);
for user in users.clone() {
ensure!(!User::<T>::contains_key(pot, &user), Error::<T>::UserAlreadyRegistered);
T::Currency::reserve(&who, T::UserDeposit::get())?;
UserRegistrationCount::<T>::mutate(&user, |count| {
if count.is_zero() {
frame_system::Pallet::<T>::inc_providers(&user);
}
count.saturating_inc();
});
let proxy = Self::pure_account(&user, &pot).ok_or(Error::<T>::CannotCreateProxy)?;
frame_system::Pallet::<T>::inc_providers(&proxy);
<User<T>>::insert(
pot,
user,
UserDetailsOf::<T> {
proxy,
fee_quota: LimitedBalance::with_limit(common_fee_quota),
reserve_quota: LimitedBalance::with_limit(common_reserve_quota),
deposit: T::UserDeposit::get(),
},
);
}
Self::deposit_event(Event::UsersRegistered {
pot,
users,
fee_quota: common_fee_quota,
reserve_quota: common_reserve_quota,
});
Ok(())
}
#[pallet::call_index(3)]
#[pallet::weight(T::WeightInfo::remove_users(users.len() as u32))]
pub fn remove_users(origin: OriginFor<T>, pot: T::PotId, users: Vec<T::AccountId>) -> DispatchResult {
let who = ensure_signed(origin)?;
let mut pot_details = Pot::<T>::get(pot).ok_or(Error::<T>::PotNotExist)?;
ensure!(pot_details.sponsor == who, Error::<T>::NoPermission);
for user in users.clone() {
let user_details = User::<T>::get(pot, &user).ok_or(Error::<T>::UserNotRegistered)?;
let repaid = Self::settle_user_accounts(
&pot_details.sponsor,
&user,
&user_details.proxy,
user_details.reserve_quota.balance(),
)?;
pot_details.reserve_quota.saturating_sub(repaid);
UserRegistrationCount::<T>::mutate(&user, |count| {
if count.is_one() {
let _ = frame_system::Pallet::<T>::dec_providers(&user);
}
count.saturating_dec();
});
T::Currency::unreserve(&who, user_details.deposit);
<User<T>>::remove(pot, user);
}
<Pot<T>>::insert(pot, pot_details);
Self::deposit_event(Event::UsersRemoved { pot, users });
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));
let dispatch_class = if dispatch_infos.iter().all(|di| di.class == DispatchClass::Operational) {
DispatchClass::Operational
} else {
DispatchClass::Normal
};
(dispatch_weight + < T as Config >::WeightInfo::pre_sponsor() + < T as Config >::WeightInfo::post_sponsor() + T::DbWeight::get().reads_writes(2, 2), dispatch_class, Pays::No)
})]
pub fn sponsor_for(
origin: OriginFor<T>,
pot: T::PotId,
calls: Vec<Box<<T as Config>::RuntimeCall>>,
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
let preps = Self::pre_sponsor_for(who.clone(), pot)?;
for call in calls.into_iter() {
call.dispatch(preps.proxy_origin.clone()).map_err(|e| e.error)?;
}
Self::post_sponsor_for(
who,
pot,
preps.pot_details,
preps.user_details,
preps.paid,
preps.proxy_balance,
)?;
Ok(().into())
}
#[pallet::call_index(5)]
#[pallet::weight(< T as Config >::WeightInfo::update_pot_limits())]
pub fn update_pot_limits(
origin: OriginFor<T>,
pot: T::PotId,
new_fee_quota: BalanceOf<T>,
new_reserve_quota: BalanceOf<T>,
) -> DispatchResult {
let who = ensure_signed(origin)?;
let mut pot_details = Pot::<T>::get(pot).ok_or(Error::<T>::PotNotExist)?;
ensure!(pot_details.sponsor == who, Error::<T>::NoPermission);
pot_details
.fee_quota
.update_limit(new_fee_quota)
.map_err(|_| Error::<T>::CannotUpdateFeeLimit)?;
pot_details
.reserve_quota
.update_limit(new_reserve_quota)
.map_err(|_| Error::<T>::CannotUpdateReserveLimit)?;
<Pot<T>>::insert(pot, pot_details);
Self::deposit_event(Event::PotUpdated {
pot,
fee_quota: new_fee_quota,
reserve_quota: new_reserve_quota,
});
Ok(())
}
#[pallet::call_index(6)]
#[pallet::weight(< T as Config >::WeightInfo::update_users_limits(users.len() as u32))]
pub fn update_users_limits(
origin: OriginFor<T>,
pot: T::PotId,
new_fee_quota: BalanceOf<T>,
new_reserve_quota: BalanceOf<T>,
users: Vec<T::AccountId>,
) -> DispatchResult {
let who = ensure_signed(origin)?;
let pot_details = Pot::<T>::get(pot).ok_or(Error::<T>::PotNotExist)?;
ensure!(pot_details.sponsor == who, Error::<T>::NoPermission);
for user in &users {
let mut user_details = User::<T>::get(pot, user).ok_or(Error::<T>::UserNotRegistered)?;
user_details
.fee_quota
.update_limit(new_fee_quota)
.map_err(|_| Error::<T>::CannotUpdateFeeLimit)?;
user_details
.reserve_quota
.update_limit(new_reserve_quota)
.map_err(|_| Error::<T>::CannotUpdateReserveLimit)?;
<User<T>>::insert(pot, user, user_details);
}
<Pot<T>>::insert(pot, pot_details);
Self::deposit_event(Event::UsersLimitsUpdated {
pot,
users,
fee_quota: new_fee_quota,
reserve_quota: new_reserve_quota,
});
Ok(())
}
#[pallet::call_index(7)]
#[pallet::weight(< T as Config >::WeightInfo::update_sponsorship_type())]
pub fn update_sponsorship_type(
origin: OriginFor<T>,
pot: T::PotId,
sponsorship_type: T::SponsorshipType,
) -> DispatchResult {
let who = ensure_signed(origin)?;
Pot::<T>::try_mutate(pot, |maybe_pot_details| -> DispatchResult {
let pot_details = maybe_pot_details.as_mut().ok_or(Error::<T>::PotNotExist)?;
ensure!(pot_details.sponsor == who, Error::<T>::NoPermission);
pot_details.sponsorship_type = sponsorship_type.clone();
Ok(())
})?;
Self::deposit_event(Event::PotSponsorshipTypeUpdated { pot, sponsorship_type });
Ok(())
}
}
}
struct SponsorCallPreps<T: Config> {
pot_details: PotDetailsOf<T>,
user_details: UserDetailsOf<T>,
proxy_origin: <T as frame_system::Config>::RuntimeOrigin,
paid: BalanceOf<T>,
proxy_balance: BalanceOf<T>,
}
impl<T: Config> Pallet<T> {
fn pure_account(who: &T::AccountId, pot_id: &T::PotId) -> Option<T::AccountId> {
let entropy = (b"modlsp/sponsorship", who, pot_id).using_encoded(blake2_256);
Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref())).ok()
}
fn settle_user_accounts(
sponsor: &T::AccountId,
user: &T::AccountId,
proxy: &T::AccountId,
owing: BalanceOf<T>,
) -> Result<BalanceOf<T>, sp_runtime::DispatchError> {
ensure!(
T::Currency::reserved_balance(proxy) == Zero::zero(),
Error::<T>::CannotRemoveProxy
);
let proxy_free_balance = T::Currency::free_balance(proxy);
let repay = proxy_free_balance.min(owing);
T::Currency::transfer(proxy, sponsor, repay, AllowDeath)?;
T::Currency::transfer(proxy, user, proxy_free_balance.saturating_sub(repay), AllowDeath)?;
frame_system::Pallet::<T>::dec_providers(proxy)?;
Ok(repay)
}
fn pre_sponsor_for(who: T::AccountId, pot: T::PotId) -> Result<SponsorCallPreps<T>, sp_runtime::DispatchError> {
let mut pot_details = Pot::<T>::get(pot).ok_or(Error::<T>::PotNotExist)?;
let mut user_details = User::<T>::get(pot, &who).ok_or(Error::<T>::UserNotRegistered)?;
let mut proxy_origin: T::RuntimeOrigin = frame_system::RawOrigin::Signed(user_details.proxy.clone()).into();
let sponsorship = pot_details.sponsorship_type.clone();
proxy_origin.add_filter(move |c: &<T as frame_system::Config>::RuntimeCall| {
let c = <T as Config>::RuntimeCall::from_ref(c);
sponsorship.filter(c)
});
let fund_for_reserve = user_details
.reserve_quota
.available_margin()
.min(pot_details.reserve_quota.available_margin());
let paid = user_details
.reserve_quota
.limit()
.saturating_sub(T::Currency::free_balance(&user_details.proxy))
.min(fund_for_reserve);
T::Currency::transfer(&pot_details.sponsor, &user_details.proxy, paid, KeepAlive)?;
pot_details.reserve_quota.saturating_add(paid);
user_details.reserve_quota.saturating_add(paid);
let proxy_balance = T::Currency::total_balance(&user_details.proxy);
Ok(SponsorCallPreps {
pot_details,
user_details,
proxy_origin,
paid,
proxy_balance,
})
}
fn post_sponsor_for(
who: T::AccountId,
pot: T::PotId,
mut pot_details: PotDetailsOf<T>,
mut user_details: UserDetailsOf<T>,
paid: BalanceOf<T>,
proxy_balance: BalanceOf<T>,
) -> Result<(), sp_runtime::DispatchError> {
let new_proxy_balance = T::Currency::total_balance(&user_details.proxy);
ensure!(new_proxy_balance >= proxy_balance, Error::<T>::BalanceLeak);
let repayable = T::Currency::free_balance(&user_details.proxy).saturating_sub(T::Currency::minimum_balance());
let repaid = repayable.min(user_details.reserve_quota.balance());
T::Currency::transfer(&user_details.proxy, &pot_details.sponsor, repaid, KeepAlive)?;
user_details.reserve_quota.saturating_sub(repaid);
pot_details.reserve_quota.saturating_sub(repaid);
Pot::<T>::insert(pot, pot_details);
User::<T>::insert(pot, &who, user_details);
Self::deposit_event(Event::Sponsored { paid, repaid });
Ok(())
}
}
#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct ChargeSponsor<T: Config>(PhantomData<BalanceOf<T>>);
impl<T: Config> Debug for ChargeSponsor<T> {
#[cfg(feature = "std")]
fn fmt(&self, f: &mut Formatter) -> FmtResult {
write!(f, "ChargeTransactionPayment<{:?}>", self.0)
}
#[cfg(not(feature = "std"))]
fn fmt(&self, _: &mut Formatter) -> FmtResult {
Ok(())
}
}
pub struct PreDispatchSponsorCallData<T: Config> {
pot: T::PotId,
pot_details: PotDetailsOf<T>,
user: T::AccountId,
user_details: UserDetailsOf<T>,
fee_imbalance: LiquidityInfoOf<T>,
}
pub type Pre<T> = Option<PreDispatchSponsorCallData<T>>;
impl<T: Config> ChargeSponsor<T>
where
BalanceOf<T>: IsType<OnChargeTransactionBalanceOf<T>>,
OnChargeTransactionBalanceOf<T>: FixedPointOperand,
<T as frame_system::Config>::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
<T as Config>::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
{
fn validate_sponsor_call(
user: &T::AccountId,
call: &<T as Config>::RuntimeCall,
info: &DispatchInfoOf<<T as Config>::RuntimeCall>,
len: usize,
) -> Result<Pre<T>, TransactionValidityError> {
match call.is_sub_type() {
Some(Call::sponsor_for { pot, .. }) => {
let pot_details = Pot::<T>::get(pot).ok_or(InvalidTransaction::Call)?;
let user_details = User::<T>::get(pot, user).ok_or(InvalidTransaction::BadSigner)?;
let mut info = *info;
info.pays_fee = Pays::Yes;
let fee = pallet_transaction_payment::Pallet::<T>::compute_fee(len as u32, &info, Zero::zero());
let available_fee_margin = pot_details
.fee_quota
.available_margin()
.min(user_details.fee_quota.available_margin());
if available_fee_margin.into_ref() < &fee {
Err(TransactionValidityError::Invalid(InvalidTransaction::Payment))?
}
let fee_imbalance = <T as pallet_transaction_payment::Config>::OnChargeTransaction::withdraw_fee(
&pot_details.sponsor,
call.into_ref(),
&info,
fee,
Zero::zero(),
)
.map_err(|_| InvalidTransaction::Payment)?;
Ok(Some(PreDispatchSponsorCallData {
pot: *pot,
pot_details,
user: user.clone(),
user_details,
fee_imbalance,
}))
}
_ => Ok(None),
}
}
}
impl<T: Config> Default for ChargeSponsor<T> {
fn default() -> Self {
Self(PhantomData)
}
}
impl<T: Config> SignedExtension for ChargeSponsor<T>
where
BalanceOf<T>: Send + Sync + IsType<OnChargeTransactionBalanceOf<T>>,
OnChargeTransactionBalanceOf<T>: FixedPointOperand,
<T as frame_system::Config>::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
<T as Config>::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
{
const IDENTIFIER: &'static str = "ChargeSponsor";
type AccountId = T::AccountId;
type Call = <T as Config>::RuntimeCall;
type AdditionalSigned = ();
type Pre = Pre<T>;
fn additional_signed(&self) -> Result<(), TransactionValidityError> {
Ok(())
}
fn validate(
&self,
who: &Self::AccountId,
call: &Self::Call,
info: &DispatchInfoOf<Self::Call>,
len: usize,
) -> TransactionValidity {
Self::validate_sponsor_call(who, call, info, len).map(|_| Ok(ValidTransaction::default()))?
}
fn pre_dispatch(
self,
who: &Self::AccountId,
call: &Self::Call,
info: &DispatchInfoOf<Self::Call>,
len: usize,
) -> Result<Self::Pre, TransactionValidityError> {
Self::validate_sponsor_call(who, call, info, len)
}
fn post_dispatch(
maybe_pre: Option<Self::Pre>,
info: &DispatchInfoOf<Self::Call>,
post_info: &PostDispatchInfoOf<Self::Call>,
len: usize,
_result: &DispatchResult,
) -> Result<(), TransactionValidityError> {
if let Some(Some(PreDispatchSponsorCallData {
pot,
mut pot_details,
user,
mut user_details,
fee_imbalance,
})) = maybe_pre
{
let mut info = *info;
info.pays_fee = Pays::Yes;
let actual_fee =
pallet_transaction_payment::Pallet::<T>::compute_actual_fee(len as u32, &info, post_info, Zero::zero());
<T as pallet_transaction_payment::Config>::OnChargeTransaction::correct_and_deposit_fee(
&pot_details.sponsor,
&info,
post_info,
actual_fee,
Zero::zero(),
fee_imbalance,
)?;
let actual_fee = *<BalanceOf<T> as IsType<OnChargeTransactionBalanceOf<T>>>::from_ref(&actual_fee);
pot_details
.fee_quota
.add(actual_fee)
.map_err(|_| InvalidTransaction::Payment)?;
user_details
.fee_quota
.add(actual_fee)
.map_err(|_| InvalidTransaction::Payment)?;
Pot::<T>::try_mutate(pot, |maybe_pot_details| -> DispatchResult {
let pot_details_to_overwrite = maybe_pot_details.as_mut().ok_or(Error::<T>::PotNotExist)?;
pot_details_to_overwrite.fee_quota = pot_details.fee_quota.clone();
Ok(())
})
.map_err(|_| InvalidTransaction::Call)?;
User::<T>::try_mutate(pot, &user, |maybe_user_details| -> DispatchResult {
let user_details_to_overwrite = maybe_user_details.as_mut().ok_or(Error::<T>::UserNotRegistered)?;
user_details_to_overwrite.fee_quota = user_details.fee_quota.clone();
Ok(())
})
.map_err(|_| InvalidTransaction::Call)?;
Pallet::<T>::deposit_event(Event::<T>::TransactionFeePaid {
sponsor: pot_details.sponsor,
fee: actual_fee,
});
}
Ok(())
}
}