Newer
Older
Self::do_teleport_assets(origin, dest, beneficiary, assets, fee_asset_item, Unlimited)
/// 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`.
/// **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 `[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.
Gavin Wood
committed
/// - `fee_asset_item`: The index into `assets` of the item which should be used to pay
/// fees.
let maybe_assets: Result<Assets, ()> = (*assets.clone()).try_into();
let maybe_dest: Result<Location, ()> = (*dest.clone()).try_into();
(Ok(assets), Ok(dest)) => {
Gavin Wood
committed
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
Gavin Wood
committed
let mut message = Xcm(vec![
SetFeesMode { jit_withdraw: true },
Gavin Wood
committed
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 reserve_transfer_assets(
dest: Box<VersionedLocation>,
beneficiary: Box<VersionedLocation>,
assets: Box<VersionedAssets>,
Self::do_reserve_transfer_assets(
origin,
dest,
beneficiary,
assets,
fee_asset_item,
/// 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.
Francisco Aguirre
committed
///
/// WARNING: DEPRECATED. `execute` will be removed after June 2024. Use `execute_blob`
/// instead.
#[allow(deprecated)]
#[deprecated(
note = "`execute` will be removed after June 2024. Use `execute_blob` instead."
)]
#[pallet::weight(max_weight.saturating_add(T::WeightInfo::execute()))]
message: Box<VersionedXcm<<T as Config>::RuntimeCall>>,
) -> DispatchResultWithPostInfo {
Francisco Aguirre
committed
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let weight_used = Self::execute_base(origin_location, message, max_weight)?;
Xiliang Chen
committed
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(())
}
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
/// 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)?;
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
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,
)
}
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
/// Claims assets trapped on this pallet because of leftover assets during XCM execution.
///
/// - `origin`: Anyone can call this extrinsic.
/// - `assets`: The exact assets that were trapped. Use the version to specify what version
/// was the latest when they were trapped.
/// - `beneficiary`: The location/account where the claimed assets will be deposited.
#[pallet::call_index(12)]
#[pallet::weight({
let assets_version = assets.identify_version();
let maybe_assets: Result<Assets, ()> = (*assets.clone()).try_into();
let maybe_beneficiary: Result<Location, ()> = (*beneficiary.clone()).try_into();
match (maybe_assets, maybe_beneficiary) {
(Ok(assets), Ok(beneficiary)) => {
let ticket: Location = GeneralIndex(assets_version as u128).into();
let mut message = Xcm(vec![
ClaimAsset { assets: assets.clone(), ticket },
DepositAsset { assets: AllCounted(assets.len() as u32).into(), beneficiary },
]);
T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| T::WeightInfo::claim_assets().saturating_add(w))
}
_ => Weight::MAX
}
})]
pub fn claim_assets(
origin: OriginFor<T>,
assets: Box<VersionedAssets>,
beneficiary: Box<VersionedLocation>,
) -> DispatchResult {
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
log::debug!(target: "xcm::pallet_xcm::claim_assets", "origin: {:?}, assets: {:?}, beneficiary: {:?}", origin_location, assets, beneficiary);
// Extract version from `assets`.
let assets_version = assets.identify_version();
let assets: Assets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
let number_of_assets = assets.len() as u32;
let beneficiary: Location =
(*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
let ticket: Location = GeneralIndex(assets_version as u128).into();
let mut message = Xcm(vec![
ClaimAsset { assets, ticket },
DepositAsset { assets: AllCounted(number_of_assets).into(), beneficiary },
]);
let weight =
T::Weigher::weight(&mut message).map_err(|()| Error::<T>::UnweighableMessage)?;
let mut hash = message.using_encoded(sp_io::hashing::blake2_256);
let outcome = T::XcmExecutor::prepare_and_execute(
origin_location,
message,
&mut hash,
weight,
weight,
);
outcome.ensure_complete().map_err(|error| {
log::error!(target: "xcm::pallet_xcm::claim_assets", "XCM execution failed with error: {:?}", error);
Error::<T>::LocalExecutionIncomplete
})?;
Ok(())
}
Francisco Aguirre
committed
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
/// Execute an XCM from a local, signed, origin.
///
/// An event is deposited indicating whether the message 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.
///
/// The message is passed in encoded. It needs to be decodable as a [`VersionedXcm`].
#[pallet::call_index(13)]
#[pallet::weight(T::WeightInfo::execute_blob())]
pub fn execute_blob(
origin: OriginFor<T>,
encoded_message: BoundedVec<u8, MaxXcmEncodedSize>,
max_weight: Weight,
) -> DispatchResultWithPostInfo {
let weight_used = <Self as ExecuteController<_, _>>::execute_blob(
origin,
encoded_message,
max_weight,
)?;
Ok(Some(weight_used.saturating_add(T::WeightInfo::execute_blob())).into())
}
/// Send an XCM from a local, signed, origin.
///
/// The destination, `dest`, will receive this message with a `DescendOrigin` instruction
/// that makes the origin of the message be the origin on this system.
///
/// The message is passed in encoded. It needs to be decodable as a [`VersionedXcm`].
#[pallet::call_index(14)]
#[pallet::weight(T::WeightInfo::send_blob())]
pub fn send_blob(
origin: OriginFor<T>,
dest: Box<VersionedLocation>,
encoded_message: BoundedVec<u8, MaxXcmEncodedSize>,
) -> DispatchResult {
<Self as SendController<_>>::send_blob(origin, dest, encoded_message)?;
Ok(())
}
/// 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 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<()>,
) -> Result<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: 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: 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))
}