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, RuntimeDebug, 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 type FreezeIdentifier = Self::RuntimeFreezeReason;
239
240 type DustRemoval = ();
241
242 type MaxLocks = ConstU32<100>;
243 type MaxReserves = ConstU32<100>;
244 type MaxFreezes = VariantCountOf<Self::RuntimeFreezeReason>;
245
246 type WeightInfo = ();
247 type DoneSlashHandler = ();
248 }
249 }
250
251 #[pallet::config(with_default)]
252 pub trait Config<I: 'static = ()>: frame_system::Config {
253 #[pallet::no_default_bounds]
255 type RuntimeEvent: From<Event<Self, I>>
256 + IsType<<Self as frame_system::Config>::RuntimeEvent>;
257
258 #[pallet::no_default_bounds]
260 type RuntimeHoldReason: Parameter + Member + MaxEncodedLen + Copy + VariantCount;
261
262 #[pallet::no_default_bounds]
264 type RuntimeFreezeReason: VariantCount;
265
266 type WeightInfo: WeightInfo;
268
269 type Balance: Parameter
271 + Member
272 + AtLeast32BitUnsigned
273 + Codec
274 + HasCompact<Type: DecodeWithMemTracking>
275 + Default
276 + Copy
277 + MaybeSerializeDeserialize
278 + Debug
279 + MaxEncodedLen
280 + TypeInfo
281 + FixedPointOperand;
282
283 #[pallet::no_default_bounds]
285 type DustRemoval: OnUnbalanced<CreditOf<Self, I>>;
286
287 #[pallet::constant]
296 #[pallet::no_default_bounds]
297 type ExistentialDeposit: Get<Self::Balance>;
298
299 #[pallet::no_default]
301 type AccountStore: StoredMap<Self::AccountId, AccountData<Self::Balance>>;
302
303 type ReserveIdentifier: Parameter + Member + MaxEncodedLen + Ord + Copy;
307
308 type FreezeIdentifier: Parameter + Member + MaxEncodedLen + Copy;
310
311 #[pallet::constant]
316 type MaxLocks: Get<u32>;
317
318 #[pallet::constant]
322 type MaxReserves: Get<u32>;
323
324 #[pallet::constant]
326 type MaxFreezes: Get<u32>;
327
328 type DoneSlashHandler: fungible::hold::DoneSlash<
331 Self::RuntimeHoldReason,
332 Self::AccountId,
333 Self::Balance,
334 >;
335 }
336
337 const STORAGE_VERSION: frame_support::traits::StorageVersion =
339 frame_support::traits::StorageVersion::new(1);
340
341 #[pallet::pallet]
342 #[pallet::storage_version(STORAGE_VERSION)]
343 pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
344
345 #[pallet::event]
346 #[pallet::generate_deposit(pub(super) fn deposit_event)]
347 pub enum Event<T: Config<I>, I: 'static = ()> {
348 Endowed { account: T::AccountId, free_balance: T::Balance },
350 DustLost { account: T::AccountId, amount: T::Balance },
353 Transfer { from: T::AccountId, to: T::AccountId, amount: T::Balance },
355 BalanceSet { who: T::AccountId, free: T::Balance },
357 Reserved { who: T::AccountId, amount: T::Balance },
359 Unreserved { who: T::AccountId, amount: T::Balance },
361 ReserveRepatriated {
364 from: T::AccountId,
365 to: T::AccountId,
366 amount: T::Balance,
367 destination_status: Status,
368 },
369 Deposit { who: T::AccountId, amount: T::Balance },
371 Withdraw { who: T::AccountId, amount: T::Balance },
373 Slashed { who: T::AccountId, amount: T::Balance },
375 Minted { who: T::AccountId, amount: T::Balance },
377 Burned { who: T::AccountId, amount: T::Balance },
379 Suspended { who: T::AccountId, amount: T::Balance },
381 Restored { who: T::AccountId, amount: T::Balance },
383 Upgraded { who: T::AccountId },
385 Issued { amount: T::Balance },
387 Rescinded { amount: T::Balance },
389 Locked { who: T::AccountId, amount: T::Balance },
391 Unlocked { who: T::AccountId, amount: T::Balance },
393 Frozen { who: T::AccountId, amount: T::Balance },
395 Thawed { who: T::AccountId, amount: T::Balance },
397 TotalIssuanceForced { old: T::Balance, new: T::Balance },
399 }
400
401 #[pallet::error]
402 pub enum Error<T, I = ()> {
403 VestingBalance,
405 LiquidityRestrictions,
407 InsufficientBalance,
409 ExistentialDeposit,
411 Expendability,
413 ExistingVestingSchedule,
415 DeadAccount,
417 TooManyReserves,
419 TooManyHolds,
421 TooManyFreezes,
423 IssuanceDeactivated,
425 DeltaZero,
427 }
428
429 #[pallet::storage]
431 #[pallet::whitelist_storage]
432 pub type TotalIssuance<T: Config<I>, I: 'static = ()> = StorageValue<_, T::Balance, ValueQuery>;
433
434 #[pallet::storage]
436 #[pallet::whitelist_storage]
437 pub type InactiveIssuance<T: Config<I>, I: 'static = ()> =
438 StorageValue<_, T::Balance, ValueQuery>;
439
440 #[pallet::storage]
465 pub type Account<T: Config<I>, I: 'static = ()> =
466 StorageMap<_, Blake2_128Concat, T::AccountId, AccountData<T::Balance>, ValueQuery>;
467
468 #[pallet::storage]
473 pub type Locks<T: Config<I>, I: 'static = ()> = StorageMap<
474 _,
475 Blake2_128Concat,
476 T::AccountId,
477 WeakBoundedVec<BalanceLock<T::Balance>, T::MaxLocks>,
478 ValueQuery,
479 >;
480
481 #[pallet::storage]
485 pub type Reserves<T: Config<I>, I: 'static = ()> = StorageMap<
486 _,
487 Blake2_128Concat,
488 T::AccountId,
489 BoundedVec<ReserveData<T::ReserveIdentifier, T::Balance>, T::MaxReserves>,
490 ValueQuery,
491 >;
492
493 #[pallet::storage]
495 pub type Holds<T: Config<I>, I: 'static = ()> = StorageMap<
496 _,
497 Blake2_128Concat,
498 T::AccountId,
499 BoundedVec<
500 IdAmount<T::RuntimeHoldReason, T::Balance>,
501 VariantCountOf<T::RuntimeHoldReason>,
502 >,
503 ValueQuery,
504 >;
505
506 #[pallet::storage]
508 pub type Freezes<T: Config<I>, I: 'static = ()> = StorageMap<
509 _,
510 Blake2_128Concat,
511 T::AccountId,
512 BoundedVec<IdAmount<T::FreezeIdentifier, T::Balance>, T::MaxFreezes>,
513 ValueQuery,
514 >;
515
516 #[pallet::genesis_config]
517 pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
518 pub balances: Vec<(T::AccountId, T::Balance)>,
519 pub dev_accounts: Option<(u32, T::Balance, Option<String>)>,
526 }
527
528 impl<T: Config<I>, I: 'static> Default for GenesisConfig<T, I> {
529 fn default() -> Self {
530 Self { balances: Default::default(), dev_accounts: None }
531 }
532 }
533
534 #[pallet::genesis_build]
535 impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
536 fn build(&self) {
537 let total = self.balances.iter().fold(Zero::zero(), |acc: T::Balance, &(_, n)| acc + n);
538
539 <TotalIssuance<T, I>>::put(total);
540
541 for (_, balance) in &self.balances {
542 assert!(
543 *balance >= <T as Config<I>>::ExistentialDeposit::get(),
544 "the balance of any account should always be at least the existential deposit.",
545 )
546 }
547
548 let endowed_accounts = self
550 .balances
551 .iter()
552 .map(|(x, _)| x)
553 .cloned()
554 .collect::<alloc::collections::btree_set::BTreeSet<_>>();
555
556 assert!(
557 endowed_accounts.len() == self.balances.len(),
558 "duplicate balances in genesis."
559 );
560
561 if let Some((num_accounts, balance, ref derivation)) = self.dev_accounts {
563 Pallet::<T, I>::derive_dev_account(
565 num_accounts,
566 balance,
567 derivation.as_deref().unwrap_or(DEFAULT_ADDRESS_URI),
568 );
569 }
570 for &(ref who, free) in self.balances.iter() {
571 frame_system::Pallet::<T>::inc_providers(who);
572 assert!(T::AccountStore::insert(who, AccountData { free, ..Default::default() })
573 .is_ok());
574 }
575 }
576 }
577
578 #[pallet::hooks]
579 impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
580 fn integrity_test() {
581 #[cfg(not(feature = "insecure_zero_ed"))]
582 assert!(
583 !<T as Config<I>>::ExistentialDeposit::get().is_zero(),
584 "The existential deposit must be greater than zero!"
585 );
586
587 assert!(
588 T::MaxFreezes::get() >= <T::RuntimeFreezeReason as VariantCount>::VARIANT_COUNT,
589 "MaxFreezes should be greater than or equal to the number of freeze reasons: {} < {}",
590 T::MaxFreezes::get(), <T::RuntimeFreezeReason as VariantCount>::VARIANT_COUNT,
591 );
592 }
593
594 #[cfg(feature = "try-runtime")]
595 fn try_state(_n: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
596 Holds::<T, I>::iter_keys().try_for_each(|k| {
597 if Holds::<T, I>::decode_len(k).unwrap_or(0) >
598 T::RuntimeHoldReason::VARIANT_COUNT as usize
599 {
600 Err("Found `Hold` with too many elements")
601 } else {
602 Ok(())
603 }
604 })?;
605
606 Freezes::<T, I>::iter_keys().try_for_each(|k| {
607 if Freezes::<T, I>::decode_len(k).unwrap_or(0) > T::MaxFreezes::get() as usize {
608 Err("Found `Freeze` with too many elements")
609 } else {
610 Ok(())
611 }
612 })?;
613
614 Ok(())
615 }
616 }
617
618 #[pallet::call(weight(<T as Config<I>>::WeightInfo))]
619 impl<T: Config<I>, I: 'static> Pallet<T, I> {
620 #[pallet::call_index(0)]
628 pub fn transfer_allow_death(
629 origin: OriginFor<T>,
630 dest: AccountIdLookupOf<T>,
631 #[pallet::compact] value: T::Balance,
632 ) -> DispatchResult {
633 let source = ensure_signed(origin)?;
634 let dest = T::Lookup::lookup(dest)?;
635 <Self as fungible::Mutate<_>>::transfer(&source, &dest, value, Expendable)?;
636 Ok(())
637 }
638
639 #[pallet::call_index(2)]
642 pub fn force_transfer(
643 origin: OriginFor<T>,
644 source: AccountIdLookupOf<T>,
645 dest: AccountIdLookupOf<T>,
646 #[pallet::compact] value: T::Balance,
647 ) -> DispatchResult {
648 ensure_root(origin)?;
649 let source = T::Lookup::lookup(source)?;
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(3)]
662 pub fn transfer_keep_alive(
663 origin: OriginFor<T>,
664 dest: AccountIdLookupOf<T>,
665 #[pallet::compact] value: T::Balance,
666 ) -> DispatchResult {
667 let source = ensure_signed(origin)?;
668 let dest = T::Lookup::lookup(dest)?;
669 <Self as fungible::Mutate<_>>::transfer(&source, &dest, value, Preserve)?;
670 Ok(())
671 }
672
673 #[pallet::call_index(4)]
689 pub fn transfer_all(
690 origin: OriginFor<T>,
691 dest: AccountIdLookupOf<T>,
692 keep_alive: bool,
693 ) -> DispatchResult {
694 let transactor = ensure_signed(origin)?;
695 let keep_alive = if keep_alive { Preserve } else { Expendable };
696 let reducible_balance = <Self as fungible::Inspect<_>>::reducible_balance(
697 &transactor,
698 keep_alive,
699 Fortitude::Polite,
700 );
701 let dest = T::Lookup::lookup(dest)?;
702 <Self as fungible::Mutate<_>>::transfer(
703 &transactor,
704 &dest,
705 reducible_balance,
706 keep_alive,
707 )?;
708 Ok(())
709 }
710
711 #[pallet::call_index(5)]
715 pub fn force_unreserve(
716 origin: OriginFor<T>,
717 who: AccountIdLookupOf<T>,
718 amount: T::Balance,
719 ) -> DispatchResult {
720 ensure_root(origin)?;
721 let who = T::Lookup::lookup(who)?;
722 let _leftover = <Self as ReservableCurrency<_>>::unreserve(&who, amount);
723 Ok(())
724 }
725
726 #[pallet::call_index(6)]
735 #[pallet::weight(T::WeightInfo::upgrade_accounts(who.len() as u32))]
736 pub fn upgrade_accounts(
737 origin: OriginFor<T>,
738 who: Vec<T::AccountId>,
739 ) -> DispatchResultWithPostInfo {
740 ensure_signed(origin)?;
741 if who.is_empty() {
742 return Ok(Pays::Yes.into())
743 }
744 let mut upgrade_count = 0;
745 for i in &who {
746 let upgraded = Self::ensure_upgraded(i);
747 if upgraded {
748 upgrade_count.saturating_inc();
749 }
750 }
751 let proportion_upgraded = Perbill::from_rational(upgrade_count, who.len() as u32);
752 if proportion_upgraded >= Perbill::from_percent(90) {
753 Ok(Pays::No.into())
754 } else {
755 Ok(Pays::Yes.into())
756 }
757 }
758
759 #[pallet::call_index(8)]
763 #[pallet::weight(
764 T::WeightInfo::force_set_balance_creating() .max(T::WeightInfo::force_set_balance_killing()) )]
767 pub fn force_set_balance(
768 origin: OriginFor<T>,
769 who: AccountIdLookupOf<T>,
770 #[pallet::compact] new_free: T::Balance,
771 ) -> DispatchResult {
772 ensure_root(origin)?;
773 let who = T::Lookup::lookup(who)?;
774 let existential_deposit = Self::ed();
775
776 let wipeout = new_free < existential_deposit;
777 let new_free = if wipeout { Zero::zero() } else { new_free };
778
779 let old_free = Self::mutate_account_handling_dust(&who, |account| {
781 let old_free = account.free;
782 account.free = new_free;
783 old_free
784 })?;
785
786 if new_free > old_free {
789 mem::drop(PositiveImbalance::<T, I>::new(new_free - old_free));
790 } else if new_free < old_free {
791 mem::drop(NegativeImbalance::<T, I>::new(old_free - new_free));
792 }
793
794 Self::deposit_event(Event::BalanceSet { who, free: new_free });
795 Ok(())
796 }
797
798 #[doc = docify::embed!("./src/tests/dispatchable_tests.rs", force_adjust_total_issuance_example)]
804 #[pallet::call_index(9)]
805 #[pallet::weight(T::WeightInfo::force_adjust_total_issuance())]
806 pub fn force_adjust_total_issuance(
807 origin: OriginFor<T>,
808 direction: AdjustmentDirection,
809 #[pallet::compact] delta: T::Balance,
810 ) -> DispatchResult {
811 ensure_root(origin)?;
812
813 ensure!(delta > Zero::zero(), Error::<T, I>::DeltaZero);
814
815 let old = TotalIssuance::<T, I>::get();
816 let new = match direction {
817 AdjustmentDirection::Increase => old.saturating_add(delta),
818 AdjustmentDirection::Decrease => old.saturating_sub(delta),
819 };
820
821 ensure!(InactiveIssuance::<T, I>::get() <= new, Error::<T, I>::IssuanceDeactivated);
822 TotalIssuance::<T, I>::set(new);
823
824 Self::deposit_event(Event::<T, I>::TotalIssuanceForced { old, new });
825
826 Ok(())
827 }
828
829 #[pallet::call_index(10)]
837 #[pallet::weight(if *keep_alive {T::WeightInfo::burn_allow_death() } else {T::WeightInfo::burn_keep_alive()})]
838 pub fn burn(
839 origin: OriginFor<T>,
840 #[pallet::compact] value: T::Balance,
841 keep_alive: bool,
842 ) -> DispatchResult {
843 let source = ensure_signed(origin)?;
844 let preservation = if keep_alive { Preserve } else { Expendable };
845 <Self as fungible::Mutate<_>>::burn_from(
846 &source,
847 value,
848 preservation,
849 Precision::Exact,
850 Polite,
851 )?;
852 Ok(())
853 }
854 }
855
856 impl<T: Config<I>, I: 'static> Pallet<T, I> {
857 pub fn total_issuance() -> T::Balance {
859 TotalIssuance::<T, I>::get()
860 }
861
862 pub fn inactive_issuance() -> T::Balance {
864 InactiveIssuance::<T, I>::get()
865 }
866
867 pub fn locks(who: &T::AccountId) -> WeakBoundedVec<BalanceLock<T::Balance>, T::MaxLocks> {
869 Locks::<T, I>::get(who)
870 }
871
872 pub fn reserves(
874 who: &T::AccountId,
875 ) -> BoundedVec<ReserveData<T::ReserveIdentifier, T::Balance>, T::MaxReserves> {
876 Reserves::<T, I>::get(who)
877 }
878
879 fn ed() -> T::Balance {
880 T::ExistentialDeposit::get()
881 }
882 pub fn ensure_upgraded(who: &T::AccountId) -> bool {
886 let mut a = T::AccountStore::get(who);
887 if a.flags.is_new_logic() {
888 return false
889 }
890 a.flags.set_new_logic();
891 if !a.reserved.is_zero() && a.frozen.is_zero() {
892 if system::Pallet::<T>::providers(who) == 0 {
893 log::warn!(
897 target: LOG_TARGET,
898 "account with a non-zero reserve balance has no provider refs, account_id: '{:?}'.",
899 who
900 );
901 a.free = a.free.max(Self::ed());
902 system::Pallet::<T>::inc_providers(who);
903 }
904 let _ = system::Pallet::<T>::inc_consumers_without_limit(who).defensive();
905 }
906 let _ = T::AccountStore::try_mutate_exists(who, |account| -> DispatchResult {
908 *account = Some(a);
909 Ok(())
910 });
911 Self::deposit_event(Event::Upgraded { who: who.clone() });
912 return true
913 }
914
915 pub fn free_balance(who: impl core::borrow::Borrow<T::AccountId>) -> T::Balance {
917 Self::account(who.borrow()).free
918 }
919
920 pub fn usable_balance(who: impl core::borrow::Borrow<T::AccountId>) -> T::Balance {
923 <Self as fungible::Inspect<_>>::reducible_balance(who.borrow(), Expendable, Polite)
924 }
925
926 pub fn usable_balance_for_fees(who: impl core::borrow::Borrow<T::AccountId>) -> T::Balance {
931 <Self as fungible::Inspect<_>>::reducible_balance(who.borrow(), Protect, Polite)
932 }
933
934 pub fn reserved_balance(who: impl core::borrow::Borrow<T::AccountId>) -> T::Balance {
936 Self::account(who.borrow()).reserved
937 }
938
939 pub(crate) fn account(who: &T::AccountId) -> AccountData<T::Balance> {
941 T::AccountStore::get(who)
942 }
943
944 pub(crate) fn mutate_account_handling_dust<R>(
956 who: &T::AccountId,
957 f: impl FnOnce(&mut AccountData<T::Balance>) -> R,
958 ) -> Result<R, DispatchError> {
959 let (r, maybe_dust) = Self::mutate_account(who, f)?;
960 if let Some(dust) = maybe_dust {
961 <Self as fungible::Unbalanced<_>>::handle_raw_dust(dust);
962 }
963 Ok(r)
964 }
965
966 pub(crate) fn try_mutate_account_handling_dust<R, E: From<DispatchError>>(
978 who: &T::AccountId,
979 f: impl FnOnce(&mut AccountData<T::Balance>, bool) -> Result<R, E>,
980 ) -> Result<R, E> {
981 let (r, maybe_dust) = Self::try_mutate_account(who, f)?;
982 if let Some(dust) = maybe_dust {
983 <Self as fungible::Unbalanced<_>>::handle_raw_dust(dust);
984 }
985 Ok(r)
986 }
987
988 pub(crate) fn mutate_account<R>(
1001 who: &T::AccountId,
1002 f: impl FnOnce(&mut AccountData<T::Balance>) -> R,
1003 ) -> Result<(R, Option<T::Balance>), DispatchError> {
1004 Self::try_mutate_account(who, |a, _| -> Result<R, DispatchError> { Ok(f(a)) })
1005 }
1006
1007 #[cfg(not(feature = "insecure_zero_ed"))]
1010 fn have_providers_or_no_zero_ed(_: &T::AccountId) -> bool {
1011 true
1012 }
1013
1014 #[cfg(feature = "insecure_zero_ed")]
1017 fn have_providers_or_no_zero_ed(who: &T::AccountId) -> bool {
1018 frame_system::Pallet::<T>::providers(who) > 0
1019 }
1020
1021 pub(crate) fn try_mutate_account<R, E: From<DispatchError>>(
1035 who: &T::AccountId,
1036 f: impl FnOnce(&mut AccountData<T::Balance>, bool) -> Result<R, E>,
1037 ) -> Result<(R, Option<T::Balance>), E> {
1038 Self::ensure_upgraded(who);
1039 let result = T::AccountStore::try_mutate_exists(who, |maybe_account| {
1040 let is_new = maybe_account.is_none();
1041 let mut account = maybe_account.take().unwrap_or_default();
1042 let did_provide =
1043 account.free >= Self::ed() && Self::have_providers_or_no_zero_ed(who);
1044 let did_consume =
1045 !is_new && (!account.reserved.is_zero() || !account.frozen.is_zero());
1046
1047 let result = f(&mut account, is_new)?;
1048
1049 let does_provide = account.free >= Self::ed();
1050 let does_consume = !account.reserved.is_zero() || !account.frozen.is_zero();
1051
1052 if !did_provide && does_provide {
1053 frame_system::Pallet::<T>::inc_providers(who);
1054 }
1055 if did_consume && !does_consume {
1056 frame_system::Pallet::<T>::dec_consumers(who);
1057 }
1058 if !did_consume && does_consume {
1059 frame_system::Pallet::<T>::inc_consumers(who)?;
1060 }
1061 if does_consume && frame_system::Pallet::<T>::consumers(who) == 0 {
1062 log::error!(target: LOG_TARGET, "Defensively bumping a consumer ref.");
1066 frame_system::Pallet::<T>::inc_consumers(who)?;
1067 }
1068 if did_provide && !does_provide {
1069 frame_system::Pallet::<T>::dec_providers(who).inspect_err(|_| {
1071 if did_consume && !does_consume {
1073 let _ = frame_system::Pallet::<T>::inc_consumers(who).defensive();
1074 }
1075 if !did_consume && does_consume {
1076 let _ = frame_system::Pallet::<T>::dec_consumers(who);
1077 }
1078 })?;
1079 }
1080
1081 let maybe_endowed = if is_new { Some(account.free) } else { None };
1082
1083 let ed = Self::ed();
1095 let maybe_dust = if account.free < ed && account.reserved.is_zero() {
1096 if account.free.is_zero() {
1097 None
1098 } else {
1099 Some(account.free)
1100 }
1101 } else {
1102 assert!(
1103 account.free.is_zero() || account.free >= ed || !account.reserved.is_zero()
1104 );
1105 *maybe_account = Some(account);
1106 None
1107 };
1108 Ok((maybe_endowed, maybe_dust, result))
1109 });
1110 result.map(|(maybe_endowed, maybe_dust, result)| {
1111 if let Some(endowed) = maybe_endowed {
1112 Self::deposit_event(Event::Endowed {
1113 account: who.clone(),
1114 free_balance: endowed,
1115 });
1116 }
1117 if let Some(amount) = maybe_dust {
1118 Pallet::<T, I>::deposit_event(Event::DustLost { account: who.clone(), amount });
1119 }
1120 (result, maybe_dust)
1121 })
1122 }
1123
1124 pub(crate) fn update_locks(who: &T::AccountId, locks: &[BalanceLock<T::Balance>]) {
1126 let bounded_locks = WeakBoundedVec::<_, T::MaxLocks>::force_from(
1127 locks.to_vec(),
1128 Some("Balances Update Locks"),
1129 );
1130
1131 if locks.len() as u32 > T::MaxLocks::get() {
1132 log::warn!(
1133 target: LOG_TARGET,
1134 "Warning: A user has more currency locks than expected. \
1135 A runtime configuration adjustment may be needed."
1136 );
1137 }
1138 let freezes = Freezes::<T, I>::get(who);
1139 let mut prev_frozen = Zero::zero();
1140 let mut after_frozen = Zero::zero();
1141 let res = Self::mutate_account(who, |b| {
1144 prev_frozen = b.frozen;
1145 b.frozen = Zero::zero();
1146 for l in locks.iter() {
1147 b.frozen = b.frozen.max(l.amount);
1148 }
1149 for l in freezes.iter() {
1150 b.frozen = b.frozen.max(l.amount);
1151 }
1152 after_frozen = b.frozen;
1153 });
1154 debug_assert!(res.is_ok());
1155 if let Ok((_, maybe_dust)) = res {
1156 debug_assert!(maybe_dust.is_none(), "Not altering main balance; qed");
1157 }
1158
1159 match locks.is_empty() {
1160 true => Locks::<T, I>::remove(who),
1161 false => Locks::<T, I>::insert(who, bounded_locks),
1162 }
1163
1164 if prev_frozen > after_frozen {
1165 let amount = prev_frozen.saturating_sub(after_frozen);
1166 Self::deposit_event(Event::Unlocked { who: who.clone(), amount });
1167 } else if after_frozen > prev_frozen {
1168 let amount = after_frozen.saturating_sub(prev_frozen);
1169 Self::deposit_event(Event::Locked { who: who.clone(), amount });
1170 }
1171 }
1172
1173 pub(crate) fn update_freezes(
1175 who: &T::AccountId,
1176 freezes: BoundedSlice<IdAmount<T::FreezeIdentifier, T::Balance>, T::MaxFreezes>,
1177 ) -> DispatchResult {
1178 let mut prev_frozen = Zero::zero();
1179 let mut after_frozen = Zero::zero();
1180 let (_, maybe_dust) = Self::mutate_account(who, |b| {
1181 prev_frozen = b.frozen;
1182 b.frozen = Zero::zero();
1183 for l in Locks::<T, I>::get(who).iter() {
1184 b.frozen = b.frozen.max(l.amount);
1185 }
1186 for l in freezes.iter() {
1187 b.frozen = b.frozen.max(l.amount);
1188 }
1189 after_frozen = b.frozen;
1190 })?;
1191 debug_assert!(maybe_dust.is_none(), "Not altering main balance; qed");
1192 if freezes.is_empty() {
1193 Freezes::<T, I>::remove(who);
1194 } else {
1195 Freezes::<T, I>::insert(who, freezes);
1196 }
1197 if prev_frozen > after_frozen {
1198 let amount = prev_frozen.saturating_sub(after_frozen);
1199 Self::deposit_event(Event::Thawed { who: who.clone(), amount });
1200 } else if after_frozen > prev_frozen {
1201 let amount = after_frozen.saturating_sub(prev_frozen);
1202 Self::deposit_event(Event::Frozen { who: who.clone(), amount });
1203 }
1204 Ok(())
1205 }
1206
1207 pub(crate) fn do_transfer_reserved(
1214 slashed: &T::AccountId,
1215 beneficiary: &T::AccountId,
1216 value: T::Balance,
1217 precision: Precision,
1218 fortitude: Fortitude,
1219 status: Status,
1220 ) -> Result<T::Balance, DispatchError> {
1221 if value.is_zero() {
1222 return Ok(Zero::zero())
1223 }
1224
1225 let max = <Self as fungible::InspectHold<_>>::reducible_total_balance_on_hold(
1226 slashed, fortitude,
1227 );
1228 let actual = match precision {
1229 Precision::BestEffort => value.min(max),
1230 Precision::Exact => value,
1231 };
1232 ensure!(actual <= max, TokenError::FundsUnavailable);
1233 if slashed == beneficiary {
1234 return match status {
1235 Status::Free => Ok(actual.saturating_sub(Self::unreserve(slashed, actual))),
1236 Status::Reserved => Ok(actual),
1237 }
1238 }
1239
1240 let ((_, maybe_dust_1), maybe_dust_2) = Self::try_mutate_account(
1241 beneficiary,
1242 |to_account, is_new| -> Result<((), Option<T::Balance>), DispatchError> {
1243 ensure!(!is_new, Error::<T, I>::DeadAccount);
1244 Self::try_mutate_account(slashed, |from_account, _| -> DispatchResult {
1245 match status {
1246 Status::Free =>
1247 to_account.free = to_account
1248 .free
1249 .checked_add(&actual)
1250 .ok_or(ArithmeticError::Overflow)?,
1251 Status::Reserved =>
1252 to_account.reserved = to_account
1253 .reserved
1254 .checked_add(&actual)
1255 .ok_or(ArithmeticError::Overflow)?,
1256 }
1257 from_account.reserved.saturating_reduce(actual);
1258 Ok(())
1259 })
1260 },
1261 )?;
1262
1263 if let Some(dust) = maybe_dust_1 {
1264 <Self as fungible::Unbalanced<_>>::handle_raw_dust(dust);
1265 }
1266 if let Some(dust) = maybe_dust_2 {
1267 <Self as fungible::Unbalanced<_>>::handle_raw_dust(dust);
1268 }
1269
1270 Self::deposit_event(Event::ReserveRepatriated {
1271 from: slashed.clone(),
1272 to: beneficiary.clone(),
1273 amount: actual,
1274 destination_status: status,
1275 });
1276 Ok(actual)
1277 }
1278
1279 pub fn derive_dev_account(num_accounts: u32, balance: T::Balance, derivation: &str) {
1281 assert!(num_accounts > 0, "num_accounts must be greater than zero");
1283
1284 assert!(
1285 balance >= <T as Config<I>>::ExistentialDeposit::get(),
1286 "the balance of any account should always be at least the existential deposit.",
1287 );
1288
1289 assert!(
1290 derivation.contains("{}"),
1291 "Invalid derivation, expected `{{}}` as part of the derivation"
1292 );
1293
1294 for index in 0..num_accounts {
1295 let derivation_string = derivation.replace("{}", &index.to_string());
1297
1298 let pair: SrPair = Pair::from_string(&derivation_string, None)
1300 .expect(&format!("Failed to parse derivation string: {derivation_string}"));
1301
1302 let who = T::AccountId::decode(&mut &pair.public().encode()[..])
1304 .expect(&format!("Failed to decode public key from pair: {:?}", pair.public()));
1305
1306 Self::mutate_account_handling_dust(&who, |account| {
1308 account.free = balance;
1309 })
1310 .expect(&format!("Failed to add account to keystore: {:?}", who));
1311 }
1312 }
1313 }
1314}