Introduce XcmFeesToAccount fee manager (#1234)

Combination of paritytech/polkadot#7005, its addon PR
paritytech/polkadot#7585 and its companion paritytech/cumulus#2433.

This PR introduces a new XcmFeesToAccount struct which implements the
`FeeManager` trait, and assigns this struct as the `FeeManager` in the
XCM config for all runtimes.

The struct simply deposits all fees handled by the XCM executor to a
specified account. In all runtimes, the specified account is configured
as the treasury account.

XCM __delivery__ fees are now being introduced (unless the root origin
is sending a message to a system parachain on behalf of the originating
chain).

# Note for reviewers

Most file changes are tests that had to be modified to account for the
new fees.
Main changes are in:
- cumulus/pallets/xcmp-queue/src/lib.rs <- To make it track the delivery
fees exponential factor
- polkadot/xcm/xcm-builder/src/fee_handling.rs <- Added. Has the
FeeManager implementation
- All runtime xcm_config files <- To add the FeeManager to the XCM
configuration

# Important note

After this change, instructions that create and send a new XCM (Query*,
Report*, ExportMessage, InitiateReserveWithdraw, InitiateTeleport,
DepositReserveAsset, TransferReserveAsset, LockAsset and RequestUnlock)
will require the corresponding origin account in the origin register to
pay for transport delivery fees, and the onward message will fail to be
sent if the origin account does not have the required amount. This
delivery fee is on top of what we already collect as tx fees in
pallet-xcm and XCM BuyExecution fees!

Wallet UIs that want to expose the new delivery fee can do so using the
formula:

```
delivery_fee_factor * (base_fee + encoded_msg_len * per_byte_fee)
```

where the delivery fee factor can be obtained from the corresponding
pallet based on which transport you are using (UMP, HRMP or bridges),
the base fee is a constant, the encoded message length from the message
itself and the per byte fee is the same as the configured per byte fee
for txs (i.e. `TransactionByteFee`).

---------

Co-authored-by: Branislav Kontur <bkontur@gmail.com>
Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>
Co-authored-by: Giles Cope <gilescope@gmail.com>
Co-authored-by: command-bot <>
Co-authored-by: Francisco Aguirre <franciscoaguirreperez@gmail.com>
Co-authored-by: Liam Aharon <liam.aharon@hotmail.com>
Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
This commit is contained in:
Keith Yeung
2023-10-18 23:22:25 +08:00
committed by GitHub
parent 1cf7d3aafa
commit 3dece311be
99 changed files with 2229 additions and 468 deletions
@@ -15,7 +15,7 @@
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
use super::*;
use crate::{account_and_location, new_executor, AssetTransactorOf, XcmCallOf};
use crate::{account_and_location, new_executor, AssetTransactorOf, EnsureDelivery, XcmCallOf};
use frame_benchmarking::{benchmarks_instance_pallet, BenchmarkError, BenchmarkResult};
use frame_support::{
pallet_prelude::Get,
@@ -24,8 +24,8 @@ use frame_support::{
};
use sp_runtime::traits::{Bounded, Zero};
use sp_std::{prelude::*, vec};
use xcm::latest::prelude::*;
use xcm_executor::traits::{ConvertLocation, TransactAsset};
use xcm::latest::{prelude::*, MAX_ITEMS_IN_MULTIASSETS};
use xcm_executor::traits::{ConvertLocation, FeeReason, TransactAsset};
benchmarks_instance_pallet! {
where_clause { where
@@ -45,15 +45,7 @@ benchmarks_instance_pallet! {
let worst_case_holding = T::worst_case_holding(0);
let asset = T::get_multi_asset();
<AssetTransactorOf<T>>::deposit_asset(
&asset,
&sender_location,
&XcmContext {
origin: Some(sender_location.clone()),
message_id: [0; 32],
topic: None,
},
).unwrap();
<AssetTransactorOf<T>>::deposit_asset(&asset, &sender_location, None).unwrap();
// check the assets of origin.
assert!(!T::TransactAsset::balance(&sender_account).is_zero());
@@ -78,15 +70,7 @@ benchmarks_instance_pallet! {
let dest_location = T::valid_destination()?;
let dest_account = T::AccountIdConverter::convert_location(&dest_location).unwrap();
<AssetTransactorOf<T>>::deposit_asset(
&asset,
&sender_location,
&XcmContext {
origin: Some(sender_location.clone()),
message_id: [0; 32],
topic: None,
},
).unwrap();
<AssetTransactorOf<T>>::deposit_asset(&asset, &sender_location, None).unwrap();
assert!(T::TransactAsset::balance(&dest_account).is_zero());
let mut executor = new_executor::<T>(sender_location);
@@ -104,20 +88,27 @@ benchmarks_instance_pallet! {
let dest_location = T::valid_destination()?;
let dest_account = T::AccountIdConverter::convert_location(&dest_location).unwrap();
let asset = T::get_multi_asset();
<AssetTransactorOf<T>>::deposit_asset(
&asset,
let (expected_fees_mode, expected_assets_in_holding) = T::DeliveryHelper::ensure_successful_delivery(
&sender_location,
&XcmContext {
origin: Some(sender_location.clone()),
message_id: [0; 32],
topic: None,
},
).unwrap();
&dest_location,
FeeReason::TransferReserveAsset
);
let sender_account_balance_before = T::TransactAsset::balance(&sender_account);
let asset = T::get_multi_asset();
<AssetTransactorOf<T>>::deposit_asset(&asset, &sender_location, None).unwrap();
assert!(T::TransactAsset::balance(&sender_account) > sender_account_balance_before);
let assets: MultiAssets = vec![ asset ].into();
assert!(T::TransactAsset::balance(&dest_account).is_zero());
let mut executor = new_executor::<T>(sender_location);
if let Some(expected_fees_mode) = expected_fees_mode {
executor.set_fees_mode(expected_fees_mode);
}
if let Some(expected_assets_in_holding) = expected_assets_in_holding {
executor.set_holding(expected_assets_in_holding.into());
}
let instruction = Instruction::TransferReserveAsset {
assets,
dest: dest_location,
@@ -127,7 +118,7 @@ benchmarks_instance_pallet! {
}: {
executor.bench_process(xcm)?;
} verify {
assert!(T::TransactAsset::balance(&sender_account).is_zero());
assert!(T::TransactAsset::balance(&sender_account) <= sender_account_balance_before);
assert!(!T::TransactAsset::balance(&dest_account).is_zero());
// TODO: Check sender queue is not empty. #4426
}
@@ -150,16 +141,33 @@ benchmarks_instance_pallet! {
}
initiate_reserve_withdraw {
let (sender_account, sender_location) = account_and_location::<T>(1);
let holding = T::worst_case_holding(1);
let assets_filter = MultiAssetFilter::Definite(holding.clone());
let assets_filter = MultiAssetFilter::Definite(holding.clone().into_inner().into_iter().take(MAX_ITEMS_IN_MULTIASSETS).collect::<Vec<_>>().into());
let reserve = T::valid_destination().map_err(|_| BenchmarkError::Skip)?;
let mut executor = new_executor::<T>(Default::default());
let (expected_fees_mode, expected_assets_in_holding) = T::DeliveryHelper::ensure_successful_delivery(
&sender_location,
&reserve,
FeeReason::InitiateReserveWithdraw,
);
let sender_account_balance_before = T::TransactAsset::balance(&sender_account);
let mut executor = new_executor::<T>(sender_location);
executor.set_holding(holding.into());
if let Some(expected_fees_mode) = expected_fees_mode {
executor.set_fees_mode(expected_fees_mode);
}
if let Some(expected_assets_in_holding) = expected_assets_in_holding {
executor.set_holding(expected_assets_in_holding.into());
}
let instruction = Instruction::InitiateReserveWithdraw { assets: assets_filter, reserve, xcm: Xcm(vec![]) };
let xcm = Xcm(vec![instruction]);
}: {
executor.bench_process(xcm)?;
} verify {
// Check we charged the delivery fees
assert!(T::TransactAsset::balance(&sender_account) <= sender_account_balance_before);
// The execute completing successfully is as good as we can check.
// TODO: Potentially add new trait to XcmSender to detect a queued outgoing message. #4426
}
@@ -19,7 +19,7 @@
use crate::{fungible as xcm_balances_benchmark, mock::*};
use frame_benchmarking::BenchmarkError;
use frame_support::{
parameter_types,
derive_impl, parameter_types,
traits::{ConstU32, Everything, Nothing},
weights::Weight,
};
@@ -75,20 +75,10 @@ parameter_types! {
pub const ExistentialDeposit: u64 = 7;
}
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig as pallet_balances::DefaultConfig)]
impl pallet_balances::Config for Test {
type MaxLocks = ();
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type Balance = u64;
type DustRemoval = ();
type RuntimeEvent = RuntimeEvent;
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
type WeightInfo = ();
type RuntimeHoldReason = RuntimeHoldReason;
type FreezeIdentifier = ();
type MaxHolds = ConstU32<0>;
type MaxFreezes = ConstU32<0>;
}
parameter_types! {
@@ -157,6 +147,7 @@ impl xcm_executor::Config for XcmConfig {
impl crate::Config for Test {
type XcmConfig = XcmConfig;
type AccountIdConverter = AccountIdConverter;
type DeliveryHelper = ();
fn valid_destination() -> Result<MultiLocation, BenchmarkError> {
let valid_destination: MultiLocation =
X1(AccountId32 { network: None, id: [0u8; 32] }).into();
@@ -15,27 +15,45 @@
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
use super::*;
use crate::{new_executor, XcmCallOf};
use crate::{account_and_location, new_executor, EnsureDelivery, XcmCallOf};
use codec::Encode;
use frame_benchmarking::{benchmarks, BenchmarkError};
use frame_support::dispatch::GetDispatchInfo;
use frame_support::{dispatch::GetDispatchInfo, traits::fungible::Inspect};
use sp_std::vec;
use xcm::{
latest::{prelude::*, MaxDispatchErrorLen, MaybeErrorCode, Weight},
DoubleEncoded,
};
use xcm_executor::{ExecutorError, FeesMode};
use xcm_executor::{
traits::{ConvertLocation, FeeReason},
ExecutorError, FeesMode,
};
benchmarks! {
report_holding {
let (sender_account, sender_location) = account_and_location::<T>(1);
let holding = T::worst_case_holding(0);
let destination = T::valid_destination().map_err(|_| BenchmarkError::Skip)?;
let mut executor = new_executor::<T>(Default::default());
let (expected_fees_mode, expected_assets_in_holding) = T::DeliveryHelper::ensure_successful_delivery(
&sender_location,
&destination,
FeeReason::Report,
);
let sender_account_balance_before = T::TransactAsset::balance(&sender_account);
let mut executor = new_executor::<T>(sender_location);
executor.set_holding(holding.clone().into());
if let Some(expected_fees_mode) = expected_fees_mode {
executor.set_fees_mode(expected_fees_mode);
}
if let Some(expected_assets_in_holding) = expected_assets_in_holding {
executor.set_holding(expected_assets_in_holding.into());
}
let instruction = Instruction::<XcmCallOf<T>>::ReportHolding {
response_info: QueryResponseInfo {
destination: T::valid_destination()?,
destination,
query_id: Default::default(),
max_weight: Weight::MAX,
},
@@ -44,11 +62,11 @@ benchmarks! {
};
let xcm = Xcm(vec![instruction]);
} : {
executor.bench_process(xcm)?;
} verify {
// The completion of execution above is enough to validate this is completed.
// Check we charged the delivery fees
assert!(T::TransactAsset::balance(&sender_account) <= sender_account_balance_before);
}
// This benchmark does not use any additional orders or instructions. This should be managed
@@ -182,11 +200,26 @@ benchmarks! {
}
report_error {
let mut executor = new_executor::<T>(Default::default());
executor.set_error(Some((0u32, XcmError::Unimplemented)));
let (sender_account, sender_location) = account_and_location::<T>(1);
let query_id = Default::default();
let destination = T::valid_destination().map_err(|_| BenchmarkError::Skip)?;
let max_weight = Default::default();
let destination = T::valid_destination().map_err(|_| BenchmarkError::Skip)?;
let (expected_fees_mode, expected_assets_in_holding) = T::DeliveryHelper::ensure_successful_delivery(
&sender_location,
&destination,
FeeReason::Report,
);
let sender_account_balance_before = T::TransactAsset::balance(&sender_account);
let mut executor = new_executor::<T>(sender_location);
if let Some(expected_fees_mode) = expected_fees_mode {
executor.set_fees_mode(expected_fees_mode);
}
if let Some(expected_assets_in_holding) = expected_assets_in_holding {
executor.set_holding(expected_assets_in_holding.into());
}
executor.set_error(Some((0u32, XcmError::Unimplemented)));
let instruction = Instruction::ReportError(QueryResponseInfo {
query_id, destination, max_weight
@@ -195,7 +228,8 @@ benchmarks! {
}: {
executor.bench_process(xcm)?;
} verify {
// the execution succeeding is all we need to verify this xcm was successful
// Check we charged the delivery fees
assert!(T::TransactAsset::balance(&sender_account) <= sender_account_balance_before);
}
claim_asset {
@@ -360,10 +394,24 @@ benchmarks! {
}
query_pallet {
let (sender_account, sender_location) = account_and_location::<T>(1);
let query_id = Default::default();
let destination = T::valid_destination().map_err(|_| BenchmarkError::Skip)?;
let max_weight = Default::default();
let mut executor = new_executor::<T>(Default::default());
let (expected_fees_mode, expected_assets_in_holding) = T::DeliveryHelper::ensure_successful_delivery(
&sender_location,
&destination,
FeeReason::QueryPallet,
);
let sender_account_balance_before = T::TransactAsset::balance(&sender_account);
let mut executor = new_executor::<T>(sender_location);
if let Some(expected_fees_mode) = expected_fees_mode {
executor.set_fees_mode(expected_fees_mode);
}
if let Some(expected_assets_in_holding) = expected_assets_in_holding {
executor.set_holding(expected_assets_in_holding.into());
}
let instruction = Instruction::QueryPallet {
module_name: b"frame_system".to_vec(),
@@ -373,6 +421,8 @@ benchmarks! {
}: {
executor.bench_process(xcm)?;
} verify {
// Check we charged the delivery fees
assert!(T::TransactAsset::balance(&sender_account) <= sender_account_balance_before);
// TODO: Potentially add new trait to XcmSender to detect a queued outgoing message. #4426
}
@@ -394,11 +444,25 @@ benchmarks! {
}
report_transact_status {
let (sender_account, sender_location) = account_and_location::<T>(1);
let query_id = Default::default();
let destination = T::valid_destination().map_err(|_| BenchmarkError::Skip)?;
let max_weight = Default::default();
let mut executor = new_executor::<T>(Default::default());
let (expected_fees_mode, expected_assets_in_holding) = T::DeliveryHelper::ensure_successful_delivery(
&sender_location,
&destination,
FeeReason::Report,
);
let sender_account_balance_before = T::TransactAsset::balance(&sender_account);
let mut executor = new_executor::<T>(sender_location);
if let Some(expected_fees_mode) = expected_fees_mode {
executor.set_fees_mode(expected_fees_mode);
}
if let Some(expected_assets_in_holding) = expected_assets_in_holding {
executor.set_holding(expected_assets_in_holding.into());
}
executor.set_transact_status(b"MyError".to_vec().into());
let instruction = Instruction::ReportTransactStatus(QueryResponseInfo {
@@ -410,6 +474,8 @@ benchmarks! {
}: {
executor.bench_process(xcm)?;
} verify {
// Check we charged the delivery fees
assert!(T::TransactAsset::balance(&sender_account) <= sender_account_balance_before);
// TODO: Potentially add new trait to XcmSender to detect a queued outgoing message. #4426
}
@@ -491,14 +557,30 @@ benchmarks! {
let inner_xcm = Xcm(vec![ClearOrigin; x as usize]);
// Get `origin`, `network` and `destination` from configured runtime.
let (origin, network, destination) = T::export_message_origin_and_destination()?;
let (expected_fees_mode, expected_assets_in_holding) = T::DeliveryHelper::ensure_successful_delivery(
&origin,
&destination.into(),
FeeReason::Export(network),
);
let sender_account = T::AccountIdConverter::convert_location(&origin).unwrap();
let sender_account_balance_before = T::TransactAsset::balance(&sender_account);
let mut executor = new_executor::<T>(origin);
if let Some(expected_fees_mode) = expected_fees_mode {
executor.set_fees_mode(expected_fees_mode);
}
if let Some(expected_assets_in_holding) = expected_assets_in_holding {
executor.set_holding(expected_assets_in_holding.into());
}
let xcm = Xcm(vec![ExportMessage {
network, destination, xcm: inner_xcm,
}]);
}: {
executor.bench_process(xcm)?;
} verify {
// The execute completing successfully is as good as we can check.
// Check we charged the delivery fees
assert!(T::TransactAsset::balance(&sender_account) <= sender_account_balance_before);
// TODO: Potentially add new trait to XcmSender to detect a queued outgoing message. #4426
}
@@ -517,14 +599,30 @@ benchmarks! {
lock_asset {
let (unlocker, owner, asset) = T::unlockable_asset()?;
let (expected_fees_mode, expected_assets_in_holding) = T::DeliveryHelper::ensure_successful_delivery(
&owner,
&unlocker,
FeeReason::LockAsset,
);
let sender_account = T::AccountIdConverter::convert_location(&owner).unwrap();
let sender_account_balance_before = T::TransactAsset::balance(&sender_account);
let mut executor = new_executor::<T>(owner);
executor.set_holding(asset.clone().into());
if let Some(expected_fees_mode) = expected_fees_mode {
executor.set_fees_mode(expected_fees_mode);
}
if let Some(expected_assets_in_holding) = expected_assets_in_holding {
executor.set_holding(expected_assets_in_holding.into());
}
let instruction = Instruction::LockAsset { asset, unlocker };
let xcm = Xcm(vec![instruction]);
}: {
executor.bench_process(xcm)?;
} verify {
// Check delivery fees
assert!(T::TransactAsset::balance(&sender_account) <= sender_account_balance_before);
// TODO: Potentially add new trait to XcmSender to detect a queued outgoing message. #4426
}
@@ -595,13 +693,29 @@ benchmarks! {
.enact()
.map_err(|_| BenchmarkError::Skip)?;
let (expected_fees_mode, expected_assets_in_holding) = T::DeliveryHelper::ensure_successful_delivery(
&owner,
&locker,
FeeReason::RequestUnlock,
);
let sender_account = T::AccountIdConverter::convert_location(&owner).unwrap();
let sender_account_balance_before = T::TransactAsset::balance(&sender_account);
// ... then request for an unlock with the RequestUnlock instruction.
let mut executor = new_executor::<T>(owner);
if let Some(expected_fees_mode) = expected_fees_mode {
executor.set_fees_mode(expected_fees_mode);
}
if let Some(expected_assets_in_holding) = expected_assets_in_holding {
executor.set_holding(expected_assets_in_holding.into());
}
let instruction = Instruction::RequestUnlock { asset, locker };
let xcm = Xcm(vec![instruction]);
}: {
executor.bench_process(xcm)?;
} verify {
// Check we charged the delivery fees
assert!(T::TransactAsset::balance(&sender_account) <= sender_account_balance_before);
// TODO: Potentially add new trait to XcmSender to detect a queued outgoing message. #4426
}
@@ -19,7 +19,7 @@
use crate::{generic, mock::*, *};
use codec::Decode;
use frame_support::{
match_types, parameter_types,
derive_impl, match_types, parameter_types,
traits::{Everything, OriginTrait},
weights::Weight,
};
@@ -40,6 +40,7 @@ frame_support::construct_runtime!(
pub enum Test
{
System: frame_system::{Pallet, Call, Config<T>, Storage, Event<T>},
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
XcmGenericBenchmarks: generic::{Pallet},
}
);
@@ -79,7 +80,11 @@ impl frame_system::Config for Test {
/// The benchmarks in this pallet should never need an asset transactor to begin with.
pub struct NoAssetTransactor;
impl xcm_executor::traits::TransactAsset for NoAssetTransactor {
fn deposit_asset(_: &MultiAsset, _: &MultiLocation, _: &XcmContext) -> Result<(), XcmError> {
fn deposit_asset(
_: &MultiAsset,
_: &MultiLocation,
_: Option<&XcmContext>,
) -> Result<(), XcmError> {
unreachable!();
}
@@ -133,9 +138,20 @@ impl xcm_executor::Config for XcmConfig {
type Aliasers = Aliasers;
}
parameter_types! {
pub const ExistentialDeposit: u64 = 7;
}
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig as pallet_balances::DefaultConfig)]
impl pallet_balances::Config for Test {
type ReserveIdentifier = [u8; 8];
type AccountStore = System;
}
impl crate::Config for Test {
type XcmConfig = XcmConfig;
type AccountIdConverter = AccountIdConverter;
type DeliveryHelper = ();
fn valid_destination() -> Result<MultiLocation, BenchmarkError> {
let valid_destination: MultiLocation =
Junction::AccountId32 { network: None, id: [0u8; 32] }.into();
@@ -151,6 +167,7 @@ impl crate::Config for Test {
}
impl generic::Config for Test {
type TransactAsset = Balances;
type RuntimeCall = RuntimeCall;
fn worst_case_response() -> (u64, Response) {
@@ -183,7 +200,7 @@ impl generic::Config for Test {
fn unlockable_asset() -> Result<(MultiLocation, MultiLocation, MultiAsset), BenchmarkError> {
let assets: MultiAsset = (Concrete(Here.into()), 100).into();
Ok((Default::default(), Default::default(), assets))
Ok((Default::default(), account_id_junction::<Test>(1).into(), assets))
}
fn export_message_origin_and_destination(
@@ -38,6 +38,11 @@ pub mod pallet {
+ From<frame_system::Call<Self>>
+ Encode;
/// The type of `fungible` that is being used under the hood.
///
/// This is useful for testing and checking.
type TransactAsset: frame_support::traits::fungible::Mutate<Self::AccountId>;
/// The response which causes the most runtime weight.
fn worst_case_response() -> (u64, Response);
+33 -1
View File
@@ -22,7 +22,10 @@ use codec::Encode;
use frame_benchmarking::{account, BenchmarkError};
use sp_std::prelude::*;
use xcm::latest::prelude::*;
use xcm_executor::{traits::ConvertLocation, Config as XcmConfig};
use xcm_executor::{
traits::{ConvertLocation, FeeReason},
Config as XcmConfig, FeesMode,
};
pub mod fungible;
pub mod generic;
@@ -41,6 +44,9 @@ pub trait Config: frame_system::Config {
/// A converter between a multi-location to a sovereign account.
type AccountIdConverter: ConvertLocation<Self::AccountId>;
/// Helper that ensures successful delivery for XCM instructions which need `SendXcm`.
type DeliveryHelper: EnsureDelivery;
/// Does any necessary setup to create a valid destination for XCM messages.
/// Returns that destination's multi-location to be used in benchmarks.
fn valid_destination() -> Result<MultiLocation, BenchmarkError>;
@@ -108,3 +114,29 @@ pub fn account_and_location<T: Config>(index: u32) -> (T::AccountId, MultiLocati
(account, location)
}
/// Trait for a type which ensures all requirements for successful delivery with XCM transport
/// layers.
pub trait EnsureDelivery {
/// Prepare all requirements for successful `XcmSender: SendXcm` passing (accounts, balances,
/// channels ...). Returns:
/// - possible `FeesMode` which is expected to be set to executor
/// - possible `MultiAssets` which are expected to be subsume to the Holding Register
fn ensure_successful_delivery(
origin_ref: &MultiLocation,
dest: &MultiLocation,
fee_reason: FeeReason,
) -> (Option<FeesMode>, Option<MultiAssets>);
}
/// `()` implementation does nothing which means no special requirements for environment.
impl EnsureDelivery for () {
fn ensure_successful_delivery(
_origin_ref: &MultiLocation,
_dest: &MultiLocation,
_fee_reason: FeeReason,
) -> (Option<FeesMode>, Option<MultiAssets>) {
// doing nothing
(None, None)
}
}