1#![recursion_limit = "1024"]
151#![cfg_attr(not(feature = "std"), no_std)]
153
154#[cfg(feature = "runtime-benchmarks")]
155pub mod benchmarking;
156pub mod migration;
157#[cfg(test)]
158pub mod mock;
159#[cfg(test)]
160mod tests;
161pub mod weights;
162
163mod extra_mutator;
164pub use extra_mutator::*;
165mod functions;
166mod impl_fungibles;
167mod impl_stored_map;
168mod types;
169pub use types::*;
170
171extern crate alloc;
172extern crate core;
173
174use scale_info::TypeInfo;
175use sp_runtime::{
176 traits::{AtLeast32BitUnsigned, CheckedAdd, CheckedSub, Saturating, StaticLookup, Zero},
177 ArithmeticError, DispatchError, TokenError,
178};
179
180use alloc::vec::Vec;
181use core::{fmt::Debug, marker::PhantomData};
182use frame_support::{
183 dispatch::DispatchResult,
184 ensure,
185 pallet_prelude::DispatchResultWithPostInfo,
186 storage::KeyPrefixIterator,
187 traits::{
188 tokens::{
189 fungibles, DepositConsequence, Fortitude,
190 Preservation::{Expendable, Preserve},
191 WithdrawConsequence,
192 },
193 BalanceStatus::Reserved,
194 Currency, EnsureOriginWithArg, Incrementable, ReservableCurrency, StoredMap,
195 },
196};
197use frame_system::Config as SystemConfig;
198
199pub use pallet::*;
200pub use weights::WeightInfo;
201
202type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
203const LOG_TARGET: &str = "runtime::assets";
204
205pub trait AssetsCallback<AssetId, AccountId> {
207 fn created(_id: &AssetId, _owner: &AccountId) -> Result<(), ()> {
209 Ok(())
210 }
211
212 fn destroyed(_id: &AssetId) -> Result<(), ()> {
214 Ok(())
215 }
216}
217
218#[impl_trait_for_tuples::impl_for_tuples(10)]
219impl<AssetId, AccountId> AssetsCallback<AssetId, AccountId> for Tuple {
220 fn created(id: &AssetId, owner: &AccountId) -> Result<(), ()> {
221 for_tuples!( #( Tuple::created(id, owner)?; )* );
222 Ok(())
223 }
224
225 fn destroyed(id: &AssetId) -> Result<(), ()> {
226 for_tuples!( #( Tuple::destroyed(id)?; )* );
227 Ok(())
228 }
229}
230
231pub struct AutoIncAssetId<T, I = ()>(PhantomData<(T, I)>);
235impl<T: Config<I>, I> AssetsCallback<T::AssetId, T::AccountId> for AutoIncAssetId<T, I>
236where
237 T::AssetId: Incrementable,
238{
239 fn created(_: &T::AssetId, _: &T::AccountId) -> Result<(), ()> {
240 let Some(next_id) = NextAssetId::<T, I>::get() else {
241 return Ok(());
243 };
244 let next_id = next_id.increment().ok_or(())?;
245 NextAssetId::<T, I>::put(next_id);
246 Ok(())
247 }
248}
249
250#[frame_support::pallet]
251pub mod pallet {
252 use super::*;
253 use codec::HasCompact;
254 use frame_support::{
255 pallet_prelude::*,
256 traits::{tokens::ProvideAssetReserves, AccountTouch, ContainsPair},
257 };
258 use frame_system::pallet_prelude::*;
259
260 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
262
263 pub const MAX_RESERVES: u32 = 5;
265
266 #[pallet::pallet]
267 #[pallet::storage_version(STORAGE_VERSION)]
268 pub struct Pallet<T, I = ()>(_);
269
270 #[cfg(feature = "runtime-benchmarks")]
271 pub trait BenchmarkHelper<AssetIdParameter, ReserveIdParameter> {
272 fn create_asset_id_parameter(id: u32) -> AssetIdParameter;
273 fn create_reserve_id_parameter(id: u32) -> ReserveIdParameter;
274 }
275 #[cfg(feature = "runtime-benchmarks")]
276 impl<AssetIdParameter: From<u32>> BenchmarkHelper<AssetIdParameter, ()> for () {
277 fn create_asset_id_parameter(id: u32) -> AssetIdParameter {
278 id.into()
279 }
280 fn create_reserve_id_parameter(_: u32) -> () {
281 ()
282 }
283 }
284
285 pub mod config_preludes {
287 use super::*;
288 use frame_support::derive_impl;
289 pub struct TestDefaultConfig;
290
291 #[derive_impl(frame_system::config_preludes::TestDefaultConfig, no_aggregated_types)]
292 impl frame_system::DefaultConfig for TestDefaultConfig {}
293
294 #[frame_support::register_default_impl(TestDefaultConfig)]
295 impl DefaultConfig for TestDefaultConfig {
296 #[inject_runtime_type]
297 type RuntimeEvent = ();
298 type Balance = u64;
299 type RemoveItemsLimit = ConstU32<5>;
300 type AssetId = u32;
301 type AssetIdParameter = u32;
302 type ReserveData = ();
303 type AssetDeposit = ConstUint<1>;
304 type AssetAccountDeposit = ConstUint<10>;
305 type MetadataDepositBase = ConstUint<1>;
306 type MetadataDepositPerByte = ConstUint<1>;
307 type ApprovalDeposit = ConstUint<1>;
308 type StringLimit = ConstU32<50>;
309 type Freezer = ();
310 type Holder = ();
311 type Extra = ();
312 type CallbackHandle = ();
313 type WeightInfo = ();
314 #[cfg(feature = "runtime-benchmarks")]
315 type BenchmarkHelper = ();
316 }
317 }
318
319 #[pallet::config(with_default)]
320 pub trait Config<I: 'static = ()>: frame_system::Config {
322 #[pallet::no_default_bounds]
324 #[allow(deprecated)]
325 type RuntimeEvent: From<Event<Self, I>>
326 + IsType<<Self as frame_system::Config>::RuntimeEvent>;
327
328 type Balance: Member
330 + Parameter
331 + HasCompact<Type: DecodeWithMemTracking>
332 + AtLeast32BitUnsigned
333 + Default
334 + Copy
335 + MaybeSerializeDeserialize
336 + MaxEncodedLen
337 + TypeInfo;
338
339 #[pallet::constant]
343 type RemoveItemsLimit: Get<u32>;
344
345 type AssetId: Member + Parameter + Clone + MaybeSerializeDeserialize + MaxEncodedLen;
347
348 type AssetIdParameter: Parameter + From<Self::AssetId> + Into<Self::AssetId> + MaxEncodedLen;
356
357 type ReserveData: Debug + Parameter + MaybeSerializeDeserialize + MaxEncodedLen;
359
360 #[pallet::no_default]
362 type Currency: ReservableCurrency<Self::AccountId>;
363
364 #[pallet::no_default]
367 type CreateOrigin: EnsureOriginWithArg<
368 Self::RuntimeOrigin,
369 Self::AssetId,
370 Success = Self::AccountId,
371 >;
372
373 #[pallet::no_default]
376 type ForceOrigin: EnsureOrigin<Self::RuntimeOrigin>;
377
378 #[pallet::constant]
380 #[pallet::no_default_bounds]
381 type AssetDeposit: Get<DepositBalanceOf<Self, I>>;
382
383 #[pallet::constant]
386 #[pallet::no_default_bounds]
387 type AssetAccountDeposit: Get<DepositBalanceOf<Self, I>>;
388
389 #[pallet::constant]
391 #[pallet::no_default_bounds]
392 type MetadataDepositBase: Get<DepositBalanceOf<Self, I>>;
393
394 #[pallet::constant]
397 #[pallet::no_default_bounds]
398 type MetadataDepositPerByte: Get<DepositBalanceOf<Self, I>>;
399
400 #[pallet::constant]
402 #[pallet::no_default_bounds]
403 type ApprovalDeposit: Get<DepositBalanceOf<Self, I>>;
404
405 #[pallet::constant]
407 type StringLimit: Get<u32>;
408
409 type Freezer: FrozenBalance<Self::AssetId, Self::AccountId, Self::Balance>;
412
413 type Holder: BalanceOnHold<Self::AssetId, Self::AccountId, Self::Balance>;
416
417 type Extra: Member + Parameter + Default + MaxEncodedLen;
419
420 type CallbackHandle: AssetsCallback<Self::AssetId, Self::AccountId>;
427
428 type WeightInfo: WeightInfo;
430
431 #[cfg(feature = "runtime-benchmarks")]
433 type BenchmarkHelper: BenchmarkHelper<Self::AssetIdParameter, Self::ReserveData>;
434 }
435
436 #[pallet::storage]
437 pub type Asset<T: Config<I>, I: 'static = ()> = StorageMap<
439 _,
440 Blake2_128Concat,
441 T::AssetId,
442 AssetDetails<T::Balance, T::AccountId, DepositBalanceOf<T, I>>,
443 >;
444
445 #[pallet::storage]
446 pub type Account<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
448 _,
449 Blake2_128Concat,
450 T::AssetId,
451 Blake2_128Concat,
452 T::AccountId,
453 AssetAccountOf<T, I>,
454 >;
455
456 #[pallet::storage]
457 pub type Approvals<T: Config<I>, I: 'static = ()> = StorageNMap<
461 _,
462 (
463 NMapKey<Blake2_128Concat, T::AssetId>,
464 NMapKey<Blake2_128Concat, T::AccountId>, NMapKey<Blake2_128Concat, T::AccountId>, ),
467 Approval<T::Balance, DepositBalanceOf<T, I>>,
468 >;
469
470 #[pallet::storage]
471 pub type Metadata<T: Config<I>, I: 'static = ()> = StorageMap<
473 _,
474 Blake2_128Concat,
475 T::AssetId,
476 AssetMetadata<DepositBalanceOf<T, I>, BoundedVec<u8, T::StringLimit>>,
477 ValueQuery,
478 >;
479
480 #[pallet::storage]
482 pub type Reserves<T: Config<I>, I: 'static = ()> = StorageMap<
483 _,
484 Blake2_128Concat,
485 T::AssetId,
486 BoundedVec<T::ReserveData, ConstU32<MAX_RESERVES>>,
487 ValueQuery,
488 >;
489
490 #[pallet::storage]
500 pub type NextAssetId<T: Config<I>, I: 'static = ()> = StorageValue<_, T::AssetId, OptionQuery>;
501
502 #[pallet::genesis_config]
503 #[derive(frame_support::DefaultNoBound)]
504 pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
505 pub assets: Vec<(T::AssetId, T::AccountId, bool, T::Balance)>,
507 pub metadata: Vec<(T::AssetId, Vec<u8>, Vec<u8>, u8)>,
509 pub accounts: Vec<(T::AssetId, T::AccountId, T::Balance)>,
511 pub next_asset_id: Option<T::AssetId>,
518 pub reserves: Vec<(T::AssetId, Vec<T::ReserveData>)>,
520 }
521
522 #[pallet::genesis_build]
523 impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
524 fn build(&self) {
525 for (id, owner, is_sufficient, min_balance) in &self.assets {
526 assert!(!Asset::<T, I>::contains_key(id), "Asset id already in use");
527 assert!(!min_balance.is_zero(), "Min balance should not be zero");
528 Asset::<T, I>::insert(
529 id,
530 AssetDetails {
531 owner: owner.clone(),
532 issuer: owner.clone(),
533 admin: owner.clone(),
534 freezer: owner.clone(),
535 supply: Zero::zero(),
536 deposit: Zero::zero(),
537 min_balance: *min_balance,
538 is_sufficient: *is_sufficient,
539 accounts: 0,
540 sufficients: 0,
541 approvals: 0,
542 status: AssetStatus::Live,
543 },
544 );
545 }
546
547 for (id, name, symbol, decimals) in &self.metadata {
548 assert!(Asset::<T, I>::contains_key(id), "Asset does not exist");
549
550 let bounded_name: BoundedVec<u8, T::StringLimit> =
551 name.clone().try_into().expect("asset name is too long");
552 let bounded_symbol: BoundedVec<u8, T::StringLimit> =
553 symbol.clone().try_into().expect("asset symbol is too long");
554
555 let metadata = AssetMetadata {
556 deposit: Zero::zero(),
557 name: bounded_name,
558 symbol: bounded_symbol,
559 decimals: *decimals,
560 is_frozen: false,
561 };
562 Metadata::<T, I>::insert(id, metadata);
563 }
564
565 for (id, account_id, amount) in &self.accounts {
566 let result = <Pallet<T, I>>::increase_balance(
567 id.clone(),
568 account_id,
569 *amount,
570 |details| -> DispatchResult {
571 debug_assert!(
572 details.supply.checked_add(&amount).is_some(),
573 "checked in prep; qed"
574 );
575 details.supply = details.supply.saturating_add(*amount);
576 Ok(())
577 },
578 );
579 assert!(result.is_ok());
580 }
581
582 if let Some(next_asset_id) = &self.next_asset_id {
583 NextAssetId::<T, I>::put(next_asset_id);
584 }
585
586 for (id, reserves) in &self.reserves {
587 assert!(!Reserves::<T, I>::contains_key(id), "Asset id already in use");
588 let reserves = BoundedVec::try_from(reserves.clone()).expect("too many reserves");
589 Reserves::<T, I>::insert(id, reserves);
590 }
591 }
592 }
593
594 #[pallet::event]
595 #[pallet::generate_deposit(pub(super) fn deposit_event)]
596 pub enum Event<T: Config<I>, I: 'static = ()> {
597 Created { asset_id: T::AssetId, creator: T::AccountId, owner: T::AccountId },
599 Issued { asset_id: T::AssetId, owner: T::AccountId, amount: T::Balance },
601 Transferred {
603 asset_id: T::AssetId,
604 from: T::AccountId,
605 to: T::AccountId,
606 amount: T::Balance,
607 },
608 Burned { asset_id: T::AssetId, owner: T::AccountId, balance: T::Balance },
610 TeamChanged {
612 asset_id: T::AssetId,
613 issuer: T::AccountId,
614 admin: T::AccountId,
615 freezer: T::AccountId,
616 },
617 OwnerChanged { asset_id: T::AssetId, owner: T::AccountId },
619 Frozen { asset_id: T::AssetId, who: T::AccountId },
621 Thawed { asset_id: T::AssetId, who: T::AccountId },
623 AssetFrozen { asset_id: T::AssetId },
625 AssetThawed { asset_id: T::AssetId },
627 AccountsDestroyed { asset_id: T::AssetId, accounts_destroyed: u32, accounts_remaining: u32 },
629 ApprovalsDestroyed {
631 asset_id: T::AssetId,
632 approvals_destroyed: u32,
633 approvals_remaining: u32,
634 },
635 DestructionStarted { asset_id: T::AssetId },
637 Destroyed { asset_id: T::AssetId },
639 ForceCreated { asset_id: T::AssetId, owner: T::AccountId },
641 MetadataSet {
643 asset_id: T::AssetId,
644 name: Vec<u8>,
645 symbol: Vec<u8>,
646 decimals: u8,
647 is_frozen: bool,
648 },
649 MetadataCleared { asset_id: T::AssetId },
651 ApprovedTransfer {
653 asset_id: T::AssetId,
654 source: T::AccountId,
655 delegate: T::AccountId,
656 amount: T::Balance,
657 },
658 ApprovalCancelled { asset_id: T::AssetId, owner: T::AccountId, delegate: T::AccountId },
660 TransferredApproved {
663 asset_id: T::AssetId,
664 owner: T::AccountId,
665 delegate: T::AccountId,
666 destination: T::AccountId,
667 amount: T::Balance,
668 },
669 AssetStatusChanged { asset_id: T::AssetId },
671 AssetMinBalanceChanged { asset_id: T::AssetId, new_min_balance: T::Balance },
673 Touched { asset_id: T::AssetId, who: T::AccountId, depositor: T::AccountId },
675 Blocked { asset_id: T::AssetId, who: T::AccountId },
677 Deposited { asset_id: T::AssetId, who: T::AccountId, amount: T::Balance },
679 Withdrawn { asset_id: T::AssetId, who: T::AccountId, amount: T::Balance },
681 ReservesUpdated { asset_id: T::AssetId, reserves: Vec<T::ReserveData> },
683 ReservesRemoved { asset_id: T::AssetId },
685 IssuedCredit { asset_id: T::AssetId, amount: T::Balance },
687 BurnedCredit { asset_id: T::AssetId, amount: T::Balance },
689 IssuedDebt { asset_id: T::AssetId, amount: T::Balance },
691 BurnedDebt { asset_id: T::AssetId, amount: T::Balance },
693 }
694
695 #[pallet::error]
696 pub enum Error<T, I = ()> {
697 BalanceLow,
699 NoAccount,
701 NoPermission,
703 Unknown,
705 Frozen,
707 InUse,
709 BadWitness,
711 MinBalanceZero,
713 UnavailableConsumer,
717 BadMetadata,
719 Unapproved,
721 WouldDie,
723 AlreadyExists,
725 NoDeposit,
727 WouldBurn,
729 LiveAsset,
732 AssetNotLive,
734 IncorrectStatus,
736 NotFrozen,
738 CallbackFailed,
740 BadAssetId,
742 ContainsFreezes,
744 ContainsHolds,
746 TooManyReserves,
748 }
749
750 #[pallet::hooks]
751 impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
752 #[cfg(feature = "try-runtime")]
753 fn try_state(_n: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
754 Self::do_try_state()
755 }
756 }
757
758 #[pallet::call(weight(<T as Config<I>>::WeightInfo))]
759 impl<T: Config<I>, I: 'static> Pallet<T, I> {
760 #[pallet::call_index(0)]
780 pub fn create(
781 origin: OriginFor<T>,
782 id: T::AssetIdParameter,
783 admin: AccountIdLookupOf<T>,
784 min_balance: T::Balance,
785 ) -> DispatchResult {
786 let id: T::AssetId = id.into();
787 let owner = T::CreateOrigin::ensure_origin(origin, &id)?;
788 let admin = T::Lookup::lookup(admin)?;
789
790 ensure!(!Asset::<T, I>::contains_key(&id), Error::<T, I>::InUse);
791 ensure!(!min_balance.is_zero(), Error::<T, I>::MinBalanceZero);
792
793 if let Some(next_id) = NextAssetId::<T, I>::get() {
794 ensure!(id == next_id, Error::<T, I>::BadAssetId);
795 }
796
797 let deposit = T::AssetDeposit::get();
798 T::Currency::reserve(&owner, deposit)?;
799
800 Asset::<T, I>::insert(
801 id.clone(),
802 AssetDetails {
803 owner: owner.clone(),
804 issuer: admin.clone(),
805 admin: admin.clone(),
806 freezer: admin.clone(),
807 supply: Zero::zero(),
808 deposit,
809 min_balance,
810 is_sufficient: false,
811 accounts: 0,
812 sufficients: 0,
813 approvals: 0,
814 status: AssetStatus::Live,
815 },
816 );
817 ensure!(T::CallbackHandle::created(&id, &owner).is_ok(), Error::<T, I>::CallbackFailed);
818 Self::deposit_event(Event::Created {
819 asset_id: id,
820 creator: owner.clone(),
821 owner: admin,
822 });
823
824 Ok(())
825 }
826
827 #[pallet::call_index(1)]
847 pub fn force_create(
848 origin: OriginFor<T>,
849 id: T::AssetIdParameter,
850 owner: AccountIdLookupOf<T>,
851 is_sufficient: bool,
852 #[pallet::compact] min_balance: T::Balance,
853 ) -> DispatchResult {
854 T::ForceOrigin::ensure_origin(origin)?;
855 let owner = T::Lookup::lookup(owner)?;
856 let id: T::AssetId = id.into();
857 Self::do_force_create(id, owner, is_sufficient, min_balance)
858 }
859
860 #[pallet::call_index(2)]
873 pub fn start_destroy(origin: OriginFor<T>, id: T::AssetIdParameter) -> DispatchResult {
874 let maybe_check_owner = match T::ForceOrigin::try_origin(origin) {
875 Ok(_) => None,
876 Err(origin) => Some(ensure_signed(origin)?),
877 };
878 let id: T::AssetId = id.into();
879 Self::do_start_destroy(id, maybe_check_owner)
880 }
881
882 #[pallet::call_index(3)]
895 #[pallet::weight(T::WeightInfo::destroy_accounts(T::RemoveItemsLimit::get()))]
896 pub fn destroy_accounts(
897 origin: OriginFor<T>,
898 id: T::AssetIdParameter,
899 ) -> DispatchResultWithPostInfo {
900 ensure_signed(origin)?;
901 let id: T::AssetId = id.into();
902 let removed_accounts = Self::do_destroy_accounts(id, T::RemoveItemsLimit::get())?;
903 Ok(Some(T::WeightInfo::destroy_accounts(removed_accounts)).into())
904 }
905
906 #[pallet::call_index(4)]
919 #[pallet::weight(T::WeightInfo::destroy_approvals(T::RemoveItemsLimit::get()))]
920 pub fn destroy_approvals(
921 origin: OriginFor<T>,
922 id: T::AssetIdParameter,
923 ) -> DispatchResultWithPostInfo {
924 ensure_signed(origin)?;
925 let id: T::AssetId = id.into();
926 let removed_approvals = Self::do_destroy_approvals(id, T::RemoveItemsLimit::get())?;
927 Ok(Some(T::WeightInfo::destroy_approvals(removed_approvals)).into())
928 }
929
930 #[pallet::call_index(5)]
941 pub fn finish_destroy(origin: OriginFor<T>, id: T::AssetIdParameter) -> DispatchResult {
942 ensure_signed(origin)?;
943 let id: T::AssetId = id.into();
944 Self::do_finish_destroy(id)
945 }
946
947 #[pallet::call_index(6)]
960 pub fn mint(
961 origin: OriginFor<T>,
962 id: T::AssetIdParameter,
963 beneficiary: AccountIdLookupOf<T>,
964 #[pallet::compact] amount: T::Balance,
965 ) -> DispatchResult {
966 let origin = ensure_signed(origin)?;
967 let beneficiary = T::Lookup::lookup(beneficiary)?;
968 let id: T::AssetId = id.into();
969 Self::do_mint(id, &beneficiary, amount, Some(origin))?;
970 Ok(())
971 }
972
973 #[pallet::call_index(7)]
989 pub fn burn(
990 origin: OriginFor<T>,
991 id: T::AssetIdParameter,
992 who: AccountIdLookupOf<T>,
993 #[pallet::compact] amount: T::Balance,
994 ) -> DispatchResult {
995 let origin = ensure_signed(origin)?;
996 let who = T::Lookup::lookup(who)?;
997 let id: T::AssetId = id.into();
998
999 let f = DebitFlags { keep_alive: false, best_effort: true };
1000 Self::do_burn(id, &who, amount, Some(origin), f)?;
1001 Ok(())
1002 }
1003
1004 #[pallet::call_index(8)]
1023 pub fn transfer(
1024 origin: OriginFor<T>,
1025 id: T::AssetIdParameter,
1026 target: AccountIdLookupOf<T>,
1027 #[pallet::compact] amount: T::Balance,
1028 ) -> DispatchResult {
1029 let origin = ensure_signed(origin)?;
1030 let dest = T::Lookup::lookup(target)?;
1031 let id: T::AssetId = id.into();
1032
1033 let f = TransferFlags { keep_alive: false, best_effort: false, burn_dust: false };
1034 Self::do_transfer(id, &origin, &dest, amount, None, f).map(|_| ())
1035 }
1036
1037 #[pallet::call_index(9)]
1056 pub fn transfer_keep_alive(
1057 origin: OriginFor<T>,
1058 id: T::AssetIdParameter,
1059 target: AccountIdLookupOf<T>,
1060 #[pallet::compact] amount: T::Balance,
1061 ) -> DispatchResult {
1062 let source = ensure_signed(origin)?;
1063 let dest = T::Lookup::lookup(target)?;
1064 let id: T::AssetId = id.into();
1065
1066 let f = TransferFlags { keep_alive: true, best_effort: false, burn_dust: false };
1067 Self::do_transfer(id, &source, &dest, amount, None, f).map(|_| ())
1068 }
1069
1070 #[pallet::call_index(10)]
1090 pub fn force_transfer(
1091 origin: OriginFor<T>,
1092 id: T::AssetIdParameter,
1093 source: AccountIdLookupOf<T>,
1094 dest: AccountIdLookupOf<T>,
1095 #[pallet::compact] amount: T::Balance,
1096 ) -> DispatchResult {
1097 let origin = ensure_signed(origin)?;
1098 let source = T::Lookup::lookup(source)?;
1099 let dest = T::Lookup::lookup(dest)?;
1100 let id: T::AssetId = id.into();
1101
1102 let f = TransferFlags { keep_alive: false, best_effort: false, burn_dust: false };
1103 Self::do_transfer(id, &source, &dest, amount, Some(origin), f).map(|_| ())
1104 }
1105
1106 #[pallet::call_index(11)]
1119 pub fn freeze(
1120 origin: OriginFor<T>,
1121 id: T::AssetIdParameter,
1122 who: AccountIdLookupOf<T>,
1123 ) -> DispatchResult {
1124 let origin = ensure_signed(origin)?;
1125 let id: T::AssetId = id.into();
1126
1127 let d = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
1128 ensure!(
1129 d.status == AssetStatus::Live || d.status == AssetStatus::Frozen,
1130 Error::<T, I>::IncorrectStatus
1131 );
1132 ensure!(origin == d.freezer, Error::<T, I>::NoPermission);
1133 let who = T::Lookup::lookup(who)?;
1134
1135 Account::<T, I>::try_mutate(&id, &who, |maybe_account| -> DispatchResult {
1136 maybe_account.as_mut().ok_or(Error::<T, I>::NoAccount)?.status =
1137 AccountStatus::Frozen;
1138 Ok(())
1139 })?;
1140
1141 Self::deposit_event(Event::<T, I>::Frozen { asset_id: id, who });
1142 Ok(())
1143 }
1144
1145 #[pallet::call_index(12)]
1156 pub fn thaw(
1157 origin: OriginFor<T>,
1158 id: T::AssetIdParameter,
1159 who: AccountIdLookupOf<T>,
1160 ) -> DispatchResult {
1161 let origin = ensure_signed(origin)?;
1162 let id: T::AssetId = id.into();
1163
1164 let details = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
1165 ensure!(
1166 details.status == AssetStatus::Live || details.status == AssetStatus::Frozen,
1167 Error::<T, I>::IncorrectStatus
1168 );
1169 ensure!(origin == details.admin, Error::<T, I>::NoPermission);
1170 let who = T::Lookup::lookup(who)?;
1171
1172 Account::<T, I>::try_mutate(&id, &who, |maybe_account| -> DispatchResult {
1173 maybe_account.as_mut().ok_or(Error::<T, I>::NoAccount)?.status =
1174 AccountStatus::Liquid;
1175 Ok(())
1176 })?;
1177
1178 Self::deposit_event(Event::<T, I>::Thawed { asset_id: id, who });
1179 Ok(())
1180 }
1181
1182 #[pallet::call_index(13)]
1192 pub fn freeze_asset(origin: OriginFor<T>, id: T::AssetIdParameter) -> DispatchResult {
1193 let origin = ensure_signed(origin)?;
1194 let id: T::AssetId = id.into();
1195
1196 Asset::<T, I>::try_mutate(id.clone(), |maybe_details| {
1197 let d = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
1198 ensure!(d.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
1199 ensure!(origin == d.freezer, Error::<T, I>::NoPermission);
1200
1201 d.status = AssetStatus::Frozen;
1202
1203 Self::deposit_event(Event::<T, I>::AssetFrozen { asset_id: id });
1204 Ok(())
1205 })
1206 }
1207
1208 #[pallet::call_index(14)]
1218 pub fn thaw_asset(origin: OriginFor<T>, id: T::AssetIdParameter) -> DispatchResult {
1219 let origin = ensure_signed(origin)?;
1220 let id: T::AssetId = id.into();
1221
1222 Asset::<T, I>::try_mutate(id.clone(), |maybe_details| {
1223 let d = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
1224 ensure!(origin == d.admin, Error::<T, I>::NoPermission);
1225 ensure!(d.status == AssetStatus::Frozen, Error::<T, I>::NotFrozen);
1226
1227 d.status = AssetStatus::Live;
1228
1229 Self::deposit_event(Event::<T, I>::AssetThawed { asset_id: id });
1230 Ok(())
1231 })
1232 }
1233
1234 #[pallet::call_index(15)]
1245 pub fn transfer_ownership(
1246 origin: OriginFor<T>,
1247 id: T::AssetIdParameter,
1248 owner: AccountIdLookupOf<T>,
1249 ) -> DispatchResult {
1250 let origin = ensure_signed(origin)?;
1251 let owner = T::Lookup::lookup(owner)?;
1252 let id: T::AssetId = id.into();
1253
1254 Asset::<T, I>::try_mutate(id.clone(), |maybe_details| {
1255 let details = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
1256 ensure!(details.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
1257 ensure!(origin == details.owner, Error::<T, I>::NoPermission);
1258 if details.owner == owner {
1259 return Ok(());
1260 }
1261
1262 let metadata_deposit = Metadata::<T, I>::get(&id).deposit;
1263 let deposit = details.deposit + metadata_deposit;
1264
1265 T::Currency::repatriate_reserved(&details.owner, &owner, deposit, Reserved)?;
1267
1268 details.owner = owner.clone();
1269
1270 Self::deposit_event(Event::OwnerChanged { asset_id: id, owner });
1271 Ok(())
1272 })
1273 }
1274
1275 #[pallet::call_index(16)]
1288 pub fn set_team(
1289 origin: OriginFor<T>,
1290 id: T::AssetIdParameter,
1291 issuer: AccountIdLookupOf<T>,
1292 admin: AccountIdLookupOf<T>,
1293 freezer: AccountIdLookupOf<T>,
1294 ) -> DispatchResult {
1295 let origin = ensure_signed(origin)?;
1296 let issuer = T::Lookup::lookup(issuer)?;
1297 let admin = T::Lookup::lookup(admin)?;
1298 let freezer = T::Lookup::lookup(freezer)?;
1299 let id: T::AssetId = id.into();
1300
1301 Asset::<T, I>::try_mutate(id.clone(), |maybe_details| {
1302 let details = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
1303 ensure!(details.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
1304 ensure!(origin == details.owner, Error::<T, I>::NoPermission);
1305
1306 details.issuer = issuer.clone();
1307 details.admin = admin.clone();
1308 details.freezer = freezer.clone();
1309
1310 Self::deposit_event(Event::TeamChanged { asset_id: id, issuer, admin, freezer });
1311 Ok(())
1312 })
1313 }
1314
1315 #[pallet::call_index(17)]
1332 #[pallet::weight(T::WeightInfo::set_metadata(name.len() as u32, symbol.len() as u32))]
1333 pub fn set_metadata(
1334 origin: OriginFor<T>,
1335 id: T::AssetIdParameter,
1336 name: Vec<u8>,
1337 symbol: Vec<u8>,
1338 decimals: u8,
1339 ) -> DispatchResult {
1340 let origin = ensure_signed(origin)?;
1341 let id: T::AssetId = id.into();
1342 Self::do_set_metadata(id, &origin, name, symbol, decimals)
1343 }
1344
1345 #[pallet::call_index(18)]
1357 pub fn clear_metadata(origin: OriginFor<T>, id: T::AssetIdParameter) -> DispatchResult {
1358 let origin = ensure_signed(origin)?;
1359 let id: T::AssetId = id.into();
1360
1361 let d = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
1362 ensure!(d.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
1363 ensure!(origin == d.owner, Error::<T, I>::NoPermission);
1364
1365 Metadata::<T, I>::try_mutate_exists(id.clone(), |metadata| {
1366 let deposit = metadata.take().ok_or(Error::<T, I>::Unknown)?.deposit;
1367 T::Currency::unreserve(&d.owner, deposit);
1368 Self::deposit_event(Event::MetadataCleared { asset_id: id });
1369 Ok(())
1370 })
1371 }
1372
1373 #[pallet::call_index(19)]
1388 #[pallet::weight(T::WeightInfo::force_set_metadata(name.len() as u32, symbol.len() as u32))]
1389 pub fn force_set_metadata(
1390 origin: OriginFor<T>,
1391 id: T::AssetIdParameter,
1392 name: Vec<u8>,
1393 symbol: Vec<u8>,
1394 decimals: u8,
1395 is_frozen: bool,
1396 ) -> DispatchResult {
1397 T::ForceOrigin::ensure_origin(origin)?;
1398 let id: T::AssetId = id.into();
1399
1400 let bounded_name: BoundedVec<u8, T::StringLimit> =
1401 name.clone().try_into().map_err(|_| Error::<T, I>::BadMetadata)?;
1402
1403 let bounded_symbol: BoundedVec<u8, T::StringLimit> =
1404 symbol.clone().try_into().map_err(|_| Error::<T, I>::BadMetadata)?;
1405
1406 ensure!(Asset::<T, I>::contains_key(&id), Error::<T, I>::Unknown);
1407 Metadata::<T, I>::try_mutate_exists(id.clone(), |metadata| {
1408 let deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit);
1409 *metadata = Some(AssetMetadata {
1410 deposit,
1411 name: bounded_name,
1412 symbol: bounded_symbol,
1413 decimals,
1414 is_frozen,
1415 });
1416
1417 Self::deposit_event(Event::MetadataSet {
1418 asset_id: id,
1419 name,
1420 symbol,
1421 decimals,
1422 is_frozen,
1423 });
1424 Ok(())
1425 })
1426 }
1427
1428 #[pallet::call_index(20)]
1440 pub fn force_clear_metadata(
1441 origin: OriginFor<T>,
1442 id: T::AssetIdParameter,
1443 ) -> DispatchResult {
1444 T::ForceOrigin::ensure_origin(origin)?;
1445 let id: T::AssetId = id.into();
1446
1447 let d = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
1448 Metadata::<T, I>::try_mutate_exists(id.clone(), |metadata| {
1449 let deposit = metadata.take().ok_or(Error::<T, I>::Unknown)?.deposit;
1450 T::Currency::unreserve(&d.owner, deposit);
1451 Self::deposit_event(Event::MetadataCleared { asset_id: id });
1452 Ok(())
1453 })
1454 }
1455
1456 #[pallet::call_index(21)]
1479 pub fn force_asset_status(
1480 origin: OriginFor<T>,
1481 id: T::AssetIdParameter,
1482 owner: AccountIdLookupOf<T>,
1483 issuer: AccountIdLookupOf<T>,
1484 admin: AccountIdLookupOf<T>,
1485 freezer: AccountIdLookupOf<T>,
1486 #[pallet::compact] min_balance: T::Balance,
1487 is_sufficient: bool,
1488 is_frozen: bool,
1489 ) -> DispatchResult {
1490 T::ForceOrigin::ensure_origin(origin)?;
1491 let id: T::AssetId = id.into();
1492
1493 Asset::<T, I>::try_mutate(id.clone(), |maybe_asset| {
1494 let mut asset = maybe_asset.take().ok_or(Error::<T, I>::Unknown)?;
1495 ensure!(asset.status != AssetStatus::Destroying, Error::<T, I>::AssetNotLive);
1496 asset.owner = T::Lookup::lookup(owner)?;
1497 asset.issuer = T::Lookup::lookup(issuer)?;
1498 asset.admin = T::Lookup::lookup(admin)?;
1499 asset.freezer = T::Lookup::lookup(freezer)?;
1500 asset.min_balance = min_balance;
1501 asset.is_sufficient = is_sufficient;
1502 if is_frozen {
1503 asset.status = AssetStatus::Frozen;
1504 } else {
1505 asset.status = AssetStatus::Live;
1506 }
1507 *maybe_asset = Some(asset);
1508
1509 Self::deposit_event(Event::AssetStatusChanged { asset_id: id });
1510 Ok(())
1511 })
1512 }
1513
1514 #[pallet::call_index(22)]
1535 pub fn approve_transfer(
1536 origin: OriginFor<T>,
1537 id: T::AssetIdParameter,
1538 delegate: AccountIdLookupOf<T>,
1539 #[pallet::compact] amount: T::Balance,
1540 ) -> DispatchResult {
1541 let owner = ensure_signed(origin)?;
1542 let delegate = T::Lookup::lookup(delegate)?;
1543 let id: T::AssetId = id.into();
1544 Self::do_approve_transfer(id, &owner, &delegate, amount)
1545 }
1546
1547 #[pallet::call_index(23)]
1561 pub fn cancel_approval(
1562 origin: OriginFor<T>,
1563 id: T::AssetIdParameter,
1564 delegate: AccountIdLookupOf<T>,
1565 ) -> DispatchResult {
1566 let owner = ensure_signed(origin)?;
1567 let delegate = T::Lookup::lookup(delegate)?;
1568 let id: T::AssetId = id.into();
1569 let mut d = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
1570 ensure!(d.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
1571
1572 let approval = Approvals::<T, I>::take((id.clone(), &owner, &delegate))
1573 .ok_or(Error::<T, I>::Unknown)?;
1574 T::Currency::unreserve(&owner, approval.deposit);
1575
1576 d.approvals.saturating_dec();
1577 Asset::<T, I>::insert(id.clone(), d);
1578
1579 Self::deposit_event(Event::ApprovalCancelled { asset_id: id, owner, delegate });
1580 Ok(())
1581 }
1582
1583 #[pallet::call_index(24)]
1597 pub fn force_cancel_approval(
1598 origin: OriginFor<T>,
1599 id: T::AssetIdParameter,
1600 owner: AccountIdLookupOf<T>,
1601 delegate: AccountIdLookupOf<T>,
1602 ) -> DispatchResult {
1603 let id: T::AssetId = id.into();
1604 let mut d = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
1605 ensure!(d.status == AssetStatus::Live, Error::<T, I>::AssetNotLive);
1606 T::ForceOrigin::try_origin(origin)
1607 .map(|_| ())
1608 .or_else(|origin| -> DispatchResult {
1609 let origin = ensure_signed(origin)?;
1610 ensure!(origin == d.admin, Error::<T, I>::NoPermission);
1611 Ok(())
1612 })?;
1613
1614 let owner = T::Lookup::lookup(owner)?;
1615 let delegate = T::Lookup::lookup(delegate)?;
1616
1617 let approval = Approvals::<T, I>::take((id.clone(), &owner, &delegate))
1618 .ok_or(Error::<T, I>::Unknown)?;
1619 T::Currency::unreserve(&owner, approval.deposit);
1620 d.approvals.saturating_dec();
1621 Asset::<T, I>::insert(id.clone(), d);
1622
1623 Self::deposit_event(Event::ApprovalCancelled { asset_id: id, owner, delegate });
1624 Ok(())
1625 }
1626
1627 #[pallet::call_index(25)]
1646 pub fn transfer_approved(
1647 origin: OriginFor<T>,
1648 id: T::AssetIdParameter,
1649 owner: AccountIdLookupOf<T>,
1650 destination: AccountIdLookupOf<T>,
1651 #[pallet::compact] amount: T::Balance,
1652 ) -> DispatchResult {
1653 let delegate = ensure_signed(origin)?;
1654 let owner = T::Lookup::lookup(owner)?;
1655 let destination = T::Lookup::lookup(destination)?;
1656 let id: T::AssetId = id.into();
1657 Self::do_transfer_approved(id, &owner, &delegate, &destination, amount)
1658 }
1659
1660 #[pallet::call_index(26)]
1670 #[pallet::weight(T::WeightInfo::touch())]
1671 pub fn touch(origin: OriginFor<T>, id: T::AssetIdParameter) -> DispatchResult {
1672 let who = ensure_signed(origin)?;
1673 let id: T::AssetId = id.into();
1674 Self::do_touch(id, who.clone(), who)
1675 }
1676
1677 #[pallet::call_index(27)]
1691 #[pallet::weight(T::WeightInfo::refund())]
1692 pub fn refund(
1693 origin: OriginFor<T>,
1694 id: T::AssetIdParameter,
1695 allow_burn: bool,
1696 ) -> DispatchResult {
1697 let id: T::AssetId = id.into();
1698 Self::do_refund(id, ensure_signed(origin)?, allow_burn)
1699 }
1700
1701 #[pallet::call_index(28)]
1714 pub fn set_min_balance(
1715 origin: OriginFor<T>,
1716 id: T::AssetIdParameter,
1717 min_balance: T::Balance,
1718 ) -> DispatchResult {
1719 let origin = ensure_signed(origin)?;
1720 let id: T::AssetId = id.into();
1721
1722 let mut details = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
1723 ensure!(origin == details.owner, Error::<T, I>::NoPermission);
1724
1725 let old_min_balance = details.min_balance;
1726 ensure!(!details.is_sufficient, Error::<T, I>::NoPermission);
1729
1730 ensure!(
1733 min_balance < old_min_balance || details.accounts == 0,
1734 Error::<T, I>::NoPermission
1735 );
1736
1737 details.min_balance = min_balance;
1738 Asset::<T, I>::insert(&id, details);
1739
1740 Self::deposit_event(Event::AssetMinBalanceChanged {
1741 asset_id: id,
1742 new_min_balance: min_balance,
1743 });
1744 Ok(())
1745 }
1746
1747 #[pallet::call_index(29)]
1759 #[pallet::weight(T::WeightInfo::touch_other())]
1760 pub fn touch_other(
1761 origin: OriginFor<T>,
1762 id: T::AssetIdParameter,
1763 who: AccountIdLookupOf<T>,
1764 ) -> DispatchResult {
1765 let origin = ensure_signed(origin)?;
1766 let who = T::Lookup::lookup(who)?;
1767 let id: T::AssetId = id.into();
1768 Self::do_touch(id, who, origin)
1769 }
1770
1771 #[pallet::call_index(30)]
1785 #[pallet::weight(T::WeightInfo::refund_other())]
1786 pub fn refund_other(
1787 origin: OriginFor<T>,
1788 id: T::AssetIdParameter,
1789 who: AccountIdLookupOf<T>,
1790 ) -> DispatchResult {
1791 let origin = ensure_signed(origin)?;
1792 let who = T::Lookup::lookup(who)?;
1793 let id: T::AssetId = id.into();
1794 Self::do_refund_other(id, &who, Some(origin))
1795 }
1796
1797 #[pallet::call_index(31)]
1808 pub fn block(
1809 origin: OriginFor<T>,
1810 id: T::AssetIdParameter,
1811 who: AccountIdLookupOf<T>,
1812 ) -> DispatchResult {
1813 let origin = ensure_signed(origin)?;
1814 let id: T::AssetId = id.into();
1815
1816 let d = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
1817 ensure!(
1818 d.status == AssetStatus::Live || d.status == AssetStatus::Frozen,
1819 Error::<T, I>::IncorrectStatus
1820 );
1821 ensure!(origin == d.freezer, Error::<T, I>::NoPermission);
1822 let who = T::Lookup::lookup(who)?;
1823
1824 Account::<T, I>::try_mutate(&id, &who, |maybe_account| -> DispatchResult {
1825 maybe_account.as_mut().ok_or(Error::<T, I>::NoAccount)?.status =
1826 AccountStatus::Blocked;
1827 Ok(())
1828 })?;
1829
1830 Self::deposit_event(Event::<T, I>::Blocked { asset_id: id, who });
1831 Ok(())
1832 }
1833
1834 #[pallet::call_index(32)]
1851 #[pallet::weight(T::WeightInfo::transfer_all())]
1852 pub fn transfer_all(
1853 origin: OriginFor<T>,
1854 id: T::AssetIdParameter,
1855 dest: AccountIdLookupOf<T>,
1856 keep_alive: bool,
1857 ) -> DispatchResult {
1858 let transactor = ensure_signed(origin)?;
1859 let keep_alive = if keep_alive { Preserve } else { Expendable };
1860 let reducible_balance = <Self as fungibles::Inspect<_>>::reducible_balance(
1861 id.clone().into(),
1862 &transactor,
1863 keep_alive,
1864 Fortitude::Polite,
1865 );
1866 let dest = T::Lookup::lookup(dest)?;
1867 <Self as fungibles::Mutate<_>>::transfer(
1868 id.into(),
1869 &transactor,
1870 &dest,
1871 reducible_balance,
1872 keep_alive,
1873 )?;
1874 Ok(())
1875 }
1876
1877 #[pallet::call_index(33)]
1887 #[pallet::weight(T::WeightInfo::set_reserves(reserves.len() as u32))]
1888 pub fn set_reserves(
1889 origin: OriginFor<T>,
1890 id: T::AssetIdParameter,
1891 reserves: BoundedVec<T::ReserveData, ConstU32<MAX_RESERVES>>,
1892 ) -> DispatchResult {
1893 let id: T::AssetId = id.into();
1894 let origin = ensure_signed(origin.clone())
1895 .or_else(|_| T::CreateOrigin::ensure_origin(origin, &id))?;
1896
1897 let details = Asset::<T, I>::get(&id).ok_or(Error::<T, I>::Unknown)?;
1898 ensure!(origin == details.owner, Error::<T, I>::NoPermission);
1899
1900 Self::unchecked_update_reserves(id, reserves)?;
1901 Ok(())
1902 }
1903 }
1904
1905 #[pallet::view_functions]
1906 impl<T: Config<I>, I: 'static> Pallet<T, I> {
1907 pub fn asset_details(
1909 id: T::AssetId,
1910 ) -> Option<AssetDetails<T::Balance, T::AccountId, DepositBalanceOf<T, I>>> {
1911 Asset::<T, I>::get(id)
1912 }
1913
1914 pub fn balance_of(who: T::AccountId, id: T::AssetId) -> Option<<T as Config<I>>::Balance> {
1916 Account::<T, I>::get(id, who).map(|account| account.balance)
1917 }
1918
1919 pub fn get_metadata(
1921 id: T::AssetId,
1922 ) -> Option<AssetMetadata<DepositBalanceOf<T, I>, BoundedVec<u8, T::StringLimit>>> {
1923 Metadata::<T, I>::try_get(id).ok()
1924 }
1925
1926 pub fn get_reserves_data(id: T::AssetId) -> Vec<T::ReserveData> {
1928 Self::reserves(&id)
1929 }
1930 }
1931
1932 impl<T: Config<I>, I: 'static> AccountTouch<T::AssetId, T::AccountId> for Pallet<T, I> {
1935 type Balance = DepositBalanceOf<T, I>;
1936
1937 fn deposit_required(_: T::AssetId) -> Self::Balance {
1938 T::AssetAccountDeposit::get()
1939 }
1940
1941 fn should_touch(asset: T::AssetId, who: &T::AccountId) -> bool {
1942 match Asset::<T, I>::get(&asset) {
1943 Some(info) if info.is_sufficient => false,
1945 Some(_) if frame_system::Pallet::<T>::can_accrue_consumers(who, 2) => false,
1946 Some(_) => !Account::<T, I>::contains_key(asset, who),
1947 _ => true,
1948 }
1949 }
1950
1951 fn touch(
1952 asset: T::AssetId,
1953 who: &T::AccountId,
1954 depositor: &T::AccountId,
1955 ) -> DispatchResult {
1956 Self::do_touch(asset, who.clone(), depositor.clone())
1957 }
1958 }
1959
1960 impl<T: Config<I>, I: 'static> ContainsPair<T::AssetId, T::AccountId> for Pallet<T, I> {
1962 fn contains(asset: &T::AssetId, who: &T::AccountId) -> bool {
1964 Account::<T, I>::contains_key(asset, who)
1965 }
1966 }
1967
1968 impl<T: Config<I>, I: 'static> ProvideAssetReserves<T::AssetId, T::ReserveData> for Pallet<T, I> {
1971 fn reserves(id: &T::AssetId) -> Vec<T::ReserveData> {
1973 Reserves::<T, I>::get(id).into_inner()
1974 }
1975 }
1976}
1977
1978#[cfg(any(feature = "try-runtime", test))]
1979impl<T: Config<I>, I: 'static> Pallet<T, I> {
1980 pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
1981 for asset_id in Reserves::<T, I>::iter_keys() {
1982 ensure!(Asset::<T, I>::contains_key(asset_id.clone()), "Orphaned Reserves data found");
1983 }
1984
1985 for asset_id in Metadata::<T, I>::iter_keys() {
1986 ensure!(Asset::<T, I>::contains_key(asset_id.clone()), "Orphaned Metadata found");
1987 }
1988
1989 for (asset_id, _, _) in Approvals::<T, I>::iter_keys() {
1990 ensure!(Asset::<T, I>::contains_key(asset_id.clone()), "Orphaned Approval found");
1991 }
1992
1993 for (asset_id, _) in Account::<T, I>::iter_keys() {
1994 ensure!(Asset::<T, I>::contains_key(asset_id.clone()), "Orphaned Account found");
1995 }
1996
1997 for (asset_id, details) in Asset::<T, I>::iter() {
1998 if details.status == AssetStatus::Destroying {
1999 continue;
2000 }
2001
2002 let mut calculated_supply = T::Balance::zero();
2003 let mut calculated_accounts = 0u32;
2004 let mut calculated_sufficients = 0u32;
2005
2006 for (who, account) in Account::<T, I>::iter_prefix(&asset_id) {
2007 let held = T::Holder::balance_on_hold(asset_id.clone(), &who).unwrap_or_default();
2008 calculated_supply =
2009 calculated_supply.saturating_add(account.balance).saturating_add(held);
2010 calculated_accounts += 1;
2011
2012 if matches!(account.reason, ExistenceReason::Sufficient) {
2013 calculated_sufficients += 1;
2014 }
2015
2016 if account.balance < details.min_balance {
2017 ensure!(
2018 matches!(
2019 account.reason,
2020 ExistenceReason::DepositHeld(_) | ExistenceReason::DepositFrom(_, _)
2021 ),
2022 "Account below min_balance must have a deposit"
2023 );
2024 }
2025 }
2026
2027 ensure!(details.supply >= calculated_supply, "Asset supply mismatch");
2033 ensure!(details.accounts == calculated_accounts, "Asset account count mismatch");
2034 ensure!(
2035 details.sufficients == calculated_sufficients,
2036 "Asset sufficients count mismatch"
2037 );
2038
2039 let calculated_approvals = Approvals::<T, I>::iter_prefix((&asset_id,)).count() as u32;
2040
2041 if details.approvals != calculated_approvals {
2042 log::error!(
2043 "Asset {asset_id:?} approvals count mismatch: calculated {calculated_approvals} vs expected {}",
2044 details.approvals,
2045 );
2046
2047 return Err("Asset approvals count mismatch".into());
2048 }
2049 }
2050 Ok(())
2051 }
2052}
2053
2054sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $);