Files
pezkuwi-subxt/substrate/frame/assets
Liam Aharon 46090ff114 Unbalanced and Balanced fungible conformance tests, and fungible fixes (#1296)
Original PR https://github.com/paritytech/substrate/pull/14655

---

Partial https://github.com/paritytech/polkadot-sdk/issues/225

- [x] Adds conformance tests for Unbalanced
- [x] Adds conformance tests for Balanced
- Several minor fixes to fungible default implementations and the
Balances pallet
- [x] `Unbalanced::decrease_balance` can reap account when
`Preservation` is `Preserve`
- [x] `Balanced::pair` can return pairs of imbalances which do not
cancel each other out
   - [x] Balances pallet `active_issuance` 'underflow'
- [x] Refactors the conformance test file structure to match the
fungible file structure: tests for traits in regular.rs go into a test
file named regular.rs, tests for traits in freezes.rs go into a test
file named freezes.rs, etc.
 - [x] Improve doc comments
 - [x] Simplify macros

## Fixes

### `Unbalanced::decrease_balance` can reap account when called with
`Preservation::Preserve`
There is a potential issue in the default implementation of
`Unbalanced::decrease_balance`. The implementation can delete an account
even when it is called with `preservation: Preservation::Preserve`. This
seems to contradict the documentation of `Preservation::Preserve`:

```rust
	/// The account may not be killed and our provider reference must remain (in the context of
	/// tokens, this means that the account may not be dusted).
	Preserve,
```

I updated `Unbalanced::decrease_balance` to return
`Err(TokenError::BelowMinimum)` when a withdrawal would cause the
account to be reaped and `preservation: Preservation::Preserve`.

- [ ] TODO Confirm with @gavofyork that this is correct behavior

Test for this behavior:


https://github.com/paritytech/polkadot-sdk/blob/e5c876dd6b59e2b7dbacaa4538cb42c802db3730/substrate/frame/support/src/traits/tokens/fungible/conformance_tests/regular.rs#L912-L937

### `Balanced::pair` returning non-canceling pairs

`Balanced::pair` is supposed to create a pair of imbalances that cancel
each other out. However this is not the case when the method is called
with an amount greater than the total supply.

In the existing default implementation, `Balanced::pair` creates a pair
by first rescinding the balance, creating `Debt`, and then issuing the
balance, creating `Credit`.

When creating `Debt`, if the amount to create exceeds the
`total_supply`, `total_supply` units of `Debt` are created *instead* of
`amount` units of `Debt`. This can lead to non-canceling amount of
`Credit` and `Debt` being created.

To address this, I create the credit and debt directly in the method
instead of calling `issue` and `rescind`.

Test for this behavior:


https://github.com/paritytech/polkadot-sdk/blob/e5c876dd6b59e2b7dbacaa4538cb42c802db3730/substrate/frame/support/src/traits/tokens/fungible/conformance_tests/regular.rs#L1323-L1346

### `Balances` pallet `active_issuance` 'underflow'

This PR resolves an issue in the `Balances` pallet that can lead to odd
behavior of `active_issuance`.

Currently, the Balances pallet doesn't check if `InactiveIssuance`
remains less than or equal to `TotalIssuance` when supply is
deactivated. This allows `InactiveIssuance` to be greater than
`TotalIssuance`, which can result in unexpected behavior from the
perspective of the fungible API.

`active_issuance` is derived from
`TotalIssuance.saturating_sub(InactiveIssuance)`.

If an `amount` is deactivated that causes `InactiveIssuance` to become
greater TotalIssuance, `active_issuance` will return 0. However once in
that state, reactivating an amount will not increase `active_issuance`
by the reactivated `amount` as expected.

Consider this test where the last assertion would fail due to this
issue:


https://github.com/paritytech/polkadot-sdk/blob/e5c876dd6b59e2b7dbacaa4538cb42c802db3730/substrate/frame/support/src/traits/tokens/fungible/conformance_tests/regular.rs#L1036-L1071

To address this, I've modified the `deactivate` function to ensure
`InactiveIssuance` never surpasses `TotalIssuance`.

---------

Co-authored-by: Muharem <ismailov.m.h@gmail.com>
2024-01-15 12:30:51 +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