Newer
Older
/// 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.
Adrian Catangiu
committed
#[allow(deprecated)]
#[deprecated(
note = "This extrinsic uses `WeightLimit::Unlimited`, please migrate to `limited_reserve_transfer_assets` or `transfer_assets`"
)]
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.
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.
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.
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
Adrian Catangiu
committed
/// is needed than `weight_limit`, then the operation will fail and the sent assets may be
///
/// - `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.
Adrian Catangiu
committed
#[pallet::weight(T::WeightInfo::reserve_transfer_assets())]
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
Adrian Catangiu
committed
/// is needed than `weight_limit`, then the operation will fail and the sent assets may be
///
/// - `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.
Adrian Catangiu
committed
#[pallet::weight(T::WeightInfo::teleport_assets())]
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)]
pub fn force_suspension(origin: OriginFor<T>, suspended: bool) -> DispatchResult {
T::AdminOrigin::ensure_origin(origin)?;
XcmExecutionSuspended::<T>::set(suspended);
Ok(())
}
/// 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
Adrian Catangiu
committed
/// operation will fail and the sent assets may be at risk.
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
///
/// `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)]
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)?;
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);
Adrian Catangiu
committed
let assets = assets.into_inner();
let fee_asset_item = fee_asset_item as usize;
// 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)?;
Adrian Catangiu
committed
Self::do_transfer_assets(
origin,
dest,
Adrian Catangiu
committed
Either::Left(beneficiary),
assets,
assets_transfer_type,
Adrian Catangiu
committed
fee_asset_item,
fees_transfer_type,
weight_limit,
)
}
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
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
/// 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)]
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
/// 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)]
Adrian Catangiu
committed
#[pallet::weight(max_weight.saturating_add(T::WeightInfo::execute_blob()))]
Francisco Aguirre
committed
1377
1378
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
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)]
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(())
}
Adrian Catangiu
committed
/// Transfer assets from the local chain to the destination chain using explicit transfer
/// types for assets and fees.
///
/// `assets` must have same reserve location or may be teleportable to `dest`. Caller must
/// provide the `assets_transfer_type` to be used for `assets`:
/// - `TransferType::LocalReserve`: transfer assets to sovereign account of destination
/// chain and forward a notification XCM to `dest` to mint and deposit reserve-based
/// assets to `beneficiary`.
/// - `TransferType::DestinationReserve`: 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`.
/// - `TransferType::RemoteReserve(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`. Typically
/// the remote `reserve` is Asset Hub.
/// - `TransferType::Teleport`: burn local assets and forward XCM to `dest` chain to
/// mint/teleport assets and deposit them to `beneficiary`.
///
Adrian Catangiu
committed
/// On the destination chain, as well as any intermediary hops, `BuyExecution` is used to
/// buy execution using transferred `assets` identified by `remote_fees_id`.
/// Make sure enough of the specified `remote_fees_id` asset is included in the given list
/// of `assets`. `remote_fees_id` should be enough to pay for `weight_limit`. If more weight
/// is needed than `weight_limit`, then the operation will fail and the sent assets may be
/// at risk.
///
/// `remote_fees_id` may use different transfer type than rest of `assets` and can be
/// specified through `fees_transfer_type`.
Adrian Catangiu
committed
///
Adrian Catangiu
committed
/// The caller needs to specify what should happen to the transferred assets once they reach
/// the `dest` chain. This is done through the `custom_xcm_on_dest` parameter, which
/// contains the instructions to execute on `dest` as a final step.
/// This is usually as simple as:
/// `Xcm(vec![DepositAsset { assets: Wild(AllCounted(assets.len())), beneficiary }])`,
/// but could be something more exotic like sending the `assets` even further.
Adrian Catangiu
committed
///
/// - `origin`: Must be capable of withdrawing the `assets` and executing XCM.
/// - `dest`: Destination context for the assets. Will typically be `[Parent,
/// Parachain(..)]` to send from parachain to parachain, or `[Parachain(..)]` to send from
/// relay to parachain, or `(parents: 2, (GlobalConsensus(..), ..))` to send from
/// parachain across a bridge to another ecosystem destination.
/// - `assets`: The assets to be withdrawn. This should include the assets used to pay the
/// fee on the `dest` (and possibly reserve) chains.
/// - `assets_transfer_type`: The XCM `TransferType` used to transfer the `assets`.
Adrian Catangiu
committed
/// - `remote_fees_id`: One of the included `assets` to be be used to pay fees.
Adrian Catangiu
committed
/// - `fees_transfer_type`: The XCM `TransferType` used to transfer the `fees` assets.
Adrian Catangiu
committed
/// - `custom_xcm_on_dest`: The XCM to be executed on `dest` chain as the last step of the
/// transfer, which also determines what happens to the assets on the destination chain.
Adrian Catangiu
committed
/// - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase.
#[pallet::call_index(15)]
#[pallet::weight(T::WeightInfo::transfer_assets())]
Adrian Catangiu
committed
pub fn transfer_assets_using_type_and_then(
Adrian Catangiu
committed
origin: OriginFor<T>,
dest: Box<VersionedLocation>,
assets: Box<VersionedAssets>,
assets_transfer_type: Box<TransferType>,
Adrian Catangiu
committed
remote_fees_id: Box<VersionedAssetId>,
Adrian Catangiu
committed
fees_transfer_type: Box<TransferType>,
Adrian Catangiu
committed
custom_xcm_on_dest: Box<VersionedXcm<()>>,
Adrian Catangiu
committed
weight_limit: WeightLimit,
) -> DispatchResult {
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let dest: Location = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
let assets: Assets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
Adrian Catangiu
committed
let fees_id: AssetId =
(*remote_fees_id).try_into().map_err(|()| Error::<T>::BadVersion)?;
let remote_xcm: Xcm<()> =
(*custom_xcm_on_dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
Adrian Catangiu
committed
log::debug!(
Adrian Catangiu
committed
target: "xcm::pallet_xcm::transfer_assets_using_type_and_then",
"origin {origin_location:?}, dest {dest:?}, assets {assets:?} through {assets_transfer_type:?}, \
remote_fees_id {fees_id:?} through {fees_transfer_type:?}, \
custom_xcm_on_dest {remote_xcm:?}, weight-limit {weight_limit:?}",
Adrian Catangiu
committed
);
let assets = assets.into_inner();
ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
let fee_asset_index =
assets.iter().position(|a| a.id == fees_id).ok_or(Error::<T>::FeesNotMet)?;
Self::do_transfer_assets(
origin_location,
dest,
Adrian Catangiu
committed
Either::Right(remote_xcm),
Adrian Catangiu
committed
assets,
*assets_transfer_type,
fee_asset_index,
*fees_transfer_type,
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 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
Adrian Catangiu
committed
let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
origin.clone(),
dest.clone(),
Adrian Catangiu
committed
Either::Left(beneficiary),
Adrian Catangiu
committed
assets,
assets_transfer_type,
FeesHandling::Batched { fees },
Adrian Catangiu
committed
weight_limit,
Adrian Catangiu
committed
)?;
Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm)
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();
Adrian Catangiu
committed
let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
origin_location.clone(),
dest.clone(),
Adrian Catangiu
committed
Either::Left(beneficiary),
Adrian Catangiu
committed
assets,
TransferType::Teleport,
FeesHandling::Batched { fees },
Adrian Catangiu
committed
weight_limit,
Adrian Catangiu
committed
)?;
Self::execute_xcm_transfer(origin_location, dest, local_xcm, remote_xcm)
}
fn do_transfer_assets(
origin: Location,
dest: Location,
Adrian Catangiu
committed
beneficiary: Either<Location, Xcm<()>>,
Adrian Catangiu
committed
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
mut assets: Vec<Asset>,
assets_transfer_type: TransferType,
fee_asset_index: usize,
fees_transfer_type: TransferType,
weight_limit: WeightLimit,
) -> DispatchResult {
// local and remote XCM programs to potentially handle fees separately
let fees = if fees_transfer_type == assets_transfer_type {
let fees = assets.get(fee_asset_index).ok_or(Error::<T>::Empty)?.clone();
// 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_index);
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(
origin.clone(),
dest.clone(),
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 }
};
let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
origin.clone(),
dest.clone(),
beneficiary,
assets,
assets_transfer_type,
fees,
weight_limit,
)?;
Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm)
Adrian Catangiu
committed
}
Adrian Catangiu
committed
fn build_xcm_transfer_type(
Adrian Catangiu
committed
beneficiary: Either<Location, Xcm<()>>,
Adrian Catangiu
committed
transfer_type: TransferType,
fees: FeesHandling<T>,
Adrian Catangiu
committed
weight_limit: WeightLimit,
Adrian Catangiu
committed
) -> Result<(Xcm<<T as Config>::RuntimeCall>, Option<Xcm<()>>), Error<T>> {
log::debug!(
Adrian Catangiu
committed
target: "xcm::pallet_xcm::build_xcm_transfer_type",
Adrian Catangiu
committed
"origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, transfer_type {:?}, \
fees_handling {:?}, weight_limit: {:?}",
origin, dest, beneficiary, assets, transfer_type, fees, weight_limit,
Adrian Catangiu
committed
);
Adrian Catangiu
committed
match transfer_type {
TransferType::LocalReserve => Self::local_reserve_transfer_programs(
origin.clone(),
dest.clone(),
beneficiary,
assets,
fees,
weight_limit,
)
.map(|(local, remote)| (local, Some(remote))),
TransferType::DestinationReserve => Self::destination_reserve_transfer_programs(
origin.clone(),
dest.clone(),
beneficiary,
assets,
fees,
weight_limit,
)
.map(|(local, remote)| (local, Some(remote))),
TransferType::RemoteReserve(reserve) => {
let fees = match fees {
FeesHandling::Batched { fees } => fees,
_ => return Err(Error::<T>::InvalidAssetUnsupportedReserve.into()),
};
Adrian Catangiu
committed
Self::remote_reserve_transfer_program(
Adrian Catangiu
committed
reserve.try_into().map_err(|()| Error::<T>::BadVersion)?,
Adrian Catangiu
committed
beneficiary,
assets,
fees,
weight_limit,
Adrian Catangiu
committed
)
.map(|local| (local, None))
},
Adrian Catangiu
committed
TransferType::Teleport => Self::teleport_assets_program(
origin.clone(),
dest.clone(),
beneficiary,
assets,
fees,
weight_limit,
)
.map(|(local, remote)| (local, Some(remote))),
}
Adrian Catangiu
committed
}
fn execute_xcm_transfer(
origin: Location,
dest: Location,
mut local_xcm: Xcm<<T as Config>::RuntimeCall>,
remote_xcm: Option<Xcm<()>>,
) -> DispatchResult {
log::debug!(
target: "xcm::pallet_xcm::execute_xcm_transfer",
"origin {:?}, dest {:?}, local_xcm {:?}, remote_xcm {:?}",
origin, dest, local_xcm, remote_xcm,
);
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!(
Adrian Catangiu
committed
target: "xcm::pallet_xcm::execute_xcm_transfer",
Xiliang Chen
committed
"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!(
Adrian Catangiu
committed
target: "xcm::pallet_xcm::execute_xcm_transfer",
Xiliang Chen
committed
"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(
Adrian Catangiu
committed
beneficiary: Either<Location, Xcm<()>>,
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
// Use custom XCM on remote chain, or just default to depositing everything to beneficiary.
let custom_remote_xcm = match beneficiary {
Either::Right(custom_xcm) => custom_xcm,
Either::Left(beneficiary) => {
// deposit all remaining assets in holding to `beneficiary` location
Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
},
};
xcm_on_dest.0.extend(custom_remote_xcm.into_iter());
Adrian Catangiu
committed
Ok((local_execute_xcm, xcm_on_dest))