Files
pezkuwi-subxt/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/mock.rs
T
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

273 lines
8.3 KiB
Rust

// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::*;
use crate as pallet_asset_conversion_tx_payment;
use frame_support::{
derive_impl,
dispatch::DispatchClass,
instances::Instance2,
ord_parameter_types,
pallet_prelude::*,
parameter_types,
traits::{
tokens::{
fungible::{NativeFromLeft, NativeOrWithId, UnionOf},
imbalance::ResolveAssetTo,
},
AsEnsureOriginWithArg, ConstU32, ConstU64, ConstU8, Imbalance, OnUnbalanced,
},
weights::{Weight, WeightToFee as WeightToFeeT},
PalletId,
};
use frame_system as system;
use frame_system::{EnsureRoot, EnsureSignedBy};
use pallet_asset_conversion::{Ascending, Chain, WithFirstAsset};
use pallet_transaction_payment::CurrencyAdapter;
use sp_core::H256;
use sp_runtime::{
traits::{AccountIdConversion, BlakeTwo256, IdentityLookup, SaturatedConversion},
Permill,
};
type Block = frame_system::mocking::MockBlock<Runtime>;
type Balance = u64;
type AccountId = u64;
frame_support::construct_runtime!(
pub enum Runtime
{
System: system,
Balances: pallet_balances,
TransactionPayment: pallet_transaction_payment,
Assets: pallet_assets,
PoolAssets: pallet_assets::<Instance2>,
AssetConversion: pallet_asset_conversion,
AssetTxPayment: pallet_asset_conversion_tx_payment,
}
);
parameter_types! {
pub(crate) static ExtrinsicBaseWeight: Weight = Weight::zero();
}
pub struct BlockWeights;
impl Get<frame_system::limits::BlockWeights> for BlockWeights {
fn get() -> frame_system::limits::BlockWeights {
frame_system::limits::BlockWeights::builder()
.base_block(Weight::zero())
.for_class(DispatchClass::all(), |weights| {
weights.base_extrinsic = ExtrinsicBaseWeight::get().into();
})
.for_class(DispatchClass::non_mandatory(), |weights| {
weights.max_total = Weight::from_parts(1024, u64::MAX).into();
})
.build_or_panic()
}
}
parameter_types! {
pub static WeightToFee: u64 = 1;
pub static TransactionByteFee: u64 = 1;
}
#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
impl frame_system::Config for Runtime {
type BaseCallFilter = frame_support::traits::Everything;
type BlockWeights = BlockWeights;
type BlockLength = ();
type DbWeight = ();
type RuntimeOrigin = RuntimeOrigin;
type Nonce = u64;
type RuntimeCall = RuntimeCall;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = AccountId;
type Lookup = IdentityLookup<Self::AccountId>;
type Block = Block;
type RuntimeEvent = RuntimeEvent;
type BlockHashCount = ConstU64<250>;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = pallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = ();
type OnSetCode = ();
type MaxConsumers = ConstU32<16>;
}
parameter_types! {
pub const ExistentialDeposit: u64 = 10;
}
impl pallet_balances::Config for Runtime {
type Balance = Balance;
type RuntimeEvent = RuntimeEvent;
type DustRemoval = ();
type ExistentialDeposit = ConstU64<10>;
type AccountStore = System;
type MaxLocks = ();
type WeightInfo = ();
type MaxReserves = ConstU32<50>;
type ReserveIdentifier = [u8; 8];
type FreezeIdentifier = ();
type MaxFreezes = ();
type RuntimeHoldReason = ();
type RuntimeFreezeReason = ();
}
impl WeightToFeeT for WeightToFee {
type Balance = u64;
fn weight_to_fee(weight: &Weight) -> Self::Balance {
Self::Balance::saturated_from(weight.ref_time())
.saturating_mul(WEIGHT_TO_FEE.with(|v| *v.borrow()))
}
}
impl WeightToFeeT for TransactionByteFee {
type Balance = u64;
fn weight_to_fee(weight: &Weight) -> Self::Balance {
Self::Balance::saturated_from(weight.ref_time())
.saturating_mul(TRANSACTION_BYTE_FEE.with(|v| *v.borrow()))
}
}
parameter_types! {
pub(crate) static TipUnbalancedAmount: u64 = 0;
pub(crate) static FeeUnbalancedAmount: u64 = 0;
}
pub struct DealWithFees;
impl OnUnbalanced<pallet_balances::NegativeImbalance<Runtime>> for DealWithFees {
fn on_unbalanceds<B>(
mut fees_then_tips: impl Iterator<Item = pallet_balances::NegativeImbalance<Runtime>>,
) {
if let Some(fees) = fees_then_tips.next() {
FeeUnbalancedAmount::mutate(|a| *a += fees.peek());
if let Some(tips) = fees_then_tips.next() {
TipUnbalancedAmount::mutate(|a| *a += tips.peek());
}
}
}
}
impl pallet_transaction_payment::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type OnChargeTransaction = CurrencyAdapter<Balances, DealWithFees>;
type WeightToFee = WeightToFee;
type LengthToFee = TransactionByteFee;
type FeeMultiplierUpdate = ();
type OperationalFeeMultiplier = ConstU8<5>;
}
type AssetId = u32;
impl pallet_assets::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type Balance = Balance;
type AssetId = AssetId;
type AssetIdParameter = codec::Compact<AssetId>;
type Currency = Balances;
type CreateOrigin = AsEnsureOriginWithArg<frame_system::EnsureSigned<AccountId>>;
type ForceOrigin = EnsureRoot<AccountId>;
type AssetDeposit = ConstU64<2>;
type AssetAccountDeposit = ConstU64<2>;
type MetadataDepositBase = ConstU64<0>;
type MetadataDepositPerByte = ConstU64<0>;
type ApprovalDeposit = ConstU64<0>;
type StringLimit = ConstU32<20>;
type Freezer = ();
type Extra = ();
type CallbackHandle = ();
type WeightInfo = ();
type RemoveItemsLimit = ConstU32<1000>;
pallet_assets::runtime_benchmarks_enabled! {
type BenchmarkHelper = ();
}
}
impl pallet_assets::Config<Instance2> for Runtime {
type RuntimeEvent = RuntimeEvent;
type Balance = u64;
type RemoveItemsLimit = ConstU32<1000>;
type AssetId = u32;
type AssetIdParameter = u32;
type Currency = Balances;
type CreateOrigin = AsEnsureOriginWithArg<EnsureSignedBy<AssetConversionOrigin, u64>>;
type ForceOrigin = frame_system::EnsureRoot<u64>;
type AssetDeposit = ConstU64<0>;
type AssetAccountDeposit = ConstU64<0>;
type MetadataDepositBase = ConstU64<0>;
type MetadataDepositPerByte = ConstU64<0>;
type ApprovalDeposit = ConstU64<0>;
type StringLimit = ConstU32<50>;
type Freezer = ();
type Extra = ();
type WeightInfo = ();
type CallbackHandle = ();
pallet_assets::runtime_benchmarks_enabled! {
type BenchmarkHelper = ();
}
}
parameter_types! {
pub const AssetConversionPalletId: PalletId = PalletId(*b"py/ascon");
pub storage LiquidityWithdrawalFee: Permill = Permill::from_percent(0);
pub const MaxSwapPathLength: u32 = 4;
pub const Native: NativeOrWithId<u32> = NativeOrWithId::Native;
}
ord_parameter_types! {
pub const AssetConversionOrigin: u64 = AccountIdConversion::<u64>::into_account_truncating(&AssetConversionPalletId::get());
}
impl pallet_asset_conversion::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type Balance = Balance;
type HigherPrecisionBalance = u128;
type AssetKind = NativeOrWithId<u32>;
type Assets = UnionOf<Balances, Assets, NativeFromLeft, NativeOrWithId<u32>, AccountId>;
type PoolId = (Self::AssetKind, Self::AssetKind);
type PoolLocator = Chain<
WithFirstAsset<Native, AccountId, NativeOrWithId<u32>>,
Ascending<AccountId, NativeOrWithId<u32>>,
>;
type PoolAssetId = u32;
type PoolAssets = PoolAssets;
type PoolSetupFee = ConstU64<100>; // should be more or equal to the existential deposit
type PoolSetupFeeAsset = Native;
type PoolSetupFeeTarget = ResolveAssetTo<AssetConversionOrigin, Self::Assets>;
type PalletId = AssetConversionPalletId;
type LPFee = ConstU32<3>; // means 0.3%
type LiquidityWithdrawalFee = LiquidityWithdrawalFee;
type MaxSwapPathLength = MaxSwapPathLength;
type MintMinLiquidity = ConstU64<100>; // 100 is good enough when the main currency has 12 decimals.
type WeightInfo = ();
pallet_asset_conversion::runtime_benchmarks_enabled! {
type BenchmarkHelper = ();
}
}
impl Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type Fungibles = Assets;
type OnChargeAssetTransaction = AssetConversionAdapter<Balances, AssetConversion, Native>;
}