#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
mod types;
pub mod weights;
pub use pallet::*;
pub use types::*;
pub use weights::WeightInfo;
use core::cmp;
#[cfg(feature = "runtime-benchmarks")]
use frame_support::traits::tokens::fungible::Mutate;
use frame_support::{
pallet_prelude::*,
traits::{
fungible,
fungible::{Credit, Inspect},
tokens::WithdrawConsequence,
IsSubType, StorageVersion, StoredMap,
},
};
use frame_system::pallet_prelude::*;
use pallet_quota::traits::RefundFee;
use pallet_transaction_payment::OnChargeTransaction;
use sp_runtime::traits::{DispatchInfoOf, PostDispatchInfoOf, Saturating};
use sp_std::fmt::Debug;
#[frame_support::pallet]
pub mod pallet {
use super::*;
pub type IdtyIdOf<T> = <T as pallet_identity::Config>::IdtyIndex;
pub type CurrencyOf<T> = pallet_balances::Pallet<T>;
type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
pub type BalanceOf<T> = <CurrencyOf<T> as fungible::Inspect<AccountIdOf<T>>>::Balance;
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
#[pallet::without_storage_info]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config:
frame_system::Config<AccountData = AccountData<Self::Balance, IdtyIdOf<Self>>>
+ pallet_balances::Config
+ pallet_transaction_payment::Config
+ pallet_treasury::Config<Currency = pallet_balances::Pallet<Self>>
+ pallet_quota::Config
{
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type WeightInfo: WeightInfo;
type InnerOnChargeTransaction: OnChargeTransaction<Self>;
type Refund: pallet_quota::traits::RefundFee<Self>;
}
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
pub accounts: sp_std::collections::btree_map::BTreeMap<
T::AccountId,
GenesisAccountData<T::Balance, IdtyIdOf<T>>,
>,
pub treasury_balance: T::Balance,
}
impl<T: Config> Default for GenesisConfig<T> {
fn default() -> Self {
Self {
accounts: Default::default(),
treasury_balance: T::ExistentialDeposit::get(),
}
}
}
#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) {
frame_system::Account::<T>::mutate(
pallet_treasury::Pallet::<T>::account_id(),
|account| {
account.data.free = self.treasury_balance;
account.providers = 1;
},
);
let endowed_accounts = self
.accounts
.keys()
.cloned()
.collect::<sp_std::collections::btree_set::BTreeSet<_>>();
assert!(
endowed_accounts.len() == self.accounts.len(),
"duplicate balances in genesis."
);
for (account_id, GenesisAccountData { balance, idty_id }) in &self.accounts {
assert!(balance >= &T::ExistentialDeposit::get() || idty_id.is_some());
frame_system::Account::<T>::mutate(account_id, |account| {
account.data.free = *balance;
if idty_id.is_some() {
account.data.linked_idty = *idty_id;
}
if balance >= &T::ExistentialDeposit::get() {
account.providers = 1;
}
});
}
}
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
AccountLinked {
who: T::AccountId,
identity: IdtyIdOf<T>,
},
AccountUnlinked(T::AccountId),
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(<T as pallet::Config>::WeightInfo::unlink_identity())]
pub fn unlink_identity(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
Self::do_unlink_identity(who);
Ok(().into())
}
}
impl<T: Config> Pallet<T> {
pub fn do_unlink_identity(account_id: T::AccountId) {
frame_system::Account::<T>::mutate(&account_id, |account| {
if account.data.linked_idty.is_some() {
Self::deposit_event(Event::AccountUnlinked(account_id.clone()));
}
account.data.linked_idty = None;
})
}
pub fn do_link_identity(account_id: &T::AccountId, idty_id: IdtyIdOf<T>) {
if frame_system::Account::<T>::get(account_id).data.linked_idty != Some(idty_id) {
frame_system::Account::<T>::mutate(account_id, |account| {
account.data.linked_idty = Some(idty_id);
Self::deposit_event(Event::AccountLinked {
who: account_id.clone(),
identity: idty_id,
});
})
};
}
}
}
impl<T> pallet_identity::traits::LinkIdty<T::AccountId, IdtyIdOf<T>> for Pallet<T>
where
T: Config,
{
fn link_identity(account_id: &T::AccountId, idty_id: IdtyIdOf<T>) -> Result<(), DispatchError> {
ensure!(
(frame_system::Account::<T>::get(account_id).providers >= 1)
|| (frame_system::Account::<T>::get(account_id).sufficients >= 1),
pallet_identity::Error::<T>::AccountNotExist
);
Self::do_link_identity(account_id, idty_id);
Ok(())
}
}
impl<T, AccountId, Balance>
frame_support::traits::StoredMap<AccountId, pallet_balances::AccountData<Balance>> for Pallet<T>
where
AccountId: Parameter
+ Member
+ MaybeSerializeDeserialize
+ Debug
+ sp_runtime::traits::MaybeDisplay
+ Ord
+ Into<[u8; 32]>
+ codec::MaxEncodedLen,
Balance: Parameter
+ Member
+ sp_runtime::traits::AtLeast32BitUnsigned
+ codec::Codec
+ Default
+ Copy
+ MaybeSerializeDeserialize
+ Debug
+ codec::MaxEncodedLen
+ scale_info::TypeInfo,
T: Config
+ frame_system::Config<AccountId = AccountId, AccountData = AccountData<Balance, IdtyIdOf<T>>>
+ pallet_balances::Config<Balance = Balance>,
{
fn get(k: &AccountId) -> pallet_balances::AccountData<Balance> {
frame_system::Account::<T>::get(k).data.into()
}
fn try_mutate_exists<R, E: From<sp_runtime::DispatchError>>(
account_id: &AccountId,
f: impl FnOnce(&mut Option<pallet_balances::AccountData<Balance>>) -> Result<R, E>,
) -> Result<R, E> {
let account = frame_system::Account::<T>::get(account_id);
let was_providing = !account.data.free.is_zero() || !account.data.reserved.is_zero();
let mut some_data = if was_providing {
Some(account.data.into())
} else {
None
};
let result = f(&mut some_data)?;
let is_providing = some_data.is_some();
match (was_providing, is_providing) {
(false, true) => {
frame_system::Pallet::<T>::inc_providers(account_id);
}
(true, false) => {
match frame_system::Pallet::<T>::dec_providers(account_id)? {
frame_system::DecRefStatus::Reaped => return Ok(result),
frame_system::DecRefStatus::Exists => {
}
}
}
(false, false) => {
return Ok(result);
}
(true, true) => {
}
}
frame_system::Account::<T>::mutate(account_id, |a| {
a.data.set_balances(some_data.unwrap_or_default())
});
Ok(result)
}
}
impl<T: Config> OnChargeTransaction<T> for Pallet<T>
where
T::RuntimeCall: IsSubType<Call<T>>,
T::InnerOnChargeTransaction: OnChargeTransaction<
T,
Balance = BalanceOf<T>,
LiquidityInfo = Option<Credit<T::AccountId, T::Currency>>,
>,
{
type Balance = BalanceOf<T>;
type LiquidityInfo = Option<Credit<T::AccountId, T::Currency>>;
fn withdraw_fee(
who: &T::AccountId,
call: &T::RuntimeCall,
dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
fee: Self::Balance,
tip: Self::Balance,
) -> Result<Self::LiquidityInfo, TransactionValidityError> {
T::InnerOnChargeTransaction::withdraw_fee(who, call, dispatch_info, fee, tip)
}
fn correct_and_deposit_fee(
who: &T::AccountId,
dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
post_info: &PostDispatchInfoOf<T::RuntimeCall>,
corrected_fee: Self::Balance,
tip: Self::Balance,
already_withdrawn: Self::LiquidityInfo,
) -> Result<(), TransactionValidityError> {
T::InnerOnChargeTransaction::correct_and_deposit_fee(
who,
dispatch_info,
post_info,
corrected_fee,
tip,
already_withdrawn,
)?;
let account_data = frame_system::Pallet::<T>::get(who);
if let Some(idty_index) = account_data.linked_idty {
T::Refund::request_refund(who.clone(), idty_index, corrected_fee.saturating_sub(tip));
}
Ok(())
}
}
impl<AccountId, T: Config> pallet_identity::traits::CheckAccountWorthiness<T> for Pallet<T>
where
T: frame_system::Config<AccountId = AccountId>,
AccountId: cmp::Eq,
{
fn check_account_worthiness(account: &AccountId) -> Result<(), DispatchError> {
ensure!(
frame_system::Pallet::<T>::providers(account) > 0,
pallet_identity::Error::<T>::AccountNotExist
);
ensure!(
T::Currency::can_withdraw(account, T::Currency::minimum_balance() * 2u32.into())
== WithdrawConsequence::Success,
pallet_identity::Error::<T>::InsufficientBalance
);
Ok(())
}
#[cfg(feature = "runtime-benchmarks")]
fn set_worthy(account: &AccountId) {
T::Currency::set_balance(account, T::Currency::minimum_balance() * 4u32.into());
}
}