Skip to content

Commit c886e4f

Browse files
acatangiufranciscoaguirre
authored andcommitted
xcm-executor: take transport fee from transferred assets if necessary (paritytech#4834)
# Description Sending XCM messages to other chains requires paying a "transport fee". This can be paid either: - from `origin` local account if `jit_withdraw = true`, - taken from Holding register otherwise. This currently works for following hops/scenarios: 1. On destination no transport fee needed (only sending costs, not receiving), 2. Local/originating chain: just set JIT=true and fee will be paid from signed account, 3. Intermediary hops - only if intermediary is acting as reserve between two untrusted chains (aka only for `DepositReserveAsset` instruction) - this was fixed in paritytech#3142 But now we're seeing more complex asset transfers that are mixing reserve transfers with teleports depending on the involved chains. # Example E.g. transferring DOT between Relay and parachain, but through AH (using AH instead of the Relay chain as parachain's DOT reserve). In the `Parachain --1--> AssetHub --2--> Relay` scenario, DOT has to be reserve-withdrawn in leg `1`, then teleported in leg `2`. On the intermediary hop (AssetHub), `InitiateTeleport` fails to send onward message because of missing transport fees. We also can't rely on `jit_withdraw` because the original origin is lost on the way, and even if it weren't we can't rely on the user having funded accounts on each hop along the way. # Solution/Changes - Charge the transport fee in the executor from the transferred assets (if available), - Only charge from transferred assets if JIT_WITHDRAW was not set, - Only charge from transferred assets if unless using XCMv5 `PayFees` where we do not have this problem. # Testing Added regression tests in emulated transfers. Fixes paritytech#4832 Fixes paritytech#6637 --------- Signed-off-by: Adrian Catangiu <adrian@parity.io> Co-authored-by: Francisco Aguirre <franciscoaguirreperez@gmail.com>
1 parent 46d3175 commit c886e4f

15 files changed

Lines changed: 230 additions & 44 deletions

File tree

bridges/snowbridge/primitives/router/src/inbound/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,9 @@ where
358358
}])),
359359
// Perform a deposit reserve to send to destination chain.
360360
DepositReserveAsset {
361-
assets: Definite(vec![dest_para_fee_asset.clone(), asset].into()),
361+
// Send over assets and unspent fees, XCM delivery fee will be charged from
362+
// here.
363+
assets: Wild(AllCounted(2)),
362364
dest: Location::new(1, [Parachain(dest_para_id)]),
363365
xcm: vec![
364366
// Buy execution on target.

cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ pub fn system_para_to_para_sender_assertions(t: SystemParaToParaTest) {
115115
assert_expected_events!(
116116
AssetHubRococo,
117117
vec![
118-
// Transport fees are paid
118+
// Delivery fees are paid
119119
RuntimeEvent::PolkadotXcm(pallet_xcm::Event::FeesPaid { .. }) => {},
120120
]
121121
);
@@ -274,7 +274,7 @@ fn system_para_to_para_assets_sender_assertions(t: SystemParaToParaTest) {
274274
t.args.dest.clone()
275275
),
276276
},
277-
// Transport fees are paid
277+
// Delivery fees are paid
278278
RuntimeEvent::PolkadotXcm(
279279
pallet_xcm::Event::FeesPaid { .. }
280280
) => {},
@@ -305,7 +305,7 @@ fn para_to_system_para_assets_sender_assertions(t: ParaToSystemParaTest) {
305305
owner: *owner == t.sender.account_id,
306306
balance: *balance == t.args.amount,
307307
},
308-
// Transport fees are paid
308+
// Delivery fees are paid
309309
RuntimeEvent::PolkadotXcm(
310310
pallet_xcm::Event::FeesPaid { .. }
311311
) => {},

cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ mod imports {
106106
pub type ParaToParaThroughRelayTest = Test<PenpalA, PenpalB, Westend>;
107107
pub type ParaToParaThroughAHTest = Test<PenpalA, PenpalB, AssetHubWestend>;
108108
pub type RelayToParaThroughAHTest = Test<Westend, PenpalA, AssetHubWestend>;
109+
pub type PenpalToRelayThroughAHTest = Test<PenpalA, Westend, AssetHubWestend>;
109110
}
110111

111112
#[cfg(test)]

cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs

Lines changed: 136 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -658,13 +658,13 @@ fn bidirectional_teleport_foreign_asset_between_para_and_asset_hub_using_explici
658658
}
659659

660660
// ===============================================================
661-
// ===== Transfer - Native Asset - Relay->AssetHub->Parachain ====
661+
// ====== Transfer - Native Asset - Relay->AssetHub->Penpal ======
662662
// ===============================================================
663-
/// Transfers of native asset Relay to Parachain (using AssetHub reserve). Parachains want to avoid
663+
/// Transfers of native asset Relay to Penpal (using AssetHub reserve). Parachains want to avoid
664664
/// managing SAs on all system chains, thus want all their DOT-in-reserve to be held in their
665665
/// Sovereign Account on Asset Hub.
666666
#[test]
667-
fn transfer_native_asset_from_relay_to_para_through_asset_hub() {
667+
fn transfer_native_asset_from_relay_to_penpal_through_asset_hub() {
668668
// Init values for Relay
669669
let destination = Westend::child_location_of(PenpalA::para_id());
670670
let sender = WestendSender::get();
@@ -820,6 +820,137 @@ fn transfer_native_asset_from_relay_to_para_through_asset_hub() {
820820
assert!(receiver_assets_after < receiver_assets_before + amount_to_send);
821821
}
822822

823+
// ===============================================================
824+
// ===== Transfer - Native Asset - Penpal->AssetHub->Relay =======
825+
// ===============================================================
826+
/// Transfers of native asset Penpal to Relay (using AssetHub reserve). Parachains want to avoid
827+
/// managing SAs on all system chains, thus want all their DOT-in-reserve to be held in their
828+
/// Sovereign Account on Asset Hub.
829+
#[test]
830+
fn transfer_native_asset_from_penpal_to_relay_through_asset_hub() {
831+
// Init values for Penpal
832+
let destination = RelayLocation::get();
833+
let sender = PenpalASender::get();
834+
let amount_to_send: Balance = WESTEND_ED * 100;
835+
836+
// Init values for Penpal
837+
let relay_native_asset_location = RelayLocation::get();
838+
let receiver = WestendReceiver::get();
839+
840+
// Init Test
841+
let test_args = TestContext {
842+
sender: sender.clone(),
843+
receiver: receiver.clone(),
844+
args: TestArgs::new_para(
845+
destination.clone(),
846+
receiver.clone(),
847+
amount_to_send,
848+
(Parent, amount_to_send).into(),
849+
None,
850+
0,
851+
),
852+
};
853+
let mut test = PenpalToRelayThroughAHTest::new(test_args);
854+
855+
let sov_penpal_on_ah = AssetHubWestend::sovereign_account_id_of(
856+
AssetHubWestend::sibling_location_of(PenpalA::para_id()),
857+
);
858+
// fund Penpal's sender account
859+
PenpalA::mint_foreign_asset(
860+
<PenpalA as Chain>::RuntimeOrigin::signed(PenpalAssetOwner::get()),
861+
relay_native_asset_location.clone(),
862+
sender.clone(),
863+
amount_to_send * 2,
864+
);
865+
// fund Penpal's SA on AssetHub with the assets held in reserve
866+
AssetHubWestend::fund_accounts(vec![(sov_penpal_on_ah.clone().into(), amount_to_send * 2)]);
867+
868+
// prefund Relay checking account so we accept teleport "back" from AssetHub
869+
let check_account =
870+
Westend::execute_with(|| <Westend as WestendPallet>::XcmPallet::check_account());
871+
Westend::fund_accounts(vec![(check_account, amount_to_send)]);
872+
873+
// Query initial balances
874+
let sender_balance_before = PenpalA::execute_with(|| {
875+
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
876+
<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.clone(), &sender)
877+
});
878+
let sov_penpal_on_ah_before = AssetHubWestend::execute_with(|| {
879+
<AssetHubWestend as AssetHubWestendPallet>::Balances::free_balance(sov_penpal_on_ah.clone())
880+
});
881+
let receiver_balance_before = Westend::execute_with(|| {
882+
<Westend as WestendPallet>::Balances::free_balance(receiver.clone())
883+
});
884+
885+
fn transfer_assets_dispatchable(t: PenpalToRelayThroughAHTest) -> DispatchResult {
886+
let fee_idx = t.args.fee_asset_item as usize;
887+
let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
888+
let asset_hub_location = PenpalA::sibling_location_of(AssetHubWestend::para_id());
889+
let context = PenpalUniversalLocation::get();
890+
891+
// reanchor fees to the view of destination (Westend Relay)
892+
let mut remote_fees = fee.clone().reanchored(&t.args.dest, &context).unwrap();
893+
if let Fungible(ref mut amount) = remote_fees.fun {
894+
// we already spent some fees along the way, just use half of what we started with
895+
*amount = *amount / 2;
896+
}
897+
let xcm_on_final_dest = Xcm::<()>(vec![
898+
BuyExecution { fees: remote_fees, weight_limit: t.args.weight_limit.clone() },
899+
DepositAsset {
900+
assets: Wild(AllCounted(t.args.assets.len() as u32)),
901+
beneficiary: t.args.beneficiary,
902+
},
903+
]);
904+
905+
// reanchor final dest (Westend Relay) to the view of hop (Asset Hub)
906+
let mut dest = t.args.dest.clone();
907+
dest.reanchor(&asset_hub_location, &context).unwrap();
908+
// on Asset Hub
909+
let xcm_on_hop = Xcm::<()>(vec![InitiateTeleport {
910+
assets: Wild(AllCounted(t.args.assets.len() as u32)),
911+
dest,
912+
xcm: xcm_on_final_dest,
913+
}]);
914+
915+
// First leg is a reserve-withdraw, from there a teleport to final dest
916+
<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type_and_then(
917+
t.signed_origin,
918+
bx!(asset_hub_location.into()),
919+
bx!(t.args.assets.into()),
920+
bx!(TransferType::DestinationReserve),
921+
bx!(fee.id.into()),
922+
bx!(TransferType::DestinationReserve),
923+
bx!(VersionedXcm::from(xcm_on_hop)),
924+
t.args.weight_limit,
925+
)
926+
}
927+
test.set_dispatchable::<PenpalA>(transfer_assets_dispatchable);
928+
test.assert();
929+
930+
// Query final balances
931+
let sender_balance_after = PenpalA::execute_with(|| {
932+
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
933+
<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.clone(), &sender)
934+
});
935+
let sov_penpal_on_ah_after = AssetHubWestend::execute_with(|| {
936+
<AssetHubWestend as AssetHubWestendPallet>::Balances::free_balance(sov_penpal_on_ah.clone())
937+
});
938+
let receiver_balance_after = Westend::execute_with(|| {
939+
<Westend as WestendPallet>::Balances::free_balance(receiver.clone())
940+
});
941+
942+
// Sender's asset balance is reduced by amount sent plus delivery fees
943+
assert!(sender_balance_after < sender_balance_before - amount_to_send);
944+
// SA on AH balance is decreased by `amount_to_send`
945+
assert_eq!(sov_penpal_on_ah_after, sov_penpal_on_ah_before - amount_to_send);
946+
// Receiver's balance is increased
947+
assert!(receiver_balance_after > receiver_balance_before);
948+
// Receiver's balance increased by `amount_to_send - delivery_fees - bought_execution`;
949+
// `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but
950+
// should be non-zero
951+
assert!(receiver_balance_after < receiver_balance_before + amount_to_send);
952+
}
953+
823954
// ==============================================================================================
824955
// ==== Bidirectional Transfer - Native + Teleportable Foreign Assets - Parachain<->AssetHub ====
825956
// ==============================================================================================
@@ -839,7 +970,7 @@ fn bidirectional_transfer_multiple_assets_between_penpal_and_asset_hub() {
839970
// xcm to be executed at dest
840971
let xcm_on_dest = Xcm(vec![
841972
// since this is the last hop, we don't need to further use any assets previously
842-
// reserved for fees (there are no further hops to cover transport fees for); we
973+
// reserved for fees (there are no further hops to cover delivery fees for); we
843974
// RefundSurplus to get back any unspent fees
844975
RefundSurplus,
845976
DepositAsset { assets: Wild(All), beneficiary: t.args.beneficiary },
@@ -875,7 +1006,7 @@ fn bidirectional_transfer_multiple_assets_between_penpal_and_asset_hub() {
8751006
// xcm to be executed at dest
8761007
let xcm_on_dest = Xcm(vec![
8771008
// since this is the last hop, we don't need to further use any assets previously
878-
// reserved for fees (there are no further hops to cover transport fees for); we
1009+
// reserved for fees (there are no further hops to cover delivery fees for); we
8791010
// RefundSurplus to get back any unspent fees
8801011
RefundSurplus,
8811012
DepositAsset { assets: Wild(All), beneficiary: t.args.beneficiary },

cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ pub fn system_para_to_para_sender_assertions(t: SystemParaToParaTest) {
115115
assert_expected_events!(
116116
AssetHubWestend,
117117
vec![
118-
// Transport fees are paid
118+
// Delivery fees are paid
119119
RuntimeEvent::PolkadotXcm(pallet_xcm::Event::FeesPaid { .. }) => {},
120120
]
121121
);
@@ -274,7 +274,7 @@ fn system_para_to_para_assets_sender_assertions(t: SystemParaToParaTest) {
274274
t.args.dest.clone()
275275
),
276276
},
277-
// Transport fees are paid
277+
// Delivery fees are paid
278278
RuntimeEvent::PolkadotXcm(
279279
pallet_xcm::Event::FeesPaid { .. }
280280
) => {},
@@ -305,7 +305,7 @@ fn para_to_system_para_assets_sender_assertions(t: ParaToSystemParaTest) {
305305
owner: *owner == t.sender.account_id,
306306
balance: *balance == t.args.amount,
307307
},
308-
// Transport fees are paid
308+
// Delivery fees are paid
309309
RuntimeEvent::PolkadotXcm(
310310
pallet_xcm::Event::FeesPaid { .. }
311311
) => {},

cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ fn transfer_and_transact_in_same_xcm(
4646
Transact { origin_kind: OriginKind::Xcm, call, fallback_max_weight: None },
4747
ExpectTransactStatus(MaybeErrorCode::Success),
4848
// since this is the last hop, we don't need to further use any assets previously
49-
// reserved for fees (there are no further hops to cover transport fees for); we
49+
// reserved for fees (there are no further hops to cover delivery fees for); we
5050
// RefundSurplus to get back any unspent fees
5151
RefundSurplus,
5252
DepositAsset { assets: Wild(All), beneficiary },

cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
use crate::tests::*;
1717

1818
fn send_assets_over_bridge<F: FnOnce()>(send_fn: F) {
19-
// fund the AHR's SA on BHR for paying bridge transport fees
19+
// fund the AHR's SA on BHR for paying bridge delivery fees
2020
BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id(), 10_000_000_000_000u128);
2121

2222
// set XCM versions

cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/register_bridged_assets.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ fn register_rococo_asset_on_wah_from_rah() {
5858

5959
let destination = asset_hub_westend_location();
6060

61-
// fund the RAH's SA on RBH for paying bridge transport fees
61+
// fund the RAH's SA on RBH for paying bridge delivery fees
6262
BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id(), 10_000_000_000_000u128);
6363

6464
// set XCM versions

cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ fn send_xcm_through_opened_lane_with_different_xcm_version_on_hops_works() {
6565
let native_token = Location::parent();
6666
let amount = ASSET_HUB_ROCOCO_ED * 1_000;
6767

68-
// fund the AHR's SA on BHR for paying bridge transport fees
68+
// fund the AHR's SA on BHR for paying bridge delivery fees
6969
BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id(), 10_000_000_000_000u128);
7070
// fund sender
7171
AssetHubRococo::fund_accounts(vec![(AssetHubRococoSender::get().into(), amount * 10)]);

cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use crate::{create_pool_with_native_on, tests::*};
1717
use xcm::latest::AssetTransferFilter;
1818

1919
fn send_assets_over_bridge<F: FnOnce()>(send_fn: F) {
20-
// fund the AHW's SA on BHW for paying bridge transport fees
20+
// fund the AHW's SA on BHW for paying bridge delivery fees
2121
BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id(), 10_000_000_000_000u128);
2222

2323
// set XCM versions
@@ -592,7 +592,7 @@ fn do_send_pens_and_wnds_from_penpal_westend_via_ahw_to_asset_hub_rococo(
592592
// XCM to be executed at dest (Rococo Asset Hub)
593593
let xcm_on_dest = Xcm(vec![
594594
// since this is the last hop, we don't need to further use any assets previously
595-
// reserved for fees (there are no further hops to cover transport fees for); we
595+
// reserved for fees (there are no further hops to cover delivery fees for); we
596596
// RefundSurplus to get back any unspent fees
597597
RefundSurplus,
598598
// deposit everything to final beneficiary

0 commit comments

Comments
 (0)