diff --git a/Cargo.dev.toml b/Cargo.dev.toml index 7ce5324c2c059be8ea72f13adeb95fcdf9c3cda0..38460e6bfa69832cda9560088468c8baaa9fe4ec 100644 --- a/Cargo.dev.toml +++ b/Cargo.dev.toml @@ -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" } diff --git a/tokens/Cargo.toml b/tokens/Cargo.toml index 3a6e836d885594cee54e89f8be2abc6b0ca86e2c..6f652e6fdf407c5c3bca727cd3ba04a94e1dd872 100644 --- a/tokens/Cargo.toml +++ b/tokens/Cargo.toml @@ -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"] diff --git a/traits/Cargo.toml b/traits/Cargo.toml index 56f521995f55395a1f4a4ffa620568795e782fb1..2d7db473fd55f99036219042f01cb53c43ef583e 100644 --- a/traits/Cargo.toml +++ b/traits/Cargo.toml @@ -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", ] diff --git a/traits/src/lib.rs b/traits/src/lib.rs index ff3c3b9a9f9003acf16063197ef4d44628417fff..7ec0efb4aba61a07e86bbd07c36abde0cd4c23d5 100644 --- a/traits/src/lib.rs +++ b/traits/src/lib.rs @@ -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; diff --git a/traits/src/location.rs b/traits/src/location.rs index a0b9bdd0b3a2a43257006c80bf1b7966609ef76a..95620b0ce1e6b48f4d52a979a9faff327245d19f 100644 --- a/traits/src/location.rs +++ b/traits/src/location.rs @@ -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 { diff --git a/unknown-tokens/src/tests.rs b/unknown-tokens/src/tests.rs index 33347834788c16ab6529b65577b984744fee489d..d17abf55dfc2dd05e600316d2173afe9ab8f9f8a 100644 --- a/unknown-tokens/src/tests.rs +++ b/unknown-tokens/src/tests.rs @@ -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!( diff --git a/xcm-support/src/currency_adapter.rs b/xcm-support/src/currency_adapter.rs index 8a9b820bf94aedb548291f42b9b321dc979963f3..fb2cd364d226c267cfe816aa2013d57cf611795a 100644 --- a/xcm-support/src/currency_adapter.rs +++ b/xcm-support/src/currency_adapter.rs @@ -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()) } } diff --git a/xcm-support/src/lib.rs b/xcm-support/src/lib.rs index 039af0295328f41e108ec3cff4ee171116ac77b4..a5070a4903b5ce49a13e3b712672283a688c6303 100644 --- a/xcm-support/src/lib.rs +++ b/xcm-support/src/lib.rs @@ -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 diff --git a/xcm-support/src/tests.rs b/xcm-support/src/tests.rs index c83b5021be30ca78e224f1af632c8e81dbd62163..d866793abd071d20d880b1d324a7083fed5ada45 100644 --- a/xcm-support/src/tests.rs +++ b/xcm-support/src/tests.rs @@ -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), diff --git a/xtokens/Cargo.toml b/xtokens/Cargo.toml index e4d7b9066d2fea49d506114a6512f02c3b49b381..ba1dada7ff5d9902208aed1efba91f15920c757c 100644 --- a/xtokens/Cargo.toml +++ b/xtokens/Cargo.toml @@ -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", ] diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs index c989811453cdb77a3b2a3f51daf68c3439f7cf31..3563519399e6cf51d0dbadfe526baaf093176708 100644 --- a/xtokens/src/lib.rs +++ b/xtokens/src/lib.rs @@ -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), + } + }) +}