Skip to content
Snippets Groups Projects
Unverified Commit 55053241 authored by Shaun Wang's avatar Shaun Wang Committed by GitHub
Browse files

The return of the xcm (#518)

* Update xcm support.

* Fix unit tests.

* fmt

* Add patches.

* Update xtokens.

* fmt

* Clean up.

* Cross-chain transfer weight.

* Weight for xcm execution.

* Keep clippy happy.

* Clean up.

* Buy execution on dest location.

* Better transfer error handling.

* Update deps.

* fmt

* fix fmt

* Update events in unit tests.
parent 2ff4dbbc
No related branches found
No related tags found
No related merge requests found
......@@ -17,9 +17,9 @@ members = [
"vesting",
"rewards",
"nft",
# "xtokens",
# "xcm-support",
# "unknown-tokens",
"xtokens",
"xcm-support",
"unknown-tokens",
"build-script-utils",
"weight-gen",
"weight-meter",
......@@ -82,20 +82,19 @@ sp-trie = { git = "https://github.com/paritytech//substrate", rev = "9c572625f65
sp-version = { git = "https://github.com/paritytech//substrate", rev = "9c572625f6557dfdb19f47474369a0327d51dfbc" }
sp-wasm-interface = { git = "https://github.com/paritytech//substrate", rev = "9c572625f6557dfdb19f47474369a0327d51dfbc" }
# [patch.'https://github.com/paritytech/cumulus']
# cumulus-primitives-core = { git = "https://github.com/paritytech//cumulus", rev = "05ab7a377d2c3b73566c0c3bd41b003486d6913b" }
# cumulus-pallet-parachain-system = { git = "https://github.com/paritytech//cumulus", rev = "05ab7a377d2c3b73566c0c3bd41b003486d6913b" }
# parachain-info = { git = "https://github.com/paritytech//cumulus", rev = "05ab7a377d2c3b73566c0c3bd41b003486d6913b" }
# cumulus-pallet-xcm-handler = { git = "https://github.com/paritytech//cumulus", rev = "05ab7a377d2c3b73566c0c3bd41b003486d6913b" }
[patch.'https://github.com/paritytech/cumulus']
cumulus-primitives-core = { git = "https://github.com/paritytech//cumulus", rev = "05ab7a377d2c3b73566c0c3bd41b003486d6913b" }
cumulus-pallet-parachain-system = { git = "https://github.com/paritytech//cumulus", rev = "05ab7a377d2c3b73566c0c3bd41b003486d6913b" }
parachain-info = { git = "https://github.com/paritytech//cumulus", rev = "05ab7a377d2c3b73566c0c3bd41b003486d6913b" }
# [patch.'https://github.com/paritytech/polkadot']
# xcm = { git = "https://github.com/paritytech//polkadot", rev = "3ce09da502dd74d88f24d76f579d19fd4e6c5624" }
# xcm-executor = { git = "https://github.com/paritytech//polkadot", rev = "3ce09da502dd74d88f24d76f579d19fd4e6c5624" }
# xcm-builder = { git = "https://github.com/paritytech//polkadot", rev = "3ce09da502dd74d88f24d76f579d19fd4e6c5624" }
# polkadot-core-primitives = { git = "https://github.com/paritytech//polkadot", rev = "3ce09da502dd74d88f24d76f579d19fd4e6c5624" }
# polkadot-runtime-parachains = { git = "https://github.com/paritytech//polkadot", rev = "3ce09da502dd74d88f24d76f579d19fd4e6c5624" }
# polkadot-parachain = { git = "https://github.com/paritytech//polkadot", rev = "3ce09da502dd74d88f24d76f579d19fd4e6c5624" }
# polkadot-primitives = { git = "https://github.com/paritytech//polkadot", rev = "3ce09da502dd74d88f24d76f579d19fd4e6c5624" }
[patch.'https://github.com/paritytech/polkadot']
xcm = { git = "https://github.com/paritytech//polkadot", rev = "3ce09da502dd74d88f24d76f579d19fd4e6c5624" }
xcm-executor = { git = "https://github.com/paritytech//polkadot", rev = "3ce09da502dd74d88f24d76f579d19fd4e6c5624" }
xcm-builder = { git = "https://github.com/paritytech//polkadot", rev = "3ce09da502dd74d88f24d76f579d19fd4e6c5624" }
polkadot-core-primitives = { git = "https://github.com/paritytech//polkadot", rev = "3ce09da502dd74d88f24d76f579d19fd4e6c5624" }
polkadot-runtime-parachains = { git = "https://github.com/paritytech//polkadot", rev = "3ce09da502dd74d88f24d76f579d19fd4e6c5624" }
polkadot-parachain = { git = "https://github.com/paritytech//polkadot", rev = "3ce09da502dd74d88f24d76f579d19fd4e6c5624" }
polkadot-primitives = { git = "https://github.com/paritytech//polkadot", rev = "3ce09da502dd74d88f24d76f579d19fd4e6c5624" }
# [patch.'https://github.com/shaunxw/xcm-simulator']
# xcm-simulator = { git = "https://github.com/shaunxw//xcm-simulator", rev = "c52bd64a41a51d08bd5a1e27f32c47419b41f3e5" }
......@@ -23,7 +23,7 @@ sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-
pallet-treasury = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.5" }
# Patch doesn't work as `pallet-elections-phragmen` is now 4.0.0 version. Revert `rev` to `statemint` branch after
# other `statemint` dependencies upgraded.
pallet-elections-phragmen = { git = "https://github.com/paritytech/substrate", rev = "85fa0ab80c3ceccf4bb98380d7833578aaf8815e" }
pallet-elections-phragmen = { git = "https://github.com/paritytech/substrate", rev = "1d04678e20555e623c974ee1127bc8a45abcf3d6" }
[features]
default = ["std"]
......
......@@ -17,7 +17,7 @@ num-traits = { version = "0.2.14", default-features = false }
impl-trait-for-tuples = "0.2.1"
frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.5", default-features = false }
orml-utilities = { path = "../utilities", version = "0.4.1-dev", default-features = false }
# xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.5", default-features = false }
xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.5", default-features = false }
[features]
default = ["std"]
......@@ -30,5 +30,5 @@ std = [
"num-traits/std",
"frame-support/std",
"orml-utilities/std",
# "xcm/std",
"xcm/std",
]
......@@ -27,7 +27,7 @@ pub mod auction;
pub mod currency;
pub mod data_provider;
pub mod get_by_key;
// pub mod location;
pub mod location;
pub mod nft;
pub mod price;
pub mod rewards;
......
......@@ -12,15 +12,15 @@ pub trait Parse {
}
fn is_chain_junction(junction: Option<&Junction>) -> bool {
matches!(junction, Some(Parent) | Some(Parachain { id: _ }))
matches!(junction, Some(Parent) | Some(Parachain(_)))
}
impl Parse for MultiLocation {
fn chain_part(&self) -> Option<MultiLocation> {
match (self.first(), self.at(1)) {
(Some(Parent), Some(Parachain { id })) => Some((Parent, Parachain { id: *id }).into()),
(Some(Parent), Some(Parachain(id))) => Some((Parent, Parachain(*id)).into()),
(Some(Parent), _) => Some(Parent.into()),
(Some(Parachain { id }), _) => Some(Parachain { id: *id }.into()),
(Some(Parachain(id)), _) => Some(Parachain(*id).into()),
_ => None,
}
}
......@@ -58,7 +58,7 @@ impl Reserve for MultiAsset {
mod tests {
use super::*;
const PARACHAIN: Junction = Parachain { id: 1 };
const PARACHAIN: Junction = Parachain(1);
const GENERAL_INDEX: Junction = GeneralIndex { id: 1 };
fn concrete_fungible(id: MultiLocation) -> MultiAsset {
......
......@@ -38,7 +38,7 @@ fn deposit_concrete_fungible_asset_works() {
UnknownTokens::concrete_fungible_balances(&MOCK_RECIPIENT, &MOCK_CONCRETE_FUNGIBLE_ID),
3
);
System::assert_last_event(Event::unknown_tokens(crate::Event::Deposited(asset, MOCK_RECIPIENT)));
System::assert_last_event(Event::UnknownTokens(crate::Event::Deposited(asset, MOCK_RECIPIENT)));
// overflow case
let max_asset = concrete_fungible(u128::max_value());
......@@ -58,7 +58,7 @@ fn deposit_abstract_fungible_asset() {
UnknownTokens::abstract_fungible_balances(&MOCK_RECIPIENT, &mock_abstract_fungible_id()),
3
);
System::assert_last_event(Event::unknown_tokens(crate::Event::Deposited(asset, MOCK_RECIPIENT)));
System::assert_last_event(Event::UnknownTokens(crate::Event::Deposited(asset, MOCK_RECIPIENT)));
// overflow case
let max_asset = abstract_fungible(u128::max_value());
......@@ -94,7 +94,10 @@ fn withdraw_concrete_fungible_asset_works() {
UnknownTokens::concrete_fungible_balances(&MOCK_RECIPIENT, &MOCK_CONCRETE_FUNGIBLE_ID),
0
);
System::assert_last_event(Event::unknown_tokens(crate::Event::Withdrawn(asset.clone(), MOCK_RECIPIENT)));
System::assert_last_event(Event::UnknownTokens(crate::Event::Withdrawn(
asset.clone(),
MOCK_RECIPIENT,
)));
// balance too low case
assert_err!(
......@@ -115,7 +118,10 @@ fn withdraw_abstract_fungible_asset_works() {
UnknownTokens::abstract_fungible_balances(&MOCK_RECIPIENT, &mock_abstract_fungible_id()),
0
);
System::assert_last_event(Event::unknown_tokens(crate::Event::Withdrawn(asset.clone(), MOCK_RECIPIENT)));
System::assert_last_event(Event::UnknownTokens(crate::Event::Withdrawn(
asset.clone(),
MOCK_RECIPIENT,
)));
// balance too low case
assert_err!(
......
......@@ -9,7 +9,10 @@ use sp_std::{
};
use xcm::v0::{Error as XcmError, MultiAsset, MultiLocation, Result};
use xcm_executor::traits::{LocationConversion, MatchesFungible, TransactAsset};
use xcm_executor::{
traits::{Convert as MoreConvert, MatchesFungible, TransactAsset},
Assets,
};
use crate::UnknownAsset as UnknownAssetT;
......@@ -34,13 +37,14 @@ impl From<Error> for XcmError {
}
/// The `TransactAsset` implementation, to handle `MultiAsset` deposit/withdraw.
/// Note that teleport related functions are unimplemented.
///
/// If the asset is known, deposit/withdraw will be handled by `MultiCurrency`,
/// else by `UnknownAsset` if unknown.
pub struct MultiCurrencyAdapter<
MultiCurrency,
UnknownAsset,
Matcher,
Match,
AccountId,
AccountIdConvert,
CurrencyId,
......@@ -49,7 +53,7 @@ pub struct MultiCurrencyAdapter<
PhantomData<(
MultiCurrency,
UnknownAsset,
Matcher,
Match,
AccountId,
AccountIdConvert,
CurrencyId,
......@@ -60,30 +64,22 @@ pub struct MultiCurrencyAdapter<
impl<
MultiCurrency: orml_traits::MultiCurrency<AccountId, CurrencyId = CurrencyId>,
UnknownAsset: UnknownAssetT,
Matcher: MatchesFungible<MultiCurrency::Balance>,
AccountId: sp_std::fmt::Debug,
AccountIdConvert: LocationConversion<AccountId>,
Match: MatchesFungible<MultiCurrency::Balance>,
AccountId: sp_std::fmt::Debug + Clone,
AccountIdConvert: MoreConvert<MultiLocation, AccountId>,
CurrencyId: FullCodec + Eq + PartialEq + Copy + MaybeSerializeDeserialize + Debug,
CurrencyIdConvert: Convert<MultiAsset, Option<CurrencyId>>,
> TransactAsset
for MultiCurrencyAdapter<
MultiCurrency,
UnknownAsset,
Matcher,
AccountId,
AccountIdConvert,
CurrencyId,
CurrencyIdConvert,
>
for MultiCurrencyAdapter<MultiCurrency, UnknownAsset, Match, AccountId, AccountIdConvert, CurrencyId, CurrencyIdConvert>
{
fn deposit_asset(asset: &MultiAsset, location: &MultiLocation) -> Result {
match (
AccountIdConvert::from_location(location),
AccountIdConvert::convert_ref(location),
CurrencyIdConvert::convert(asset.clone()),
Matcher::matches_fungible(&asset),
Match::matches_fungible(&asset),
) {
// known asset
(Some(who), Some(currency_id), Some(amount)) => {
(Ok(who), Some(currency_id), Some(amount)) => {
MultiCurrency::deposit(currency_id, &who, amount).map_err(|e| XcmError::FailedToTransactAsset(e.into()))
}
// unknown asset
......@@ -91,18 +87,18 @@ impl<
}
}
fn withdraw_asset(asset: &MultiAsset, location: &MultiLocation) -> result::Result<MultiAsset, XcmError> {
fn withdraw_asset(asset: &MultiAsset, location: &MultiLocation) -> result::Result<Assets, XcmError> {
UnknownAsset::withdraw(asset, location).or_else(|_| {
let who = AccountIdConvert::from_location(location)
.ok_or_else(|| XcmError::from(Error::AccountIdConversionFailed))?;
let who = AccountIdConvert::convert_ref(location)
.map_err(|_| XcmError::from(Error::AccountIdConversionFailed))?;
let currency_id = CurrencyIdConvert::convert(asset.clone())
.ok_or_else(|| XcmError::from(Error::CurrencyIdConversionFailed))?;
let amount: MultiCurrency::Balance = Matcher::matches_fungible(&asset)
let amount: MultiCurrency::Balance = Match::matches_fungible(&asset)
.ok_or_else(|| XcmError::from(Error::FailedToMatchFungible))?
.saturated_into();
MultiCurrency::withdraw(currency_id, &who, amount).map_err(|e| XcmError::FailedToTransactAsset(e.into()))
})?;
Ok(asset.clone())
Ok(asset.clone().into())
}
}
......@@ -25,8 +25,8 @@ mod currency_adapter;
mod tests;
/// The XCM handler to execute XCM locally.
pub trait XcmHandler<AccountId> {
fn execute_xcm(origin: AccountId, xcm: Xcm) -> DispatchResult;
pub trait ExecuteXcm<AccountId, Call> {
fn execute_xcm(origin: AccountId, xcm: Xcm<Call>) -> DispatchResult;
}
/// A `MatchesFungible` implementation. It matches concrete fungible assets
......
......@@ -21,8 +21,8 @@ impl Convert<MultiLocation, Option<TestCurrencyId>> for CurrencyIdConvert {
let token_b: Vec<u8> = "TokenB".into();
match l {
X1(Parent) => Some(RelayChainToken),
X3(Parent, Parachain { id: 1 }, GeneralKey(k)) if k == token_a => Some(TokenA),
X3(Parent, Parachain { id: 2 }, GeneralKey(k)) if k == token_b => Some(TokenB),
X3(Parent, Parachain(1), GeneralKey(k)) if k == token_a => Some(TokenA),
X3(Parent, Parachain(2), GeneralKey(k)) if k == token_b => Some(TokenB),
_ => None,
}
}
......@@ -41,14 +41,14 @@ fn is_native_concrete_matches_native_currencies() {
);
assert_eq!(
MatchesCurrencyId::matches_fungible(&ConcreteFungible {
id: X3(Parent, Parachain { id: 1 }, GeneralKey("TokenA".into())),
id: X3(Parent, Parachain(1), GeneralKey("TokenA".into())),
amount: 100
}),
Some(100),
);
assert_eq!(
MatchesCurrencyId::matches_fungible(&ConcreteFungible {
id: X3(Parent, Parachain { id: 2 }, GeneralKey("TokenB".into())),
id: X3(Parent, Parachain(2), GeneralKey("TokenB".into())),
amount: 100
}),
Some(100),
......@@ -59,14 +59,14 @@ fn is_native_concrete_matches_native_currencies() {
fn is_native_concrete_does_not_matches_non_native_currencies() {
assert!(
<MatchesCurrencyId as MatchesFungible<u128>>::matches_fungible(&ConcreteFungible {
id: X3(Parent, Parachain { id: 2 }, GeneralKey("TokenC".into())),
id: X3(Parent, Parachain(2), GeneralKey("TokenC".into())),
amount: 100
})
.is_none()
);
assert!(
<MatchesCurrencyId as MatchesFungible<u128>>::matches_fungible(&ConcreteFungible {
id: X3(Parent, Parachain { id: 1 }, GeneralKey("TokenB".into())),
id: X3(Parent, Parachain(1), GeneralKey("TokenB".into())),
amount: 100
})
.is_none()
......@@ -91,15 +91,15 @@ fn multi_native_asset() {
));
assert!(MultiNativeAsset::filter_asset_location(
&ConcreteFungible {
id: X3(Parent, Parachain { id: 1 }, GeneralKey("TokenA".into())),
id: X3(Parent, Parachain(1), GeneralKey("TokenA".into())),
amount: 10,
},
&X2(Parent, Parachain { id: 1 }),
&X2(Parent, Parachain(1)),
));
assert_eq!(
MultiNativeAsset::filter_asset_location(
&ConcreteFungible {
id: X3(Parent, Parachain { id: 1 }, GeneralKey("TokenA".into())),
id: X3(Parent, Parachain(1), GeneralKey("TokenA".into())),
amount: 10,
},
&X1(Parent),
......
......@@ -20,23 +20,23 @@ frame-system = { git = "https://github.com/paritytech/substrate", branch = "polk
cumulus-primitives-core = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.5", default-features = false }
xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.5", default-features = false }
xcm-executor = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.5", default-features = false }
orml-xcm-support = { path = "../xcm-support", default-features = false }
orml-traits = { path = "../traits", default-features = false}
[dev-dependencies]
sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.5" }
polkadot-core-primitives = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.5" }
polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.5" }
pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.5" }
xcm-simulator = { git = "https://github.com/shaunxw/xcm-simulator", branch = "master" }
cumulus-pallet-xcm-handler = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.5" }
parachain-info = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.5" }
xcm-executor = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.5" }
xcm-builder = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.5" }
orml-tokens = { path = "../tokens", version = "0.4.1-dev" }
orml-traits = { path = "../traits", version = "0.4.1-dev" }
# sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.5" }
# polkadot-core-primitives = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.5" }
# polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.5" }
# pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.5" }
# xcm-simulator = { git = "https://github.com/shaunxw/xcm-simulator", branch = "master" }
# parachain-info = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.5" }
# xcm-executor = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.5" }
# xcm-builder = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.5" }
# orml-tokens = { path = "../tokens" }
# orml-traits = { path = "../traits" }
[features]
default = ["std"]
......@@ -50,6 +50,7 @@ std = [
"frame-system/std",
"cumulus-primitives-core/std",
"xcm/std",
"xcm-executor/std",
"orml-xcm-support/std",
"orml-traits/std",
]
......
......@@ -22,7 +22,12 @@
#![allow(clippy::unused_unit)]
#![allow(clippy::large_enum_variant)]
use frame_support::{pallet_prelude::*, traits::Get, transactional, Parameter};
use frame_support::{
pallet_prelude::*,
storage::{with_transaction, TransactionOutcome},
traits::Get,
Parameter,
};
use frame_system::{ensure_signed, pallet_prelude::*};
use sp_runtime::{
traits::{AtLeast32BitUnsigned, Convert, MaybeSerializeDeserialize, Member, Zero},
......@@ -30,20 +35,24 @@ use sp_runtime::{
};
use sp_std::prelude::*;
use xcm::v0::{
Junction::*,
MultiAsset, MultiLocation, Order,
Order::*,
Xcm::{self, *},
};
use xcm::v0::prelude::*;
use xcm_executor::traits::WeightBounds;
pub use module::*;
use orml_traits::location::{Parse, Reserve};
use orml_xcm_support::XcmHandler;
mod mock;
mod tests;
// mod mock;
// mod tests;
pub use module::*;
enum TransferKind {
/// Transfer self reserve asset.
SelfReserveAsset,
/// To reserve location.
ToReserve,
/// To non-reserve location.
ToNonReserve,
}
use TransferKind::*;
#[frame_support::pallet]
pub mod module {
......@@ -65,18 +74,21 @@ pub mod module {
/// Currency Id.
type CurrencyId: Parameter + Member + Clone;
/// Convert `T::CurrencyIn` to `MultiLocation`.
/// Convert `T::CurrencyId` to `MultiLocation`.
type CurrencyIdConvert: Convert<Self::CurrencyId, Option<MultiLocation>>;
/// Convert `Self::Account` to `AccountId32`
type AccountId32Convert: Convert<Self::AccountId, [u8; 32]>;
/// Convert `T::AccountId` to `MultiLocation`.
type AccountIdToMultiLocation: Convert<Self::AccountId, MultiLocation>;
/// Self chain location.
#[pallet::constant]
type SelfLocation: Get<MultiLocation>;
/// Xcm handler to execute XCM.
type XcmHandler: XcmHandler<Self::AccountId>;
/// XCM executor.
type XcmExecutor: ExecuteXcm<Self::Call>;
/// Means of measuring the weight consumed by an XCM message locally.
type Weigher: WeightBounds<Self::Call>;
}
#[pallet::event]
......@@ -85,8 +97,14 @@ pub mod module {
pub enum Event<T: Config> {
/// Transferred. \[sender, currency_id, amount, dest\]
Transferred(T::AccountId, T::CurrencyId, T::Balance, MultiLocation),
/// Transfer XCM execution failed. \[sender, currency_id, amount, dest,
/// xcm_err\]
TransferFailed(T::AccountId, T::CurrencyId, T::Balance, MultiLocation, XcmError),
/// Transferred `MultiAsset`. \[sender, asset, dest\]
TransferredMultiAsset(T::AccountId, MultiAsset, MultiLocation),
/// Transfer `MultiAsset` XCM execution failed. \[sender, asset, dest,
/// xcm_err\]
TransferredMultiAssetFailed(T::AccountId, MultiAsset, MultiLocation, XcmError),
}
#[pallet::error]
......@@ -99,6 +117,8 @@ pub mod module {
InvalidDest,
/// Currency is not cross-chain transferable.
NotCrossChainTransferableCurrency,
/// The message's weight could not be determined.
UnweighableMessage,
}
#[pallet::hooks]
......@@ -110,18 +130,18 @@ pub mod module {
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Transfer native currencies.
#[transactional]
#[pallet::weight(1000)]
#[pallet::weight(Pallet::<T>::weight_of_transfer(currency_id.clone(), *amount, &dest))]
pub fn transfer(
origin: OriginFor<T>,
currency_id: T::CurrencyId,
amount: T::Balance,
dest: MultiLocation,
) -> DispatchResultWithPostInfo {
dest_weight: Weight,
) -> DispatchResult {
let who = ensure_signed(origin)?;
if amount == Zero::zero() {
return Ok(().into());
return Ok(());
}
let id: MultiLocation = T::CurrencyIdConvert::convert(currency_id.clone())
......@@ -130,28 +150,43 @@ pub mod module {
id,
amount: amount.into(),
};
Self::do_transfer_multiasset(who.clone(), asset, dest.clone())?;
Self::deposit_event(Event::<T>::Transferred(who, currency_id, amount, dest));
Ok(().into())
let maybe_xcm_err = with_xcm_execution_transaction(|| {
Self::do_transfer_multiasset(who.clone(), asset, dest.clone(), dest_weight)
})?;
if let Some(xcm_err) = maybe_xcm_err {
Self::deposit_event(Event::<T>::TransferFailed(who, currency_id, amount, dest, xcm_err));
} else {
Self::deposit_event(Event::<T>::Transferred(who, currency_id, amount, dest));
}
Ok(())
}
/// Transfer `MultiAsset`.
#[transactional]
#[pallet::weight(1000)]
#[pallet::weight(Pallet::<T>::weight_of_transfer_multiasset(&asset, &dest))]
pub fn transfer_multiasset(
origin: OriginFor<T>,
asset: MultiAsset,
dest: MultiLocation,
) -> DispatchResultWithPostInfo {
dest_weight: Weight,
) -> DispatchResult {
let who = ensure_signed(origin)?;
if Self::is_zero_amount(&asset) {
return Ok(().into());
return Ok(());
}
Self::do_transfer_multiasset(who.clone(), asset.clone(), dest.clone())?;
Self::deposit_event(Event::<T>::TransferredMultiAsset(who, asset, dest));
Ok(().into())
let maybe_xcm_err = with_xcm_execution_transaction(|| {
Self::do_transfer_multiasset(who.clone(), asset.clone(), dest.clone(), dest_weight)
})?;
if let Some(xcm_err) = maybe_xcm_err {
Self::deposit_event(Event::<T>::TransferredMultiAssetFailed(who, asset, dest, xcm_err));
} else {
Self::deposit_event(Event::<T>::TransferredMultiAsset(who, asset, dest));
}
Ok(())
}
}
......@@ -161,44 +196,62 @@ pub mod module {
who: T::AccountId,
asset: MultiAsset,
dest: MultiLocation,
) -> DispatchResultWithPostInfo {
let (dest, recipient) = Self::ensure_valid_dest(dest)?;
let self_location = T::SelfLocation::get();
ensure!(dest != self_location, Error::<T>::NotCrossChainTransfer);
let reserve = asset.reserve().ok_or(Error::<T>::AssetHasNoReserve)?;
let xcm = if reserve == self_location {
Self::transfer_self_reserve_asset(asset, dest, recipient)
} else if reserve == dest {
Self::transfer_to_reserve(asset, dest, recipient)
} else {
Self::transfer_to_non_reserve(asset, reserve, dest, recipient)
dest_weight: Weight,
) -> XcmExecutionResult {
let (transfer_kind, reserve, dest, recipient) = Self::transfer_kind(&asset, &dest)?;
let buy_order = BuyExecution {
fees: All,
// Zero weight for additional XCM (since there are none to execute)
weight: 0,
debt: dest_weight,
halt_on_error: false,
xcm: vec![],
};
let mut msg = match transfer_kind {
SelfReserveAsset => Self::transfer_self_reserve_asset(asset, dest, recipient, buy_order),
ToReserve => Self::transfer_to_reserve(asset, dest, recipient, buy_order),
ToNonReserve => Self::transfer_to_non_reserve(asset, reserve, dest, recipient, buy_order),
};
T::XcmHandler::execute_xcm(who, xcm)?;
Ok(().into())
let origin_location = T::AccountIdToMultiLocation::convert(who);
let weight = T::Weigher::weight(&mut msg).map_err(|()| Error::<T>::UnweighableMessage)?;
let outcome = T::XcmExecutor::execute_xcm_in_credit(origin_location, msg, weight, weight);
let maybe_xcm_err: Option<XcmError> = match outcome {
Outcome::Complete(_w) => Option::None,
Outcome::Incomplete(_w, err) => Some(err),
Outcome::Error(err) => Some(err),
};
Ok(maybe_xcm_err)
}
fn transfer_self_reserve_asset(asset: MultiAsset, dest: MultiLocation, recipient: MultiLocation) -> Xcm {
fn transfer_self_reserve_asset(
asset: MultiAsset,
dest: MultiLocation,
recipient: MultiLocation,
buy_order: Order<()>,
) -> Xcm<T::Call> {
WithdrawAsset {
assets: vec![asset],
effects: vec![DepositReserveAsset {
assets: vec![MultiAsset::All],
dest,
effects: Self::deposit_asset(recipient),
effects: vec![buy_order, Self::deposit_asset(recipient)],
}],
}
}
fn transfer_to_reserve(asset: MultiAsset, reserve: MultiLocation, recipient: MultiLocation) -> Xcm {
fn transfer_to_reserve(
asset: MultiAsset,
reserve: MultiLocation,
recipient: MultiLocation,
buy_order: Order<()>,
) -> Xcm<T::Call> {
WithdrawAsset {
assets: vec![asset],
effects: vec![InitiateReserveWithdraw {
assets: vec![MultiAsset::All],
reserve,
effects: Self::deposit_asset(recipient),
effects: vec![buy_order, Self::deposit_asset(recipient)],
}],
}
}
......@@ -208,11 +261,12 @@ pub mod module {
reserve: MultiLocation,
dest: MultiLocation,
recipient: MultiLocation,
) -> Xcm {
buy_order: Order<()>,
) -> Xcm<T::Call> {
let mut reanchored_dest = dest.clone();
if reserve == Parent.into() {
if let MultiLocation::X2(Parent, Parachain { id }) = dest {
reanchored_dest = Parachain { id }.into();
if let MultiLocation::X2(Parent, Parachain(id)) = dest {
reanchored_dest = Parachain(id).into();
}
}
......@@ -221,20 +275,23 @@ pub mod module {
effects: vec![InitiateReserveWithdraw {
assets: vec![MultiAsset::All],
reserve,
effects: vec![DepositReserveAsset {
assets: vec![MultiAsset::All],
dest: reanchored_dest,
effects: Self::deposit_asset(recipient),
}],
effects: vec![
buy_order.clone(),
DepositReserveAsset {
assets: vec![MultiAsset::All],
dest: reanchored_dest,
effects: vec![buy_order, Self::deposit_asset(recipient)],
},
],
}],
}
}
fn deposit_asset(recipient: MultiLocation) -> Vec<Order> {
vec![DepositAsset {
fn deposit_asset(recipient: MultiLocation) -> Order<()> {
DepositAsset {
assets: vec![MultiAsset::All],
dest: recipient,
}]
}
}
fn is_zero_amount(asset: &MultiAsset) -> bool {
......@@ -255,7 +312,7 @@ pub mod module {
/// Ensure has the `dest` has chain part and recipient part.
fn ensure_valid_dest(
dest: MultiLocation,
dest: &MultiLocation,
) -> sp_std::result::Result<(MultiLocation, MultiLocation), DispatchError> {
if let (Some(dest), Some(recipient)) = (dest.chain_part(), dest.non_chain_part()) {
Ok((dest, recipient))
......@@ -263,5 +320,92 @@ pub mod module {
Err(Error::<T>::InvalidDest.into())
}
}
/// Get the transfer kind.
///
/// Returns `Err` if `asset` and `dest` combination doesn't make sense,
/// else returns a tuple of:
/// - `transfer_kind`.
/// - asset's `reserve` parachain or relay chain location,
/// - `dest` parachain or relay chain location.
/// - `recipient` location.
fn transfer_kind(
asset: &MultiAsset,
dest: &MultiLocation,
) -> sp_std::result::Result<(TransferKind, MultiLocation, MultiLocation, MultiLocation), DispatchError> {
let (dest, recipient) = Self::ensure_valid_dest(dest)?;
let self_location = T::SelfLocation::get();
ensure!(dest != self_location, Error::<T>::NotCrossChainTransfer);
let reserve = asset.reserve().ok_or(Error::<T>::AssetHasNoReserve)?;
let transfer_kind = if reserve == self_location {
SelfReserveAsset
} else if reserve == dest {
ToReserve
} else {
ToNonReserve
};
Ok((transfer_kind, dest, reserve, recipient))
}
}
// weights
impl<T: Config> Pallet<T> {
/// Returns weight of `transfer_multiasset` call.
fn weight_of_transfer_multiasset(asset: &MultiAsset, dest: &MultiLocation) -> Weight {
if let Ok((transfer_kind, dest, _, reserve)) = Self::transfer_kind(asset, dest) {
let mut msg = match transfer_kind {
SelfReserveAsset => WithdrawAsset {
assets: vec![asset.clone()],
effects: vec![DepositReserveAsset {
assets: vec![All],
dest,
effects: vec![],
}],
},
ToReserve | ToNonReserve => {
WithdrawAsset {
assets: vec![asset.clone()],
effects: vec![InitiateReserveWithdraw {
assets: vec![All],
// `dest` is always (equal to) `reserve` in both cases
reserve,
effects: vec![],
}],
}
}
};
T::Weigher::weight(&mut msg).map_or(Weight::max_value(), |w| 100_000_000 + w)
} else {
0
}
}
/// Returns weight of `transfer` call.
fn weight_of_transfer(currency_id: T::CurrencyId, amount: T::Balance, dest: &MultiLocation) -> Weight {
if let Some(id) = T::CurrencyIdConvert::convert(currency_id) {
let asset = MultiAsset::ConcreteFungible {
id,
amount: amount.into(),
};
Self::weight_of_transfer_multiasset(&asset, &dest)
} else {
0
}
}
}
}
type XcmExecutionResult = sp_std::result::Result<Option<XcmError>, DispatchError>;
/// Only commit storage if no `DispatchError` and no `XcmError`, else roll back.
fn with_xcm_execution_transaction(f: impl FnOnce() -> XcmExecutionResult) -> XcmExecutionResult {
with_transaction(|| {
let res = f();
match res {
Ok(ref err) if err.is_none() => TransactionOutcome::Commit(res),
_ => TransactionOutcome::Rollback(res),
}
})
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment