b6d35f6faf
Updated 4763 files with dual copyright: - Parity Technologies (UK) Ltd. - Dijital Kurdistan Tech Institute
395 lines
12 KiB
Rust
395 lines
12 KiB
Rust
// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
|
|
// This file is part of Pezkuwi.
|
|
|
|
// Pezkuwi is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
|
|
// Pezkuwi is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
//! `TransferOverXcm` struct for paying through XCM and getting the status back.
|
|
|
|
use crate::LocatableAssetId;
|
|
use alloc::vec;
|
|
use core::{fmt::Debug, marker::PhantomData};
|
|
use pezframe_support::traits::Get;
|
|
use pezsp_runtime::traits::TryConvert;
|
|
use xcm::{latest::Error, opaque::lts::Weight, prelude::*};
|
|
use xcm_executor::traits::{FeeManager, FeeReason, QueryHandler, QueryResponseStatus};
|
|
|
|
pub use pezframe_support::traits::tokens::transfer::{Transfer, TransferStatus};
|
|
|
|
const LOG_TARGET: &str = "xcm::transfer_over_xcm";
|
|
|
|
/// Transfers an asset to a beneficiary on a remote chain via XCM.
|
|
///
|
|
/// The sender account on the destination chain is derived from the local account. For example:
|
|
///
|
|
/// `
|
|
/// Location::new(1, X2([Teyrchain(SourceParaId), from_location.interior]))
|
|
/// `
|
|
///
|
|
/// For a more specialized implementation, see [`super::pay::PayOverXcm`].
|
|
/// That variant assumes:
|
|
/// - The origin chain does **not** pay remote XCM execution fees on the destination chain.
|
|
/// - The sender account is fixed to a static `Interior` location of the origin chain.
|
|
///
|
|
/// The low-level XCM construction and configuration is handled by the generic
|
|
/// [`TransferOverXcmHelper`] type.
|
|
///
|
|
/// **NOTE**: It is assumed that the origin location has free execution on the remote chain
|
|
/// the `remote_fee` is `None.
|
|
pub struct TransferOverXcm<TransactorRefToLocation, TransferOverXcmHelper>(
|
|
PhantomData<(TransactorRefToLocation, TransferOverXcmHelper)>,
|
|
);
|
|
|
|
impl<TransactorRefToLocation, TransferOverXcmHelper> Transfer
|
|
for TransferOverXcm<TransactorRefToLocation, TransferOverXcmHelper>
|
|
where
|
|
TransferOverXcmHelper: TransferOverXcmHelperT<Balance = u128, QueryId = QueryId>,
|
|
TransactorRefToLocation: for<'a> TryConvert<&'a TransferOverXcmHelper::Beneficiary, Location>,
|
|
{
|
|
type Balance = u128;
|
|
type Sender = TransferOverXcmHelper::Beneficiary;
|
|
type Beneficiary = TransferOverXcmHelper::Beneficiary;
|
|
type AssetKind = TransferOverXcmHelper::AssetKind;
|
|
type RemoteFeeAsset = Asset;
|
|
|
|
type Id = TransferOverXcmHelper::QueryId;
|
|
type Error = Error;
|
|
|
|
fn transfer(
|
|
from: &Self::Sender,
|
|
to: &Self::Beneficiary,
|
|
asset_kind: Self::AssetKind,
|
|
amount: Self::Balance,
|
|
remote_fee: Option<Self::RemoteFeeAsset>,
|
|
) -> Result<Self::Id, Self::Error> {
|
|
let from_location = TransactorRefToLocation::try_convert(from).map_err(|error| {
|
|
tracing::error!(target: LOG_TARGET, ?error, "Failed to convert `Sender` to location");
|
|
Error::InvalidLocation
|
|
})?;
|
|
|
|
TransferOverXcmHelper::send_remote_transfer_xcm(
|
|
from_location.clone(),
|
|
to,
|
|
asset_kind,
|
|
amount,
|
|
remote_fee,
|
|
)
|
|
}
|
|
|
|
fn check_transfer(id: Self::Id) -> TransferStatus {
|
|
TransferOverXcmHelper::check_transfer(id)
|
|
}
|
|
|
|
#[cfg(feature = "runtime-benchmarks")]
|
|
fn ensure_successful(
|
|
beneficiary: &Self::Beneficiary,
|
|
asset_kind: Self::AssetKind,
|
|
balance: Self::Balance,
|
|
) {
|
|
TransferOverXcmHelper::ensure_successful(beneficiary, asset_kind, balance);
|
|
}
|
|
|
|
#[cfg(feature = "runtime-benchmarks")]
|
|
fn ensure_concluded(id: Self::Id) {
|
|
TransferOverXcmHelper::ensure_concluded(id);
|
|
}
|
|
}
|
|
|
|
/// Helper type to make a transfer on another chain via XCM.
|
|
pub struct TransferOverXcmHelper<
|
|
Router,
|
|
Querier,
|
|
XcmFeeHandler,
|
|
Timeout,
|
|
Transactor,
|
|
AssetKind,
|
|
AssetKindToLocatableAsset,
|
|
BeneficiaryRefToLocation,
|
|
>(
|
|
PhantomData<(
|
|
Router,
|
|
Querier,
|
|
XcmFeeHandler,
|
|
Timeout,
|
|
Transactor,
|
|
AssetKind,
|
|
AssetKindToLocatableAsset,
|
|
BeneficiaryRefToLocation,
|
|
)>,
|
|
);
|
|
|
|
/// Helper trait to abstract away the complexities of executing a remote transfer.
|
|
pub trait TransferOverXcmHelperT {
|
|
/// The beneficiary of a remote transfer.
|
|
type Beneficiary: Debug + Clone;
|
|
/// The type for the kinds of asset that are going to be paid.
|
|
type AssetKind: Debug + Clone;
|
|
/// The units of the currency.
|
|
type Balance: Debug + Clone;
|
|
/// The query id of an XCM Payment
|
|
type QueryId: Debug + Clone;
|
|
/// Construct and send a remote transfer xcm to the destination chain.
|
|
fn send_remote_transfer_xcm(
|
|
from_location: Location,
|
|
to: &Self::Beneficiary,
|
|
asset_kind: Self::AssetKind,
|
|
amount: Self::Balance,
|
|
remote_fee: Option<Asset>,
|
|
) -> Result<QueryId, Error>;
|
|
|
|
fn check_transfer(id: Self::QueryId) -> TransferStatus;
|
|
/// Ensure that a call to `send_remote_transfer_xcm` with the given parameters will be
|
|
/// successful if done immediately after this call. Used in benchmarking code.
|
|
#[cfg(feature = "runtime-benchmarks")]
|
|
fn ensure_successful(
|
|
beneficiary: &Self::Beneficiary,
|
|
asset_kind: Self::AssetKind,
|
|
balance: Self::Balance,
|
|
);
|
|
|
|
#[cfg(feature = "runtime-benchmarks")]
|
|
fn ensure_concluded(id: Self::QueryId);
|
|
}
|
|
|
|
impl<
|
|
Router: SendXcm,
|
|
Querier: QueryHandler,
|
|
XcmFeeHandler: FeeManager,
|
|
Timeout: Get<Querier::BlockNumber>,
|
|
Beneficiary: Clone + Debug,
|
|
AssetKind: Clone + Debug,
|
|
AssetKindToLocatableAsset: TryConvert<AssetKind, LocatableAssetId>,
|
|
BeneficiaryRefToLocation: for<'a> TryConvert<&'a Beneficiary, Location>,
|
|
> TransferOverXcmHelperT
|
|
for TransferOverXcmHelper<
|
|
Router,
|
|
Querier,
|
|
XcmFeeHandler,
|
|
Timeout,
|
|
Beneficiary,
|
|
AssetKind,
|
|
AssetKindToLocatableAsset,
|
|
BeneficiaryRefToLocation,
|
|
>
|
|
{
|
|
type Beneficiary = Beneficiary;
|
|
type AssetKind = AssetKind;
|
|
type Balance = u128;
|
|
type QueryId = QueryId;
|
|
|
|
/// Gets the XCM executing the transfer on the remote chain.
|
|
fn send_remote_transfer_xcm(
|
|
from_location: Location,
|
|
to: &Beneficiary,
|
|
asset_kind: AssetKind,
|
|
amount: Self::Balance,
|
|
remote_fee: Option<Asset>,
|
|
) -> Result<QueryId, Error> {
|
|
let locatable = Self::locatable_asset_id(asset_kind)?;
|
|
let LocatableAssetId { asset_id, location: asset_location } = locatable;
|
|
|
|
let origin_location_on_remote = Self::origin_location_on_remote(&asset_location)?;
|
|
|
|
let beneficiary = BeneficiaryRefToLocation::try_convert(to).map_err(|error| {
|
|
tracing::debug!(target: LOG_TARGET, ?error, "Failed to convert beneficiary to location");
|
|
Error::InvalidLocation
|
|
})?;
|
|
|
|
let query_id = Querier::new_query(
|
|
asset_location.clone(),
|
|
Timeout::get(),
|
|
from_location.interior.clone(),
|
|
);
|
|
|
|
let message = match remote_fee {
|
|
None => remote_transfer_xcm_free_execution(
|
|
from_location.clone(),
|
|
origin_location_on_remote,
|
|
beneficiary,
|
|
asset_id,
|
|
amount,
|
|
query_id,
|
|
)?,
|
|
Some(fee_asset) => remote_transfer_xcm_paying_fees(
|
|
from_location.clone(),
|
|
origin_location_on_remote,
|
|
beneficiary,
|
|
asset_id,
|
|
amount,
|
|
fee_asset,
|
|
query_id,
|
|
)?,
|
|
};
|
|
|
|
let (ticket, delivery_fees) =
|
|
Router::validate(&mut Some(asset_location), &mut Some(message))?;
|
|
Router::deliver(ticket)?;
|
|
|
|
if !XcmFeeHandler::is_waived(Some(&from_location), FeeReason::ChargeFees) {
|
|
XcmFeeHandler::handle_fee(delivery_fees, None, FeeReason::ChargeFees)
|
|
}
|
|
|
|
Ok(query_id)
|
|
}
|
|
|
|
fn check_transfer(id: Self::QueryId) -> TransferStatus {
|
|
use QueryResponseStatus::*;
|
|
match Querier::take_response(id) {
|
|
Ready { response, .. } => match response {
|
|
Response::ExecutionResult(None) => TransferStatus::Success,
|
|
Response::ExecutionResult(Some(_)) => TransferStatus::Failure,
|
|
_ => TransferStatus::Unknown,
|
|
},
|
|
Pending { .. } => TransferStatus::InProgress,
|
|
NotFound | UnexpectedVersion => TransferStatus::Unknown,
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "runtime-benchmarks")]
|
|
fn ensure_successful(_: &Self::Beneficiary, asset_kind: Self::AssetKind, _: Self::Balance) {
|
|
let locatable = AssetKindToLocatableAsset::try_convert(asset_kind).unwrap();
|
|
Router::ensure_successful_delivery(Some(locatable.location));
|
|
}
|
|
/// Ensure that a call to `check_payment` with the given parameters will return either `Success`
|
|
/// or `Failure`.
|
|
#[cfg(feature = "runtime-benchmarks")]
|
|
fn ensure_concluded(id: Self::QueryId) {
|
|
Querier::expect_response(id, Response::ExecutionResult(None));
|
|
}
|
|
}
|
|
|
|
impl<
|
|
Router,
|
|
Querier: QueryHandler,
|
|
XcmFeeHandler,
|
|
Timeout,
|
|
Beneficiary: Clone + Debug,
|
|
AssetKind: Clone + Debug,
|
|
AssetKindToLocatableAsset: TryConvert<AssetKind, LocatableAssetId>,
|
|
BeneficiaryRefToLocation: for<'a> TryConvert<&'a Beneficiary, Location>,
|
|
>
|
|
TransferOverXcmHelper<
|
|
Router,
|
|
Querier,
|
|
XcmFeeHandler,
|
|
Timeout,
|
|
Beneficiary,
|
|
AssetKind,
|
|
AssetKindToLocatableAsset,
|
|
BeneficiaryRefToLocation,
|
|
>
|
|
{
|
|
/// Returns the `from` relative to the asset's location.
|
|
///
|
|
/// This is the account that executes the transfer on the remote chain.
|
|
pub fn from_relative_to_asset_location(
|
|
from: &Beneficiary,
|
|
asset_kind: AssetKind,
|
|
) -> Result<Location, Error> {
|
|
let from_location = BeneficiaryRefToLocation::try_convert(from).map_err(|error| {
|
|
tracing::error!(target: LOG_TARGET, ?error, "Failed to convert from to location");
|
|
Error::InvalidLocation
|
|
})?;
|
|
|
|
let locatable = Self::locatable_asset_id(asset_kind)?;
|
|
|
|
let origin_location_on_remote = Self::origin_location_on_remote(&locatable.location)?;
|
|
append_from_to_target(from_location, origin_location_on_remote)
|
|
}
|
|
|
|
fn origin_location_on_remote(asset_location: &Location) -> Result<Location, Error> {
|
|
let origin_on_remote =
|
|
Querier::UniversalLocation::get().invert_target(asset_location).map_err(|()| {
|
|
tracing::debug!(target: LOG_TARGET, "Failed to invert asset location");
|
|
Error::LocationNotInvertible
|
|
})?;
|
|
tracing::trace!(target: LOG_TARGET, ?origin_on_remote, "Origin on destination");
|
|
Ok(origin_on_remote)
|
|
}
|
|
|
|
fn locatable_asset_id(asset_kind: AssetKind) -> Result<LocatableAssetId, Error> {
|
|
AssetKindToLocatableAsset::try_convert(asset_kind).map_err(|error| {
|
|
tracing::debug!(target: LOG_TARGET, ?error, "Failed to convert asset kind to locatable asset");
|
|
Error::InvalidLocation
|
|
})
|
|
}
|
|
}
|
|
|
|
fn remote_transfer_xcm_paying_fees(
|
|
from_location: Location,
|
|
origin_relative_to_remote: Location,
|
|
beneficiary: Location,
|
|
asset_id: AssetId,
|
|
amount: u128,
|
|
remote_fee: Asset,
|
|
query_id: QueryId,
|
|
) -> Result<Xcm<()>, Error> {
|
|
// Transform `from` into Location::new(1, XX([Teyrchain(source), from.interior }])
|
|
// We need this one for the refunds.
|
|
let from_at_target =
|
|
append_from_to_target(from_location.clone(), origin_relative_to_remote.clone())?;
|
|
tracing::trace!(target: LOG_TARGET, ?from_at_target, "From at target");
|
|
|
|
let xcm = Xcm(vec![
|
|
// Transform origin into Location::new(1, X2([Teyrchain(SourceParaId), from.interior }])
|
|
DescendOrigin(from_location.interior.clone()),
|
|
WithdrawAsset(vec![remote_fee.clone()].into()),
|
|
PayFees { asset: remote_fee },
|
|
SetAppendix(Xcm(vec![
|
|
ReportError(QueryResponseInfo {
|
|
destination: origin_relative_to_remote.clone(),
|
|
query_id,
|
|
max_weight: Weight::MAX,
|
|
}),
|
|
RefundSurplus,
|
|
DepositAsset { assets: AssetFilter::Wild(WildAsset::All), beneficiary: from_at_target },
|
|
])),
|
|
TransferAsset { beneficiary, assets: (asset_id, amount).into() },
|
|
]);
|
|
|
|
Ok(xcm)
|
|
}
|
|
|
|
fn remote_transfer_xcm_free_execution(
|
|
from_location: Location,
|
|
origin_relative_to_remote: Location,
|
|
beneficiary: Location,
|
|
asset_id: AssetId,
|
|
amount: u128,
|
|
query_id: QueryId,
|
|
) -> Result<Xcm<()>, Error> {
|
|
let xcm = Xcm(vec![
|
|
DescendOrigin(from_location.interior),
|
|
UnpaidExecution { weight_limit: Unlimited, check_origin: None },
|
|
SetAppendix(Xcm(vec![
|
|
SetFeesMode { jit_withdraw: true },
|
|
ReportError(QueryResponseInfo {
|
|
destination: origin_relative_to_remote,
|
|
query_id,
|
|
max_weight: Weight::zero(),
|
|
}),
|
|
])),
|
|
TransferAsset {
|
|
beneficiary,
|
|
assets: vec![Asset { id: asset_id, fun: Fungibility::Fungible(amount) }].into(),
|
|
},
|
|
]);
|
|
|
|
Ok(xcm)
|
|
}
|
|
|
|
fn append_from_to_target(from: Location, target: Location) -> Result<Location, Error> {
|
|
let from_at_target = target.appended_with(from).map_err(|_| Error::LocationFull)?;
|
|
Ok(from_at_target)
|
|
}
|