1#![cfg_attr(not(feature = "std"), no_std)]
145mod benchmarking;
146mod impl_currency;
147mod impl_fungible;
148pub mod migration;
149mod tests;
150mod types;
151pub mod weights;
152
153extern crate alloc;
154
155use alloc::{
156 format,
157 string::{String, ToString},
158 vec::Vec,
159};
160use codec::{Codec, MaxEncodedLen};
161use core::{cmp, fmt::Debug, mem, result};
162use frame_support::{
163 ensure,
164 pallet_prelude::DispatchResult,
165 traits::{
166 tokens::{
167 fungible, BalanceStatus as Status, DepositConsequence,
168 Fortitude::{self, Force, Polite},
169 IdAmount,
170 Preservation::{Expendable, Preserve, Protect},
171 WithdrawConsequence,
172 },
173 Currency, Defensive, Get, OnUnbalanced, ReservableCurrency, StoredMap,
174 },
175 BoundedSlice, WeakBoundedVec,
176};
177use frame_system as system;
178pub use impl_currency::{NegativeImbalance, PositiveImbalance};
179use scale_info::TypeInfo;
180use sp_core::{sr25519::Pair as SrPair, Pair};
181use sp_runtime::{
182 traits::{
183 AtLeast32BitUnsigned, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Saturating,
184 StaticLookup, Zero,
185 },
186 ArithmeticError, DispatchError, FixedPointOperand, Perbill, TokenError,
187};
188
189pub use types::{
190 AccountData, AdjustmentDirection, BalanceLock, DustCleaner, ExtraFlags, Reasons, ReserveData,
191};
192pub use weights::WeightInfo;
193
194pub use pallet::*;
195
196const LOG_TARGET: &str = "runtime::balances";
197
198const DEFAULT_ADDRESS_URI: &str = "//Sender//{}";
200
201type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
202
203#[frame_support::pallet]
204pub mod pallet {
205 use super::*;
206 use codec::HasCompact;
207 use frame_support::{
208 pallet_prelude::*,
209 traits::{fungible::Credit, tokens::Precision, VariantCount, VariantCountOf},
210 };
211 use frame_system::pallet_prelude::*;
212
213 pub type CreditOf<T, I> = Credit<<T as frame_system::Config>::AccountId, Pallet<T, I>>;
214
215 pub mod config_preludes {
217 use super::*;
218 use frame_support::derive_impl;
219
220 pub struct TestDefaultConfig;
221
222 #[derive_impl(frame_system::config_preludes::TestDefaultConfig, no_aggregated_types)]
223 impl frame_system::DefaultConfig for TestDefaultConfig {}
224
225 #[frame_support::register_default_impl(TestDefaultConfig)]
226 impl DefaultConfig for TestDefaultConfig {
227 #[inject_runtime_type]
228 type RuntimeEvent = ();
229 #[inject_runtime_type]
230 type RuntimeHoldReason = ();
231 #[inject_runtime_type]
232 type RuntimeFreezeReason = ();
233
234 type Balance = u64;
235 type ExistentialDeposit = ConstUint<1>;
236
237 type ReserveIdentifier = ();
238
239 type DustRemoval = ();
240
241 type MaxLocks = ConstU32<100>;
242 type MaxReserves = ConstU32<100>;
243
244 type WeightInfo = ();
245 type DoneSlashHandler = ();
246 }
247 }
248
249 #[pallet::config(with_default)]
250 pub trait Config<I: 'static = ()>: frame_system::Config {
251 #[pallet::no_default_bounds]
253 #[allow(deprecated)]
254 type RuntimeEvent: From<Event<Self, I>>
255 + IsType<<Self as frame_system::Config>::RuntimeEvent>;
256
257 #[pallet::no_default_bounds]
259 type RuntimeHoldReason: Parameter + Member + MaxEncodedLen + Copy + VariantCount;
260
261 #[pallet::no_default_bounds]
266 type RuntimeFreezeReason: Parameter + Member + MaxEncodedLen + Copy + VariantCount;
267
268 type WeightInfo: WeightInfo;
270
271 type Balance: Parameter
273 + Member
274 + AtLeast32BitUnsigned
275 + Codec
276 + HasCompact<Type: DecodeWithMemTracking>
277 + Default
278 + Copy
279 + MaybeSerializeDeserialize
280 + Debug
281 + MaxEncodedLen
282 + TypeInfo
283 + FixedPointOperand;
284
285 #[pallet::no_default_bounds]
287 type DustRemoval: OnUnbalanced<CreditOf<Self, I>>;
288
289 #[pallet::constant]
298 #[pallet::no_default_bounds]
299 type ExistentialDeposit: Get<Self::Balance>;
300
301 #[pallet::no_default]
303 type AccountStore: StoredMap<Self::AccountId, AccountData<Self::Balance>>;
304
305 type ReserveIdentifier: Parameter + Member + MaxEncodedLen + Ord + Copy;
309
310 #[pallet::constant]
315 type MaxLocks: Get<u32>;
316
317 #[pallet::constant]
321 type MaxReserves: Get<u32>;
322
323 type DoneSlashHandler: fungible::hold::DoneSlash<
326 Self::RuntimeHoldReason,
327 Self::AccountId,
328 Self::Balance,
329 >;
330 }
331
332 const STORAGE_VERSION: frame_support::traits::StorageVersion =
334 frame_support::traits::StorageVersion::new(1);
335
336 #[pallet::pallet]
337 #[pallet::storage_version(STORAGE_VERSION)]
338 pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
339
340 #[pallet::event]
341 #[pallet::generate_deposit(pub(super) fn deposit_event)]
342 pub enum Event<T: Config<I>, I: 'static = ()> {
343 Endowed { account: T::AccountId, free_balance: T::Balance },
345 DustLost { account: T::AccountId, amount: T::Balance },
348 Transfer { from: T::AccountId, to: T::AccountId, amount: T::Balance },
350 BalanceSet { who: T::AccountId, free: T::Balance },
352 Reserved { who: T::AccountId, amount: T::Balance },
354 Unreserved { who: T::AccountId, amount: T::Balance },
356 ReserveRepatriated {
359 from: T::AccountId,
360 to: T::AccountId,
361 amount: T::Balance,
362 destination_status: Status,
363 },
364 Deposit { who: T::AccountId, amount: T::Balance },
366 Withdraw { who: T::AccountId, amount: T::Balance },
368 Slashed { who: T::AccountId, amount: T::Balance },
370 Minted { who: T::AccountId, amount: T::Balance },
372 MintedCredit { amount: T::Balance },
374 Burned { who: T::AccountId, amount: T::Balance },
376 BurnedDebt { amount: T::Balance },
378 Suspended { who: T::AccountId, amount: T::Balance },
380 Restored { who: T::AccountId, amount: T::Balance },
382 Upgraded { who: T::AccountId },
384 Issued { amount: T::Balance },
386 Rescinded { amount: T::Balance },
388 Locked { who: T::AccountId, amount: T::Balance },
390 Unlocked { who: T::AccountId, amount: T::Balance },
392 Frozen { who: T::AccountId, amount: T::Balance },
394 Thawed { who: T::AccountId, amount: T::Balance },
396 TotalIssuanceForced { old: T::Balance, new: T::Balance },
398 Held { reason: T::RuntimeHoldReason, who: T::AccountId, amount: T::Balance },
400 BurnedHeld { reason: T::RuntimeHoldReason, who: T::AccountId, amount: T::Balance },
402 TransferOnHold {
404 reason: T::RuntimeHoldReason,
405 source: T::AccountId,
406 dest: T::AccountId,
407 amount: T::Balance,
408 },
409 TransferAndHold {
411 reason: T::RuntimeHoldReason,
412 source: T::AccountId,
413 dest: T::AccountId,
414 transferred: T::Balance,
415 },
416 Released { reason: T::RuntimeHoldReason, who: T::AccountId, amount: T::Balance },
418 Unexpected(UnexpectedKind),
420 }
421
422 #[derive(Clone, Encode, Decode, DecodeWithMemTracking, PartialEq, TypeInfo, Debug)]
426 pub enum UnexpectedKind {
427 BalanceUpdated,
429 FailedToMutateAccount,
432 }
433
434 #[pallet::error]
435 pub enum Error<T, I = ()> {
436 VestingBalance,
438 LiquidityRestrictions,
440 InsufficientBalance,
442 ExistentialDeposit,
444 Expendability,
446 ExistingVestingSchedule,
448 DeadAccount,
450 TooManyReserves,
452 TooManyHolds,
454 TooManyFreezes,
456 IssuanceDeactivated,
458 DeltaZero,
460 }
461
462 #[pallet::storage]
464 #[pallet::whitelist_storage]
465 pub type TotalIssuance<T: Config<I>, I: 'static = ()> = StorageValue<_, T::Balance, ValueQuery>;
466
467 #[pallet::storage]
473 #[pallet::whitelist_storage]
474 pub type InactiveIssuance<T: Config<I>, I: 'static = ()> =
475 StorageValue<_, T::Balance, ValueQuery>;
476
477 #[pallet::storage]
502 pub type Account<T: Config<I>, I: 'static = ()> =
503 StorageMap<_, Blake2_128Concat, T::AccountId, AccountData<T::Balance>, ValueQuery>;
504
505 #[pallet::storage]
510 pub type Locks<T: Config<I>, I: 'static = ()> = StorageMap<
511 _,
512 Blake2_128Concat,
513 T::AccountId,
514 WeakBoundedVec<BalanceLock<T::Balance>, T::MaxLocks>,
515 ValueQuery,
516 >;
517
518 #[pallet::storage]
522 pub type Reserves<T: Config<I>, I: 'static = ()> = StorageMap<
523 _,
524 Blake2_128Concat,
525 T::AccountId,
526 BoundedVec<ReserveData<T::ReserveIdentifier, T::Balance>, T::MaxReserves>,
527 ValueQuery,
528 >;
529
530 #[pallet::storage]
532 pub type Holds<T: Config<I>, I: 'static = ()> = StorageMap<
533 _,
534 Blake2_128Concat,
535 T::AccountId,
536 BoundedVec<
537 IdAmount<T::RuntimeHoldReason, T::Balance>,
538 VariantCountOf<T::RuntimeHoldReason>,
539 >,
540 ValueQuery,
541 >;
542
543 #[pallet::storage]
545 pub type Freezes<T: Config<I>, I: 'static = ()> = StorageMap<
546 _,
547 Blake2_128Concat,
548 T::AccountId,
549 BoundedVec<
550 IdAmount<T::RuntimeFreezeReason, T::Balance>,
551 VariantCountOf<T::RuntimeFreezeReason>,
552 >,
553 ValueQuery,
554 >;
555
556 #[pallet::genesis_config]
557 pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
558 pub balances: Vec<(T::AccountId, T::Balance)>,
559 pub dev_accounts: Option<(u32, T::Balance, Option<String>)>,
566 }
567
568 impl<T: Config<I>, I: 'static> Default for GenesisConfig<T, I> {
569 fn default() -> Self {
570 Self { balances: Default::default(), dev_accounts: None }
571 }
572 }
573
574 #[pallet::genesis_build]
575 impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
576 fn build(&self) {
577 let total = self.balances.iter().fold(Zero::zero(), |acc: T::Balance, &(_, n)| acc + n);
578
579 <TotalIssuance<T, I>>::put(total);
580
581 for (_, balance) in &self.balances {
582 assert!(
583 *balance >= <T as Config<I>>::ExistentialDeposit::get(),
584 "the balance of any account should always be at least the existential deposit.",
585 )
586 }
587
588 let endowed_accounts = self
590 .balances
591 .iter()
592 .map(|(x, _)| x)
593 .cloned()
594 .collect::<alloc::collections::btree_set::BTreeSet<_>>();
595
596 assert!(
597 endowed_accounts.len() == self.balances.len(),
598 "duplicate balances in genesis."
599 );
600
601 if let Some((num_accounts, balance, ref derivation)) = self.dev_accounts {
603 Pallet::<T, I>::derive_dev_account(
605 num_accounts,
606 balance,
607 derivation.as_deref().unwrap_or(DEFAULT_ADDRESS_URI),
608 );
609 }
610 for &(ref who, free) in self.balances.iter() {
611 frame_system::Pallet::<T>::inc_providers(who);
612 assert!(T::AccountStore::insert(who, AccountData { free, ..Default::default() })
613 .is_ok());
614 }
615 }
616 }
617
618 #[pallet::hooks]
619 impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
620 fn integrity_test() {
621 #[cfg(not(feature = "insecure_zero_ed"))]
622 assert!(
623 !<T as Config<I>>::ExistentialDeposit::get().is_zero(),
624 "The existential deposit must be greater than zero!"
625 );
626 }
627
628 #[cfg(feature = "try-runtime")]
629 fn try_state(n: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
630 Self::do_try_state(n)
631 }
632 }
633
634 #[pallet::call(weight(<T as Config<I>>::WeightInfo))]
635 impl<T: Config<I>, I: 'static> Pallet<T, I> {
636 #[pallet::call_index(0)]
644 pub fn transfer_allow_death(
645 origin: OriginFor<T>,
646 dest: AccountIdLookupOf<T>,
647 #[pallet::compact] value: T::Balance,
648 ) -> DispatchResult {
649 let source = ensure_signed(origin)?;
650 let dest = T::Lookup::lookup(dest)?;
651 <Self as fungible::Mutate<_>>::transfer(&source, &dest, value, Expendable)?;
652 Ok(())
653 }
654
655 #[pallet::call_index(2)]
658 pub fn force_transfer(
659 origin: OriginFor<T>,
660 source: AccountIdLookupOf<T>,
661 dest: AccountIdLookupOf<T>,
662 #[pallet::compact] value: T::Balance,
663 ) -> DispatchResult {
664 ensure_root(origin)?;
665 let source = T::Lookup::lookup(source)?;
666 let dest = T::Lookup::lookup(dest)?;
667 <Self as fungible::Mutate<_>>::transfer(&source, &dest, value, Expendable)?;
668 Ok(())
669 }
670
671 #[pallet::call_index(3)]
678 pub fn transfer_keep_alive(
679 origin: OriginFor<T>,
680 dest: AccountIdLookupOf<T>,
681 #[pallet::compact] value: T::Balance,
682 ) -> DispatchResult {
683 let source = ensure_signed(origin)?;
684 let dest = T::Lookup::lookup(dest)?;
685 <Self as fungible::Mutate<_>>::transfer(&source, &dest, value, Preserve)?;
686 Ok(())
687 }
688
689 #[pallet::call_index(4)]
705 pub fn transfer_all(
706 origin: OriginFor<T>,
707 dest: AccountIdLookupOf<T>,
708 keep_alive: bool,
709 ) -> DispatchResult {
710 let transactor = ensure_signed(origin)?;
711 let keep_alive = if keep_alive { Preserve } else { Expendable };
712 let reducible_balance = <Self as fungible::Inspect<_>>::reducible_balance(
713 &transactor,
714 keep_alive,
715 Fortitude::Polite,
716 );
717 let dest = T::Lookup::lookup(dest)?;
718 <Self as fungible::Mutate<_>>::transfer(
719 &transactor,
720 &dest,
721 reducible_balance,
722 keep_alive,
723 )?;
724 Ok(())
725 }
726
727 #[pallet::call_index(5)]
731 pub fn force_unreserve(
732 origin: OriginFor<T>,
733 who: AccountIdLookupOf<T>,
734 amount: T::Balance,
735 ) -> DispatchResult {
736 ensure_root(origin)?;
737 let who = T::Lookup::lookup(who)?;
738 let _leftover = <Self as ReservableCurrency<_>>::unreserve(&who, amount);
739 Ok(())
740 }
741
742 #[pallet::call_index(6)]
751 #[pallet::weight(T::WeightInfo::upgrade_accounts(who.len() as u32))]
752 pub fn upgrade_accounts(
753 origin: OriginFor<T>,
754 who: Vec<T::AccountId>,
755 ) -> DispatchResultWithPostInfo {
756 ensure_signed(origin)?;
757 if who.is_empty() {
758 return Ok(Pays::Yes.into());
759 }
760 let mut upgrade_count = 0;
761 for i in &who {
762 let upgraded = Self::ensure_upgraded(i);
763 if upgraded {
764 upgrade_count.saturating_inc();
765 }
766 }
767 let proportion_upgraded = Perbill::from_rational(upgrade_count, who.len() as u32);
768 if proportion_upgraded >= Perbill::from_percent(90) {
769 Ok(Pays::No.into())
770 } else {
771 Ok(Pays::Yes.into())
772 }
773 }
774
775 #[pallet::call_index(8)]
779 #[pallet::weight(
780 T::WeightInfo::force_set_balance_creating() .max(T::WeightInfo::force_set_balance_killing()) )]
783 pub fn force_set_balance(
784 origin: OriginFor<T>,
785 who: AccountIdLookupOf<T>,
786 #[pallet::compact] new_free: T::Balance,
787 ) -> DispatchResult {
788 ensure_root(origin)?;
789 let who = T::Lookup::lookup(who)?;
790 let existential_deposit = Self::ed();
791
792 let wipeout = new_free < existential_deposit;
793 let new_free = if wipeout { Zero::zero() } else { new_free };
794
795 let old_free = Self::mutate_account_handling_dust(&who, false, |account| {
797 let old_free = account.free;
798 account.free = new_free;
799 old_free
800 })?;
801
802 if new_free > old_free {
805 mem::drop(PositiveImbalance::<T, I>::new(new_free - old_free));
806 } else if new_free < old_free {
807 mem::drop(NegativeImbalance::<T, I>::new(old_free - new_free));
808 }
809
810 Self::deposit_event(Event::BalanceSet { who, free: new_free });
811 Ok(())
812 }
813
814 #[doc = docify::embed!("./src/tests/dispatchable_tests.rs", force_adjust_total_issuance_example)]
820 #[pallet::call_index(9)]
821 #[pallet::weight(T::WeightInfo::force_adjust_total_issuance())]
822 pub fn force_adjust_total_issuance(
823 origin: OriginFor<T>,
824 direction: AdjustmentDirection,
825 #[pallet::compact] delta: T::Balance,
826 ) -> DispatchResult {
827 ensure_root(origin)?;
828
829 ensure!(delta > Zero::zero(), Error::<T, I>::DeltaZero);
830
831 let old = TotalIssuance::<T, I>::get();
832 let new = match direction {
833 AdjustmentDirection::Increase => old.saturating_add(delta),
834 AdjustmentDirection::Decrease => old.saturating_sub(delta),
835 };
836
837 ensure!(InactiveIssuance::<T, I>::get() <= new, Error::<T, I>::IssuanceDeactivated);
838 TotalIssuance::<T, I>::set(new);
839
840 Self::deposit_event(Event::<T, I>::TotalIssuanceForced { old, new });
841
842 Ok(())
843 }
844
845 #[pallet::call_index(10)]
853 #[pallet::weight(if *keep_alive {T::WeightInfo::burn_keep_alive()} else {T::WeightInfo::burn_allow_death()})]
854 pub fn burn(
855 origin: OriginFor<T>,
856 #[pallet::compact] value: T::Balance,
857 keep_alive: bool,
858 ) -> DispatchResult {
859 let source = ensure_signed(origin)?;
860 let preservation = if keep_alive { Preserve } else { Expendable };
861 <Self as fungible::Mutate<_>>::burn_from(
862 &source,
863 value,
864 preservation,
865 Precision::Exact,
866 Polite,
867 )?;
868 Ok(())
869 }
870 }
871
872 impl<T: Config<I>, I: 'static> Pallet<T, I> {
873 pub fn total_issuance() -> T::Balance {
875 TotalIssuance::<T, I>::get()
876 }
877
878 pub fn inactive_issuance() -> T::Balance {
880 InactiveIssuance::<T, I>::get()
881 }
882
883 pub fn locks(who: &T::AccountId) -> WeakBoundedVec<BalanceLock<T::Balance>, T::MaxLocks> {
885 Locks::<T, I>::get(who)
886 }
887
888 pub fn reserves(
890 who: &T::AccountId,
891 ) -> BoundedVec<ReserveData<T::ReserveIdentifier, T::Balance>, T::MaxReserves> {
892 Reserves::<T, I>::get(who)
893 }
894
895 fn ed() -> T::Balance {
896 T::ExistentialDeposit::get()
897 }
898 pub fn ensure_upgraded(who: &T::AccountId) -> bool {
902 let mut a = T::AccountStore::get(who);
903 if a.flags.is_new_logic() {
904 return false;
905 }
906 a.flags.set_new_logic();
907 if !a.reserved.is_zero() && a.frozen.is_zero() {
908 if system::Pallet::<T>::providers(who) == 0 {
909 log::warn!(
913 target: LOG_TARGET,
914 "account with a non-zero reserve balance has no provider refs, account_id: '{:?}'.",
915 who
916 );
917 a.free = a.free.max(Self::ed());
918 system::Pallet::<T>::inc_providers(who);
919 }
920 let _ = system::Pallet::<T>::inc_consumers_without_limit(who).defensive();
921 }
922 let _ = T::AccountStore::try_mutate_exists(who, |account| -> DispatchResult {
924 *account = Some(a);
925 Ok(())
926 });
927 Self::deposit_event(Event::Upgraded { who: who.clone() });
928 return true;
929 }
930
931 pub fn free_balance(who: impl core::borrow::Borrow<T::AccountId>) -> T::Balance {
933 Self::account(who.borrow()).free
934 }
935
936 pub fn usable_balance(who: impl core::borrow::Borrow<T::AccountId>) -> T::Balance {
939 <Self as fungible::Inspect<_>>::reducible_balance(who.borrow(), Expendable, Polite)
940 }
941
942 pub fn usable_balance_for_fees(who: impl core::borrow::Borrow<T::AccountId>) -> T::Balance {
947 <Self as fungible::Inspect<_>>::reducible_balance(who.borrow(), Protect, Polite)
948 }
949
950 pub fn reserved_balance(who: impl core::borrow::Borrow<T::AccountId>) -> T::Balance {
952 Self::account(who.borrow()).reserved
953 }
954
955 pub(crate) fn account(who: &T::AccountId) -> AccountData<T::Balance> {
957 T::AccountStore::get(who)
958 }
959
960 pub(crate) fn mutate_account_handling_dust<R>(
972 who: &T::AccountId,
973 force_consumer_bump: bool,
974 f: impl FnOnce(&mut AccountData<T::Balance>) -> R,
975 ) -> Result<R, DispatchError> {
976 let (r, maybe_dust) = Self::mutate_account(who, force_consumer_bump, f)?;
977 if let Some(dust) = maybe_dust {
978 <Self as fungible::Unbalanced<_>>::handle_raw_dust(dust);
979 }
980 Ok(r)
981 }
982
983 pub(crate) fn try_mutate_account_handling_dust<R, E: From<DispatchError>>(
995 who: &T::AccountId,
996 force_consumer_bump: bool,
997 f: impl FnOnce(&mut AccountData<T::Balance>, bool) -> Result<R, E>,
998 ) -> Result<R, E> {
999 let (r, maybe_dust) = Self::try_mutate_account(who, force_consumer_bump, f)?;
1000 if let Some(dust) = maybe_dust {
1001 <Self as fungible::Unbalanced<_>>::handle_raw_dust(dust);
1002 }
1003 Ok(r)
1004 }
1005
1006 pub(crate) fn mutate_account<R>(
1023 who: &T::AccountId,
1024 force_consumer_bump: bool,
1025 f: impl FnOnce(&mut AccountData<T::Balance>) -> R,
1026 ) -> Result<(R, Option<T::Balance>), DispatchError> {
1027 Self::try_mutate_account(who, force_consumer_bump, |a, _| -> Result<R, DispatchError> {
1028 Ok(f(a))
1029 })
1030 }
1031
1032 #[cfg(not(feature = "insecure_zero_ed"))]
1035 fn have_providers_or_no_zero_ed(_: &T::AccountId) -> bool {
1036 true
1037 }
1038
1039 #[cfg(feature = "insecure_zero_ed")]
1042 fn have_providers_or_no_zero_ed(who: &T::AccountId) -> bool {
1043 frame_system::Pallet::<T>::providers(who) > 0
1044 }
1045
1046 pub(crate) fn try_mutate_account<R, E: From<DispatchError>>(
1060 who: &T::AccountId,
1061 force_consumer_bump: bool,
1062 f: impl FnOnce(&mut AccountData<T::Balance>, bool) -> Result<R, E>,
1063 ) -> Result<(R, Option<T::Balance>), E> {
1064 Self::ensure_upgraded(who);
1065 let result = T::AccountStore::try_mutate_exists(who, |maybe_account| {
1066 let is_new = maybe_account.is_none();
1067 let mut account = maybe_account.take().unwrap_or_default();
1068 let did_provide =
1069 account.free >= Self::ed() && Self::have_providers_or_no_zero_ed(who);
1070 let did_consume =
1071 !is_new && (!account.reserved.is_zero() || !account.frozen.is_zero());
1072
1073 let result = f(&mut account, is_new)?;
1074
1075 let does_provide = account.free >= Self::ed();
1076 let does_consume = !account.reserved.is_zero() || !account.frozen.is_zero();
1077
1078 if !did_provide && does_provide {
1079 frame_system::Pallet::<T>::inc_providers(who);
1080 }
1081 if did_consume && !does_consume {
1082 frame_system::Pallet::<T>::dec_consumers(who);
1083 }
1084 if !did_consume && does_consume {
1085 if force_consumer_bump {
1086 frame_system::Pallet::<T>::inc_consumers_without_limit(who)?;
1088 } else {
1089 frame_system::Pallet::<T>::inc_consumers(who)?;
1090 }
1091 }
1092 if does_consume && frame_system::Pallet::<T>::consumers(who) == 0 {
1093 log::error!(target: LOG_TARGET, "Defensively bumping a consumer ref.");
1097 frame_system::Pallet::<T>::inc_consumers(who)?;
1098 }
1099 if did_provide && !does_provide {
1100 frame_system::Pallet::<T>::dec_providers(who).inspect_err(|_| {
1102 if did_consume && !does_consume {
1104 let _ = frame_system::Pallet::<T>::inc_consumers(who).defensive();
1105 }
1106 if !did_consume && does_consume {
1107 let _ = frame_system::Pallet::<T>::dec_consumers(who);
1108 }
1109 })?;
1110 }
1111
1112 let maybe_endowed = if is_new { Some(account.free) } else { None };
1113
1114 let ed = Self::ed();
1126 let maybe_dust = if account.free < ed && !does_consume {
1127 if account.free.is_zero() {
1128 None
1129 } else {
1130 Some(account.free)
1131 }
1132 } else {
1133 *maybe_account = Some(account);
1134 None
1135 };
1136 Ok((maybe_endowed, maybe_dust, result))
1137 });
1138 result.map(|(maybe_endowed, maybe_dust, result)| {
1139 if let Some(endowed) = maybe_endowed {
1140 Self::deposit_event(Event::Endowed {
1141 account: who.clone(),
1142 free_balance: endowed,
1143 });
1144 }
1145 if let Some(amount) = maybe_dust {
1146 Pallet::<T, I>::deposit_event(Event::DustLost { account: who.clone(), amount });
1147 }
1148 (result, maybe_dust)
1149 })
1150 }
1151
1152 pub(crate) fn update_locks(who: &T::AccountId, locks: &[BalanceLock<T::Balance>]) {
1154 let bounded_locks = WeakBoundedVec::<_, T::MaxLocks>::force_from(
1155 locks.to_vec(),
1156 Some("Balances Update Locks"),
1157 );
1158
1159 if locks.len() as u32 > T::MaxLocks::get() {
1160 log::warn!(
1161 target: LOG_TARGET,
1162 "Warning: A user has more currency locks than expected. \
1163 A runtime configuration adjustment may be needed."
1164 );
1165 }
1166 let freezes = Freezes::<T, I>::get(who);
1167 let mut prev_frozen = Zero::zero();
1168 let mut after_frozen = Zero::zero();
1169 let res = Self::mutate_account(who, true, |b| {
1172 prev_frozen = b.frozen;
1173 b.frozen = Zero::zero();
1174 for l in locks.iter() {
1175 b.frozen = b.frozen.max(l.amount);
1176 }
1177 for l in freezes.iter() {
1178 b.frozen = b.frozen.max(l.amount);
1179 }
1180 after_frozen = b.frozen;
1181 });
1182 let maybe_dust = match res {
1183 Ok((_, maybe_dust)) => maybe_dust,
1184 Err(_) => {
1185 Self::deposit_event(Event::Unexpected(UnexpectedKind::FailedToMutateAccount));
1186 defensive!("errored in mutate_account");
1187 None
1188 },
1189 };
1190
1191 match locks.is_empty() {
1192 true => Locks::<T, I>::remove(who),
1193 false => Locks::<T, I>::insert(who, bounded_locks),
1194 }
1195
1196 if let Some(dust) = maybe_dust {
1197 <Self as fungible::Unbalanced<_>>::handle_raw_dust(dust);
1198 }
1199
1200 if prev_frozen > after_frozen {
1201 let amount = prev_frozen.saturating_sub(after_frozen);
1202 Self::deposit_event(Event::Unlocked { who: who.clone(), amount });
1203 } else if after_frozen > prev_frozen {
1204 let amount = after_frozen.saturating_sub(prev_frozen);
1205 Self::deposit_event(Event::Locked { who: who.clone(), amount });
1206 }
1207 }
1208
1209 pub(crate) fn update_freezes(
1211 who: &T::AccountId,
1212 freezes: BoundedSlice<
1213 IdAmount<T::RuntimeFreezeReason, T::Balance>,
1214 VariantCountOf<T::RuntimeFreezeReason>,
1215 >,
1216 ) -> DispatchResult {
1217 let mut prev_frozen = Zero::zero();
1218 let mut after_frozen = Zero::zero();
1219 let (_, maybe_dust) = Self::mutate_account(who, false, |b| {
1220 prev_frozen = b.frozen;
1221 b.frozen = Zero::zero();
1222 for l in Locks::<T, I>::get(who).iter() {
1223 b.frozen = b.frozen.max(l.amount);
1224 }
1225 for l in freezes.iter() {
1226 b.frozen = b.frozen.max(l.amount);
1227 }
1228 after_frozen = b.frozen;
1229 })?;
1230
1231 if freezes.is_empty() {
1232 Freezes::<T, I>::remove(who);
1233 } else {
1234 Freezes::<T, I>::insert(who, freezes);
1235 }
1236
1237 if let Some(dust) = maybe_dust {
1238 <Self as fungible::Unbalanced<_>>::handle_raw_dust(dust);
1239 }
1240
1241 if prev_frozen > after_frozen {
1242 let amount = prev_frozen.saturating_sub(after_frozen);
1243 Self::deposit_event(Event::Thawed { who: who.clone(), amount });
1244 } else if after_frozen > prev_frozen {
1245 let amount = after_frozen.saturating_sub(prev_frozen);
1246 Self::deposit_event(Event::Frozen { who: who.clone(), amount });
1247 }
1248 Ok(())
1249 }
1250
1251 pub(crate) fn do_transfer_reserved(
1258 slashed: &T::AccountId,
1259 beneficiary: &T::AccountId,
1260 value: T::Balance,
1261 precision: Precision,
1262 fortitude: Fortitude,
1263 status: Status,
1264 ) -> Result<T::Balance, DispatchError> {
1265 if value.is_zero() {
1266 return Ok(Zero::zero());
1267 }
1268
1269 let max = <Self as fungible::InspectHold<_>>::reducible_total_balance_on_hold(
1270 slashed, fortitude,
1271 );
1272 let actual = match precision {
1273 Precision::BestEffort => value.min(max),
1274 Precision::Exact => value,
1275 };
1276 ensure!(actual <= max, TokenError::FundsUnavailable);
1277 if slashed == beneficiary {
1278 return match status {
1279 Status::Free => Ok(actual.saturating_sub(Self::unreserve(slashed, actual))),
1280 Status::Reserved => Ok(actual),
1281 };
1282 }
1283
1284 let ((_, maybe_dust_1), maybe_dust_2) = Self::try_mutate_account(
1285 beneficiary,
1286 false,
1287 |to_account, is_new| -> Result<((), Option<T::Balance>), DispatchError> {
1288 ensure!(!is_new, Error::<T, I>::DeadAccount);
1289 Self::try_mutate_account(slashed, false, |from_account, _| -> DispatchResult {
1290 match status {
1291 Status::Free => {
1292 to_account.free = to_account
1293 .free
1294 .checked_add(&actual)
1295 .ok_or(ArithmeticError::Overflow)?
1296 },
1297 Status::Reserved => {
1298 to_account.reserved = to_account
1299 .reserved
1300 .checked_add(&actual)
1301 .ok_or(ArithmeticError::Overflow)?
1302 },
1303 }
1304 from_account.reserved.saturating_reduce(actual);
1305 Ok(())
1306 })
1307 },
1308 )?;
1309
1310 if let Some(dust) = maybe_dust_1 {
1311 <Self as fungible::Unbalanced<_>>::handle_raw_dust(dust);
1312 }
1313 if let Some(dust) = maybe_dust_2 {
1314 <Self as fungible::Unbalanced<_>>::handle_raw_dust(dust);
1315 }
1316
1317 Self::deposit_event(Event::ReserveRepatriated {
1318 from: slashed.clone(),
1319 to: beneficiary.clone(),
1320 amount: actual,
1321 destination_status: status,
1322 });
1323 Ok(actual)
1324 }
1325
1326 pub fn derive_dev_account(num_accounts: u32, balance: T::Balance, derivation: &str) {
1328 assert!(num_accounts > 0, "num_accounts must be greater than zero");
1330
1331 assert!(
1332 balance >= <T as Config<I>>::ExistentialDeposit::get(),
1333 "the balance of any account should always be at least the existential deposit.",
1334 );
1335
1336 assert!(
1337 derivation.contains("{}"),
1338 "Invalid derivation, expected `{{}}` as part of the derivation"
1339 );
1340
1341 for index in 0..num_accounts {
1342 let derivation_string = derivation.replace("{}", &index.to_string());
1344
1345 let pair: SrPair = Pair::from_string(&derivation_string, None)
1347 .expect(&format!("Failed to parse derivation string: {derivation_string}"));
1348
1349 let who = T::AccountId::decode(&mut &pair.public().encode()[..])
1351 .expect(&format!("Failed to decode public key from pair: {:?}", pair.public()));
1352
1353 Self::mutate_account_handling_dust(&who, false, |account| {
1355 account.free = balance;
1356 })
1357 .expect(&format!("Failed to add account to keystore: {:?}", who));
1358 }
1359 }
1360 }
1361
1362 #[cfg(any(test, feature = "try-runtime"))]
1363 impl<T: Config<I>, I: 'static> Pallet<T, I> {
1364 pub(crate) fn do_try_state(
1365 _n: BlockNumberFor<T>,
1366 ) -> Result<(), sp_runtime::TryRuntimeError> {
1367 Self::hold_and_freeze_count()?;
1368 Self::account_frozen_greater_than_locks()?;
1369 Self::account_frozen_greater_than_freezes()?;
1370 Ok(())
1371 }
1372
1373 fn hold_and_freeze_count() -> Result<(), sp_runtime::TryRuntimeError> {
1374 Holds::<T, I>::iter_keys().try_for_each(|k| {
1375 if Holds::<T, I>::decode_len(k).unwrap_or(0) >
1376 T::RuntimeHoldReason::VARIANT_COUNT as usize
1377 {
1378 Err("Found `Hold` with too many elements")
1379 } else {
1380 Ok(())
1381 }
1382 })?;
1383
1384 Freezes::<T, I>::iter_keys().try_for_each(|k| {
1385 if Freezes::<T, I>::decode_len(k).unwrap_or(0) >
1386 T::RuntimeFreezeReason::VARIANT_COUNT as usize
1387 {
1388 Err("Found `Freeze` with too many elements")
1389 } else {
1390 Ok(())
1391 }
1392 })?;
1393
1394 Ok(())
1395 }
1396
1397 fn account_frozen_greater_than_locks() -> Result<(), sp_runtime::TryRuntimeError> {
1398 Locks::<T, I>::iter().try_for_each(|(who, locks)| {
1399 let max_locks = locks.iter().map(|l| l.amount).max().unwrap_or_default();
1400 let frozen = T::AccountStore::get(&who).frozen;
1401 if max_locks > frozen {
1402 log::warn!(
1403 target: crate::LOG_TARGET,
1404 "Maximum lock of {:?} ({:?}) is greater than the frozen balance {:?}",
1405 who,
1406 max_locks,
1407 frozen
1408 );
1409 Err("bad locks".into())
1410 } else {
1411 Ok(())
1412 }
1413 })
1414 }
1415
1416 fn account_frozen_greater_than_freezes() -> Result<(), sp_runtime::TryRuntimeError> {
1417 Freezes::<T, I>::iter().try_for_each(|(who, freezes)| {
1418 let max_locks = freezes.iter().map(|l| l.amount).max().unwrap_or_default();
1419 let frozen = T::AccountStore::get(&who).frozen;
1420 if max_locks > frozen {
1421 log::warn!(
1422 target: crate::LOG_TARGET,
1423 "Maximum freeze of {:?} ({:?}) is greater than the frozen balance {:?}",
1424 who,
1425 max_locks,
1426 frozen
1427 );
1428 Err("bad freezes".into())
1429 } else {
1430 Ok(())
1431 }
1432 })
1433 }
1434 }
1435}