Newer
Older
/// successfully to completion; only that it was attempted.
#[pallet::weight(max_weight.saturating_add(T::WeightInfo::execute()))]
message: Box<VersionedXcm<<T as Config>::RuntimeCall>>,
) -> DispatchResultWithPostInfo {
Xiliang Chen
committed
log::trace!(target: "xcm::pallet_xcm::execute", "message {:?}, max_weight {:?}", message, max_weight);
let outcome = <Self as ExecuteController<_, _>>::execute(origin, message, max_weight)?;
Xiliang Chen
committed
let weight_used = outcome.weight_used();
outcome.ensure_complete().map_err(|error| {
log::error!(target: "xcm::pallet_xcm::execute", "XCM execution failed with error {:?}", error);
Error::<T>::LocalExecutionIncomplete
})?;
Ok(Some(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.
pub fn force_xcm_version(
origin: OriginFor<T>,
T::AdminOrigin::ensure_origin(origin)?;
SupportedVersion::<T>::insert(XCM_VERSION, LatestVersionedLocation(&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::weight(T::WeightInfo::force_default_xcm_version())]
pub fn force_default_xcm_version(
origin: OriginFor<T>,
maybe_xcm_version: Option<XcmVersion>,
) -> DispatchResult {
T::AdminOrigin::ensure_origin(origin)?;
SafeXcmVersion::<T>::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::weight(T::WeightInfo::force_subscribe_version_notify())]
pub fn force_subscribe_version_notify(
origin: OriginFor<T>,
T::AdminOrigin::ensure_origin(origin)?;
Keith Yeung
committed
(*location).try_into().map_err(|()| Error::<T>::BadLocation)?;
Self::request_version_notify(location).map_err(|e| {
match e {
XcmError::InvalidLocation => Error::<T>::AlreadySubscribed,
_ => Error::<T>::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::weight(T::WeightInfo::force_unsubscribe_version_notify())]
pub fn force_unsubscribe_version_notify(
origin: OriginFor<T>,
T::AdminOrigin::ensure_origin(origin)?;
Keith Yeung
committed
(*location).try_into().map_err(|()| Error::<T>::BadLocation)?;
Self::unrequest_version_notify(location).map_err(|e| {
match e {
XcmError::InvalidLocation => Error::<T>::NoSubscription,
_ => Error::<T>::InvalidOrigin,
}
.into()
})
}
/// Transfer some assets from the local chain to the destination chain through their local,
/// destination or remote reserve.
///
/// `assets` must have same reserve location and may not be teleportable to `dest`.
/// - `assets` have local reserve: transfer assets to sovereign account of destination
/// chain and forward a notification XCM to `dest` to mint and deposit reserve-based
/// assets to `beneficiary`.
/// - `assets` have destination reserve: burn local assets and forward a notification to
/// `dest` chain to withdraw the reserve assets from this chain's sovereign account and
/// deposit them to `beneficiary`.
/// - `assets` have remote reserve: burn local assets, forward XCM to reserve chain to move
/// reserves from this chain's SA to `dest` chain's SA, and forward another XCM to `dest`
/// to mint and deposit reserve-based assets to `beneficiary`.
/// 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 `[Parent,
/// Parachain(..)]` to send from parachain to parachain, or `[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` (and possibly reserve) chains.
/// - `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.
let maybe_assets: Result<Assets, ()> = (*assets.clone()).try_into();
let maybe_dest: Result<Location, ()> = (*dest.clone()).try_into();
(Ok(assets), Ok(dest)) => {
use sp_std::vec;
Adrian Catangiu
committed
// 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))
}
}
})]
pub fn limited_reserve_transfer_assets(
origin: OriginFor<T>,
dest: Box<VersionedLocation>,
beneficiary: Box<VersionedLocation>,
assets: Box<VersionedAssets>,
fee_asset_item: u32,
weight_limit: WeightLimit,
) -> DispatchResult {
Self::do_reserve_transfer_assets(
origin,
dest,
beneficiary,
assets,
fee_asset_item,
)
}
/// 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 `[Parent,
/// Parachain(..)]` to send from parachain to parachain, or `[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` chain.
/// - `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.
let maybe_assets: Result<Assets, ()> = (*assets.clone()).try_into();
let maybe_dest: Result<Location, ()> = (*dest.clone()).try_into();
match (maybe_assets, maybe_dest) {
(Ok(assets), Ok(dest)) => {
use sp_std::vec;
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))
}
}
})]
pub fn limited_teleport_assets(
origin: OriginFor<T>,
dest: Box<VersionedLocation>,
beneficiary: Box<VersionedLocation>,
assets: Box<VersionedAssets>,
fee_asset_item: u32,
weight_limit: WeightLimit,
) -> DispatchResult {
Self::do_teleport_assets(
origin,
dest,
beneficiary,
assets,
fee_asset_item,
/// 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<T>, suspended: bool) -> DispatchResult {
T::AdminOrigin::ensure_origin(origin)?;
XcmExecutionSuspended::<T>::set(suspended);
Ok(())
}
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
/// Transfer some assets from the local chain to the destination chain through their local,
/// destination or remote reserve, or through teleports.
///
/// Fee payment on the destination side is made from the asset in the `assets` vector of
/// index `fee_asset_item` (hence referred to as `fees`), 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 sent may be at risk.
///
/// `assets` (excluding `fees`) must have same reserve location or otherwise be teleportable
/// to `dest`, no limitations imposed on `fees`.
/// - for local reserve: transfer assets to sovereign account of destination chain and
/// forward a notification XCM to `dest` to mint and deposit reserve-based assets to
/// `beneficiary`.
/// - for destination reserve: burn local assets and forward a notification to `dest` chain
/// to withdraw the reserve assets from this chain's sovereign account and deposit them
/// to `beneficiary`.
/// - for remote reserve: burn local assets, forward XCM to reserve chain to move reserves
/// from this chain's SA to `dest` chain's SA, and forward another XCM to `dest` to mint
/// and deposit reserve-based assets to `beneficiary`.
/// - for teleports: burn local assets and forward XCM to `dest` chain to mint/teleport
/// assets and deposit them to `beneficiary`.
///
/// - `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` (and possibly reserve) chains.
/// - `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(11)]
#[pallet::weight({
let maybe_assets: Result<Assets, ()> = (*assets.clone()).try_into();
let maybe_dest: Result<Location, ()> = (*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 withdrawing fees,
// burning them, transferring rest of assets to SA, reanchoring them, extending XCM program,
// and sending onward XCM
let mut message = Xcm(vec![
SetFeesMode { jit_withdraw: true },
WithdrawAsset(assets.clone()),
BurnAsset(assets.clone()),
TransferReserveAsset { assets, dest, xcm: Xcm(vec![]) }
]);
T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| T::WeightInfo::transfer_assets().saturating_add(w))
}
_ => Weight::MAX,
}
})]
pub fn transfer_assets(
origin: OriginFor<T>,
dest: Box<VersionedLocation>,
beneficiary: Box<VersionedLocation>,
assets: Box<VersionedAssets>,
fee_asset_item: u32,
weight_limit: WeightLimit,
) -> DispatchResult {
let origin = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let dest = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
(*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
let assets: Assets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
log::debug!(
target: "xcm::pallet_xcm::transfer_assets",
"origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, fee-idx {:?}, weight_limit {:?}",
origin, dest, beneficiary, assets, fee_asset_item, weight_limit,
);
ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
let mut assets = assets.into_inner();
let fee_asset_item = fee_asset_item as usize;
let fees = assets.get(fee_asset_item as usize).ok_or(Error::<T>::Empty)?.clone();
// Find transfer types for fee and non-fee assets.
let (fees_transfer_type, assets_transfer_type) =
Self::find_fee_and_assets_transfer_types(&assets, fee_asset_item, &dest)?;
// local and remote XCM programs to potentially handle fees separately
let fees = if fees_transfer_type == assets_transfer_type {
// no need for custom fees instructions, fees are batched with assets
FeesHandling::Batched { fees }
} 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::<T>::InvalidAssetUnsupportedReserve
);
let weight_limit = weight_limit.clone();
// remove `fees` from `assets` and build separate fees transfer instructions to be
// added to assets transfers XCM programs
let fees = assets.remove(fee_asset_item);
let (local_xcm, remote_xcm) = match fees_transfer_type {
TransferType::LocalReserve => Self::local_reserve_fees_instructions(
origin.clone(),
dest.clone(),
fees,
weight_limit,
)?,
TransferType::DestinationReserve =>
Self::destination_reserve_fees_instructions(
fees,
weight_limit,
)?,
TransferType::Teleport => Self::teleport_fees_instructions(
origin.clone(),
dest.clone(),
fees,
weight_limit,
)?,
TransferType::RemoteReserve(_) =>
return Err(Error::<T>::InvalidAssetUnsupportedReserve.into()),
};
FeesHandling::Separate { local_xcm, remote_xcm }
};
Self::build_and_execute_xcm_transfer_type(
origin,
dest,
beneficiary,
assets,
assets_transfer_type,
fees,
weight_limit,
)
}
/// The maximum number of distinct assets allowed to be transferred in a single helper extrinsic.
const MAX_ASSETS_FOR_TRANSFER: usize = 2;
/// Specify how assets used for fees are handled during asset transfers.
#[derive(Clone, PartialEq)]
enum FeesHandling<T: Config> {
/// `fees` asset can be batch-transferred with rest of assets using same XCM instructions.
/// fees cannot be batched, they are handled separately using XCM programs here.
Separate { local_xcm: Xcm<<T as Config>::RuntimeCall>, remote_xcm: Xcm<()> },
}
impl<T: Config> sp_std::fmt::Debug for FeesHandling<T> {
fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result {
match self {
Self::Batched { fees } => write!(f, "FeesHandling::Batched({:?})", fees),
Self::Separate { local_xcm, remote_xcm } => write!(
f,
"FeesHandling::Separate(local: {:?}, remote: {:?})",
local_xcm, remote_xcm
),
}
}
}
impl<T: Config> QueryHandler for Pallet<T> {
type QueryId = u64;
type BlockNumber = BlockNumberFor<T>;
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(
timeout: BlockNumberFor<T>,
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<()>,
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
timeout: Self::BlockNumber,
) -> Result<Self::QueryId, Self::Error> {
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<Self::BlockNumber> {
match Queries::<T>::get(query_id) {
Some(QueryStatus::Ready { response, at }) => match response.try_into() {
Ok(response) => {
Queries::<T>::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::<T>::insert(
id,
QueryStatus::Ready { response, at: frame_system::Pallet::<T>::block_number() },
);
}
}
/// Find `TransferType`s for `assets` and fee identified through `fee_asset_item`, when
/// transferring to `dest`.
///
/// Validate `assets` to all have same `TransferType`.
fn find_fee_and_assets_transfer_types(
fee_asset_item: usize,
) -> Result<(TransferType, TransferType), Error<T>> {
let mut fees_transfer_type = None;
let mut assets_transfer_type = None;
for (idx, asset) in assets.iter().enumerate() {
Adrian Catangiu
committed
if let Fungible(x) = asset.fun {
// If fungible asset, ensure non-zero amount.
ensure!(!x.is_zero(), Error::<T>::Empty);
}
let transfer_type =
T::XcmExecutor::determine_for(&asset, dest).map_err(Error::<T>::from)?;
if idx == fee_asset_item {
fees_transfer_type = Some(transfer_type);
Adrian Catangiu
committed
} else {
if let Some(existing) = assets_transfer_type.as_ref() {
// Ensure transfer for multiple assets uses same transfer type (only fee may
// have different transfer type/path)
ensure!(existing == &transfer_type, Error::<T>::TooManyReserves);
} else {
// asset reserve identified
assets_transfer_type = Some(transfer_type);
}
Adrian Catangiu
committed
}
}
// single asset also marked as fee item
if assets.len() == 1 {
}
Ok((
fees_transfer_type.ok_or(Error::<T>::Empty)?,
assets_transfer_type.ok_or(Error::<T>::Empty)?,
))
Adrian Catangiu
committed
}
dest: Box<VersionedLocation>,
beneficiary: Box<VersionedLocation>,
assets: Box<VersionedAssets>,
) -> DispatchResult {
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let dest = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
(*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
let assets: Assets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
log::debug!(
Adrian Catangiu
committed
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::<T>::TooManyAssets);
let value = (origin_location, assets.into_inner());
ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
let (origin, assets) = value;
Adrian Catangiu
committed
let fee_asset_item = fee_asset_item as usize;
let fees = assets.get(fee_asset_item as usize).ok_or(Error::<T>::Empty)?.clone();
Adrian Catangiu
committed
// Find transfer types for fee and non-fee assets.
let (fees_transfer_type, assets_transfer_type) =
Self::find_fee_and_assets_transfer_types(&assets, fee_asset_item, &dest)?;
// Ensure assets (and fees according to check below) are not teleportable to `dest`.
ensure!(assets_transfer_type != TransferType::Teleport, Error::<T>::Filtered);
// Ensure all assets (including fees) have same reserve location.
ensure!(assets_transfer_type == fees_transfer_type, Error::<T>::TooManyReserves);
Adrian Catangiu
committed
Self::build_and_execute_xcm_transfer_type(
origin,
Adrian Catangiu
committed
dest,
beneficiary,
assets,
assets_transfer_type,
FeesHandling::Batched { fees },
Adrian Catangiu
committed
weight_limit,
)
dest: Box<VersionedLocation>,
beneficiary: Box<VersionedLocation>,
assets: Box<VersionedAssets>,
) -> DispatchResult {
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let dest = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
(*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
let assets: Assets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
log::debug!(
target: "xcm::pallet_xcm::do_teleport_assets",
"origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, fee-idx {:?}, weight_limit {:?}",
origin_location, dest, beneficiary, assets, fee_asset_item, weight_limit,
);
ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
let value = (origin_location, assets.into_inner());
ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
let (origin_location, assets) = value;
Adrian Catangiu
committed
for asset in assets.iter() {
let transfer_type =
T::XcmExecutor::determine_for(asset, &dest).map_err(Error::<T>::from)?;
ensure!(transfer_type == TransferType::Teleport, Error::<T>::Filtered);
Adrian Catangiu
committed
}
let fees = assets.get(fee_asset_item as usize).ok_or(Error::<T>::Empty)?.clone();
Self::build_and_execute_xcm_transfer_type(
origin_location,
dest,
beneficiary,
assets,
TransferType::Teleport,
FeesHandling::Batched { fees },
Adrian Catangiu
committed
weight_limit,
)
}
fn build_and_execute_xcm_transfer_type(
origin: Location,
dest: Location,
beneficiary: Location,
assets: Vec<Asset>,
Adrian Catangiu
committed
transfer_type: TransferType,
fees: FeesHandling<T>,
Adrian Catangiu
committed
weight_limit: WeightLimit,
) -> DispatchResult {
log::debug!(
Adrian Catangiu
committed
target: "xcm::pallet_xcm::build_and_execute_xcm_transfer_type",
"origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, transfer_type {:?}, \
fees_handling {:?}, weight_limit: {:?}",
origin, dest, beneficiary, assets, transfer_type, fees, weight_limit,
Adrian Catangiu
committed
);
let (mut local_xcm, remote_xcm) = match transfer_type {
TransferType::LocalReserve => {
let (local, remote) = Self::local_reserve_transfer_programs(
Adrian Catangiu
committed
beneficiary,
assets,
fees,
weight_limit,
)?;
(local, Some(remote))
},
TransferType::DestinationReserve => {
let (local, remote) = Self::destination_reserve_transfer_programs(
Adrian Catangiu
committed
beneficiary,
assets,
fees,
weight_limit,
)?;
(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(
Adrian Catangiu
committed
reserve,
Adrian Catangiu
committed
beneficiary,
assets,
fees,
weight_limit,
)?;
(local, None)
},
TransferType::Teleport => {
let (local, remote) = Self::teleport_assets_program(
beneficiary,
assets,
fees,
weight_limit,
)?;
(local, Some(remote))
},
Adrian Catangiu
committed
};
let weight =
T::Weigher::weight(&mut local_xcm).map_err(|()| Error::<T>::UnweighableMessage)?;
let mut hash = local_xcm.using_encoded(sp_io::hashing::blake2_256);
let outcome = T::XcmExecutor::prepare_and_execute(
origin.clone(),
local_xcm,
&mut hash,
weight,
weight,
);
Adrian Catangiu
committed
Self::deposit_event(Event::Attempted { outcome: outcome.clone() });
Xiliang Chen
committed
outcome.ensure_complete().map_err(|error| {
log::error!(
target: "xcm::pallet_xcm::build_and_execute_xcm_transfer_type",
"XCM execution failed with error {:?}", error
);
Error::<T>::LocalExecutionIncomplete
})?;
Adrian Catangiu
committed
Xiliang Chen
committed
if let Some(remote_xcm) = remote_xcm {
let (ticket, price) = validate_send::<T::XcmRouter>(dest.clone(), remote_xcm.clone())
Adrian Catangiu
committed
.map_err(Error::<T>::from)?;
if origin != Here.into_location() {
Self::charge_fees(origin.clone(), price).map_err(|error| {
Xiliang Chen
committed
log::error!(
target: "xcm::pallet_xcm::build_and_execute_xcm_transfer_type",
"Unable to charge fee with error {:?}", error
);
Error::<T>::FeesNotMet
})?;
Adrian Catangiu
committed
}
let message_id = T::XcmRouter::deliver(ticket).map_err(Error::<T>::from)?;
let e = Event::Sent { origin, destination: dest, message: remote_xcm, message_id };
Self::deposit_event(e);
}
Ok(())
}
fn add_fees_to_xcm(
fees: FeesHandling<T>,
weight_limit: WeightLimit,
local: &mut Xcm<<T as Config>::RuntimeCall>,
remote: &mut Xcm<()>,
) -> Result<(), Error<T>> {
match fees {
FeesHandling::Batched { fees } => {
let context = T::UniversalLocation::get();
// 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::<T>::CannotReanchor)?;
// buy execution using `fees` batched together with above `reanchored_assets`
remote.inner_mut().push(BuyExecution { fees: reanchored_fees, weight_limit });
},
FeesHandling::Separate { local_xcm: mut local_fees, remote_xcm: mut remote_fees } => {
// fees are handled by separate XCM instructions, prepend fees instructions (for
// remote XCM they have to be prepended instead of appended to pass barriers).
sp_std::mem::swap(local, &mut local_fees);
sp_std::mem::swap(remote, &mut remote_fees);
// these are now swapped so fees actually go first
local.inner_mut().append(&mut local_fees.into_inner());
remote.inner_mut().append(&mut remote_fees.into_inner());
},
}
Ok(())
}
Adrian Catangiu
committed
fn local_reserve_fees_instructions(
origin: Location,
dest: Location,
fees: Asset,
Adrian Catangiu
committed
weight_limit: WeightLimit,
) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
let value = (origin, vec![fees.clone()]);
ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
Adrian Catangiu
committed
let reanchored_fees = fees
Adrian Catangiu
committed
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(
origin: Location,
dest: Location,
beneficiary: Location,
assets: Vec<Asset>,
fees: FeesHandling<T>,
Adrian Catangiu
committed
weight_limit: WeightLimit,
) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
let value = (origin, assets);
ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
let (_, assets) = value;
Adrian Catangiu
committed
// max assets is `assets` (+ potentially separately handled fee)
let max_assets =
assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 };
Adrian Catangiu
committed
let context = T::UniversalLocation::get();
let mut reanchored_assets = assets.clone();
reanchored_assets
Adrian Catangiu
committed
.map_err(|_| Error::<T>::CannotReanchor)?;
// XCM instructions to be executed on local chain
let mut local_execute_xcm = Xcm(vec![
// locally move `assets` to `dest`s local sovereign account
]);
// XCM instructions to be executed on destination chain
let mut xcm_on_dest = Xcm(vec![
Adrian Catangiu
committed
// 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,
]);
// handle fees
Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;
Adrian Catangiu
committed
// deposit all remaining assets in holding to `beneficiary` location
xcm_on_dest
.inner_mut()
.push(DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary });
Adrian Catangiu
committed
Ok((local_execute_xcm, xcm_on_dest))
Adrian Catangiu
committed
}
fn destination_reserve_fees_instructions(
origin: Location,
dest: Location,
fees: Asset,
Adrian Catangiu
committed
weight_limit: WeightLimit,
) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
let value = (origin, vec![fees.clone()]);
ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
Adrian Catangiu
committed
let context = T::UniversalLocation::get();
let reanchored_fees = fees
.clone()
Adrian Catangiu
committed
.map_err(|_| Error::<T>::CannotReanchor)?;
Adrian Catangiu
committed
let local_execute_xcm = Xcm(vec![
// withdraw reserve-based fees (derivatives)
WithdrawAsset(fees.clone()),
// burn derivatives
BurnAsset(fees),
]);
let xcm_on_dest = Xcm(vec![
// withdraw `fees` from origin chain's sovereign account
WithdrawAsset(reanchored_fees.clone().into()),
// buy exec using `fees` in holding withdrawn in above instruction
BuyExecution { fees: reanchored_fees, weight_limit },
]);
Ok((local_execute_xcm, xcm_on_dest))
}
fn destination_reserve_transfer_programs(
origin: Location,
dest: Location,
beneficiary: Location,
assets: Vec<Asset>,
fees: FeesHandling<T>,
Adrian Catangiu
committed
weight_limit: WeightLimit,
) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
let value = (origin, assets);
ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
let (_, assets) = value;
Adrian Catangiu
committed
// max assets is `assets` (+ potentially separately handled fee)
let max_assets =
assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 };
Adrian Catangiu
committed
let context = T::UniversalLocation::get();
let mut reanchored_assets = assets.clone();
reanchored_assets
Adrian Catangiu
committed
.map_err(|_| Error::<T>::CannotReanchor)?;
// XCM instructions to be executed on local chain
let mut local_execute_xcm = Xcm(vec![
Adrian Catangiu
committed
// withdraw reserve-based assets
WithdrawAsset(assets.clone()),
// burn reserve-based assets
BurnAsset(assets),
]);
// XCM instructions to be executed on destination chain
let mut xcm_on_dest = Xcm(vec![
Adrian Catangiu
committed
// withdraw `assets` from origin chain's sovereign account
WithdrawAsset(reanchored_assets),
// following instructions are not exec'ed on behalf of origin chain anymore
ClearOrigin,
]);
// handle fees
Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;
Adrian Catangiu
committed
// deposit all remaining assets in holding to `beneficiary` location
xcm_on_dest
.inner_mut()
.push(DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary });
Adrian Catangiu
committed
Ok((local_execute_xcm, xcm_on_dest))
Adrian Catangiu
committed
}
// function assumes fees and assets have the same remote reserve
fn remote_reserve_transfer_program(
origin: Location,
reserve: Location,
dest: Location,
beneficiary: Location,
assets: Vec<Asset>,
fees: Asset,
Adrian Catangiu
committed
weight_limit: WeightLimit,
) -> Result<Xcm<<T as Config>::RuntimeCall>, Error<T>> {
let value = (origin, assets);
ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
let (_, assets) = value;
Adrian Catangiu
committed
let max_assets = assets.len() as u32;
let context = T::UniversalLocation::get();
// we spend up to half of fees for execution on reserve and other half for execution on
// destination
let (fees_half_1, fees_half_2) = Self::halve_fees(fees)?;
// identifies fee item as seen by `reserve` - to be used at reserve chain
let reserve_fees = fees_half_1
Adrian Catangiu
committed
.map_err(|_| Error::<T>::CannotReanchor)?;
// identifies fee item as seen by `dest` - to be used at destination chain
let dest_fees = fees_half_2
.reanchored(&dest, &context)
.map_err(|_| Error::<T>::CannotReanchor)?;
Adrian Catangiu
committed
// identifies `dest` as seen by `reserve`
let dest = dest.reanchored(&reserve, &context).map_err(|_| Error::<T>::CannotReanchor)?;
Adrian Catangiu
committed
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
// 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 },
]);
// xcm to be executed on reserve
let xcm_on_reserve = Xcm(vec![
BuyExecution { fees: reserve_fees, weight_limit },
DepositReserveAsset { assets: Wild(AllCounted(max_assets)), dest, xcm: xcm_on_dest },
]);
Ok(Xcm(vec![
WithdrawAsset(assets.into()),
InitiateReserveWithdraw {
assets: Wild(AllCounted(max_assets)),
reserve,
xcm: xcm_on_reserve,
},
]))
}
fn teleport_fees_instructions(
origin: Location,
dest: Location,
fees: Asset,
Adrian Catangiu
committed
weight_limit: WeightLimit,
) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
Adrian Catangiu
committed
let value = (origin, vec![fees.clone()]);
ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
Adrian Catangiu
committed
let context = T::UniversalLocation::get();
let reanchored_fees = fees
.clone()
Adrian Catangiu
committed
.map_err(|_| Error::<T>::CannotReanchor)?;
// XcmContext irrelevant in teleports checks
let dummy_context =
XcmContext { origin: None, message_id: Default::default(), topic: None };
// We should check that the asset can actually be teleported out (for this to
// be in error, there would need to be an accounting violation by ourselves,
// so it's unlikely, but we don't want to allow that kind of bug to leak into
// a trusted chain.
<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::can_check_out(
&dest,
&fees,
&dummy_context,
)
.map_err(|_| Error::<T>::CannotCheckOutTeleport)?;
// safe to do this here, we're in a transactional call that will be reverted on any
// errors down the line
Adrian Catangiu
committed
<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::check_out(
&dest,
&fees,
&dummy_context,
);
Adrian Catangiu
committed
let local_execute_xcm = Xcm(vec![
// withdraw fees
WithdrawAsset(fees.clone()),
// burn fees
BurnAsset(fees),
]);
let xcm_on_dest = Xcm(vec![
// (dest) chain receive teleported assets burned on origin chain
ReceiveTeleportedAsset(reanchored_fees.clone().into()),
// buy exec using `fees` in holding received in above instruction
BuyExecution { fees: reanchored_fees, weight_limit },
]);
Ok((local_execute_xcm, xcm_on_dest))
}
fn teleport_assets_program(
origin: Location,
dest: Location,
beneficiary: Location,
assets: Vec<Asset>,
fees: FeesHandling<T>,
Adrian Catangiu
committed
weight_limit: WeightLimit,
) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
let value = (origin, assets);
ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
let (_, assets) = value;
// max assets is `assets` (+ potentially separately handled fee)
let max_assets =
assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 };
Adrian Catangiu
committed
let context = T::UniversalLocation::get();
let mut reanchored_assets = assets.clone();
reanchored_assets
.map_err(|_| Error::<T>::CannotReanchor)?;
// XcmContext irrelevant in teleports checks
let dummy_context =
XcmContext { origin: None, message_id: Default::default(), topic: None };
for asset in assets.inner() {
// We should check that the asset can actually be teleported out (for this to
// be in error, there would need to be an accounting violation by ourselves,
// so it's unlikely, but we don't want to allow that kind of bug to leak into
// a trusted chain.
<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::can_check_out(