(
) -> frame_support::weights::Weight {
let on_chain_storage_version = ::on_chain_storage_version();
log::info!(
target: "runtime::xcm",
"Running migration storage v1 for xcm with storage version {:?}",
on_chain_storage_version,
);
if on_chain_storage_version < 1 {
let mut count = 0;
Queries::::translate::>, _>(|_key, value| {
count += 1;
Some(value.into())
});
StorageVersion::new(1).put::();
log::info!(
target: "runtime::xcm",
"Running migration storage v1 for xcm with storage version {:?} was complete",
on_chain_storage_version,
);
// calculate and return migration weights
T::DbWeight::get().reads_writes(count as u64 + 1, count as u64 + 1)
} else {
log::warn!(
target: "runtime::xcm",
"Attempted to apply migration to v1 but failed because storage version is {:?}",
on_chain_storage_version,
);
T::DbWeight::get().reads(1)
}
}
}
#[pallet::call]
impl Pallet {
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::send())]
pub fn send(
origin: OriginFor,
dest: Box,
message: Box>,
) -> DispatchResult {
>::send(origin, dest, message)?;
Ok(())
}
/// Teleport some assets from the local chain to some destination chain.
///
/// **This function is deprecated: Use `limited_teleport_assets` instead.**
///
/// Fee payment on the destination side is made from the asset in the `assets` vector of
/// index `fee_asset_item`. The weight limit for fees is not provided and thus is unlimited,
/// with all fees taken as needed from the asset.
///
/// - `origin`: Must be capable of withdrawing the `assets` and executing XCM.
/// - `dest`: Destination context for the assets. Will typically be `X2(Parent,
/// Parachain(..))` to send from parachain to parachain, or `X1(Parachain(..))` to send
/// from relay to parachain.
/// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will
/// generally be an `AccountId32` value.
/// - `assets`: The assets to be withdrawn. The first item should be the currency used to to
/// pay the fee on the `dest` side. May not be empty.
/// - `fee_asset_item`: The index into `assets` of the item which should be used to pay
/// fees.
#[pallet::call_index(1)]
#[pallet::weight({
let maybe_assets: Result = (*assets.clone()).try_into();
let maybe_dest: Result = (*dest.clone()).try_into();
match (maybe_assets, maybe_dest) {
(Ok(assets), Ok(dest)) => {
use sp_std::vec;
let count = assets.len() as u32;
let mut message = Xcm(vec![
WithdrawAsset(assets),
SetFeesMode { jit_withdraw: true },
InitiateTeleport { assets: Wild(AllCounted(count)), dest, xcm: Xcm(vec![]) },
]);
T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| T::WeightInfo::teleport_assets().saturating_add(w))
}
_ => Weight::MAX,
}
})]
pub fn teleport_assets(
origin: OriginFor,
dest: Box,
beneficiary: Box,
assets: Box,
fee_asset_item: u32,
) -> DispatchResult {
Self::do_teleport_assets(origin, dest, beneficiary, assets, fee_asset_item, Unlimited)
}
/// Transfer some assets from the local chain to the sovereign account of a destination
/// chain and forward a notification XCM.
///
/// **This function is deprecated: Use `limited_reserve_transfer_assets` instead.**
///
/// Fee payment on the destination side is made from the asset in the `assets` vector of
/// index `fee_asset_item`. The weight limit for fees is not provided and thus is unlimited,
/// with all fees taken as needed from the asset.
///
/// - `origin`: Must be capable of withdrawing the `assets` and executing XCM.
/// - `dest`: Destination context for the assets. Will typically be `X2(Parent,
/// Parachain(..))` to send from parachain to parachain, or `X1(Parachain(..))` to send
/// from relay to parachain.
/// - `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` side.
/// - `fee_asset_item`: The index into `assets` of the item which should be used to pay
/// fees.
#[pallet::call_index(2)]
#[pallet::weight({
let maybe_assets: Result = (*assets.clone()).try_into();
let maybe_dest: Result = (*dest.clone()).try_into();
match (maybe_assets, maybe_dest) {
(Ok(assets), Ok(dest)) => {
use sp_std::vec;
// heaviest version of locally executed XCM program: equivalent in weight to
// transfer assets to SA, reanchor them, extend XCM program, and send onward XCM
let mut message = Xcm(vec![
SetFeesMode { jit_withdraw: true },
TransferReserveAsset { assets, dest, xcm: Xcm(vec![]) }
]);
T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| T::WeightInfo::reserve_transfer_assets().saturating_add(w))
}
_ => Weight::MAX,
}
})]
pub fn reserve_transfer_assets(
origin: OriginFor,
dest: Box,
beneficiary: Box,
assets: Box,
fee_asset_item: u32,
) -> DispatchResult {
Self::do_reserve_transfer_assets(
origin,
dest,
beneficiary,
assets,
fee_asset_item,
Unlimited,
)
}
/// Execute an XCM message from a local, signed, origin.
///
/// An event is deposited indicating whether `msg` could be executed completely or only
/// partially.
///
/// No more than `max_weight` will be used in its attempted execution. If this is less than
/// the maximum amount of weight that the message could take to be executed, then no
/// execution attempt will be made.
///
/// NOTE: A successful return to this does *not* imply that the `msg` was executed
/// successfully to completion; only that it was attempted.
#[pallet::call_index(3)]
#[pallet::weight(max_weight.saturating_add(T::WeightInfo::execute()))]
pub fn execute(
origin: OriginFor,
message: Box::RuntimeCall>>,
max_weight: Weight,
) -> DispatchResultWithPostInfo {
let outcome = >::execute(origin, message, max_weight)?;
Ok(Some(outcome.weight_used().saturating_add(T::WeightInfo::execute())).into())
}
/// Extoll that a particular destination can be communicated with through a particular
/// version of XCM.
///
/// - `origin`: Must be an origin specified by AdminOrigin.
/// - `location`: The destination that is being described.
/// - `xcm_version`: The latest version of XCM that `location` supports.
#[pallet::call_index(4)]
#[pallet::weight(T::WeightInfo::force_xcm_version())]
pub fn force_xcm_version(
origin: OriginFor,
location: Box,
version: XcmVersion,
) -> DispatchResult {
T::AdminOrigin::ensure_origin(origin)?;
let location = *location;
SupportedVersion::::insert(
XCM_VERSION,
LatestVersionedMultiLocation(&location),
version,
);
Self::deposit_event(Event::SupportedVersionChanged { location, version });
Ok(())
}
/// Set a safe XCM version (the version that XCM should be encoded with if the most recent
/// version a destination can accept is unknown).
///
/// - `origin`: Must be an origin specified by AdminOrigin.
/// - `maybe_xcm_version`: The default XCM encoding version, or `None` to disable.
#[pallet::call_index(5)]
#[pallet::weight(T::WeightInfo::force_default_xcm_version())]
pub fn force_default_xcm_version(
origin: OriginFor,
maybe_xcm_version: Option,
) -> DispatchResult {
T::AdminOrigin::ensure_origin(origin)?;
SafeXcmVersion::::set(maybe_xcm_version);
Ok(())
}
/// Ask a location to notify us regarding their XCM version and any changes to it.
///
/// - `origin`: Must be an origin specified by AdminOrigin.
/// - `location`: The location to which we should subscribe for XCM version notifications.
#[pallet::call_index(6)]
#[pallet::weight(T::WeightInfo::force_subscribe_version_notify())]
pub fn force_subscribe_version_notify(
origin: OriginFor,
location: Box,
) -> DispatchResult {
T::AdminOrigin::ensure_origin(origin)?;
let location: MultiLocation =
(*location).try_into().map_err(|()| Error::::BadLocation)?;
Self::request_version_notify(location).map_err(|e| {
match e {
XcmError::InvalidLocation => Error::::AlreadySubscribed,
_ => Error::::InvalidOrigin,
}
.into()
})
}
/// Require that a particular destination should no longer notify us regarding any XCM
/// version changes.
///
/// - `origin`: Must be an origin specified by AdminOrigin.
/// - `location`: The location to which we are currently subscribed for XCM version
/// notifications which we no longer desire.
#[pallet::call_index(7)]
#[pallet::weight(T::WeightInfo::force_unsubscribe_version_notify())]
pub fn force_unsubscribe_version_notify(
origin: OriginFor,
location: Box,
) -> DispatchResult {
T::AdminOrigin::ensure_origin(origin)?;
let location: MultiLocation =
(*location).try_into().map_err(|()| Error::::BadLocation)?;
Self::unrequest_version_notify(location).map_err(|e| {
match e {
XcmError::InvalidLocation => Error::::NoSubscription,
_ => Error::::InvalidOrigin,
}
.into()
})
}
/// Transfer some assets from the local chain to the sovereign account of a destination
/// chain and forward a notification XCM.
///
/// Fee payment on the destination side is made from the asset in the `assets` vector of
/// index `fee_asset_item`, up to enough to pay for `weight_limit` of weight. If more weight
/// is needed than `weight_limit`, then the operation will fail and the assets send may be
/// at risk.
///
/// - `origin`: Must be capable of withdrawing the `assets` and executing XCM.
/// - `dest`: Destination context for the assets. Will typically be `X2(Parent,
/// Parachain(..))` to send from parachain to parachain, or `X1(Parachain(..))` to send
/// from relay to parachain.
/// - `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` side.
/// - `fee_asset_item`: The index into `assets` of the item which should be used to pay
/// fees.
/// - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase.
#[pallet::call_index(8)]
#[pallet::weight({
let maybe_assets: Result = (*assets.clone()).try_into();
let maybe_dest: Result = (*dest.clone()).try_into();
match (maybe_assets, maybe_dest) {
(Ok(assets), Ok(dest)) => {
use sp_std::vec;
// heaviest version of locally executed XCM program: equivalent in weight to
// transfer assets to SA, reanchor them, extend XCM program, and send onward XCM
let mut message = Xcm(vec![
SetFeesMode { jit_withdraw: true },
TransferReserveAsset { assets, dest, xcm: Xcm(vec![]) }
]);
T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| T::WeightInfo::reserve_transfer_assets().saturating_add(w))
}
_ => Weight::MAX,
}
})]
pub fn limited_reserve_transfer_assets(
origin: OriginFor,
dest: Box,
beneficiary: Box,
assets: Box,
fee_asset_item: u32,
weight_limit: WeightLimit,
) -> DispatchResult {
Self::do_reserve_transfer_assets(
origin,
dest,
beneficiary,
assets,
fee_asset_item,
weight_limit,
)
}
/// Teleport some assets from the local chain to some destination chain.
///
/// Fee payment on the destination side is made from the asset in the `assets` vector of
/// index `fee_asset_item`, up to enough to pay for `weight_limit` of weight. If more weight
/// is needed than `weight_limit`, then the operation will fail and the assets send may be
/// at risk.
///
/// - `origin`: Must be capable of withdrawing the `assets` and executing XCM.
/// - `dest`: Destination context for the assets. Will typically be `X2(Parent,
/// Parachain(..))` to send from parachain to parachain, or `X1(Parachain(..))` to send
/// from relay to parachain.
/// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will
/// generally be an `AccountId32` value.
/// - `assets`: The assets to be withdrawn. The first item should be the currency used to to
/// pay the fee on the `dest` side. May not be empty.
/// - `fee_asset_item`: The index into `assets` of the item which should be used to pay
/// fees.
/// - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase.
#[pallet::call_index(9)]
#[pallet::weight({
let maybe_assets: Result = (*assets.clone()).try_into();
let maybe_dest: Result = (*dest.clone()).try_into();
match (maybe_assets, maybe_dest) {
(Ok(assets), Ok(dest)) => {
use sp_std::vec;
let count = assets.len() as u32;
let mut message = Xcm(vec![
WithdrawAsset(assets),
SetFeesMode { jit_withdraw: true },
InitiateTeleport { assets: Wild(AllCounted(count)), dest, xcm: Xcm(vec![]) },
]);
T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| T::WeightInfo::teleport_assets().saturating_add(w))
}
_ => Weight::MAX,
}
})]
pub fn limited_teleport_assets(
origin: OriginFor,
dest: Box,
beneficiary: Box,
assets: Box,
fee_asset_item: u32,
weight_limit: WeightLimit,
) -> DispatchResult {
Self::do_teleport_assets(
origin,
dest,
beneficiary,
assets,
fee_asset_item,
weight_limit,
)
}
/// Set or unset the global suspension state of the XCM executor.
///
/// - `origin`: Must be an origin specified by AdminOrigin.
/// - `suspended`: `true` to suspend, `false` to resume.
#[pallet::call_index(10)]
#[pallet::weight(T::WeightInfo::force_suspension())]
pub fn force_suspension(origin: OriginFor, suspended: bool) -> DispatchResult {
T::AdminOrigin::ensure_origin(origin)?;
XcmExecutionSuspended::::set(suspended);
Ok(())
}
}
}
/// The maximum number of distinct assets allowed to be transferred in a single helper extrinsic.
const MAX_ASSETS_FOR_TRANSFER: usize = 2;
impl QueryHandler for Pallet {
type QueryId = u64;
type BlockNumber = BlockNumberFor;
type Error = XcmError;
type UniversalLocation = T::UniversalLocation;
/// Attempt to create a new query ID and register it as a query that is yet to respond.
fn new_query(
responder: impl Into,
timeout: BlockNumberFor,
match_querier: impl Into,
) -> Self::QueryId {
Self::do_new_query(responder, None, timeout, match_querier)
}
/// To check the status of the query, use `fn query()` passing the resultant `QueryId`
/// value.
fn report_outcome(
message: &mut Xcm<()>,
responder: impl Into,
timeout: Self::BlockNumber,
) -> Result {
let responder = responder.into();
let destination = Self::UniversalLocation::get()
.invert_target(&responder)
.map_err(|()| XcmError::LocationNotInvertible)?;
let query_id = Self::new_query(responder, timeout, Here);
let response_info = QueryResponseInfo { destination, query_id, max_weight: Weight::zero() };
let report_error = Xcm(vec![ReportError(response_info)]);
message.0.insert(0, SetAppendix(report_error));
Ok(query_id)
}
/// Removes response when ready and emits [Event::ResponseTaken] event.
fn take_response(query_id: Self::QueryId) -> QueryResponseStatus {
match Queries::::get(query_id) {
Some(QueryStatus::Ready { response, at }) => match response.try_into() {
Ok(response) => {
Queries::::remove(query_id);
Self::deposit_event(Event::ResponseTaken { query_id });
QueryResponseStatus::Ready { response, at }
},
Err(_) => QueryResponseStatus::UnexpectedVersion,
},
Some(QueryStatus::Pending { timeout, .. }) => QueryResponseStatus::Pending { timeout },
Some(_) => QueryResponseStatus::UnexpectedVersion,
None => QueryResponseStatus::NotFound,
}
}
#[cfg(feature = "runtime-benchmarks")]
fn expect_response(id: Self::QueryId, response: Response) {
let response = response.into();
Queries::::insert(
id,
QueryStatus::Ready { response, at: frame_system::Pallet::::block_number() },
);
}
}
impl Pallet {
/// Validate `assets` to be reserve-transferred and return their reserve location.
fn validate_assets_and_find_reserve(
assets: &[MultiAsset],
dest: &MultiLocation,
) -> Result> {
let mut reserve = None;
for asset in assets.iter() {
if let Fungible(x) = asset.fun {
// If fungible asset, ensure non-zero amount.
ensure!(!x.is_zero(), Error::::Empty);
}
let transfer_type =
T::XcmExecutor::determine_for(&asset, dest).map_err(Error::::from)?;
// Ensure asset is not teleportable to `dest`.
ensure!(transfer_type != TransferType::Teleport, Error::::Filtered);
if let Some(reserve) = reserve.as_ref() {
// Ensure transfer for multiple assets uses same reserve location (only fee may have
// different reserve location)
ensure!(reserve == &transfer_type, Error::::TooManyReserves);
} else {
// asset reserve identified
reserve = Some(transfer_type);
}
}
reserve.ok_or(Error::::Empty)
}
fn do_reserve_transfer_assets(
origin: OriginFor,
dest: Box,
beneficiary: Box,
assets: Box,
fee_asset_item: u32,
weight_limit: WeightLimit,
) -> DispatchResult {
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let dest = (*dest).try_into().map_err(|()| Error::::BadVersion)?;
let beneficiary: MultiLocation =
(*beneficiary).try_into().map_err(|()| Error::::BadVersion)?;
let assets: MultiAssets = (*assets).try_into().map_err(|()| Error::::BadVersion)?;
log::trace!(
target: "xcm::pallet_xcm::do_reserve_transfer_assets",
"origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, fee-idx {:?}",
origin_location, dest, beneficiary, assets, fee_asset_item,
);
ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::::TooManyAssets);
let value = (origin_location, assets.into_inner());
ensure!(T::XcmReserveTransferFilter::contains(&value), Error::::Filtered);
let (origin_location, mut assets) = value;
if fee_asset_item as usize >= assets.len() {
return Err(Error::::Empty.into())
}
let fees = assets.swap_remove(fee_asset_item as usize);
let fees_transfer_type =
T::XcmExecutor::determine_for(&fees, &dest).map_err(Error::::from)?;
let assets_transfer_type = if assets.is_empty() {
// Single asset to transfer (one used for fees where transfer type is determined above).
ensure!(fees_transfer_type != TransferType::Teleport, Error::::Filtered);
fees_transfer_type
} else {
// Find reserve for non-fee assets.
Self::validate_assets_and_find_reserve(&assets, &dest)?
};
// local and remote XCM programs to potentially handle fees separately
let separate_fees_instructions: Option<(Xcm<::RuntimeCall>, Xcm<()>)>;
if fees_transfer_type == assets_transfer_type {
// Same reserve location (fees not teleportable), we can batch together fees and assets
// in same reserve-based-transfer.
assets.push(fees.clone());
// no need for custom fees instructions, fees are batched with assets
separate_fees_instructions = None;
} else {
// Disallow _remote reserves_ unless assets & fees have same remote reserve (covered by
// branch above). The reason for this is that we'd need to send XCMs to separate chains
// with no guarantee of delivery order on final destination; therefore we cannot
// guarantee to have fees in place on final destination chain to pay for assets
// transfer.
ensure!(
!matches!(assets_transfer_type, TransferType::RemoteReserve(_)),
Error::::InvalidAssetUnsupportedReserve
);
let fees = fees.clone();
let weight_limit = weight_limit.clone();
// build fees transfer instructions to be added to assets transfers XCM programs
separate_fees_instructions = Some(match fees_transfer_type {
TransferType::LocalReserve =>
Self::local_reserve_fees_instructions(dest, fees, weight_limit)?,
TransferType::DestinationReserve =>
Self::destination_reserve_fees_instructions(dest, fees, weight_limit)?,
TransferType::Teleport =>
Self::teleport_fees_instructions(dest, fees, weight_limit)?,
TransferType::RemoteReserve(_) =>
return Err(Error::::InvalidAssetUnsupportedReserve.into()),
});
};
Self::build_and_execute_xcm_transfer_type(
origin_location,
dest,
beneficiary,
assets,
assets_transfer_type,
fees,
separate_fees_instructions,
weight_limit,
)
}
fn do_teleport_assets(
origin: OriginFor,
dest: Box,
beneficiary: Box,
assets: Box,
fee_asset_item: u32,
weight_limit: WeightLimit,
) -> DispatchResult {
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let dest = (*dest).try_into().map_err(|()| Error::::BadVersion)?;
let beneficiary: MultiLocation =
(*beneficiary).try_into().map_err(|()| Error::::BadVersion)?;
let assets: MultiAssets = (*assets).try_into().map_err(|()| Error::::BadVersion)?;
ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::::TooManyAssets);
let value = (origin_location, assets.into_inner());
ensure!(T::XcmTeleportFilter::contains(&value), Error::::Filtered);
let (origin_location, assets) = value;
for asset in assets.iter() {
let transfer_type =
T::XcmExecutor::determine_for(asset, &dest).map_err(Error::::from)?;
ensure!(matches!(transfer_type, TransferType::Teleport), Error::::Filtered);
}
let fees = assets.get(fee_asset_item as usize).ok_or(Error::::Empty)?.clone();
Self::build_and_execute_xcm_transfer_type(
origin_location,
dest,
beneficiary,
assets,
TransferType::Teleport,
fees,
None,
weight_limit,
)
}
fn build_and_execute_xcm_transfer_type(
origin: MultiLocation,
dest: MultiLocation,
beneficiary: MultiLocation,
assets: Vec,
transfer_type: TransferType,
fees: MultiAsset,
separate_fees_instructions: Option<(Xcm<::RuntimeCall>, Xcm<()>)>,
weight_limit: WeightLimit,
) -> DispatchResult {
log::trace!(
target: "xcm::pallet_xcm::build_and_execute_xcm_transfer_type",
"origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, transfer_type {:?}, \
fees {:?}, fees_xcm: {:?}, weight_limit: {:?}",
origin, dest, beneficiary, assets, transfer_type, fees, separate_fees_instructions, weight_limit,
);
let (mut local_xcm, remote_xcm) = match transfer_type {
TransferType::LocalReserve => {
let (local, remote) = Self::local_reserve_transfer_programs(
dest,
beneficiary,
assets,
fees,
separate_fees_instructions,
weight_limit,
)?;
(local, Some(remote))
},
TransferType::DestinationReserve => {
let (local, remote) = Self::destination_reserve_transfer_programs(
dest,
beneficiary,
assets,
fees,
separate_fees_instructions,
weight_limit,
)?;
(local, Some(remote))
},
TransferType::RemoteReserve(reserve) => (
Self::remote_reserve_transfer_program(
reserve,
dest,
beneficiary,
assets,
fees,
weight_limit,
)?,
None,
),
TransferType::Teleport => (
Self::teleport_assets_program(dest, beneficiary, assets, fees, weight_limit)?,
None,
),
};
let weight =
T::Weigher::weight(&mut local_xcm).map_err(|()| Error::::UnweighableMessage)?;
let hash = local_xcm.using_encoded(sp_io::hashing::blake2_256);
let outcome =
T::XcmExecutor::execute_xcm_in_credit(origin, local_xcm, hash, weight, weight);
Self::deposit_event(Event::Attempted { outcome: outcome.clone() });
if let Some(remote_xcm) = remote_xcm {
outcome.ensure_complete().map_err(|_| Error::::LocalExecutionIncomplete)?;
let (ticket, price) = validate_send::(dest, remote_xcm.clone())
.map_err(Error::::from)?;
if origin != Here.into_location() {
Self::charge_fees(origin, price).map_err(|_| Error::::FeesNotMet)?;
}
let message_id = T::XcmRouter::deliver(ticket).map_err(Error::::from)?;
let e = Event::Sent { origin, destination: dest, message: remote_xcm, message_id };
Self::deposit_event(e);
}
Ok(())
}
fn local_reserve_fees_instructions(
dest: MultiLocation,
fees: MultiAsset,
weight_limit: WeightLimit,
) -> Result<(Xcm<::RuntimeCall>, Xcm<()>), Error> {
let context = T::UniversalLocation::get();
let reanchored_fees = fees
.clone()
.reanchored(&dest, context)
.map_err(|_| Error::::CannotReanchor)?;
let local_execute_xcm = Xcm(vec![
// move `fees` to `dest`s local sovereign account
TransferAsset { assets: fees.into(), beneficiary: dest },
]);
let xcm_on_dest = Xcm(vec![
// let (dest) chain know `fees` are in its SA on reserve
ReserveAssetDeposited(reanchored_fees.clone().into()),
// buy exec using `fees` in holding deposited in above instruction
BuyExecution { fees: reanchored_fees, weight_limit },
]);
Ok((local_execute_xcm, xcm_on_dest))
}
fn local_reserve_transfer_programs(
dest: MultiLocation,
beneficiary: MultiLocation,
assets: Vec,
fees: MultiAsset,
separate_fees_instructions: Option<(Xcm<::RuntimeCall>, Xcm<()>)>,
weight_limit: WeightLimit,
) -> Result<(Xcm<::RuntimeCall>, Xcm<()>), Error> {
// max assets is `assets` (+ potentially separately handled fee)
let max_assets =
assets.len() as u32 + separate_fees_instructions.as_ref().map(|_| 1).unwrap_or(0);
let assets: MultiAssets = assets.into();
let context = T::UniversalLocation::get();
let mut reanchored_assets = assets.clone();
reanchored_assets
.reanchor(&dest, context)
.map_err(|_| Error::::CannotReanchor)?;
// fees are either handled through dedicated instructions, or batched together with assets
let fees_already_handled = separate_fees_instructions.is_some();
let (fees_local_xcm, fees_remote_xcm) = separate_fees_instructions
.map(|(local, remote)| (local.into_inner(), remote.into_inner()))
.unwrap_or_default();
// start off with any necessary local fees specific instructions
let mut local_execute_xcm = fees_local_xcm;
// move `assets` to `dest`s local sovereign account
local_execute_xcm.push(TransferAsset { assets, beneficiary: dest });
// on destination chain, start off with custom fee instructions
let mut xcm_on_dest = fees_remote_xcm;
// continue with rest of assets
xcm_on_dest.extend_from_slice(&[
// let (dest) chain know assets are in its SA on reserve
ReserveAssetDeposited(reanchored_assets),
// following instructions are not exec'ed on behalf of origin chain anymore
ClearOrigin,
]);
if !fees_already_handled {
// no custom fees instructions, they are batched together with `assets` transfer;
// BuyExecution happens after receiving all `assets`
let reanchored_fees =
fees.reanchored(&dest, context).map_err(|_| Error::::CannotReanchor)?;
// buy execution using `fees` batched together with above `reanchored_assets`
xcm_on_dest.push(BuyExecution { fees: reanchored_fees, weight_limit });
}
// deposit all remaining assets in holding to `beneficiary` location
xcm_on_dest.push(DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary });
Ok((Xcm(local_execute_xcm), Xcm(xcm_on_dest)))
}
fn destination_reserve_fees_instructions(
dest: MultiLocation,
fees: MultiAsset,
weight_limit: WeightLimit,
) -> Result<(Xcm<