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
+60 -23
View File
@@ -28,29 +28,13 @@ use frame_support::{
},
weights::Weight,
};
use polkadot_runtime_common::xcm_sender::ConstantPrice;
use polkadot_runtime_common::xcm_sender::PriceForMessageDelivery;
use sp_runtime::{traits::Saturating, SaturatedConversion};
use sp_std::{marker::PhantomData, prelude::*};
use xcm::{latest::prelude::*, WrapVersion};
use xcm_builder::TakeRevenue;
use xcm_executor::traits::{MatchesFungibles, TransactAsset, WeightTrader};
pub trait PriceForParentDelivery {
fn price_for_parent_delivery(message: &Xcm<()>) -> MultiAssets;
}
impl PriceForParentDelivery for () {
fn price_for_parent_delivery(_: &Xcm<()>) -> MultiAssets {
MultiAssets::new()
}
}
impl<T: Get<MultiAssets>> PriceForParentDelivery for ConstantPrice<T> {
fn price_for_parent_delivery(_: &Xcm<()>) -> MultiAssets {
T::get()
}
}
/// Xcm router which recognises the `Parent` destination and handles it by sending the message into
/// the given UMP `UpwardMessageSender` implementation. Thus this essentially adapts an
/// `UpwardMessageSender` trait impl into a `SendXcm` trait impl.
@@ -63,7 +47,7 @@ impl<T, W, P> SendXcm for ParentAsUmp<T, W, P>
where
T: UpwardMessageSender,
W: WrapVersion,
P: PriceForParentDelivery,
P: PriceForMessageDelivery<Id = ()>,
{
type Ticket = Vec<u8>;
@@ -76,7 +60,7 @@ where
if d.contains_parents_only(1) {
// An upward message for the relay chain.
let xcm = msg.take().ok_or(SendError::MissingArgument)?;
let price = P::price_for_parent_delivery(&xcm);
let price = P::price_for_delivery((), &xcm);
let versioned_xcm =
W::wrap_version(&d, xcm).map_err(|()| SendError::DestinationUnsupported)?;
let data = versioned_xcm.encode();
@@ -280,7 +264,7 @@ pub struct XcmFeesTo32ByteAccount<FungiblesMutateAdapter, AccountId, ReceiverAcc
impl<
FungiblesMutateAdapter: TransactAsset,
AccountId: Clone + Into<[u8; 32]>,
ReceiverAccount: frame_support::traits::Get<Option<AccountId>>,
ReceiverAccount: Get<Option<AccountId>>,
> TakeRevenue for XcmFeesTo32ByteAccount<FungiblesMutateAdapter, AccountId, ReceiverAccount>
{
fn take_revenue(revenue: MultiAsset) {
@@ -288,9 +272,7 @@ impl<
let ok = FungiblesMutateAdapter::deposit_asset(
&revenue,
&(X1(AccountId32 { network: None, id: receiver.into() }).into()),
// We aren't able to track the XCM that initiated the fee deposit, so we create a
// fake message hash here
&XcmContext::with_message_id([0; 32]),
None,
)
.is_ok();
@@ -542,3 +524,58 @@ mod tests {
assert_eq!(trader.buy_weight(weight_to_buy, payment, &ctx), Err(XcmError::NotWithdrawable));
}
}
/// Implementation of `pallet_xcm_benchmarks::EnsureDelivery` which helps to ensure delivery to the
/// parent relay chain. Deposits existential deposit for origin (if needed).
/// Deposits estimated fee to the origin account (if needed).
/// Allows to trigger additional logic for specific `ParaId` (e.g. open HRMP channel) (if neeeded).
#[cfg(feature = "runtime-benchmarks")]
pub struct ToParentDeliveryHelper<XcmConfig, ExistentialDeposit, PriceForDelivery>(
sp_std::marker::PhantomData<(XcmConfig, ExistentialDeposit, PriceForDelivery)>,
);
#[cfg(feature = "runtime-benchmarks")]
impl<
XcmConfig: xcm_executor::Config,
ExistentialDeposit: Get<Option<MultiAsset>>,
PriceForDelivery: PriceForMessageDelivery<Id = ()>,
> pallet_xcm_benchmarks::EnsureDelivery
for ToParentDeliveryHelper<XcmConfig, ExistentialDeposit, PriceForDelivery>
{
fn ensure_successful_delivery(
origin_ref: &MultiLocation,
_dest: &MultiLocation,
fee_reason: xcm_executor::traits::FeeReason,
) -> (Option<xcm_executor::FeesMode>, Option<MultiAssets>) {
use xcm::latest::{MAX_INSTRUCTIONS_TO_DECODE, MAX_ITEMS_IN_MULTIASSETS};
use xcm_executor::{traits::FeeManager, FeesMode};
let mut fees_mode = None;
if !XcmConfig::FeeManager::is_waived(Some(origin_ref), fee_reason) {
// if not waived, we need to set up accounts for paying and receiving fees
// mint ED to origin if needed
if let Some(ed) = ExistentialDeposit::get() {
XcmConfig::AssetTransactor::deposit_asset(&ed, &origin_ref, None).unwrap();
}
// overestimate delivery fee
let mut max_assets: Vec<MultiAsset> = Vec::new();
for i in 0..MAX_ITEMS_IN_MULTIASSETS {
max_assets.push((GeneralIndex(i as u128), 100u128).into());
}
let overestimated_xcm =
vec![WithdrawAsset(max_assets.into()); MAX_INSTRUCTIONS_TO_DECODE as usize].into();
let overestimated_fees = PriceForDelivery::price_for_delivery((), &overestimated_xcm);
// mint overestimated fee to origin
for fee in overestimated_fees.inner() {
XcmConfig::AssetTransactor::deposit_asset(&fee, &origin_ref, None).unwrap();
}
// expected worst case - direct withdraw
fees_mode = Some(FeesMode { jit_withdraw: true });
}
(fees_mode, None)
}
}