Skip to content

Commit e0584a1

Browse files
authored
pallet-xcm::transfer_assets_using_type() supports custom actions on destination (#4260)
Change `transfer_assets_using_type()` to not assume `DepositAssets` as the intended use of the assets on the destination. Instead provides the caller with the ability to specify custom XCM that be executed on `dest` chain as the last step of the transfer, thus allowing custom usecases for the transferred assets. E.g. some are used/swapped/etc there, while some are sent further to yet another chain. Note: this is a follow-up on #3695, bringing in an API change for `transfer_assets_using_type()`. This is ok as the previous version has not been yet released. Thus, its first release will include the new API proposed by this PR. This allows usecases such as: https://forum.polkadot.network/t/managing-sas-on-multiple-reserve-chains-for-same-asset/7538/4 BTW: all this pallet-xcm asset transfers code will be massively reduced once we have polkadot-fellows/xcm-format#54 --------- Signed-off-by: Adrian Catangiu <[email protected]>
1 parent 9a0049d commit e0584a1

File tree

12 files changed

+530
-115
lines changed

12 files changed

+530
-115
lines changed

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ mod imports {
7070
LocalReservableFromAssetHub as PenpalLocalReservableFromAssetHub,
7171
LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub,
7272
};
73-
pub use rococo_runtime::xcm_config::XcmConfig as RococoXcmConfig;
73+
pub use rococo_runtime::xcm_config::{
74+
UniversalLocation as RococoUniversalLocation, XcmConfig as RococoXcmConfig,
75+
};
7476

7577
pub const ASSET_ID: u32 = 3;
7678
pub const ASSET_MIN_BALANCE: u128 = 1000;
@@ -83,6 +85,7 @@ mod imports {
8385
pub type ParaToSystemParaTest = Test<PenpalA, AssetHubRococo>;
8486
pub type ParaToParaThroughRelayTest = Test<PenpalA, PenpalB, Rococo>;
8587
pub type ParaToParaThroughAHTest = Test<PenpalA, PenpalB, AssetHubRococo>;
88+
pub type RelayToParaThroughAHTest = Test<Rococo, PenpalA, AssetHubRococo>;
8689
}
8790

8891
#[cfg(test)]

cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/foreign_assets_transfers.rs renamed to cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs

Lines changed: 193 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,29 +54,37 @@ fn para_to_para_assethub_hop_assertions(t: ParaToParaThroughAHTest) {
5454
fn ah_to_para_transfer_assets(t: SystemParaToParaTest) -> DispatchResult {
5555
let fee_idx = t.args.fee_asset_item as usize;
5656
let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
57-
<AssetHubRococo as AssetHubRococoPallet>::PolkadotXcm::transfer_assets_using_type(
57+
let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset {
58+
assets: Wild(AllCounted(t.args.assets.len() as u32)),
59+
beneficiary: t.args.beneficiary,
60+
}]);
61+
<AssetHubRococo as AssetHubRococoPallet>::PolkadotXcm::transfer_assets_using_type_and_then(
5862
t.signed_origin,
5963
bx!(t.args.dest.into()),
60-
bx!(t.args.beneficiary.into()),
6164
bx!(t.args.assets.into()),
6265
bx!(TransferType::LocalReserve),
6366
bx!(fee.id.into()),
6467
bx!(TransferType::LocalReserve),
68+
bx!(VersionedXcm::from(custom_xcm_on_dest)),
6569
t.args.weight_limit,
6670
)
6771
}
6872

6973
fn para_to_ah_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult {
7074
let fee_idx = t.args.fee_asset_item as usize;
7175
let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
72-
<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type(
76+
let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset {
77+
assets: Wild(AllCounted(t.args.assets.len() as u32)),
78+
beneficiary: t.args.beneficiary,
79+
}]);
80+
<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type_and_then(
7381
t.signed_origin,
7482
bx!(t.args.dest.into()),
75-
bx!(t.args.beneficiary.into()),
7683
bx!(t.args.assets.into()),
7784
bx!(TransferType::DestinationReserve),
7885
bx!(fee.id.into()),
7986
bx!(TransferType::DestinationReserve),
87+
bx!(VersionedXcm::from(custom_xcm_on_dest)),
8088
t.args.weight_limit,
8189
)
8290
}
@@ -85,44 +93,56 @@ fn para_to_para_transfer_assets_through_ah(t: ParaToParaThroughAHTest) -> Dispat
8593
let fee_idx = t.args.fee_asset_item as usize;
8694
let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
8795
let asset_hub_location: Location = PenpalA::sibling_location_of(AssetHubRococo::para_id());
88-
<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type(
96+
let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset {
97+
assets: Wild(AllCounted(t.args.assets.len() as u32)),
98+
beneficiary: t.args.beneficiary,
99+
}]);
100+
<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type_and_then(
89101
t.signed_origin,
90102
bx!(t.args.dest.into()),
91-
bx!(t.args.beneficiary.into()),
92103
bx!(t.args.assets.into()),
93104
bx!(TransferType::RemoteReserve(asset_hub_location.clone().into())),
94105
bx!(fee.id.into()),
95106
bx!(TransferType::RemoteReserve(asset_hub_location.into())),
107+
bx!(VersionedXcm::from(custom_xcm_on_dest)),
96108
t.args.weight_limit,
97109
)
98110
}
99111

100112
fn para_to_asset_hub_teleport_foreign_assets(t: ParaToSystemParaTest) -> DispatchResult {
101113
let fee_idx = t.args.fee_asset_item as usize;
102114
let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
103-
<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type(
115+
let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset {
116+
assets: Wild(AllCounted(t.args.assets.len() as u32)),
117+
beneficiary: t.args.beneficiary,
118+
}]);
119+
<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type_and_then(
104120
t.signed_origin,
105121
bx!(t.args.dest.into()),
106-
bx!(t.args.beneficiary.into()),
107122
bx!(t.args.assets.into()),
108123
bx!(TransferType::Teleport),
109124
bx!(fee.id.into()),
110125
bx!(TransferType::DestinationReserve),
126+
bx!(VersionedXcm::from(custom_xcm_on_dest)),
111127
t.args.weight_limit,
112128
)
113129
}
114130

115131
fn asset_hub_to_para_teleport_foreign_assets(t: SystemParaToParaTest) -> DispatchResult {
116132
let fee_idx = t.args.fee_asset_item as usize;
117133
let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
118-
<AssetHubRococo as AssetHubRococoPallet>::PolkadotXcm::transfer_assets_using_type(
134+
let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset {
135+
assets: Wild(AllCounted(t.args.assets.len() as u32)),
136+
beneficiary: t.args.beneficiary,
137+
}]);
138+
<AssetHubRococo as AssetHubRococoPallet>::PolkadotXcm::transfer_assets_using_type_and_then(
119139
t.signed_origin,
120140
bx!(t.args.dest.into()),
121-
bx!(t.args.beneficiary.into()),
122141
bx!(t.args.assets.into()),
123142
bx!(TransferType::Teleport),
124143
bx!(fee.id.into()),
125144
bx!(TransferType::LocalReserve),
145+
bx!(VersionedXcm::from(custom_xcm_on_dest)),
126146
t.args.weight_limit,
127147
)
128148
}
@@ -626,3 +646,166 @@ fn bidirectional_teleport_foreign_asset_between_para_and_asset_hub_using_explici
626646
asset_hub_to_para_teleport_foreign_assets,
627647
);
628648
}
649+
650+
// ===============================================================
651+
// ===== Transfer - Native Asset - Relay->AssetHub->Parachain ====
652+
// ===============================================================
653+
/// Transfers of native asset Relay to Parachain (using AssetHub reserve). Parachains want to avoid
654+
/// managing SAs on all system chains, thus want all their DOT-in-reserve to be held in their
655+
/// Sovereign Account on Asset Hub.
656+
#[test]
657+
fn transfer_native_asset_from_relay_to_para_through_asset_hub() {
658+
// Init values for Relay
659+
let destination = Rococo::child_location_of(PenpalA::para_id());
660+
let sender = RococoSender::get();
661+
let amount_to_send: Balance = ROCOCO_ED * 1000;
662+
663+
// Init values for Parachain
664+
let relay_native_asset_location = RelayLocation::get();
665+
let receiver = PenpalAReceiver::get();
666+
667+
// Init Test
668+
let test_args = TestContext {
669+
sender,
670+
receiver: receiver.clone(),
671+
args: TestArgs::new_relay(destination.clone(), receiver.clone(), amount_to_send),
672+
};
673+
let mut test = RelayToParaThroughAHTest::new(test_args);
674+
675+
let sov_penpal_on_ah = AssetHubRococo::sovereign_account_id_of(
676+
AssetHubRococo::sibling_location_of(PenpalA::para_id()),
677+
);
678+
// Query initial balances
679+
let sender_balance_before = test.sender.balance;
680+
let sov_penpal_on_ah_before = AssetHubRococo::execute_with(|| {
681+
<AssetHubRococo as AssetHubRococoPallet>::Balances::free_balance(sov_penpal_on_ah.clone())
682+
});
683+
let receiver_assets_before = PenpalA::execute_with(|| {
684+
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
685+
<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.clone(), &receiver)
686+
});
687+
688+
fn relay_assertions(t: RelayToParaThroughAHTest) {
689+
type RuntimeEvent = <Rococo as Chain>::RuntimeEvent;
690+
Rococo::assert_xcm_pallet_attempted_complete(None);
691+
assert_expected_events!(
692+
Rococo,
693+
vec![
694+
// Amount to teleport is withdrawn from Sender
695+
RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => {
696+
who: *who == t.sender.account_id,
697+
amount: *amount == t.args.amount,
698+
},
699+
// Amount to teleport is deposited in Relay's `CheckAccount`
700+
RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount }) => {
701+
who: *who == <Rococo as RococoPallet>::XcmPallet::check_account(),
702+
amount: *amount == t.args.amount,
703+
},
704+
]
705+
);
706+
}
707+
fn asset_hub_assertions(_: RelayToParaThroughAHTest) {
708+
type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent;
709+
let sov_penpal_on_ah = AssetHubRococo::sovereign_account_id_of(
710+
AssetHubRococo::sibling_location_of(PenpalA::para_id()),
711+
);
712+
assert_expected_events!(
713+
AssetHubRococo,
714+
vec![
715+
// Deposited to receiver parachain SA
716+
RuntimeEvent::Balances(
717+
pallet_balances::Event::Minted { who, .. }
718+
) => {
719+
who: *who == sov_penpal_on_ah,
720+
},
721+
RuntimeEvent::MessageQueue(
722+
pallet_message_queue::Event::Processed { success: true, .. }
723+
) => {},
724+
]
725+
);
726+
}
727+
fn penpal_assertions(t: RelayToParaThroughAHTest) {
728+
type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
729+
let expected_id =
730+
t.args.assets.into_inner().first().unwrap().id.0.clone().try_into().unwrap();
731+
assert_expected_events!(
732+
PenpalA,
733+
vec![
734+
RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => {
735+
asset_id: *asset_id == expected_id,
736+
owner: *owner == t.receiver.account_id,
737+
},
738+
]
739+
);
740+
}
741+
fn transfer_assets_dispatchable(t: RelayToParaThroughAHTest) -> DispatchResult {
742+
let fee_idx = t.args.fee_asset_item as usize;
743+
let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
744+
let asset_hub_location = Rococo::child_location_of(AssetHubRococo::para_id());
745+
let context = RococoUniversalLocation::get();
746+
747+
// reanchor fees to the view of destination (Penpal)
748+
let mut remote_fees = fee.clone().reanchored(&t.args.dest, &context).unwrap();
749+
if let Fungible(ref mut amount) = remote_fees.fun {
750+
// we already spent some fees along the way, just use half of what we started with
751+
*amount = *amount / 2;
752+
}
753+
let xcm_on_final_dest = Xcm::<()>(vec![
754+
BuyExecution { fees: remote_fees, weight_limit: t.args.weight_limit.clone() },
755+
DepositAsset {
756+
assets: Wild(AllCounted(t.args.assets.len() as u32)),
757+
beneficiary: t.args.beneficiary,
758+
},
759+
]);
760+
761+
// reanchor final dest (Penpal) to the view of hop (Asset Hub)
762+
let mut dest = t.args.dest.clone();
763+
dest.reanchor(&asset_hub_location, &context).unwrap();
764+
// on Asset Hub, forward assets to Penpal
765+
let xcm_on_hop = Xcm::<()>(vec![DepositReserveAsset {
766+
assets: Wild(AllCounted(t.args.assets.len() as u32)),
767+
dest,
768+
xcm: xcm_on_final_dest,
769+
}]);
770+
771+
// First leg is a teleport, from there a local-reserve-transfer to final dest
772+
<Rococo as RococoPallet>::XcmPallet::transfer_assets_using_type_and_then(
773+
t.signed_origin,
774+
bx!(asset_hub_location.into()),
775+
bx!(t.args.assets.into()),
776+
bx!(TransferType::Teleport),
777+
bx!(fee.id.into()),
778+
bx!(TransferType::Teleport),
779+
bx!(VersionedXcm::from(xcm_on_hop)),
780+
t.args.weight_limit,
781+
)
782+
}
783+
784+
// Set assertions and dispatchables
785+
test.set_assertion::<Rococo>(relay_assertions);
786+
test.set_assertion::<AssetHubRococo>(asset_hub_assertions);
787+
test.set_assertion::<PenpalA>(penpal_assertions);
788+
test.set_dispatchable::<Rococo>(transfer_assets_dispatchable);
789+
test.assert();
790+
791+
// Query final balances
792+
let sender_balance_after = test.sender.balance;
793+
let sov_penpal_on_ah_after = AssetHubRococo::execute_with(|| {
794+
<AssetHubRococo as AssetHubRococoPallet>::Balances::free_balance(sov_penpal_on_ah)
795+
});
796+
let receiver_assets_after = PenpalA::execute_with(|| {
797+
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
798+
<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location, &receiver)
799+
});
800+
801+
// Sender's balance is reduced by amount sent plus delivery fees
802+
assert!(sender_balance_after < sender_balance_before - amount_to_send);
803+
// SA on AH balance is increased
804+
assert!(sov_penpal_on_ah_after > sov_penpal_on_ah_before);
805+
// Receiver's asset balance is increased
806+
assert!(receiver_assets_after > receiver_assets_before);
807+
// Receiver's asset balance increased by `amount_to_send - delivery_fees - bought_execution`;
808+
// `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but
809+
// should be non-zero
810+
assert!(receiver_assets_after < receiver_assets_before + amount_to_send);
811+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
// See the License for the specific language governing permissions and
1414
// limitations under the License.
1515

16-
mod foreign_assets_transfers;
16+
mod hybrid_transfers;
1717
mod reserve_transfer;
1818
mod send;
1919
mod set_xcm_versions;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -574,7 +574,7 @@ fn reserve_transfer_native_asset_from_relay_to_para() {
574574
let sender = RococoSender::get();
575575
let amount_to_send: Balance = ROCOCO_ED * 1000;
576576

577-
// Init values fot Parachain
577+
// Init values for Parachain
578578
let relay_native_asset_location = RelayLocation::get();
579579
let receiver = PenpalAReceiver::get();
580580

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,9 @@ mod imports {
7474
LocalReservableFromAssetHub as PenpalLocalReservableFromAssetHub,
7575
LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub,
7676
};
77-
pub use westend_runtime::xcm_config::XcmConfig as WestendXcmConfig;
77+
pub use westend_runtime::xcm_config::{
78+
UniversalLocation as WestendUniversalLocation, XcmConfig as WestendXcmConfig,
79+
};
7880

7981
pub const ASSET_ID: u32 = 3;
8082
pub const ASSET_MIN_BALANCE: u128 = 1000;
@@ -87,6 +89,7 @@ mod imports {
8789
pub type ParaToSystemParaTest = Test<PenpalA, AssetHubWestend>;
8890
pub type ParaToParaThroughRelayTest = Test<PenpalA, PenpalB, Westend>;
8991
pub type ParaToParaThroughAHTest = Test<PenpalA, PenpalB, AssetHubWestend>;
92+
pub type RelayToParaThroughAHTest = Test<Westend, PenpalA, AssetHubWestend>;
9093
}
9194

9295
#[cfg(test)]

0 commit comments

Comments
 (0)