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