mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-31 11:01:01 +00:00
[pallet-xcm] fix transport fees for remote reserve transfers (#3792)
Currently `transfer_assets` from pallet-xcm covers 4 main different transfer types: - `localReserve` - `DestinationReserve` - `Teleport` - `RemoteReserve` For the first three, the local execution and the remote message sending are separated, and fees are deducted in pallet-xcm itself: https://github.com/paritytech/polkadot-sdk/blob/3410dfb3929462da88be2da813f121d8b1cf46b3/polkadot/xcm/pallet-xcm/src/lib.rs#L1758. For the 4th case `RemoteReserve`, pallet-xcm is still relying on the xcm-executor itself to send the message (through the `initiateReserveWithdraw` instruction). In this case, if delivery fees need to be charged, it is not possible to do so because the `jit_withdraw` mode has not being set. This PR proposes to still use the `initiateReserveWithdraw` but prepending a `setFeesMode { jit_withdraw: true }` to make sure delivery fees can be paid. A test-case is also added to present the aforementioned case --------- Co-authored-by: Adrian Catangiu <adrian@parity.io>
This commit is contained in:
@@ -1990,6 +1990,7 @@ impl<T: Config> Pallet<T> {
|
||||
]);
|
||||
Ok(Xcm(vec![
|
||||
WithdrawAsset(assets.into()),
|
||||
SetFeesMode { jit_withdraw: true },
|
||||
InitiateReserveWithdraw {
|
||||
assets: Wild(AllCounted(max_assets)),
|
||||
reserve,
|
||||
|
||||
@@ -361,6 +361,10 @@ parameter_types! {
|
||||
0,
|
||||
[Parachain(FOREIGN_ASSET_RESERVE_PARA_ID)]
|
||||
);
|
||||
pub PaidParaForeignReserveLocation: Location = Location::new(
|
||||
0,
|
||||
[Parachain(Para3000::get())]
|
||||
);
|
||||
pub ForeignAsset: Asset = Asset {
|
||||
fun: Fungible(10),
|
||||
id: AssetId(Location::new(
|
||||
@@ -368,6 +372,13 @@ parameter_types! {
|
||||
[Parachain(FOREIGN_ASSET_RESERVE_PARA_ID), FOREIGN_ASSET_INNER_JUNCTION],
|
||||
)),
|
||||
};
|
||||
pub PaidParaForeignAsset: Asset = Asset {
|
||||
fun: Fungible(10),
|
||||
id: AssetId(Location::new(
|
||||
0,
|
||||
[Parachain(Para3000::get())],
|
||||
)),
|
||||
};
|
||||
pub UsdcReserveLocation: Location = Location::new(
|
||||
0,
|
||||
[Parachain(USDC_RESERVE_PARA_ID)]
|
||||
@@ -450,6 +461,8 @@ parameter_types! {
|
||||
pub TrustedFilteredTeleport: (AssetFilter, Location) = (FilteredTeleportAsset::get().into(), FilteredTeleportLocation::get());
|
||||
pub TeleportUsdtToForeign: (AssetFilter, Location) = (Usdt::get().into(), ForeignReserveLocation::get());
|
||||
pub TrustedForeign: (AssetFilter, Location) = (ForeignAsset::get().into(), ForeignReserveLocation::get());
|
||||
pub TrustedPaidParaForeign: (AssetFilter, Location) = (PaidParaForeignAsset::get().into(), PaidParaForeignReserveLocation::get());
|
||||
|
||||
pub TrustedUsdc: (AssetFilter, Location) = (Usdc::get().into(), UsdcReserveLocation::get());
|
||||
pub const MaxInstructions: u32 = 100;
|
||||
pub const MaxAssetsIntoHolding: u32 = 64;
|
||||
@@ -483,7 +496,7 @@ impl xcm_executor::Config for XcmConfig {
|
||||
type XcmSender = XcmRouter;
|
||||
type AssetTransactor = AssetTransactors;
|
||||
type OriginConverter = LocalOriginConverter;
|
||||
type IsReserve = (Case<TrustedForeign>, Case<TrustedUsdc>);
|
||||
type IsReserve = (Case<TrustedForeign>, Case<TrustedUsdc>, Case<TrustedPaidParaForeign>);
|
||||
type IsTeleporter = (
|
||||
Case<TrustedLocal>,
|
||||
Case<TrustedSystemPara>,
|
||||
|
||||
@@ -2604,3 +2604,174 @@ fn teleport_assets_using_destination_reserve_fee_disallowed() {
|
||||
expected_result,
|
||||
);
|
||||
}
|
||||
|
||||
/// Test `tested_call` transferring single asset using remote reserve.
|
||||
///
|
||||
/// Transferring Para3000 asset (`Para3000` reserve) to
|
||||
/// `OTHER_PARA_ID` (no teleport trust), therefore triggering remote reserve.
|
||||
/// Using the same asset asset (Para3000 reserve) for fees.
|
||||
///
|
||||
/// Asserts that the sender's balance is decreased and the beneficiary's balance
|
||||
/// is increased. Verifies the correct message is sent and event is emitted.
|
||||
///
|
||||
/// Verifies that XCM router fees (`SendXcm::validate` -> `Assets`) are withdrawn from correct
|
||||
/// user account and deposited to a correct target account (`XcmFeesTargetAccount`).
|
||||
/// Verifies `expected_result`.
|
||||
fn remote_asset_reserve_and_remote_fee_reserve_paid_call<Call>(
|
||||
tested_call: Call,
|
||||
expected_result: DispatchResult,
|
||||
) where
|
||||
Call: FnOnce(
|
||||
OriginFor<Test>,
|
||||
Box<VersionedLocation>,
|
||||
Box<VersionedLocation>,
|
||||
Box<VersionedAssets>,
|
||||
u32,
|
||||
WeightLimit,
|
||||
) -> DispatchResult,
|
||||
{
|
||||
let weight = BaseXcmWeight::get() * 3;
|
||||
let user_account = AccountId::from(XCM_FEES_NOT_WAIVED_USER_ACCOUNT);
|
||||
let xcm_router_fee_amount = Para3000PaymentAmount::get();
|
||||
let paid_para_id = Para3000::get();
|
||||
let balances = vec![
|
||||
(user_account.clone(), INITIAL_BALANCE),
|
||||
(ParaId::from(paid_para_id).into_account_truncating(), INITIAL_BALANCE),
|
||||
(XcmFeesTargetAccount::get(), INITIAL_BALANCE),
|
||||
];
|
||||
let beneficiary: Location = Junction::AccountId32 { network: None, id: ALICE.into() }.into();
|
||||
new_test_ext_with_balances(balances).execute_with(|| {
|
||||
// create sufficient foreign asset BLA
|
||||
let foreign_initial_amount = 142;
|
||||
let (reserve_location, _, foreign_asset_id_location) = set_up_foreign_asset(
|
||||
paid_para_id,
|
||||
None,
|
||||
user_account.clone(),
|
||||
foreign_initial_amount,
|
||||
true,
|
||||
);
|
||||
|
||||
// transfer destination is another chain that is not the reserve location
|
||||
// the goal is to trigger the remoteReserve case
|
||||
let dest = RelayLocation::get().pushed_with_interior(Parachain(OTHER_PARA_ID)).unwrap();
|
||||
|
||||
let transferred_asset: Assets = (foreign_asset_id_location.clone(), SEND_AMOUNT).into();
|
||||
|
||||
// balances checks before
|
||||
assert_eq!(
|
||||
AssetsPallet::balance(foreign_asset_id_location.clone(), user_account.clone()),
|
||||
foreign_initial_amount
|
||||
);
|
||||
assert_eq!(Balances::free_balance(user_account.clone()), INITIAL_BALANCE);
|
||||
|
||||
// do the transfer
|
||||
let result = tested_call(
|
||||
RuntimeOrigin::signed(user_account.clone()),
|
||||
Box::new(dest.clone().into()),
|
||||
Box::new(beneficiary.clone().into()),
|
||||
Box::new(transferred_asset.into()),
|
||||
0 as u32,
|
||||
Unlimited,
|
||||
);
|
||||
assert_eq!(result, expected_result);
|
||||
if expected_result.is_err() {
|
||||
// short-circuit here for tests where we expect failure
|
||||
return;
|
||||
}
|
||||
|
||||
let mut last_events = last_events(7).into_iter();
|
||||
// asset events
|
||||
// forceCreate
|
||||
last_events.next().unwrap();
|
||||
// mint tokens
|
||||
last_events.next().unwrap();
|
||||
// burn tokens
|
||||
last_events.next().unwrap();
|
||||
// balance events
|
||||
// burn delivery fee
|
||||
last_events.next().unwrap();
|
||||
// mint delivery fee
|
||||
last_events.next().unwrap();
|
||||
assert_eq!(
|
||||
last_events.next().unwrap(),
|
||||
RuntimeEvent::XcmPallet(crate::Event::Attempted {
|
||||
outcome: Outcome::Complete { used: weight }
|
||||
})
|
||||
);
|
||||
|
||||
// user account spent (transferred) amount
|
||||
assert_eq!(
|
||||
AssetsPallet::balance(foreign_asset_id_location.clone(), user_account.clone()),
|
||||
foreign_initial_amount - SEND_AMOUNT
|
||||
);
|
||||
|
||||
// user account spent delivery fees
|
||||
assert_eq!(Balances::free_balance(user_account), INITIAL_BALANCE - xcm_router_fee_amount);
|
||||
|
||||
// XcmFeesTargetAccount where should lend xcm_router_fee_amount
|
||||
assert_eq!(
|
||||
Balances::free_balance(XcmFeesTargetAccount::get()),
|
||||
INITIAL_BALANCE + xcm_router_fee_amount
|
||||
);
|
||||
|
||||
// Verify total and active issuance of foreign BLA have decreased (burned on
|
||||
// reserve-withdraw)
|
||||
let expected_issuance = foreign_initial_amount - SEND_AMOUNT;
|
||||
assert_eq!(
|
||||
AssetsPallet::total_issuance(foreign_asset_id_location.clone()),
|
||||
expected_issuance
|
||||
);
|
||||
assert_eq!(
|
||||
AssetsPallet::active_issuance(foreign_asset_id_location.clone()),
|
||||
expected_issuance
|
||||
);
|
||||
|
||||
let context = UniversalLocation::get();
|
||||
let foreign_id_location_reanchored =
|
||||
foreign_asset_id_location.reanchored(&dest, &context).unwrap();
|
||||
let dest_reanchored = dest.reanchored(&reserve_location, &context).unwrap();
|
||||
|
||||
// Verify sent XCM program
|
||||
assert_eq!(
|
||||
sent_xcm(),
|
||||
vec![(
|
||||
reserve_location,
|
||||
// `assets` are burned on source and withdrawn from SA in remote reserve chain
|
||||
Xcm(vec![
|
||||
WithdrawAsset((Location::here(), SEND_AMOUNT).into()),
|
||||
ClearOrigin,
|
||||
buy_execution((Location::here(), SEND_AMOUNT / 2)),
|
||||
DepositReserveAsset {
|
||||
assets: Wild(AllCounted(1)),
|
||||
// final destination is `dest` as seen by `reserve`
|
||||
dest: dest_reanchored,
|
||||
// message sent onward to `dest`
|
||||
xcm: Xcm(vec![
|
||||
buy_execution((foreign_id_location_reanchored, SEND_AMOUNT / 2)),
|
||||
DepositAsset { assets: AllCounted(1).into(), beneficiary }
|
||||
])
|
||||
}
|
||||
])
|
||||
)]
|
||||
);
|
||||
});
|
||||
}
|
||||
/// Test `transfer_assets` with remote asset reserve and remote fee reserve.
|
||||
#[test]
|
||||
fn transfer_assets_with_remote_asset_reserve_and_remote_asset_fee_reserve_paid_works() {
|
||||
let expected_result = Ok(());
|
||||
remote_asset_reserve_and_remote_fee_reserve_paid_call(
|
||||
XcmPallet::transfer_assets,
|
||||
expected_result,
|
||||
);
|
||||
}
|
||||
/// Test `limited_reserve_transfer_assets` with remote asset reserve and remote fee reserve.
|
||||
#[test]
|
||||
fn limited_reserve_transfer_assets_with_remote_asset_reserve_and_remote_asset_fee_reserve_paid_works(
|
||||
) {
|
||||
let expected_result = Ok(());
|
||||
remote_asset_reserve_and_remote_fee_reserve_paid_call(
|
||||
XcmPallet::limited_reserve_transfer_assets,
|
||||
expected_result,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user