Files
pezkuwi-subxt/substrate/frame/assets
Branislav Kontur bb8ddc46c1 [frame] #[pallet::composite_enum] improved variant count handling + removed pallet_balances's MaxHolds config (#2657)
I started this investigation/issue based on @liamaharon question
[here](https://github.com/paritytech/polkadot-sdk/pull/1801#discussion_r1410452499).

## Problem

The `pallet_balances` integrity test should correctly detect that the
runtime has correct distinct `HoldReasons` variant count. I assume the
same situation exists for RuntimeFreezeReason.

It is not a critical problem, if we set `MaxHolds` with a sufficiently
large value, everything should be ok. However, in this case, the
integrity_test check becomes less useful.

**Situation for "any" runtime:**
- `HoldReason` enums from different pallets:
```rust
        /// from pallet_nis
        #[pallet::composite_enum]
	pub enum HoldReason {
		NftReceipt,
	}

        /// from pallet_preimage
        #[pallet::composite_enum]
	pub enum HoldReason {
		Preimage,
	}

        // from pallet_state-trie-migration
        #[pallet::composite_enum]
	pub enum HoldReason {
		SlashForContinueMigrate,
		SlashForMigrateCustomTop,
		SlashForMigrateCustomChild,
	}
```

- generated `RuntimeHoldReason` enum looks like:
```rust
pub enum RuntimeHoldReason {

    #[codec(index = 32u8)]
    Preimage(pallet_preimage::HoldReason),

    #[codec(index = 38u8)]
    Nis(pallet_nis::HoldReason),

    #[codec(index = 42u8)]
    StateTrieMigration(pallet_state_trie_migration::HoldReason),
}
```

- composite enum `RuntimeHoldReason` variant count is detected as `3`
- we set `type MaxHolds = ConstU32<3>`
- `pallet_balances::integrity_test` is ok with `3`(at least 3)

However, the real problem can occur in a live runtime where some
functionality might stop working. This is due to a total of 5 distinct
hold reasons (for pallets with multi-instance support, it is even more),
and not all of them can be used because of an incorrect `MaxHolds`,
which is deemed acceptable according to the `integrity_test`:
  ```
  // pseudo-code - if we try to call all of these:

T::Currency::hold(&pallet_nis::HoldReason::NftReceipt.into(),
&nft_owner, deposit)?;
T::Currency::hold(&pallet_preimage::HoldReason::Preimage.into(),
&nft_owner, deposit)?;

T::Currency::hold(&pallet_state_trie_migration::HoldReason::SlashForContinueMigrate.into(),
&nft_owner, deposit)?;

  // With `type MaxHolds = ConstU32<3>` these two will fail

T::Currency::hold(&pallet_state_trie_migration::HoldReason::SlashForMigrateCustomTop.into(),
&nft_owner, deposit)?;

T::Currency::hold(&pallet_state_trie_migration::HoldReason::SlashForMigrateCustomChild.into(),
&nft_owner, deposit)?;
  ```  


## Solutions

A macro `#[pallet::*]` expansion is extended of `VariantCount`
implementation for the `#[pallet::composite_enum]` enum type. This
expansion generates the `VariantCount` implementation for pallets'
`HoldReason`, `FreezeReason`, `LockId`, and `SlashReason`. Enum variants
must be plain enum values without fields to ensure a deterministic
count.

The composite runtime enum, `RuntimeHoldReason` and
`RuntimeFreezeReason`, now sets `VariantCount::VARIANT_COUNT` as the sum
of pallets' enum `VariantCount::VARIANT_COUNT`:
```rust
#[frame_support::pallet(dev_mode)]
mod module_single_instance {

	#[pallet::composite_enum]
	pub enum HoldReason {
		ModuleSingleInstanceReason1,
		ModuleSingleInstanceReason2,
	}
...
}

#[frame_support::pallet(dev_mode)]
mod module_multi_instance {

	#[pallet::composite_enum]
	pub enum HoldReason<I: 'static = ()> {
		ModuleMultiInstanceReason1,
		ModuleMultiInstanceReason2,
		ModuleMultiInstanceReason3,
	}
...
}


impl self::sp_api_hidden_includes_construct_runtime::hidden_include::traits::VariantCount
    for RuntimeHoldReason
{
    const VARIANT_COUNT: u32 = 0
        + module_single_instance::HoldReason::VARIANT_COUNT
        + module_multi_instance::HoldReason::<module_multi_instance::Instance1>::VARIANT_COUNT
        + module_multi_instance::HoldReason::<module_multi_instance::Instance2>::VARIANT_COUNT
        + module_multi_instance::HoldReason::<module_multi_instance::Instance3>::VARIANT_COUNT;
}
```

In addition, `MaxHolds` is removed (as suggested
[here](https://github.com/paritytech/polkadot-sdk/pull/2657#discussion_r1443324573))
from `pallet_balances`, and its `Holds` are now bounded to
`RuntimeHoldReason::VARIANT_COUNT`. Therefore, there is no need to let
the runtime specify `MaxHolds`.


## For reviewers

Relevant changes can be found here:
- `substrate/frame/support/procedural/src/lib.rs` 
-  `substrate/frame/support/procedural/src/pallet/parse/composite.rs`
-  `substrate/frame/support/procedural/src/pallet/expand/composite.rs`
-
`substrate/frame/support/procedural/src/construct_runtime/expand/composite_helper.rs`
-
`substrate/frame/support/procedural/src/construct_runtime/expand/hold_reason.rs`
-
`substrate/frame/support/procedural/src/construct_runtime/expand/freeze_reason.rs`
- `substrate/frame/support/src/traits/misc.rs`

And the rest of the files is just about removed `MaxHolds` from
`pallet_balances`

## Next steps

Do the same for `MaxFreezes`
https://github.com/paritytech/polkadot-sdk/issues/2997.

---------

Co-authored-by: command-bot <>
Co-authored-by: Bastian Köcher <git@kchr.de>
Co-authored-by: Dónal Murray <donal.murray@parity.io>
Co-authored-by: gupnik <nikhilgupta.iitk@gmail.com>
2024-01-31 06:19:16 +00:00
..
2023-09-04 12:02:32 +03:00

Assets Module

A simple, secure module for dealing with fungible assets.

Overview

The Assets module provides functionality for asset management of fungible asset classes with a fixed supply, including:

  • Asset Issuance
  • Asset Transfer
  • Asset Destruction

To use it in your runtime, you need to implement the assets assets::Config.

The supported dispatchable functions are documented in the assets::Call enum.

Terminology

  • Asset issuance: The creation of a new asset, whose total supply will belong to the account that issues the asset.
  • Asset transfer: The action of transferring assets from one account to another.
  • Asset destruction: The process of an account removing its entire holding of an asset.
  • Fungible asset: An asset whose units are interchangeable.
  • Non-fungible asset: An asset for which each unit has unique characteristics.

Goals

The assets system in Substrate is designed to make the following possible:

  • Issue a unique asset to its creator's account.
  • Move assets between accounts.
  • Remove an account's balance of an asset when requested by that account's owner and update the asset's total supply.

Interface

Dispatchable Functions

  • issue - Issues the total supply of a new fungible asset to the account of the caller of the function.
  • transfer - Transfers an amount of units of fungible asset id from the balance of the function caller's account (origin) to a target account.
  • destroy - Destroys the entire holding of a fungible asset id associated with the account that called the function.

Please refer to the Call enum and its associated variants for documentation on each function.

Public Functions

  • balance - Get the asset id balance of who.
  • total_supply - Get the total supply of an asset id.

Please refer to the Pallet struct for details on publicly available functions.

Usage

The following example shows how to use the Assets module in your runtime by exposing public functions to:

  • Issue a new fungible asset for a token distribution event (airdrop).
  • Query the fungible asset holding balance of an account.
  • Query the total supply of a fungible asset that has been issued.

Prerequisites

Import the Assets module and types and derive your runtime's configuration traits from the Assets module trait.

Simple Code Snippet

use pallet_assets as assets;
use sp_runtime::ArithmeticError;

#[frame_support::pallet]
pub mod pallet {
    use super::*;
    use frame_support::pallet_prelude::*;
    use frame_system::pallet_prelude::*;

    #[pallet::pallet]
    pub struct Pallet<T>(_);

    #[pallet::config]
    pub trait Config: frame_system::Config + assets::Config {}

    #[pallet::call]
    impl<T: Config> Pallet<T> {
        pub fn issue_token_airdrop(origin: OriginFor<T>) -> DispatchResult {
            let sender = ensure_signed(origin)?;

            const ACCOUNT_ALICE: u64 = 1;
            const ACCOUNT_BOB: u64 = 2;
            const COUNT_AIRDROP_RECIPIENTS: u64 = 2;
            const TOKENS_FIXED_SUPPLY: u64 = 100;

            ensure!(!COUNT_AIRDROP_RECIPIENTS.is_zero(), ArithmeticError::DivisionByZero);

            let asset_id = Self::next_asset_id();

            <NextAssetId<T>>::mutate(|asset_id| *asset_id += 1);
            <Balances<T>>::insert((asset_id, &ACCOUNT_ALICE), TOKENS_FIXED_SUPPLY / COUNT_AIRDROP_RECIPIENTS);
            <Balances<T>>::insert((asset_id, &ACCOUNT_BOB), TOKENS_FIXED_SUPPLY / COUNT_AIRDROP_RECIPIENTS);
            <TotalSupply<T>>::insert(asset_id, TOKENS_FIXED_SUPPLY);

            Self::deposit_event(Event::Issued(asset_id, sender, TOKENS_FIXED_SUPPLY));
            Ok(())
        }
    }
}

Assumptions

Below are assumptions that must be held when using this module. If any of them are violated, the behavior of this module is undefined.

  • The total count of assets should be less than Config::AssetId::max_value().

License: Apache-2.0