mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 20:11:09 +00:00
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 https://github.com/paritytech/polkadot-sdk/pull/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 https://github.com/paritytech/xcm-format/pull/54 --------- Signed-off-by: Adrian Catangiu <adrian@parity.io>
This commit is contained in:
+4
-1
@@ -70,7 +70,9 @@ mod imports {
|
||||
LocalReservableFromAssetHub as PenpalLocalReservableFromAssetHub,
|
||||
LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub,
|
||||
};
|
||||
pub use rococo_runtime::xcm_config::XcmConfig as RococoXcmConfig;
|
||||
pub use rococo_runtime::xcm_config::{
|
||||
UniversalLocation as RococoUniversalLocation, XcmConfig as RococoXcmConfig,
|
||||
};
|
||||
|
||||
pub const ASSET_ID: u32 = 3;
|
||||
pub const ASSET_MIN_BALANCE: u128 = 1000;
|
||||
@@ -83,6 +85,7 @@ mod imports {
|
||||
pub type ParaToSystemParaTest = Test<PenpalA, AssetHubRococo>;
|
||||
pub type ParaToParaThroughRelayTest = Test<PenpalA, PenpalB, Rococo>;
|
||||
pub type ParaToParaThroughAHTest = Test<PenpalA, PenpalB, AssetHubRococo>;
|
||||
pub type RelayToParaThroughAHTest = Test<Rococo, PenpalA, AssetHubRococo>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
+193
-10
@@ -54,14 +54,18 @@ fn para_to_para_assethub_hop_assertions(t: ParaToParaThroughAHTest) {
|
||||
fn ah_to_para_transfer_assets(t: SystemParaToParaTest) -> DispatchResult {
|
||||
let fee_idx = t.args.fee_asset_item as usize;
|
||||
let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
|
||||
<AssetHubRococo as AssetHubRococoPallet>::PolkadotXcm::transfer_assets_using_type(
|
||||
let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset {
|
||||
assets: Wild(AllCounted(t.args.assets.len() as u32)),
|
||||
beneficiary: t.args.beneficiary,
|
||||
}]);
|
||||
<AssetHubRococo as AssetHubRococoPallet>::PolkadotXcm::transfer_assets_using_type_and_then(
|
||||
t.signed_origin,
|
||||
bx!(t.args.dest.into()),
|
||||
bx!(t.args.beneficiary.into()),
|
||||
bx!(t.args.assets.into()),
|
||||
bx!(TransferType::LocalReserve),
|
||||
bx!(fee.id.into()),
|
||||
bx!(TransferType::LocalReserve),
|
||||
bx!(VersionedXcm::from(custom_xcm_on_dest)),
|
||||
t.args.weight_limit,
|
||||
)
|
||||
}
|
||||
@@ -69,14 +73,18 @@ fn ah_to_para_transfer_assets(t: SystemParaToParaTest) -> DispatchResult {
|
||||
fn para_to_ah_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult {
|
||||
let fee_idx = t.args.fee_asset_item as usize;
|
||||
let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
|
||||
<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type(
|
||||
let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset {
|
||||
assets: Wild(AllCounted(t.args.assets.len() as u32)),
|
||||
beneficiary: t.args.beneficiary,
|
||||
}]);
|
||||
<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type_and_then(
|
||||
t.signed_origin,
|
||||
bx!(t.args.dest.into()),
|
||||
bx!(t.args.beneficiary.into()),
|
||||
bx!(t.args.assets.into()),
|
||||
bx!(TransferType::DestinationReserve),
|
||||
bx!(fee.id.into()),
|
||||
bx!(TransferType::DestinationReserve),
|
||||
bx!(VersionedXcm::from(custom_xcm_on_dest)),
|
||||
t.args.weight_limit,
|
||||
)
|
||||
}
|
||||
@@ -85,14 +93,18 @@ fn para_to_para_transfer_assets_through_ah(t: ParaToParaThroughAHTest) -> Dispat
|
||||
let fee_idx = t.args.fee_asset_item as usize;
|
||||
let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
|
||||
let asset_hub_location: Location = PenpalA::sibling_location_of(AssetHubRococo::para_id());
|
||||
<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type(
|
||||
let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset {
|
||||
assets: Wild(AllCounted(t.args.assets.len() as u32)),
|
||||
beneficiary: t.args.beneficiary,
|
||||
}]);
|
||||
<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type_and_then(
|
||||
t.signed_origin,
|
||||
bx!(t.args.dest.into()),
|
||||
bx!(t.args.beneficiary.into()),
|
||||
bx!(t.args.assets.into()),
|
||||
bx!(TransferType::RemoteReserve(asset_hub_location.clone().into())),
|
||||
bx!(fee.id.into()),
|
||||
bx!(TransferType::RemoteReserve(asset_hub_location.into())),
|
||||
bx!(VersionedXcm::from(custom_xcm_on_dest)),
|
||||
t.args.weight_limit,
|
||||
)
|
||||
}
|
||||
@@ -100,14 +112,18 @@ fn para_to_para_transfer_assets_through_ah(t: ParaToParaThroughAHTest) -> Dispat
|
||||
fn para_to_asset_hub_teleport_foreign_assets(t: ParaToSystemParaTest) -> DispatchResult {
|
||||
let fee_idx = t.args.fee_asset_item as usize;
|
||||
let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
|
||||
<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type(
|
||||
let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset {
|
||||
assets: Wild(AllCounted(t.args.assets.len() as u32)),
|
||||
beneficiary: t.args.beneficiary,
|
||||
}]);
|
||||
<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type_and_then(
|
||||
t.signed_origin,
|
||||
bx!(t.args.dest.into()),
|
||||
bx!(t.args.beneficiary.into()),
|
||||
bx!(t.args.assets.into()),
|
||||
bx!(TransferType::Teleport),
|
||||
bx!(fee.id.into()),
|
||||
bx!(TransferType::DestinationReserve),
|
||||
bx!(VersionedXcm::from(custom_xcm_on_dest)),
|
||||
t.args.weight_limit,
|
||||
)
|
||||
}
|
||||
@@ -115,14 +131,18 @@ fn para_to_asset_hub_teleport_foreign_assets(t: ParaToSystemParaTest) -> Dispatc
|
||||
fn asset_hub_to_para_teleport_foreign_assets(t: SystemParaToParaTest) -> DispatchResult {
|
||||
let fee_idx = t.args.fee_asset_item as usize;
|
||||
let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
|
||||
<AssetHubRococo as AssetHubRococoPallet>::PolkadotXcm::transfer_assets_using_type(
|
||||
let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset {
|
||||
assets: Wild(AllCounted(t.args.assets.len() as u32)),
|
||||
beneficiary: t.args.beneficiary,
|
||||
}]);
|
||||
<AssetHubRococo as AssetHubRococoPallet>::PolkadotXcm::transfer_assets_using_type_and_then(
|
||||
t.signed_origin,
|
||||
bx!(t.args.dest.into()),
|
||||
bx!(t.args.beneficiary.into()),
|
||||
bx!(t.args.assets.into()),
|
||||
bx!(TransferType::Teleport),
|
||||
bx!(fee.id.into()),
|
||||
bx!(TransferType::LocalReserve),
|
||||
bx!(VersionedXcm::from(custom_xcm_on_dest)),
|
||||
t.args.weight_limit,
|
||||
)
|
||||
}
|
||||
@@ -626,3 +646,166 @@ fn bidirectional_teleport_foreign_asset_between_para_and_asset_hub_using_explici
|
||||
asset_hub_to_para_teleport_foreign_assets,
|
||||
);
|
||||
}
|
||||
|
||||
// ===============================================================
|
||||
// ===== Transfer - Native Asset - Relay->AssetHub->Parachain ====
|
||||
// ===============================================================
|
||||
/// Transfers of native asset Relay to Parachain (using AssetHub reserve). Parachains want to avoid
|
||||
/// managing SAs on all system chains, thus want all their DOT-in-reserve to be held in their
|
||||
/// Sovereign Account on Asset Hub.
|
||||
#[test]
|
||||
fn transfer_native_asset_from_relay_to_para_through_asset_hub() {
|
||||
// Init values for Relay
|
||||
let destination = Rococo::child_location_of(PenpalA::para_id());
|
||||
let sender = RococoSender::get();
|
||||
let amount_to_send: Balance = ROCOCO_ED * 1000;
|
||||
|
||||
// Init values for Parachain
|
||||
let relay_native_asset_location = RelayLocation::get();
|
||||
let receiver = PenpalAReceiver::get();
|
||||
|
||||
// Init Test
|
||||
let test_args = TestContext {
|
||||
sender,
|
||||
receiver: receiver.clone(),
|
||||
args: TestArgs::new_relay(destination.clone(), receiver.clone(), amount_to_send),
|
||||
};
|
||||
let mut test = RelayToParaThroughAHTest::new(test_args);
|
||||
|
||||
let sov_penpal_on_ah = AssetHubRococo::sovereign_account_id_of(
|
||||
AssetHubRococo::sibling_location_of(PenpalA::para_id()),
|
||||
);
|
||||
// Query initial balances
|
||||
let sender_balance_before = test.sender.balance;
|
||||
let sov_penpal_on_ah_before = AssetHubRococo::execute_with(|| {
|
||||
<AssetHubRococo as AssetHubRococoPallet>::Balances::free_balance(sov_penpal_on_ah.clone())
|
||||
});
|
||||
let receiver_assets_before = PenpalA::execute_with(|| {
|
||||
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
|
||||
<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.clone(), &receiver)
|
||||
});
|
||||
|
||||
fn relay_assertions(t: RelayToParaThroughAHTest) {
|
||||
type RuntimeEvent = <Rococo as Chain>::RuntimeEvent;
|
||||
Rococo::assert_xcm_pallet_attempted_complete(None);
|
||||
assert_expected_events!(
|
||||
Rococo,
|
||||
vec![
|
||||
// Amount to teleport is withdrawn from Sender
|
||||
RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => {
|
||||
who: *who == t.sender.account_id,
|
||||
amount: *amount == t.args.amount,
|
||||
},
|
||||
// Amount to teleport is deposited in Relay's `CheckAccount`
|
||||
RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount }) => {
|
||||
who: *who == <Rococo as RococoPallet>::XcmPallet::check_account(),
|
||||
amount: *amount == t.args.amount,
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
fn asset_hub_assertions(_: RelayToParaThroughAHTest) {
|
||||
type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent;
|
||||
let sov_penpal_on_ah = AssetHubRococo::sovereign_account_id_of(
|
||||
AssetHubRococo::sibling_location_of(PenpalA::para_id()),
|
||||
);
|
||||
assert_expected_events!(
|
||||
AssetHubRococo,
|
||||
vec![
|
||||
// Deposited to receiver parachain SA
|
||||
RuntimeEvent::Balances(
|
||||
pallet_balances::Event::Minted { who, .. }
|
||||
) => {
|
||||
who: *who == sov_penpal_on_ah,
|
||||
},
|
||||
RuntimeEvent::MessageQueue(
|
||||
pallet_message_queue::Event::Processed { success: true, .. }
|
||||
) => {},
|
||||
]
|
||||
);
|
||||
}
|
||||
fn penpal_assertions(t: RelayToParaThroughAHTest) {
|
||||
type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
|
||||
let expected_id =
|
||||
t.args.assets.into_inner().first().unwrap().id.0.clone().try_into().unwrap();
|
||||
assert_expected_events!(
|
||||
PenpalA,
|
||||
vec![
|
||||
RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => {
|
||||
asset_id: *asset_id == expected_id,
|
||||
owner: *owner == t.receiver.account_id,
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
fn transfer_assets_dispatchable(t: RelayToParaThroughAHTest) -> DispatchResult {
|
||||
let fee_idx = t.args.fee_asset_item as usize;
|
||||
let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
|
||||
let asset_hub_location = Rococo::child_location_of(AssetHubRococo::para_id());
|
||||
let context = RococoUniversalLocation::get();
|
||||
|
||||
// reanchor fees to the view of destination (Penpal)
|
||||
let mut remote_fees = fee.clone().reanchored(&t.args.dest, &context).unwrap();
|
||||
if let Fungible(ref mut amount) = remote_fees.fun {
|
||||
// we already spent some fees along the way, just use half of what we started with
|
||||
*amount = *amount / 2;
|
||||
}
|
||||
let xcm_on_final_dest = Xcm::<()>(vec![
|
||||
BuyExecution { fees: remote_fees, weight_limit: t.args.weight_limit.clone() },
|
||||
DepositAsset {
|
||||
assets: Wild(AllCounted(t.args.assets.len() as u32)),
|
||||
beneficiary: t.args.beneficiary,
|
||||
},
|
||||
]);
|
||||
|
||||
// reanchor final dest (Penpal) to the view of hop (Asset Hub)
|
||||
let mut dest = t.args.dest.clone();
|
||||
dest.reanchor(&asset_hub_location, &context).unwrap();
|
||||
// on Asset Hub, forward assets to Penpal
|
||||
let xcm_on_hop = Xcm::<()>(vec![DepositReserveAsset {
|
||||
assets: Wild(AllCounted(t.args.assets.len() as u32)),
|
||||
dest,
|
||||
xcm: xcm_on_final_dest,
|
||||
}]);
|
||||
|
||||
// First leg is a teleport, from there a local-reserve-transfer to final dest
|
||||
<Rococo as RococoPallet>::XcmPallet::transfer_assets_using_type_and_then(
|
||||
t.signed_origin,
|
||||
bx!(asset_hub_location.into()),
|
||||
bx!(t.args.assets.into()),
|
||||
bx!(TransferType::Teleport),
|
||||
bx!(fee.id.into()),
|
||||
bx!(TransferType::Teleport),
|
||||
bx!(VersionedXcm::from(xcm_on_hop)),
|
||||
t.args.weight_limit,
|
||||
)
|
||||
}
|
||||
|
||||
// Set assertions and dispatchables
|
||||
test.set_assertion::<Rococo>(relay_assertions);
|
||||
test.set_assertion::<AssetHubRococo>(asset_hub_assertions);
|
||||
test.set_assertion::<PenpalA>(penpal_assertions);
|
||||
test.set_dispatchable::<Rococo>(transfer_assets_dispatchable);
|
||||
test.assert();
|
||||
|
||||
// Query final balances
|
||||
let sender_balance_after = test.sender.balance;
|
||||
let sov_penpal_on_ah_after = AssetHubRococo::execute_with(|| {
|
||||
<AssetHubRococo as AssetHubRococoPallet>::Balances::free_balance(sov_penpal_on_ah)
|
||||
});
|
||||
let receiver_assets_after = PenpalA::execute_with(|| {
|
||||
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
|
||||
<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location, &receiver)
|
||||
});
|
||||
|
||||
// Sender's balance is reduced by amount sent plus delivery fees
|
||||
assert!(sender_balance_after < sender_balance_before - amount_to_send);
|
||||
// SA on AH balance is increased
|
||||
assert!(sov_penpal_on_ah_after > sov_penpal_on_ah_before);
|
||||
// Receiver's asset balance is increased
|
||||
assert!(receiver_assets_after > receiver_assets_before);
|
||||
// Receiver's asset balance increased by `amount_to_send - delivery_fees - bought_execution`;
|
||||
// `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but
|
||||
// should be non-zero
|
||||
assert!(receiver_assets_after < receiver_assets_before + amount_to_send);
|
||||
}
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
mod foreign_assets_transfers;
|
||||
mod hybrid_transfers;
|
||||
mod reserve_transfer;
|
||||
mod send;
|
||||
mod set_xcm_versions;
|
||||
|
||||
+1
-1
@@ -574,7 +574,7 @@ fn reserve_transfer_native_asset_from_relay_to_para() {
|
||||
let sender = RococoSender::get();
|
||||
let amount_to_send: Balance = ROCOCO_ED * 1000;
|
||||
|
||||
// Init values fot Parachain
|
||||
// Init values for Parachain
|
||||
let relay_native_asset_location = RelayLocation::get();
|
||||
let receiver = PenpalAReceiver::get();
|
||||
|
||||
|
||||
+4
-1
@@ -74,7 +74,9 @@ mod imports {
|
||||
LocalReservableFromAssetHub as PenpalLocalReservableFromAssetHub,
|
||||
LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub,
|
||||
};
|
||||
pub use westend_runtime::xcm_config::XcmConfig as WestendXcmConfig;
|
||||
pub use westend_runtime::xcm_config::{
|
||||
UniversalLocation as WestendUniversalLocation, XcmConfig as WestendXcmConfig,
|
||||
};
|
||||
|
||||
pub const ASSET_ID: u32 = 3;
|
||||
pub const ASSET_MIN_BALANCE: u128 = 1000;
|
||||
@@ -87,6 +89,7 @@ mod imports {
|
||||
pub type ParaToSystemParaTest = Test<PenpalA, AssetHubWestend>;
|
||||
pub type ParaToParaThroughRelayTest = Test<PenpalA, PenpalB, Westend>;
|
||||
pub type ParaToParaThroughAHTest = Test<PenpalA, PenpalB, AssetHubWestend>;
|
||||
pub type RelayToParaThroughAHTest = Test<Westend, PenpalA, AssetHubWestend>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
+193
-10
@@ -54,14 +54,18 @@ fn para_to_para_assethub_hop_assertions(t: ParaToParaThroughAHTest) {
|
||||
fn ah_to_para_transfer_assets(t: SystemParaToParaTest) -> DispatchResult {
|
||||
let fee_idx = t.args.fee_asset_item as usize;
|
||||
let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
|
||||
<AssetHubWestend as AssetHubWestendPallet>::PolkadotXcm::transfer_assets_using_type(
|
||||
let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset {
|
||||
assets: Wild(AllCounted(t.args.assets.len() as u32)),
|
||||
beneficiary: t.args.beneficiary,
|
||||
}]);
|
||||
<AssetHubWestend as AssetHubWestendPallet>::PolkadotXcm::transfer_assets_using_type_and_then(
|
||||
t.signed_origin,
|
||||
bx!(t.args.dest.into()),
|
||||
bx!(t.args.beneficiary.into()),
|
||||
bx!(t.args.assets.into()),
|
||||
bx!(TransferType::LocalReserve),
|
||||
bx!(fee.id.into()),
|
||||
bx!(TransferType::LocalReserve),
|
||||
bx!(VersionedXcm::from(custom_xcm_on_dest)),
|
||||
t.args.weight_limit,
|
||||
)
|
||||
}
|
||||
@@ -69,14 +73,18 @@ fn ah_to_para_transfer_assets(t: SystemParaToParaTest) -> DispatchResult {
|
||||
fn para_to_ah_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult {
|
||||
let fee_idx = t.args.fee_asset_item as usize;
|
||||
let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
|
||||
<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type(
|
||||
let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset {
|
||||
assets: Wild(AllCounted(t.args.assets.len() as u32)),
|
||||
beneficiary: t.args.beneficiary,
|
||||
}]);
|
||||
<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type_and_then(
|
||||
t.signed_origin,
|
||||
bx!(t.args.dest.into()),
|
||||
bx!(t.args.beneficiary.into()),
|
||||
bx!(t.args.assets.into()),
|
||||
bx!(TransferType::DestinationReserve),
|
||||
bx!(fee.id.into()),
|
||||
bx!(TransferType::DestinationReserve),
|
||||
bx!(VersionedXcm::from(custom_xcm_on_dest)),
|
||||
t.args.weight_limit,
|
||||
)
|
||||
}
|
||||
@@ -85,14 +93,18 @@ fn para_to_para_transfer_assets_through_ah(t: ParaToParaThroughAHTest) -> Dispat
|
||||
let fee_idx = t.args.fee_asset_item as usize;
|
||||
let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
|
||||
let asset_hub_location: Location = PenpalA::sibling_location_of(AssetHubWestend::para_id());
|
||||
<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type(
|
||||
let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset {
|
||||
assets: Wild(AllCounted(t.args.assets.len() as u32)),
|
||||
beneficiary: t.args.beneficiary,
|
||||
}]);
|
||||
<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type_and_then(
|
||||
t.signed_origin,
|
||||
bx!(t.args.dest.into()),
|
||||
bx!(t.args.beneficiary.into()),
|
||||
bx!(t.args.assets.into()),
|
||||
bx!(TransferType::RemoteReserve(asset_hub_location.clone().into())),
|
||||
bx!(fee.id.into()),
|
||||
bx!(TransferType::RemoteReserve(asset_hub_location.into())),
|
||||
bx!(VersionedXcm::from(custom_xcm_on_dest)),
|
||||
t.args.weight_limit,
|
||||
)
|
||||
}
|
||||
@@ -100,14 +112,18 @@ fn para_to_para_transfer_assets_through_ah(t: ParaToParaThroughAHTest) -> Dispat
|
||||
fn para_to_asset_hub_teleport_foreign_assets(t: ParaToSystemParaTest) -> DispatchResult {
|
||||
let fee_idx = t.args.fee_asset_item as usize;
|
||||
let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
|
||||
<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type(
|
||||
let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset {
|
||||
assets: Wild(AllCounted(t.args.assets.len() as u32)),
|
||||
beneficiary: t.args.beneficiary,
|
||||
}]);
|
||||
<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type_and_then(
|
||||
t.signed_origin,
|
||||
bx!(t.args.dest.into()),
|
||||
bx!(t.args.beneficiary.into()),
|
||||
bx!(t.args.assets.into()),
|
||||
bx!(TransferType::Teleport),
|
||||
bx!(fee.id.into()),
|
||||
bx!(TransferType::DestinationReserve),
|
||||
bx!(VersionedXcm::from(custom_xcm_on_dest)),
|
||||
t.args.weight_limit,
|
||||
)
|
||||
}
|
||||
@@ -115,14 +131,18 @@ fn para_to_asset_hub_teleport_foreign_assets(t: ParaToSystemParaTest) -> Dispatc
|
||||
fn asset_hub_to_para_teleport_foreign_assets(t: SystemParaToParaTest) -> DispatchResult {
|
||||
let fee_idx = t.args.fee_asset_item as usize;
|
||||
let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
|
||||
<AssetHubWestend as AssetHubWestendPallet>::PolkadotXcm::transfer_assets_using_type(
|
||||
let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset {
|
||||
assets: Wild(AllCounted(t.args.assets.len() as u32)),
|
||||
beneficiary: t.args.beneficiary,
|
||||
}]);
|
||||
<AssetHubWestend as AssetHubWestendPallet>::PolkadotXcm::transfer_assets_using_type_and_then(
|
||||
t.signed_origin,
|
||||
bx!(t.args.dest.into()),
|
||||
bx!(t.args.beneficiary.into()),
|
||||
bx!(t.args.assets.into()),
|
||||
bx!(TransferType::Teleport),
|
||||
bx!(fee.id.into()),
|
||||
bx!(TransferType::LocalReserve),
|
||||
bx!(VersionedXcm::from(custom_xcm_on_dest)),
|
||||
t.args.weight_limit,
|
||||
)
|
||||
}
|
||||
@@ -627,3 +647,166 @@ fn bidirectional_teleport_foreign_asset_between_para_and_asset_hub_using_explici
|
||||
asset_hub_to_para_teleport_foreign_assets,
|
||||
);
|
||||
}
|
||||
|
||||
// ===============================================================
|
||||
// ===== Transfer - Native Asset - Relay->AssetHub->Parachain ====
|
||||
// ===============================================================
|
||||
/// Transfers of native asset Relay to Parachain (using AssetHub reserve). Parachains want to avoid
|
||||
/// managing SAs on all system chains, thus want all their DOT-in-reserve to be held in their
|
||||
/// Sovereign Account on Asset Hub.
|
||||
#[test]
|
||||
fn transfer_native_asset_from_relay_to_para_through_asset_hub() {
|
||||
// Init values for Relay
|
||||
let destination = Westend::child_location_of(PenpalA::para_id());
|
||||
let sender = WestendSender::get();
|
||||
let amount_to_send: Balance = WESTEND_ED * 1000;
|
||||
|
||||
// Init values for Parachain
|
||||
let relay_native_asset_location = RelayLocation::get();
|
||||
let receiver = PenpalAReceiver::get();
|
||||
|
||||
// Init Test
|
||||
let test_args = TestContext {
|
||||
sender,
|
||||
receiver: receiver.clone(),
|
||||
args: TestArgs::new_relay(destination.clone(), receiver.clone(), amount_to_send),
|
||||
};
|
||||
let mut test = RelayToParaThroughAHTest::new(test_args);
|
||||
|
||||
let sov_penpal_on_ah = AssetHubWestend::sovereign_account_id_of(
|
||||
AssetHubWestend::sibling_location_of(PenpalA::para_id()),
|
||||
);
|
||||
// Query initial balances
|
||||
let sender_balance_before = test.sender.balance;
|
||||
let sov_penpal_on_ah_before = AssetHubWestend::execute_with(|| {
|
||||
<AssetHubWestend as AssetHubWestendPallet>::Balances::free_balance(sov_penpal_on_ah.clone())
|
||||
});
|
||||
let receiver_assets_before = PenpalA::execute_with(|| {
|
||||
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
|
||||
<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.clone(), &receiver)
|
||||
});
|
||||
|
||||
fn relay_assertions(t: RelayToParaThroughAHTest) {
|
||||
type RuntimeEvent = <Westend as Chain>::RuntimeEvent;
|
||||
Westend::assert_xcm_pallet_attempted_complete(None);
|
||||
assert_expected_events!(
|
||||
Westend,
|
||||
vec![
|
||||
// Amount to teleport is withdrawn from Sender
|
||||
RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => {
|
||||
who: *who == t.sender.account_id,
|
||||
amount: *amount == t.args.amount,
|
||||
},
|
||||
// Amount to teleport is deposited in Relay's `CheckAccount`
|
||||
RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount }) => {
|
||||
who: *who == <Westend as WestendPallet>::XcmPallet::check_account(),
|
||||
amount: *amount == t.args.amount,
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
fn asset_hub_assertions(_: RelayToParaThroughAHTest) {
|
||||
type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
|
||||
let sov_penpal_on_ah = AssetHubWestend::sovereign_account_id_of(
|
||||
AssetHubWestend::sibling_location_of(PenpalA::para_id()),
|
||||
);
|
||||
assert_expected_events!(
|
||||
AssetHubWestend,
|
||||
vec![
|
||||
// Deposited to receiver parachain SA
|
||||
RuntimeEvent::Balances(
|
||||
pallet_balances::Event::Minted { who, .. }
|
||||
) => {
|
||||
who: *who == sov_penpal_on_ah,
|
||||
},
|
||||
RuntimeEvent::MessageQueue(
|
||||
pallet_message_queue::Event::Processed { success: true, .. }
|
||||
) => {},
|
||||
]
|
||||
);
|
||||
}
|
||||
fn penpal_assertions(t: RelayToParaThroughAHTest) {
|
||||
type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
|
||||
let expected_id =
|
||||
t.args.assets.into_inner().first().unwrap().id.0.clone().try_into().unwrap();
|
||||
assert_expected_events!(
|
||||
PenpalA,
|
||||
vec![
|
||||
RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => {
|
||||
asset_id: *asset_id == expected_id,
|
||||
owner: *owner == t.receiver.account_id,
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
fn transfer_assets_dispatchable(t: RelayToParaThroughAHTest) -> DispatchResult {
|
||||
let fee_idx = t.args.fee_asset_item as usize;
|
||||
let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
|
||||
let asset_hub_location = Westend::child_location_of(AssetHubWestend::para_id());
|
||||
let context = WestendUniversalLocation::get();
|
||||
|
||||
// reanchor fees to the view of destination (Penpal)
|
||||
let mut remote_fees = fee.clone().reanchored(&t.args.dest, &context).unwrap();
|
||||
if let Fungible(ref mut amount) = remote_fees.fun {
|
||||
// we already spent some fees along the way, just use half of what we started with
|
||||
*amount = *amount / 2;
|
||||
}
|
||||
let xcm_on_final_dest = Xcm::<()>(vec![
|
||||
BuyExecution { fees: remote_fees, weight_limit: t.args.weight_limit.clone() },
|
||||
DepositAsset {
|
||||
assets: Wild(AllCounted(t.args.assets.len() as u32)),
|
||||
beneficiary: t.args.beneficiary,
|
||||
},
|
||||
]);
|
||||
|
||||
// reanchor final dest (Penpal) to the view of hop (Asset Hub)
|
||||
let mut dest = t.args.dest.clone();
|
||||
dest.reanchor(&asset_hub_location, &context).unwrap();
|
||||
// on Asset Hub, forward assets to Penpal
|
||||
let xcm_on_hop = Xcm::<()>(vec![DepositReserveAsset {
|
||||
assets: Wild(AllCounted(t.args.assets.len() as u32)),
|
||||
dest,
|
||||
xcm: xcm_on_final_dest,
|
||||
}]);
|
||||
|
||||
// First leg is a teleport, from there a local-reserve-transfer to final dest
|
||||
<Westend as WestendPallet>::XcmPallet::transfer_assets_using_type_and_then(
|
||||
t.signed_origin,
|
||||
bx!(asset_hub_location.into()),
|
||||
bx!(t.args.assets.into()),
|
||||
bx!(TransferType::Teleport),
|
||||
bx!(fee.id.into()),
|
||||
bx!(TransferType::Teleport),
|
||||
bx!(VersionedXcm::from(xcm_on_hop)),
|
||||
t.args.weight_limit,
|
||||
)
|
||||
}
|
||||
|
||||
// Set assertions and dispatchables
|
||||
test.set_assertion::<Westend>(relay_assertions);
|
||||
test.set_assertion::<AssetHubWestend>(asset_hub_assertions);
|
||||
test.set_assertion::<PenpalA>(penpal_assertions);
|
||||
test.set_dispatchable::<Westend>(transfer_assets_dispatchable);
|
||||
test.assert();
|
||||
|
||||
// Query final balances
|
||||
let sender_balance_after = test.sender.balance;
|
||||
let sov_penpal_on_ah_after = AssetHubWestend::execute_with(|| {
|
||||
<AssetHubWestend as AssetHubWestendPallet>::Balances::free_balance(sov_penpal_on_ah)
|
||||
});
|
||||
let receiver_assets_after = PenpalA::execute_with(|| {
|
||||
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
|
||||
<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location, &receiver)
|
||||
});
|
||||
|
||||
// Sender's balance is reduced by amount sent plus delivery fees
|
||||
assert!(sender_balance_after < sender_balance_before - amount_to_send);
|
||||
// SA on AH balance is increased
|
||||
assert!(sov_penpal_on_ah_after > sov_penpal_on_ah_before);
|
||||
// Receiver's asset balance is increased
|
||||
assert!(receiver_assets_after > receiver_assets_before);
|
||||
// Receiver's asset balance increased by `amount_to_send - delivery_fees - bought_execution`;
|
||||
// `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but
|
||||
// should be non-zero
|
||||
assert!(receiver_assets_after < receiver_assets_before + amount_to_send);
|
||||
}
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
mod fellowship_treasury;
|
||||
mod foreign_assets_transfers;
|
||||
mod hybrid_transfers;
|
||||
mod reserve_transfer;
|
||||
mod send;
|
||||
mod set_xcm_versions;
|
||||
|
||||
+1
-1
@@ -574,7 +574,7 @@ fn reserve_transfer_native_asset_from_relay_to_para() {
|
||||
let sender = WestendSender::get();
|
||||
let amount_to_send: Balance = WESTEND_ED * 1000;
|
||||
|
||||
// Init values fot Parachain
|
||||
// Init values for Parachain
|
||||
let relay_native_asset_location = RelayLocation::get();
|
||||
let receiver = PenpalAReceiver::get();
|
||||
|
||||
|
||||
+6
-2
@@ -60,15 +60,19 @@ fn send_asset_from_penpal_rococo_through_local_asset_hub_to_westend_asset_hub(
|
||||
AccountId32Junction { network: None, id: AssetHubWestendReceiver::get().into() }.into();
|
||||
let assets: Assets = (id.clone(), transfer_amount).into();
|
||||
let fees_id: AssetId = id.into();
|
||||
let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset {
|
||||
assets: Wild(AllCounted(assets.len() as u32)),
|
||||
beneficiary,
|
||||
}]);
|
||||
|
||||
<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type(
|
||||
<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type_and_then(
|
||||
signed_origin,
|
||||
bx!(destination.into()),
|
||||
bx!(beneficiary.into()),
|
||||
bx!(assets.clone().into()),
|
||||
bx!(TransferType::RemoteReserve(local_asset_hub.clone().into())),
|
||||
bx!(fees_id.into()),
|
||||
bx!(TransferType::RemoteReserve(local_asset_hub.into())),
|
||||
bx!(VersionedXcm::from(custom_xcm_on_dest)),
|
||||
WeightLimit::Unlimited,
|
||||
)
|
||||
}));
|
||||
|
||||
+6
-2
@@ -59,15 +59,19 @@ fn send_asset_from_penpal_westend_through_local_asset_hub_to_rococo_asset_hub(
|
||||
AccountId32Junction { network: None, id: AssetHubRococoReceiver::get().into() }.into();
|
||||
let assets: Assets = (id.clone(), transfer_amount).into();
|
||||
let fees_id: AssetId = id.into();
|
||||
let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset {
|
||||
assets: Wild(AllCounted(assets.len() as u32)),
|
||||
beneficiary,
|
||||
}]);
|
||||
|
||||
<PenpalB as PenpalBPallet>::PolkadotXcm::transfer_assets_using_type(
|
||||
<PenpalB as PenpalBPallet>::PolkadotXcm::transfer_assets_using_type_and_then(
|
||||
signed_origin,
|
||||
bx!(destination.into()),
|
||||
bx!(beneficiary.into()),
|
||||
bx!(assets.into()),
|
||||
bx!(TransferType::RemoteReserve(local_asset_hub.clone().into())),
|
||||
bx!(fees_id.into()),
|
||||
bx!(TransferType::RemoteReserve(local_asset_hub.into())),
|
||||
bx!(VersionedXcm::from(custom_xcm_on_dest)),
|
||||
WeightLimit::Unlimited,
|
||||
)
|
||||
}));
|
||||
|
||||
@@ -45,7 +45,7 @@ use sp_runtime::{
|
||||
AccountIdConversion, BadOrigin, BlakeTwo256, BlockNumberProvider, Dispatchable, Hash,
|
||||
Saturating, Zero,
|
||||
},
|
||||
RuntimeDebug,
|
||||
Either, RuntimeDebug,
|
||||
};
|
||||
use sp_std::{boxed::Box, marker::PhantomData, prelude::*, result::Result, vec};
|
||||
use xcm::{latest::QueryResponseInfo, prelude::*};
|
||||
@@ -1311,7 +1311,7 @@ pub mod pallet {
|
||||
Self::do_transfer_assets(
|
||||
origin,
|
||||
dest,
|
||||
beneficiary,
|
||||
Either::Left(beneficiary),
|
||||
assets,
|
||||
assets_transfer_type,
|
||||
fee_asset_item,
|
||||
@@ -1421,50 +1421,60 @@ pub mod pallet {
|
||||
/// - `TransferType::Teleport`: burn local assets and forward XCM to `dest` chain to
|
||||
/// mint/teleport assets and deposit them to `beneficiary`.
|
||||
///
|
||||
/// Fee payment on the source, destination and all intermediary hops, is specified through
|
||||
/// `fees_id`, but make sure enough of the specified `fees_id` asset is included in the
|
||||
/// given list of `assets`. `fees_id` should be enough to pay for `weight_limit`. If more
|
||||
/// weight is needed than `weight_limit`, then the operation will fail and the sent assets
|
||||
/// may be at risk.
|
||||
/// On the destination chain, as well as any intermediary hops, `BuyExecution` is used to
|
||||
/// buy execution using transferred `assets` identified by `remote_fees_id`.
|
||||
/// Make sure enough of the specified `remote_fees_id` asset is included in the given list
|
||||
/// of `assets`. `remote_fees_id` should be enough to pay for `weight_limit`. If more weight
|
||||
/// is needed than `weight_limit`, then the operation will fail and the sent assets may be
|
||||
/// at risk.
|
||||
///
|
||||
/// `fees_id` may use different transfer type than rest of `assets` and can be specified
|
||||
/// through `fees_transfer_type`.
|
||||
/// `remote_fees_id` may use different transfer type than rest of `assets` and can be
|
||||
/// specified through `fees_transfer_type`.
|
||||
///
|
||||
/// The caller needs to specify what should happen to the transferred assets once they reach
|
||||
/// the `dest` chain. This is done through the `custom_xcm_on_dest` parameter, which
|
||||
/// contains the instructions to execute on `dest` as a final step.
|
||||
/// This is usually as simple as:
|
||||
/// `Xcm(vec![DepositAsset { assets: Wild(AllCounted(assets.len())), beneficiary }])`,
|
||||
/// but could be something more exotic like sending the `assets` even further.
|
||||
///
|
||||
/// - `origin`: Must be capable of withdrawing the `assets` and executing XCM.
|
||||
/// - `dest`: Destination context for the assets. Will typically be `[Parent,
|
||||
/// Parachain(..)]` to send from parachain to parachain, or `[Parachain(..)]` to send from
|
||||
/// relay to parachain, or `(parents: 2, (GlobalConsensus(..), ..))` to send from
|
||||
/// parachain across a bridge to another ecosystem destination.
|
||||
/// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will
|
||||
/// generally be an `AccountId32` value.
|
||||
/// - `assets`: The assets to be withdrawn. This should include the assets used to pay the
|
||||
/// fee on the `dest` (and possibly reserve) chains.
|
||||
/// - `assets_transfer_type`: The XCM `TransferType` used to transfer the `assets`.
|
||||
/// - `fees_id`: One of the included `assets` to be be used to pay fees.
|
||||
/// - `remote_fees_id`: One of the included `assets` to be be used to pay fees.
|
||||
/// - `fees_transfer_type`: The XCM `TransferType` used to transfer the `fees` assets.
|
||||
/// - `custom_xcm_on_dest`: The XCM to be executed on `dest` chain as the last step of the
|
||||
/// transfer, which also determines what happens to the assets on the destination chain.
|
||||
/// - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase.
|
||||
#[pallet::call_index(15)]
|
||||
#[pallet::weight(T::WeightInfo::transfer_assets())]
|
||||
pub fn transfer_assets_using_type(
|
||||
pub fn transfer_assets_using_type_and_then(
|
||||
origin: OriginFor<T>,
|
||||
dest: Box<VersionedLocation>,
|
||||
beneficiary: Box<VersionedLocation>,
|
||||
assets: Box<VersionedAssets>,
|
||||
assets_transfer_type: Box<TransferType>,
|
||||
fees_id: Box<VersionedAssetId>,
|
||||
remote_fees_id: Box<VersionedAssetId>,
|
||||
fees_transfer_type: Box<TransferType>,
|
||||
custom_xcm_on_dest: Box<VersionedXcm<()>>,
|
||||
weight_limit: WeightLimit,
|
||||
) -> DispatchResult {
|
||||
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
|
||||
let dest: Location = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
|
||||
let beneficiary: Location =
|
||||
(*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
|
||||
let assets: Assets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
|
||||
let fees_id: AssetId = (*fees_id).try_into().map_err(|()| Error::<T>::BadVersion)?;
|
||||
let fees_id: AssetId =
|
||||
(*remote_fees_id).try_into().map_err(|()| Error::<T>::BadVersion)?;
|
||||
let remote_xcm: Xcm<()> =
|
||||
(*custom_xcm_on_dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
|
||||
log::debug!(
|
||||
target: "xcm::pallet_xcm::transfer_assets_using_type",
|
||||
"origin {:?}, dest {:?}, beneficiary {:?}, assets {:?} through {:?}, fees-id {:?} through {:?}",
|
||||
origin_location, dest, beneficiary, assets, assets_transfer_type, fees_id, fees_transfer_type,
|
||||
target: "xcm::pallet_xcm::transfer_assets_using_type_and_then",
|
||||
"origin {origin_location:?}, dest {dest:?}, assets {assets:?} through {assets_transfer_type:?}, \
|
||||
remote_fees_id {fees_id:?} through {fees_transfer_type:?}, \
|
||||
custom_xcm_on_dest {remote_xcm:?}, weight-limit {weight_limit:?}",
|
||||
);
|
||||
|
||||
let assets = assets.into_inner();
|
||||
@@ -1475,7 +1485,7 @@ pub mod pallet {
|
||||
Self::do_transfer_assets(
|
||||
origin_location,
|
||||
dest,
|
||||
beneficiary,
|
||||
Either::Right(remote_xcm),
|
||||
assets,
|
||||
*assets_transfer_type,
|
||||
fee_asset_index,
|
||||
@@ -1650,7 +1660,7 @@ impl<T: Config> Pallet<T> {
|
||||
let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
|
||||
origin.clone(),
|
||||
dest.clone(),
|
||||
beneficiary,
|
||||
Either::Left(beneficiary),
|
||||
assets,
|
||||
assets_transfer_type,
|
||||
FeesHandling::Batched { fees },
|
||||
@@ -1692,7 +1702,7 @@ impl<T: Config> Pallet<T> {
|
||||
let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
|
||||
origin_location.clone(),
|
||||
dest.clone(),
|
||||
beneficiary,
|
||||
Either::Left(beneficiary),
|
||||
assets,
|
||||
TransferType::Teleport,
|
||||
FeesHandling::Batched { fees },
|
||||
@@ -1704,7 +1714,7 @@ impl<T: Config> Pallet<T> {
|
||||
fn do_transfer_assets(
|
||||
origin: Location,
|
||||
dest: Location,
|
||||
beneficiary: Location,
|
||||
beneficiary: Either<Location, Xcm<()>>,
|
||||
mut assets: Vec<Asset>,
|
||||
assets_transfer_type: TransferType,
|
||||
fee_asset_index: usize,
|
||||
@@ -1770,7 +1780,7 @@ impl<T: Config> Pallet<T> {
|
||||
fn build_xcm_transfer_type(
|
||||
origin: Location,
|
||||
dest: Location,
|
||||
beneficiary: Location,
|
||||
beneficiary: Either<Location, Xcm<()>>,
|
||||
assets: Vec<Asset>,
|
||||
transfer_type: TransferType,
|
||||
fees: FeesHandling<T>,
|
||||
@@ -1782,57 +1792,51 @@ impl<T: Config> Pallet<T> {
|
||||
fees_handling {:?}, weight_limit: {:?}",
|
||||
origin, dest, beneficiary, assets, transfer_type, fees, weight_limit,
|
||||
);
|
||||
Ok(match transfer_type {
|
||||
TransferType::LocalReserve => {
|
||||
let (local, remote) = Self::local_reserve_transfer_programs(
|
||||
origin.clone(),
|
||||
dest.clone(),
|
||||
beneficiary,
|
||||
assets,
|
||||
fees,
|
||||
weight_limit,
|
||||
)?;
|
||||
(local, Some(remote))
|
||||
},
|
||||
TransferType::DestinationReserve => {
|
||||
let (local, remote) = Self::destination_reserve_transfer_programs(
|
||||
origin.clone(),
|
||||
dest.clone(),
|
||||
beneficiary,
|
||||
assets,
|
||||
fees,
|
||||
weight_limit,
|
||||
)?;
|
||||
(local, Some(remote))
|
||||
},
|
||||
match transfer_type {
|
||||
TransferType::LocalReserve => Self::local_reserve_transfer_programs(
|
||||
origin.clone(),
|
||||
dest.clone(),
|
||||
beneficiary,
|
||||
assets,
|
||||
fees,
|
||||
weight_limit,
|
||||
)
|
||||
.map(|(local, remote)| (local, Some(remote))),
|
||||
TransferType::DestinationReserve => Self::destination_reserve_transfer_programs(
|
||||
origin.clone(),
|
||||
dest.clone(),
|
||||
beneficiary,
|
||||
assets,
|
||||
fees,
|
||||
weight_limit,
|
||||
)
|
||||
.map(|(local, remote)| (local, Some(remote))),
|
||||
TransferType::RemoteReserve(reserve) => {
|
||||
let fees = match fees {
|
||||
FeesHandling::Batched { fees } => fees,
|
||||
_ => return Err(Error::<T>::InvalidAssetUnsupportedReserve.into()),
|
||||
};
|
||||
let local = Self::remote_reserve_transfer_program(
|
||||
Self::remote_reserve_transfer_program(
|
||||
origin.clone(),
|
||||
reserve.try_into().map_err(|()| Error::<T>::BadVersion)?,
|
||||
dest.clone(),
|
||||
beneficiary,
|
||||
dest.clone(),
|
||||
assets,
|
||||
fees,
|
||||
weight_limit,
|
||||
)?;
|
||||
(local, None)
|
||||
)
|
||||
.map(|local| (local, None))
|
||||
},
|
||||
TransferType::Teleport => {
|
||||
let (local, remote) = Self::teleport_assets_program(
|
||||
origin.clone(),
|
||||
dest.clone(),
|
||||
beneficiary,
|
||||
assets,
|
||||
fees,
|
||||
weight_limit,
|
||||
)?;
|
||||
(local, Some(remote))
|
||||
},
|
||||
})
|
||||
TransferType::Teleport => Self::teleport_assets_program(
|
||||
origin.clone(),
|
||||
dest.clone(),
|
||||
beneficiary,
|
||||
assets,
|
||||
fees,
|
||||
weight_limit,
|
||||
)
|
||||
.map(|(local, remote)| (local, Some(remote))),
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_xcm_transfer(
|
||||
@@ -1947,7 +1951,7 @@ impl<T: Config> Pallet<T> {
|
||||
fn local_reserve_transfer_programs(
|
||||
origin: Location,
|
||||
dest: Location,
|
||||
beneficiary: Location,
|
||||
beneficiary: Either<Location, Xcm<()>>,
|
||||
assets: Vec<Asset>,
|
||||
fees: FeesHandling<T>,
|
||||
weight_limit: WeightLimit,
|
||||
@@ -1980,10 +1984,16 @@ impl<T: Config> Pallet<T> {
|
||||
]);
|
||||
// handle fees
|
||||
Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;
|
||||
// deposit all remaining assets in holding to `beneficiary` location
|
||||
xcm_on_dest
|
||||
.inner_mut()
|
||||
.push(DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary });
|
||||
|
||||
// Use custom XCM on remote chain, or just default to depositing everything to beneficiary.
|
||||
let custom_remote_xcm = match beneficiary {
|
||||
Either::Right(custom_xcm) => custom_xcm,
|
||||
Either::Left(beneficiary) => {
|
||||
// deposit all remaining assets in holding to `beneficiary` location
|
||||
Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
|
||||
},
|
||||
};
|
||||
xcm_on_dest.0.extend(custom_remote_xcm.into_iter());
|
||||
|
||||
Ok((local_execute_xcm, xcm_on_dest))
|
||||
}
|
||||
@@ -2022,7 +2032,7 @@ impl<T: Config> Pallet<T> {
|
||||
fn destination_reserve_transfer_programs(
|
||||
origin: Location,
|
||||
dest: Location,
|
||||
beneficiary: Location,
|
||||
beneficiary: Either<Location, Xcm<()>>,
|
||||
assets: Vec<Asset>,
|
||||
fees: FeesHandling<T>,
|
||||
weight_limit: WeightLimit,
|
||||
@@ -2058,10 +2068,15 @@ impl<T: Config> Pallet<T> {
|
||||
// handle fees
|
||||
Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;
|
||||
|
||||
// deposit all remaining assets in holding to `beneficiary` location
|
||||
xcm_on_dest
|
||||
.inner_mut()
|
||||
.push(DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary });
|
||||
// Use custom XCM on remote chain, or just default to depositing everything to beneficiary.
|
||||
let custom_remote_xcm = match beneficiary {
|
||||
Either::Right(custom_xcm) => custom_xcm,
|
||||
Either::Left(beneficiary) => {
|
||||
// deposit all remaining assets in holding to `beneficiary` location
|
||||
Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
|
||||
},
|
||||
};
|
||||
xcm_on_dest.0.extend(custom_remote_xcm.into_iter());
|
||||
|
||||
Ok((local_execute_xcm, xcm_on_dest))
|
||||
}
|
||||
@@ -2070,8 +2085,8 @@ impl<T: Config> Pallet<T> {
|
||||
fn remote_reserve_transfer_program(
|
||||
origin: Location,
|
||||
reserve: Location,
|
||||
beneficiary: Either<Location, Xcm<()>>,
|
||||
dest: Location,
|
||||
beneficiary: Location,
|
||||
assets: Vec<Asset>,
|
||||
fees: Asset,
|
||||
weight_limit: WeightLimit,
|
||||
@@ -2096,10 +2111,17 @@ impl<T: Config> Pallet<T> {
|
||||
// identifies `dest` as seen by `reserve`
|
||||
let dest = dest.reanchored(&reserve, &context).map_err(|_| Error::<T>::CannotReanchor)?;
|
||||
// xcm to be executed at dest
|
||||
let xcm_on_dest = Xcm(vec![
|
||||
BuyExecution { fees: dest_fees, weight_limit: weight_limit.clone() },
|
||||
DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary },
|
||||
]);
|
||||
let mut xcm_on_dest =
|
||||
Xcm(vec![BuyExecution { fees: dest_fees, weight_limit: weight_limit.clone() }]);
|
||||
// Use custom XCM on remote chain, or just default to depositing everything to beneficiary.
|
||||
let custom_xcm_on_dest = match beneficiary {
|
||||
Either::Right(custom_xcm) => custom_xcm,
|
||||
Either::Left(beneficiary) => {
|
||||
// deposit all remaining assets in holding to `beneficiary` location
|
||||
Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
|
||||
},
|
||||
};
|
||||
xcm_on_dest.0.extend(custom_xcm_on_dest.into_iter());
|
||||
// xcm to be executed on reserve
|
||||
let xcm_on_reserve = Xcm(vec![
|
||||
BuyExecution { fees: reserve_fees, weight_limit },
|
||||
@@ -2171,7 +2193,7 @@ impl<T: Config> Pallet<T> {
|
||||
fn teleport_assets_program(
|
||||
origin: Location,
|
||||
dest: Location,
|
||||
beneficiary: Location,
|
||||
beneficiary: Either<Location, Xcm<()>>,
|
||||
assets: Vec<Asset>,
|
||||
fees: FeesHandling<T>,
|
||||
weight_limit: WeightLimit,
|
||||
@@ -2231,10 +2253,16 @@ impl<T: Config> Pallet<T> {
|
||||
]);
|
||||
// handle fees
|
||||
Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;
|
||||
// deposit all remaining assets in holding to `beneficiary` location
|
||||
xcm_on_dest
|
||||
.inner_mut()
|
||||
.push(DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary });
|
||||
|
||||
// Use custom XCM on remote chain, or just default to depositing everything to beneficiary.
|
||||
let custom_remote_xcm = match beneficiary {
|
||||
Either::Right(custom_xcm) => custom_xcm,
|
||||
Either::Left(beneficiary) => {
|
||||
// deposit all remaining assets in holding to `beneficiary` location
|
||||
Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
|
||||
},
|
||||
};
|
||||
xcm_on_dest.0.extend(custom_remote_xcm.into_iter());
|
||||
|
||||
Ok((local_execute_xcm, xcm_on_dest))
|
||||
}
|
||||
|
||||
+8
-1
@@ -6,7 +6,7 @@ title: "pallet-xcm: add new extrinsic for asset transfers using explicit reserve
|
||||
doc:
|
||||
- audience: Runtime User
|
||||
description: |
|
||||
pallet-xcm has a new extrinsic `transfer_assets_using_type` for transferring
|
||||
pallet-xcm has a new extrinsic `transfer_assets_using_type_and_then` for transferring
|
||||
assets from local chain to destination chain using an explicit XCM transfer
|
||||
types for transferring the assets and the fees:
|
||||
- `TransferType::LocalReserve`: transfer assets to sovereign account of destination
|
||||
@@ -33,6 +33,13 @@ doc:
|
||||
Same when transferring bridged assets back across the bridge, the local bridging
|
||||
parachain must be used as the explicit reserve location.
|
||||
|
||||
The new method takes a `custom_xcm_on_dest` parameter allowing the caller to specify
|
||||
what should happen to the transferred assets once they reach
|
||||
the `dest` chain. The `custom_xcm_on_dest` parameter should contains the instructions
|
||||
to execute on `dest` as a final step. Usually as simple as:
|
||||
`Xcm(vec![DepositAsset { assets: Wild(AllCounted(assets.len())), beneficiary }])`,
|
||||
but could be something more exotic like sending the `assets` even further.
|
||||
|
||||
crates:
|
||||
- name: pallet-xcm
|
||||
bump: minor
|
||||
|
||||
Reference in New Issue
Block a user