Newer
Older
/// execution attempt will be made.
/// NOTE: A successful return to this does *not* imply that the `msg` was executed
/// successfully to completion; only that it was attempted.
#[pallet::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>,
location: Box<MultiLocation>,
T::AdminOrigin::ensure_origin(origin)?;
let location = *location;
SupportedVersion::<T>::insert(
XCM_VERSION,
LatestVersionedMultiLocation(&location),
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>,
location: Box<VersionedMultiLocation>,
) -> DispatchResult {
T::AdminOrigin::ensure_origin(origin)?;
![Keith Yeung Keith Yeung's avatar](/assets/no_avatar-849f9c04a3a0d0cea2424ae97b27447dc64a7dbfae83c036c45b403392f0e8ba.png)
Keith Yeung
committed
let location: MultiLocation =
(*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>,
location: Box<VersionedMultiLocation>,
) -> DispatchResult {
T::AdminOrigin::ensure_origin(origin)?;
![Keith Yeung Keith Yeung's avatar](/assets/no_avatar-849f9c04a3a0d0cea2424ae97b27447dc64a7dbfae83c036c45b403392f0e8ba.png)
Keith Yeung
committed
let location: MultiLocation =
(*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 sovereign account of a destination
/// chain and forward a notification XCM.
/// Fee payment on the destination side is made from the asset in the `assets` vector of
/// index `fee_asset_item`, up to enough to pay for `weight_limit` of weight. If more weight
/// is needed than `weight_limit`, then the operation will fail and the assets send may be
/// at risk.
///
/// - `origin`: Must be capable of withdrawing the `assets` and executing XCM.
/// - `dest`: Destination context for the assets. Will typically be `X2(Parent,
/// Parachain(..))` to send from parachain to parachain, or `X1(Parachain(..))` to send
/// from relay to parachain.
/// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will
/// generally be an `AccountId32` value.
/// - `assets`: The assets to be withdrawn. This should include the assets used to pay the
/// fee on the `dest` side.
/// - `fee_asset_item`: The index into `assets` of the item which should be used to pay
/// fees.
/// - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase.
let maybe_assets: Result<MultiAssets, ()> = (*assets.clone()).try_into();
let maybe_dest: Result<MultiLocation, ()> = (*dest.clone()).try_into();
match (maybe_assets, maybe_dest) {
(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<VersionedMultiLocation>,
beneficiary: Box<VersionedMultiLocation>,
assets: Box<VersionedMultiAssets>,
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 `X2(Parent,
/// Parachain(..))` to send from parachain to parachain, or `X1(Parachain(..))` to send
/// from relay to parachain.
/// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will
/// generally be an `AccountId32` value.
/// - `assets`: The assets to be withdrawn. The first item should be the currency used to to
/// pay the fee on the `dest` side. May not be empty.
/// - `fee_asset_item`: The index into `assets` of the item which should be used to pay
/// fees.
/// - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase.
#[pallet::weight({
let maybe_assets: Result<MultiAssets, ()> = (*assets.clone()).try_into();
let maybe_dest: Result<MultiLocation, ()> = (*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<VersionedMultiLocation>,
beneficiary: Box<VersionedMultiLocation>,
assets: Box<VersionedMultiAssets>,
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(())
}
/// The maximum number of distinct assets allowed to be transferred in a single helper extrinsic.
const MAX_ASSETS_FOR_TRANSFER: usize = 2;
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(
responder: impl Into<MultiLocation>,
timeout: BlockNumberFor<T>,
match_querier: impl Into<MultiLocation>,
) -> Self::QueryId {
Self::do_new_query(responder, None, timeout, match_querier)
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
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
1287
1288
1289
1290
1291
1292
1293
}
/// To check the status of the query, use `fn query()` passing the resultant `QueryId`
/// value.
fn report_outcome(
message: &mut Xcm<()>,
responder: impl Into<MultiLocation>,
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() },
);
}
}
Adrian Catangiu
committed
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
/// Validate `assets` to be reserve-transferred and return their reserve location.
fn validate_assets_and_find_reserve(
assets: &[MultiAsset],
dest: &MultiLocation,
) -> Result<TransferType, Error<T>> {
let mut reserve = None;
for asset in assets.iter() {
if let Fungible(x) = asset.fun {
// If fungible asset, ensure non-zero amount.
ensure!(!x.is_zero(), Error::<T>::Empty);
}
let transfer_type =
T::XcmExecutor::determine_for(&asset, dest).map_err(Error::<T>::from)?;
// Ensure asset is not teleportable to `dest`.
ensure!(transfer_type != TransferType::Teleport, Error::<T>::Filtered);
if let Some(reserve) = reserve.as_ref() {
// Ensure transfer for multiple assets uses same reserve location (only fee may have
// different reserve location)
ensure!(reserve == &transfer_type, Error::<T>::TooManyReserves);
} else {
// asset reserve identified
reserve = Some(transfer_type);
}
}
reserve.ok_or(Error::<T>::Empty)
}
fn do_reserve_transfer_assets(
origin: OriginFor<T>,
dest: Box<VersionedMultiLocation>,
beneficiary: Box<VersionedMultiLocation>,
assets: Box<VersionedMultiAssets>,
fee_asset_item: u32,
) -> DispatchResult {
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let dest = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
let beneficiary: MultiLocation =
(*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
let assets: MultiAssets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
Adrian Catangiu
committed
log::trace!(
target: "xcm::pallet_xcm::do_reserve_transfer_assets",
"origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, fee-idx {:?}",
origin_location, dest, beneficiary, assets, fee_asset_item,
);
ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
let value = (origin_location, assets.into_inner());
ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
Adrian Catangiu
committed
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
let (origin_location, mut assets) = value;
if fee_asset_item as usize >= assets.len() {
return Err(Error::<T>::Empty.into())
}
let fees = assets.swap_remove(fee_asset_item as usize);
let fees_transfer_type =
T::XcmExecutor::determine_for(&fees, &dest).map_err(Error::<T>::from)?;
let assets_transfer_type = if assets.is_empty() {
// Single asset to transfer (one used for fees where transfer type is determined above).
ensure!(fees_transfer_type != TransferType::Teleport, Error::<T>::Filtered);
fees_transfer_type
} else {
// Find reserve for non-fee assets.
Self::validate_assets_and_find_reserve(&assets, &dest)?
};
// local and remote XCM programs to potentially handle fees separately
let separate_fees_instructions: Option<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>)>;
if fees_transfer_type == assets_transfer_type {
// Same reserve location (fees not teleportable), we can batch together fees and assets
// in same reserve-based-transfer.
assets.push(fees.clone());
// no need for custom fees instructions, fees are batched with assets
separate_fees_instructions = None;
} else {
// Disallow _remote reserves_ unless assets & fees have same remote reserve (covered by
// branch above). The reason for this is that we'd need to send XCMs to separate chains
// with no guarantee of delivery order on final destination; therefore we cannot
// guarantee to have fees in place on final destination chain to pay for assets
// transfer.
ensure!(
!matches!(assets_transfer_type, TransferType::RemoteReserve(_)),
Error::<T>::InvalidAssetUnsupportedReserve
);
let fees = fees.clone();
let weight_limit = weight_limit.clone();
// build fees transfer instructions to be added to assets transfers XCM programs
separate_fees_instructions = Some(match fees_transfer_type {
TransferType::LocalReserve =>
Self::local_reserve_fees_instructions(dest, fees, weight_limit)?,
TransferType::DestinationReserve =>
Self::destination_reserve_fees_instructions(dest, fees, weight_limit)?,
TransferType::Teleport =>
Adrian Catangiu
committed
Self::teleport_fees_instructions(origin_location, dest, fees, weight_limit)?,
Adrian Catangiu
committed
TransferType::RemoteReserve(_) =>
return Err(Error::<T>::InvalidAssetUnsupportedReserve.into()),
});
};
Self::build_and_execute_xcm_transfer_type(
origin_location,
dest,
beneficiary,
assets,
assets_transfer_type,
fees,
separate_fees_instructions,
weight_limit,
)
fn do_teleport_assets(
origin: OriginFor<T>,
dest: Box<VersionedMultiLocation>,
beneficiary: Box<VersionedMultiLocation>,
assets: Box<VersionedMultiAssets>,
fee_asset_item: u32,
) -> DispatchResult {
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let dest = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
let beneficiary: MultiLocation =
(*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
let assets: MultiAssets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
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
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
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
for asset in assets.iter() {
let transfer_type =
T::XcmExecutor::determine_for(asset, &dest).map_err(Error::<T>::from)?;
ensure!(matches!(transfer_type, TransferType::Teleport), Error::<T>::Filtered);
}
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,
fees,
None,
weight_limit,
)
}
fn build_and_execute_xcm_transfer_type(
origin: MultiLocation,
dest: MultiLocation,
beneficiary: MultiLocation,
assets: Vec<MultiAsset>,
transfer_type: TransferType,
fees: MultiAsset,
separate_fees_instructions: Option<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>)>,
weight_limit: WeightLimit,
) -> DispatchResult {
log::trace!(
target: "xcm::pallet_xcm::build_and_execute_xcm_transfer_type",
"origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, transfer_type {:?}, \
fees {:?}, fees_xcm: {:?}, weight_limit: {:?}",
origin, dest, beneficiary, assets, transfer_type, fees, separate_fees_instructions, weight_limit,
);
let (mut local_xcm, remote_xcm) = match transfer_type {
TransferType::LocalReserve => {
let (local, remote) = Self::local_reserve_transfer_programs(
dest,
beneficiary,
assets,
fees,
separate_fees_instructions,
weight_limit,
)?;
(local, Some(remote))
},
TransferType::DestinationReserve => {
let (local, remote) = Self::destination_reserve_transfer_programs(
dest,
beneficiary,
assets,
fees,
separate_fees_instructions,
weight_limit,
)?;
(local, Some(remote))
},
TransferType::RemoteReserve(reserve) => (
Self::remote_reserve_transfer_program(
reserve,
dest,
beneficiary,
assets,
fees,
weight_limit,
)?,
None,
),
TransferType::Teleport => (
Self::teleport_assets_program(dest, beneficiary, assets, fees, weight_limit)?,
None,
),
};
let weight =
T::Weigher::weight(&mut local_xcm).map_err(|()| Error::<T>::UnweighableMessage)?;
let hash = local_xcm.using_encoded(sp_io::hashing::blake2_256);
let outcome =
T::XcmExecutor::execute_xcm_in_credit(origin, local_xcm, hash, weight, weight);
Self::deposit_event(Event::Attempted { outcome: outcome.clone() });
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 {
Adrian Catangiu
committed
let (ticket, price) = validate_send::<T::XcmRouter>(dest, remote_xcm.clone())
.map_err(Error::<T>::from)?;
if origin != Here.into_location() {
Xiliang Chen
committed
Self::charge_fees(origin, price).map_err(|error| {
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 local_reserve_fees_instructions(
dest: MultiLocation,
fees: MultiAsset,
weight_limit: WeightLimit,
) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
Adrian Catangiu
committed
let reanchored_fees = fees
.clone()
.reanchored(&dest, context)
.map_err(|_| Error::<T>::CannotReanchor)?;
Adrian Catangiu
committed
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
let local_execute_xcm = Xcm(vec![
// move `fees` to `dest`s local sovereign account
TransferAsset { assets: fees.into(), beneficiary: dest },
]);
let xcm_on_dest = Xcm(vec![
// let (dest) chain know `fees` are in its SA on reserve
ReserveAssetDeposited(reanchored_fees.clone().into()),
// buy exec using `fees` in holding deposited in above instruction
BuyExecution { fees: reanchored_fees, weight_limit },
]);
Ok((local_execute_xcm, xcm_on_dest))
}
fn local_reserve_transfer_programs(
dest: MultiLocation,
beneficiary: MultiLocation,
assets: Vec<MultiAsset>,
fees: MultiAsset,
separate_fees_instructions: Option<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>)>,
weight_limit: WeightLimit,
) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
// max assets is `assets` (+ potentially separately handled fee)
let max_assets =
assets.len() as u32 + separate_fees_instructions.as_ref().map(|_| 1).unwrap_or(0);
Adrian Catangiu
committed
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
let context = T::UniversalLocation::get();
let mut reanchored_assets = assets.clone();
reanchored_assets
.reanchor(&dest, context)
.map_err(|_| Error::<T>::CannotReanchor)?;
// fees are either handled through dedicated instructions, or batched together with assets
let fees_already_handled = separate_fees_instructions.is_some();
let (fees_local_xcm, fees_remote_xcm) = separate_fees_instructions
.map(|(local, remote)| (local.into_inner(), remote.into_inner()))
.unwrap_or_default();
// start off with any necessary local fees specific instructions
let mut local_execute_xcm = fees_local_xcm;
// move `assets` to `dest`s local sovereign account
local_execute_xcm.push(TransferAsset { assets, beneficiary: dest });
// on destination chain, start off with custom fee instructions
let mut xcm_on_dest = fees_remote_xcm;
// continue with rest of assets
xcm_on_dest.extend_from_slice(&[
// let (dest) chain know assets are in its SA on reserve
ReserveAssetDeposited(reanchored_assets),
// following instructions are not exec'ed on behalf of origin chain anymore
ClearOrigin,
]);
if !fees_already_handled {
// no custom fees instructions, they are batched together with `assets` transfer;
// BuyExecution happens after receiving all `assets`
let reanchored_fees =
fees.reanchored(&dest, context).map_err(|_| Error::<T>::CannotReanchor)?;
// buy execution using `fees` batched together with above `reanchored_assets`
xcm_on_dest.push(BuyExecution { fees: reanchored_fees, weight_limit });
}
// deposit all remaining assets in holding to `beneficiary` location
xcm_on_dest.push(DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary });
Ok((Xcm(local_execute_xcm), Xcm(xcm_on_dest)))
}
fn destination_reserve_fees_instructions(
dest: MultiLocation,
fees: MultiAsset,
weight_limit: WeightLimit,
) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
let context = T::UniversalLocation::get();
let reanchored_fees = fees
.clone()
.reanchored(&dest, context)
.map_err(|_| Error::<T>::CannotReanchor)?;
let fees: MultiAssets = fees.into();
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(
dest: MultiLocation,
beneficiary: MultiLocation,
assets: Vec<MultiAsset>,
fees: MultiAsset,
separate_fees_instructions: Option<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>)>,
weight_limit: WeightLimit,
) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
// max assets is `assets` (+ potentially separately handled fee)
let max_assets =
assets.len() as u32 + separate_fees_instructions.as_ref().map(|_| 1).unwrap_or(0);
let assets: MultiAssets = assets.into();
let context = T::UniversalLocation::get();
let mut reanchored_assets = assets.clone();
reanchored_assets
.reanchor(&dest, context)
.map_err(|_| Error::<T>::CannotReanchor)?;
// fees are either handled through dedicated instructions, or batched together with assets
let fees_already_handled = separate_fees_instructions.is_some();
let (fees_local_xcm, fees_remote_xcm) = separate_fees_instructions
.map(|(local, remote)| (local.into_inner(), remote.into_inner()))
.unwrap_or_default();
// start off with any necessary local fees specific instructions
let mut local_execute_xcm = fees_local_xcm;
// continue with rest of assets
local_execute_xcm.extend_from_slice(&[
// withdraw reserve-based assets
WithdrawAsset(assets.clone()),
// burn reserve-based assets
BurnAsset(assets),
]);
// on destination chain, start off with custom fee instructions
let mut xcm_on_dest = fees_remote_xcm;
// continue with rest of assets
xcm_on_dest.extend_from_slice(&[
// withdraw `assets` from origin chain's sovereign account
WithdrawAsset(reanchored_assets),
// following instructions are not exec'ed on behalf of origin chain anymore
ClearOrigin,
]);
if !fees_already_handled {
// no custom fees instructions, they are batched together with `assets` transfer;
// BuyExecution happens after receiving all `assets`
let reanchored_fees =
fees.reanchored(&dest, context).map_err(|_| Error::<T>::CannotReanchor)?;
// buy execution using `fees` batched together with above `reanchored_assets`
xcm_on_dest.push(BuyExecution { fees: reanchored_fees, weight_limit });
}
// deposit all remaining assets in holding to `beneficiary` location
xcm_on_dest.push(DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary });
Ok((Xcm(local_execute_xcm), Xcm(xcm_on_dest)))
}
// function assumes fees and assets have the same remote reserve
fn remote_reserve_transfer_program(
reserve: MultiLocation,
dest: MultiLocation,
beneficiary: MultiLocation,
assets: Vec<MultiAsset>,
fees: MultiAsset,
weight_limit: WeightLimit,
) -> Result<Xcm<<T as Config>::RuntimeCall>, Error<T>> {
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
.reanchored(&reserve, context)
.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)?;
// identifies `dest` as seen by `reserve`
let dest = dest.reanchored(&reserve, context).map_err(|_| Error::<T>::CannotReanchor)?;
// xcm to be executed at dest
let xcm_on_dest = Xcm(vec![
BuyExecution { fees: dest_fees, weight_limit: weight_limit.clone() },
DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary },
]);
// 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(
Adrian Catangiu
committed
origin: MultiLocation,
Adrian Catangiu
committed
dest: MultiLocation,
fees: MultiAsset,
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
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
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
let context = T::UniversalLocation::get();
let reanchored_fees = fees
.clone()
.reanchored(&dest, context)
.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)?;
<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::check_out(
&dest,
&fees,
&dummy_context,
);
let fees: MultiAssets = fees.into();
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(
dest: MultiLocation,
beneficiary: MultiLocation,
assets: Vec<MultiAsset>,
mut fees: MultiAsset,
weight_limit: WeightLimit,
) -> Result<Xcm<<T as Config>::RuntimeCall>, Error<T>> {
let context = T::UniversalLocation::get();
fees.reanchor(&dest, context).map_err(|_| Error::<T>::CannotReanchor)?;
let max_assets = assets.len() as u32;
let xcm_on_dest = Xcm(vec![
BuyExecution { fees, weight_limit },
DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary },
]);
Adrian Catangiu
committed
Ok(Xcm(vec![
WithdrawAsset(assets.into()),
SetFeesMode { jit_withdraw: true },
Adrian Catangiu
committed
InitiateTeleport { assets: Wild(AllCounted(max_assets)), dest, xcm: xcm_on_dest },
]))
}
/// Halve `fees` fungible amount.
pub(crate) fn halve_fees(fees: MultiAsset) -> Result<(MultiAsset, MultiAsset), Error<T>> {
match fees.fun {
Fungible(amount) => {
let fee1 = amount.saturating_div(2);
let fee2 = amount.saturating_sub(fee1);
ensure!(fee1 > 0, Error::<T>::FeesNotMet);
ensure!(fee2 > 0, Error::<T>::FeesNotMet);
Ok((MultiAsset::from((fees.id, fee1)), MultiAsset::from((fees.id, fee2))))
},
NonFungible(_) => Err(Error::<T>::FeesNotMet),
}
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
/// Will always make progress, and will do its best not to use much more than `weight_cutoff`
/// in doing so.
pub(crate) fn check_xcm_version_change(
mut stage: VersionMigrationStage,
weight_cutoff: Weight,
) -> (Weight, Option<VersionMigrationStage>) {
let mut weight_used = Weight::zero();
let sv_migrate_weight = T::WeightInfo::migrate_supported_version();
let vn_migrate_weight = T::WeightInfo::migrate_version_notifiers();
let vnt_already_notified_weight = T::WeightInfo::already_notified_target();
let vnt_notify_weight = T::WeightInfo::notify_current_targets();
let vnt_migrate_weight = T::WeightInfo::migrate_version_notify_targets();
let vnt_migrate_fail_weight = T::WeightInfo::notify_target_migration_fail();
let vnt_notify_migrate_weight = T::WeightInfo::migrate_and_notify_old_targets();
use VersionMigrationStage::*;
if stage == MigrateSupportedVersion {
// We assume that supported XCM version only ever increases, so just cycle through lower
// XCM versioned from the current.
for v in 0..XCM_VERSION {
for (old_key, value) in SupportedVersion::<T>::drain_prefix(v) {
if let Ok(new_key) = old_key.into_latest() {
SupportedVersion::<T>::insert(XCM_VERSION, new_key, value);
}
weight_used.saturating_accrue(sv_migrate_weight);
if weight_used.any_gte(weight_cutoff) {
return (weight_used, Some(stage))
stage = MigrateVersionNotifiers;
}
if stage == MigrateVersionNotifiers {
for v in 0..XCM_VERSION {
for (old_key, value) in VersionNotifiers::<T>::drain_prefix(v) {
if let Ok(new_key) = old_key.into_latest() {
VersionNotifiers::<T>::insert(XCM_VERSION, new_key, value);
}
weight_used.saturating_accrue(vn_migrate_weight);
if weight_used.any_gte(weight_cutoff) {
return (weight_used, Some(stage))
if let NotifyCurrentTargets(maybe_last_raw_key) = stage {
let mut iter = match maybe_last_raw_key {
Some(k) => VersionNotifyTargets::<T>::iter_prefix_from(XCM_VERSION, k),
None => VersionNotifyTargets::<T>::iter_prefix(XCM_VERSION),
};
while let Some((key, value)) = iter.next() {
let (query_id, max_weight, target_xcm_version) = value;
let new_key: MultiLocation = match key.clone().try_into() {
Ok(k) if target_xcm_version != xcm_version => k,
_ => {
// We don't early return here since we need to be certain that we
// make some progress.
weight_used.saturating_accrue(vnt_already_notified_weight);
continue
},
let response = Response::Version(xcm_version);
let message =
Xcm(vec![QueryResponse { query_id, response, max_weight, querier: None }]);
let event = match send_xcm::<T::XcmRouter>(new_key, message) {
Ok((message_id, cost)) => {
let value = (query_id, max_weight, xcm_version);
VersionNotifyTargets::<T>::insert(XCM_VERSION, key, value);
Event::VersionChangeNotified {
destination: new_key,
result: xcm_version,
cost,
message_id,
}
},
Err(e) => {
VersionNotifyTargets::<T>::remove(XCM_VERSION, key);
Event::NotifyTargetSendFail { location: new_key, query_id, error: e.into() }
},
};
Self::deposit_event(event);
weight_used.saturating_accrue(vnt_notify_weight);
if weight_used.any_gte(weight_cutoff) {
let last = Some(iter.last_raw_key().into());
return (weight_used, Some(NotifyCurrentTargets(last)))
}
}
stage = MigrateAndNotifyOldTargets;
}
if stage == MigrateAndNotifyOldTargets {
for v in 0..XCM_VERSION {
for (old_key, value) in VersionNotifyTargets::<T>::drain_prefix(v) {
let (query_id, max_weight, target_xcm_version) = value;
let new_key = match MultiLocation::try_from(old_key.clone()) {
Ok(k) => k,
Err(()) => {
Self::deposit_event(Event::NotifyTargetMigrationFail {
location: old_key,
query_id: value.0,
});
weight_used.saturating_accrue(vnt_migrate_fail_weight);
if weight_used.any_gte(weight_cutoff) {
return (weight_used, Some(stage))
}
let versioned_key = LatestVersionedMultiLocation(&new_key);
if target_xcm_version == xcm_version {
VersionNotifyTargets::<T>::insert(XCM_VERSION, versioned_key, value);
weight_used.saturating_accrue(vnt_migrate_weight);
} else {
// Need to notify target.
let response = Response::Version(xcm_version);
let message = Xcm(vec![QueryResponse {
query_id,
response,
max_weight,
querier: None,
}]);
let event = match send_xcm::<T::XcmRouter>(new_key, message) {
Ok((message_id, cost)) => {
VersionNotifyTargets::<T>::insert(
XCM_VERSION,
versioned_key,
(query_id, max_weight, xcm_version),
);
Event::VersionChangeNotified {
destination: new_key,
result: xcm_version,
cost,
message_id,
}
},
Err(e) => Event::NotifyTargetSendFail {
location: new_key,
query_id,
error: e.into(),
Self::deposit_event(event);
weight_used.saturating_accrue(vnt_notify_migrate_weight);
}
if weight_used.any_gte(weight_cutoff) {
return (weight_used, Some(stage))
/// Request that `dest` informs us of its version.
pub fn request_version_notify(dest: impl Into<MultiLocation>) -> XcmResult {
let dest = dest.into();
let versioned_dest = VersionedMultiLocation::from(dest);
let already = VersionNotifiers::<T>::contains_key(XCM_VERSION, &versioned_dest);
ensure!(!already, XcmError::InvalidLocation);
let query_id = QueryCounter::<T>::mutate(|q| {
let r = *q;
q.saturating_inc();
r
});
// TODO #3735: Correct weight.
let instruction = SubscribeVersion { query_id, max_response_weight: Weight::zero() };
let (message_id, cost) = send_xcm::<T::XcmRouter>(dest, Xcm(vec![instruction]))?;
Self::deposit_event(Event::VersionNotifyRequested { destination: dest, cost, message_id });
VersionNotifiers::<T>::insert(XCM_VERSION, &versioned_dest, query_id);
let query_status =
QueryStatus::VersionNotifier { origin: versioned_dest, is_active: false };
Queries::<T>::insert(query_id, query_status);
Ok(())
}