From 43f7288f730f0773e532b690929fa8b25c7308ba Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Thu, 1 Apr 2021 14:15:41 +1300 Subject: [PATCH 01/16] Reserve chain trait. --- xtokens/src/lib.rs | 3 ++ xtokens/src/reserve_chain.rs | 59 ++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 xtokens/src/reserve_chain.rs diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs index 92f4ad9b3..526c0a9f2 100644 --- a/xtokens/src/lib.rs +++ b/xtokens/src/lib.rs @@ -22,6 +22,9 @@ #![allow(clippy::unused_unit)] #![allow(clippy::large_enum_variant)] +mod reserve_chain; +pub use reserve_chain::*; + pub use module::*; #[frame_support::pallet] diff --git a/xtokens/src/reserve_chain.rs b/xtokens/src/reserve_chain.rs new file mode 100644 index 000000000..d249e9d7c --- /dev/null +++ b/xtokens/src/reserve_chain.rs @@ -0,0 +1,59 @@ +use xcm::v0::{Junction, MultiAsset, MultiLocation}; + +pub trait ReserveChain { + fn reserve_chain(&self) -> Option; +} + +impl ReserveChain for MultiAsset { + fn reserve_chain(&self) -> Option { + if let MultiAsset::ConcreteFungible { id, .. } = self { + match (id.first(), id.at(1)) { + (Some(Junction::Parent), Some(Junction::Parachain { id: para_id })) => { + Some((Junction::Parent, Junction::Parachain { id: *para_id }).into()) + } + (Some(Junction::Parent), _) => Some(Junction::Parent.into()), + _ => None, + } + } else { + None + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn concrete_fungible(id: MultiLocation) -> MultiAsset { + MultiAsset::ConcreteFungible { id, amount: 1 } + } + + #[test] + fn parent_as_reserve_chain() { + assert_eq!( + concrete_fungible(MultiLocation::X2(Junction::Parent, Junction::GeneralIndex { id: 1 })).reserve_chain(), + Some(Junction::Parent.into()) + ); + } + + #[test] + fn sibling_parachain_as_reserve_chain() { + assert_eq!( + concrete_fungible(MultiLocation::X3( + Junction::Parent, + Junction::Parachain { id: 1 }, + Junction::GeneralIndex { id: 1 } + )) + .reserve_chain(), + Some((Junction::Parent, Junction::Parachain { id: 1 }).into()) + ); + } + + #[test] + fn no_reserve_chain() { + assert_eq!( + concrete_fungible(MultiLocation::X1(Junction::GeneralKey("DOT".into()))).reserve_chain(), + None + ); + } +} From 37b971823e521f0dab06a33713abba4890b51eef Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Thu, 1 Apr 2021 15:33:41 +1300 Subject: [PATCH 02/16] Rework cross-chain transfer. --- xtokens/src/lib.rs | 268 +++++------------- .../{reserve_chain.rs => reserve_location.rs} | 14 +- 2 files changed, 85 insertions(+), 197 deletions(-) rename xtokens/src/{reserve_chain.rs => reserve_location.rs} (81%) diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs index 526c0a9f2..80487fe91 100644 --- a/xtokens/src/lib.rs +++ b/xtokens/src/lib.rs @@ -22,59 +22,29 @@ #![allow(clippy::unused_unit)] #![allow(clippy::large_enum_variant)] -mod reserve_chain; -pub use reserve_chain::*; +mod reserve_location; pub use module::*; +pub use reserve_location::*; #[frame_support::pallet] pub mod module { - use codec::{Decode, Encode}; + use super::*; + use frame_support::{pallet_prelude::*, traits::Get, transactional, Parameter}; use frame_system::{ensure_signed, pallet_prelude::*}; - use sp_runtime::{ - traits::{AtLeast32BitUnsigned, Convert, MaybeSerializeDeserialize, Member}, - RuntimeDebug, - }; + use sp_runtime::traits::{AtLeast32BitUnsigned, Convert, MaybeSerializeDeserialize, Member}; use sp_std::prelude::*; - use cumulus_primitives_core::{relay_chain::Balance as RelayChainBalance, ParaId}; - use xcm::v0::{Junction, MultiAsset, MultiLocation, NetworkId, Order, Xcm}; + use cumulus_primitives_core::relay_chain::Balance as RelayChainBalance; + use xcm::v0::{ + MultiAsset, MultiLocation, Order, + Order::*, + Xcm::{self, *}, + }; use orml_xcm_support::XcmHandler; - #[derive(Encode, Decode, Eq, PartialEq, Clone, Copy, RuntimeDebug)] - /// Identity of chain. - pub enum ChainId { - /// The relay chain. - RelayChain, - /// A parachain. - ParaChain(ParaId), - } - - #[derive(Encode, Decode, Eq, PartialEq, Clone, RuntimeDebug)] - /// Identity of cross chain currency. - pub struct XCurrencyId { - /// The reserve chain of the currency. For instance, the reserve chain - /// of DOT is Polkadot. - pub chain_id: ChainId, - /// The identity of the currency. - pub currency_id: Vec, - } - - #[cfg(test)] - impl XCurrencyId { - pub fn new(chain_id: ChainId, currency_id: Vec) -> Self { - XCurrencyId { chain_id, currency_id } - } - } - - impl Into for XCurrencyId { - fn into(self) -> MultiLocation { - MultiLocation::X1(Junction::GeneralKey(self.currency_id)) - } - } - #[pallet::config] pub trait Config: frame_system::Config { type Event: From> + IsType<::Event>; @@ -94,12 +64,9 @@ pub mod module { /// Convert `Self::Account` to `AccountId32` type AccountId32Convert: Convert; - /// The network id of relay chain. Typically `NetworkId::Polkadot` or - /// `NetworkId::Kusama`. - type RelayChainNetworkId: Get; - - /// Self parachain ID. - type ParaId: Get; + /// Self chain location. + #[pallet::constant] + type SelfLocation: Get; /// Xcm handler to execute XCM. type XcmHandler: XcmHandler; @@ -108,16 +75,17 @@ pub mod module { #[pallet::event] #[pallet::generate_deposit(fn deposit_event)] pub enum Event { - /// Transferred to relay chain. \[src, dest, amount\] - TransferredToRelayChain(T::AccountId, T::AccountId, T::Balance), - - /// Transferred to parachain. \[x_currency_id, src, para_id, dest, - /// dest_network, amount\] - TransferredToParachain(XCurrencyId, T::AccountId, ParaId, MultiLocation, T::Balance), + /// Transferred `MultiAsset`. \[sender, asset, dest, recipient\] + TransferredMultiAsset(T::AccountId, MultiAsset, MultiLocation, MultiLocation), } #[pallet::error] - pub enum Error {} + pub enum Error { + /// Asset has no reserve location. + AssetHasNoReserve, + /// Not cross-chain transfer. + NotCrossChainTransfer, + } #[pallet::hooks] impl Hooks for Pallet {} @@ -127,174 +95,94 @@ pub mod module { #[pallet::call] impl Pallet { - /// Transfer relay chain tokens to relay chain. - #[pallet::weight(10)] + /// Transfer `asset` to `recipient` in `dest` chain. #[transactional] - pub fn transfer_to_relay_chain( + #[pallet::weight(1000)] + pub fn transfer_multiasset( origin: OriginFor, - dest: T::AccountId, - amount: T::Balance, + asset: MultiAsset, + dest: MultiLocation, + recipient: MultiLocation, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - - let xcm = Xcm::WithdrawAsset { - assets: vec![MultiAsset::ConcreteFungible { - id: MultiLocation::X1(Junction::Parent), - amount: T::ToRelayChainBalance::convert(amount), - }], - effects: vec![Order::InitiateReserveWithdraw { - assets: vec![MultiAsset::All], - reserve: MultiLocation::X1(Junction::Parent), - effects: vec![Order::DepositAsset { - assets: vec![MultiAsset::All], - dest: MultiLocation::X1(Junction::AccountId32 { - network: T::RelayChainNetworkId::get(), - id: T::AccountId32Convert::convert(dest.clone()), - }), - }], - }], - }; - T::XcmHandler::execute_xcm(who.clone(), xcm)?; - - Self::deposit_event(Event::::TransferredToRelayChain(who, dest, amount)); + Self::do_transfer_multiasset(who.clone(), asset.clone(), dest.clone(), recipient.clone())?; + Self::deposit_event(Event::::TransferredMultiAsset(who, asset, dest, recipient)); Ok(().into()) } + } - /// Transfer tokens to a sibling parachain. - #[pallet::weight(10)] - #[transactional] - pub fn transfer_to_parachain( - origin: OriginFor, - x_currency_id: XCurrencyId, - para_id: ParaId, + impl Pallet { + /// Transfer `MultiAsset` without depositing event. + fn do_transfer_multiasset( + who: T::AccountId, + asset: MultiAsset, dest: MultiLocation, - amount: T::Balance, + recipient: MultiLocation, ) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - - if para_id == T::ParaId::get() { - return Ok(().into()); - } - - let xcm = match x_currency_id.chain_id { - ChainId::RelayChain => Self::transfer_relay_chain_tokens_to_parachain(para_id, dest.clone(), amount), - ChainId::ParaChain(reserve_chain) => { - if T::ParaId::get() == reserve_chain { - Self::transfer_owned_tokens_to_parachain(x_currency_id.clone(), para_id, dest.clone(), amount) - } else { - Self::transfer_non_owned_tokens_to_parachain( - reserve_chain, - x_currency_id.clone(), - para_id, - dest.clone(), - amount, - ) - } - } + let self_location = T::SelfLocation::get(); + ensure!(dest != self_location, Error::::NotCrossChainTransfer); + + let reserve = asset.reserve().ok_or(Error::::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) }; + T::XcmHandler::execute_xcm(who.clone(), xcm)?; - Self::deposit_event(Event::::TransferredToParachain( - x_currency_id, - who, - para_id, - dest, - amount, - )); Ok(().into()) } - } - impl Pallet { - fn transfer_relay_chain_tokens_to_parachain(para_id: ParaId, dest: MultiLocation, amount: T::Balance) -> Xcm { - Xcm::WithdrawAsset { - assets: vec![MultiAsset::ConcreteFungible { - id: MultiLocation::X1(Junction::Parent), - amount: T::ToRelayChainBalance::convert(amount), + fn transfer_self_reserve_asset(asset: MultiAsset, dest: MultiLocation, recipient: MultiLocation) -> Xcm { + WithdrawAsset { + assets: vec![asset], + effects: vec![DepositReserveAsset { + assets: vec![MultiAsset::All], + dest, + effects: Self::deposit_asset(recipient), }], - effects: vec![Order::InitiateReserveWithdraw { + } + } + + fn transfer_to_reserve(asset: MultiAsset, reserve: MultiLocation, recipient: MultiLocation) -> Xcm { + WithdrawAsset { + assets: vec![asset], + effects: vec![InitiateReserveWithdraw { assets: vec![MultiAsset::All], - reserve: MultiLocation::X1(Junction::Parent), - effects: vec![Order::DepositReserveAsset { - assets: vec![MultiAsset::All], - // Reserve asset deposit dest is children parachain(of parent). - dest: MultiLocation::X1(Junction::Parachain { id: para_id.into() }), - effects: vec![Order::DepositAsset { - assets: vec![MultiAsset::All], - dest, - }], - }], + reserve, + effects: Self::deposit_asset(recipient), }], } } - /// Transfer parachain tokens "owned" by self parachain to another - /// parachain. - /// - /// NOTE - `para_id` must not be self parachain. - fn transfer_owned_tokens_to_parachain( - x_currency_id: XCurrencyId, - para_id: ParaId, + fn transfer_to_non_reserve( + asset: MultiAsset, + reserve: MultiLocation, dest: MultiLocation, - amount: T::Balance, + recipient: MultiLocation, ) -> Xcm { - Xcm::WithdrawAsset { - assets: vec![MultiAsset::ConcreteFungible { - id: x_currency_id.into(), - amount: amount.into(), - }], - effects: vec![Order::DepositReserveAsset { + WithdrawAsset { + assets: vec![asset], + effects: vec![InitiateReserveWithdraw { assets: vec![MultiAsset::All], - dest: MultiLocation::X2(Junction::Parent, Junction::Parachain { id: para_id.into() }), - effects: vec![Order::DepositAsset { + reserve, + effects: vec![DepositReserveAsset { assets: vec![MultiAsset::All], dest, + effects: Self::deposit_asset(recipient), }], }], } } - /// Transfer parachain tokens not "owned" by self chain to another - /// parachain. - fn transfer_non_owned_tokens_to_parachain( - reserve_chain: ParaId, - x_currency_id: XCurrencyId, - para_id: ParaId, - dest: MultiLocation, - amount: T::Balance, - ) -> Xcm { - let deposit_to_dest = Order::DepositAsset { + fn deposit_asset(recipient: MultiLocation) -> Vec { + vec![DepositAsset { assets: vec![MultiAsset::All], - dest, - }; - // If transfer to reserve chain, deposit to `dest` on reserve chain, - // else deposit reserve asset. - let reserve_chain_order = if para_id == reserve_chain { - deposit_to_dest - } else { - Order::DepositReserveAsset { - assets: vec![MultiAsset::All], - dest: MultiLocation::X2(Junction::Parent, Junction::Parachain { id: para_id.into() }), - effects: vec![deposit_to_dest], - } - }; - - Xcm::WithdrawAsset { - assets: vec![MultiAsset::ConcreteFungible { - id: x_currency_id.into(), - amount: amount.into(), - }], - effects: vec![Order::InitiateReserveWithdraw { - assets: vec![MultiAsset::All], - reserve: MultiLocation::X2( - Junction::Parent, - Junction::Parachain { - id: reserve_chain.into(), - }, - ), - effects: vec![reserve_chain_order], - }], - } + dest: recipient, + }] } } } diff --git a/xtokens/src/reserve_chain.rs b/xtokens/src/reserve_location.rs similarity index 81% rename from xtokens/src/reserve_chain.rs rename to xtokens/src/reserve_location.rs index d249e9d7c..c7dfd0a27 100644 --- a/xtokens/src/reserve_chain.rs +++ b/xtokens/src/reserve_location.rs @@ -1,11 +1,11 @@ use xcm::v0::{Junction, MultiAsset, MultiLocation}; -pub trait ReserveChain { - fn reserve_chain(&self) -> Option; +pub trait ReserveLocation { + fn reserve(&self) -> Option; } -impl ReserveChain for MultiAsset { - fn reserve_chain(&self) -> Option { +impl ReserveLocation for MultiAsset { + fn reserve(&self) -> Option { if let MultiAsset::ConcreteFungible { id, .. } = self { match (id.first(), id.at(1)) { (Some(Junction::Parent), Some(Junction::Parachain { id: para_id })) => { @@ -31,7 +31,7 @@ mod tests { #[test] fn parent_as_reserve_chain() { assert_eq!( - concrete_fungible(MultiLocation::X2(Junction::Parent, Junction::GeneralIndex { id: 1 })).reserve_chain(), + concrete_fungible(MultiLocation::X2(Junction::Parent, Junction::GeneralIndex { id: 1 })).reserve(), Some(Junction::Parent.into()) ); } @@ -44,7 +44,7 @@ mod tests { Junction::Parachain { id: 1 }, Junction::GeneralIndex { id: 1 } )) - .reserve_chain(), + .reserve(), Some((Junction::Parent, Junction::Parachain { id: 1 }).into()) ); } @@ -52,7 +52,7 @@ mod tests { #[test] fn no_reserve_chain() { assert_eq!( - concrete_fungible(MultiLocation::X1(Junction::GeneralKey("DOT".into()))).reserve_chain(), + concrete_fungible(MultiLocation::X1(Junction::GeneralKey("DOT".into()))).reserve(), None ); } From 55a0c2ecfc6a121164ac71344fea2b83772e4ebc Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Thu, 1 Apr 2021 15:38:26 +1300 Subject: [PATCH 03/16] Remove relay chain balance convert. --- xtokens/src/lib.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs index 80487fe91..ab2e876a6 100644 --- a/xtokens/src/lib.rs +++ b/xtokens/src/lib.rs @@ -36,7 +36,6 @@ pub mod module { use sp_runtime::traits::{AtLeast32BitUnsigned, Convert, MaybeSerializeDeserialize, Member}; use sp_std::prelude::*; - use cumulus_primitives_core::relay_chain::Balance as RelayChainBalance; use xcm::v0::{ MultiAsset, MultiLocation, Order, Order::*, @@ -58,9 +57,6 @@ pub mod module { + MaybeSerializeDeserialize + Into; - /// Convert `Balance` to `RelayChainBalance`. - type ToRelayChainBalance: Convert; - /// Convert `Self::Account` to `AccountId32` type AccountId32Convert: Convert; From 11067e2c5957fe1a21d9006e012d3199305e9a0f Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Thu, 1 Apr 2021 16:57:58 +1300 Subject: [PATCH 04/16] Add 'Parse' trait. --- xtokens/src/lib.rs | 4 +- xtokens/src/location.rs | 127 ++++++++++++++++++++++++++++++++ xtokens/src/reserve_location.rs | 59 --------------- 3 files changed, 129 insertions(+), 61 deletions(-) create mode 100644 xtokens/src/location.rs delete mode 100644 xtokens/src/reserve_location.rs diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs index ab2e876a6..4093f7f7a 100644 --- a/xtokens/src/lib.rs +++ b/xtokens/src/lib.rs @@ -22,14 +22,14 @@ #![allow(clippy::unused_unit)] #![allow(clippy::large_enum_variant)] -mod reserve_location; +pub mod location; pub use module::*; -pub use reserve_location::*; #[frame_support::pallet] pub mod module { use super::*; + use location::Reserve; use frame_support::{pallet_prelude::*, traits::Get, transactional, Parameter}; use frame_system::{ensure_signed, pallet_prelude::*}; diff --git a/xtokens/src/location.rs b/xtokens/src/location.rs new file mode 100644 index 000000000..fa56a254c --- /dev/null +++ b/xtokens/src/location.rs @@ -0,0 +1,127 @@ +use xcm::v0::{ + Junction::{self, *}, + MultiAsset, MultiLocation, +}; + +pub trait Parse { + /// Returns the "chain" location part. It could be parent, sibling + /// parachain, or child parachain. + fn chain_part(&self) -> Option; + /// Returns "non-chain" location part. + fn non_chain_part(&self) -> Option; +} + +fn is_chain_junction(junction: Option<&Junction>) -> bool { + match junction { + Some(Parent) => true, + Some(Parachain { id: _ }) => true, + _ => false, + } +} + +impl Parse for MultiLocation { + fn chain_part(&self) -> Option { + match (self.first(), self.at(1)) { + (Some(Parent), Some(Parachain { id })) => Some((Parent, Parachain { id: *id }).into()), + (Some(Parent), _) => Some(Parent.into()), + (Some(Parachain { id }), _) => Some(Parachain { id: *id }.into()), + _ => None, + } + } + + fn non_chain_part(&self) -> Option { + let mut location = self.clone(); + while is_chain_junction(location.first()) { + let _ = location.take_first(); + } + + if location != MultiLocation::Null { + Some(location) + } else { + None + } + } +} + +pub trait Reserve { + /// Returns assets reserve location. + fn reserve(&self) -> Option; +} + +impl Reserve for MultiAsset { + fn reserve(&self) -> Option { + if let MultiAsset::ConcreteFungible { id, .. } = self { + id.chain_part() + } else { + None + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const PARACHAIN: Junction = Parachain { id: 1 }; + const GENERAL_INDEX: Junction = GeneralIndex { id: 1 }; + + fn concrete_fungible(id: MultiLocation) -> MultiAsset { + MultiAsset::ConcreteFungible { id, amount: 1 } + } + + #[test] + fn parent_as_reserve_chain() { + assert_eq!( + concrete_fungible(MultiLocation::X2(Parent, GENERAL_INDEX)).reserve(), + Some(Parent.into()) + ); + } + + #[test] + fn sibling_parachain_as_reserve_chain() { + assert_eq!( + concrete_fungible(MultiLocation::X3(Parent, PARACHAIN, GENERAL_INDEX)).reserve(), + Some((Parent, PARACHAIN).into()) + ); + } + + #[test] + fn child_parachain_as_reserve_chain() { + assert_eq!( + concrete_fungible(MultiLocation::X2(PARACHAIN, GENERAL_INDEX)).reserve(), + Some(PARACHAIN.into()) + ); + } + + #[test] + fn no_reserve_chain() { + assert_eq!( + concrete_fungible(MultiLocation::X1(GeneralKey("DOT".into()))).reserve(), + None + ); + } + + #[test] + fn non_chain_part_works() { + assert_eq!(MultiLocation::X1(Parent).non_chain_part(), None); + assert_eq!(MultiLocation::X2(Parent, PARACHAIN).non_chain_part(), None); + assert_eq!(MultiLocation::X1(PARACHAIN).non_chain_part(), None); + + assert_eq!( + MultiLocation::X2(Parent, GENERAL_INDEX).non_chain_part(), + Some(GENERAL_INDEX.into()) + ); + assert_eq!( + MultiLocation::X3(Parent, GENERAL_INDEX, GENERAL_INDEX).non_chain_part(), + Some((GENERAL_INDEX, GENERAL_INDEX).into()) + ); + assert_eq!( + MultiLocation::X3(Parent, PARACHAIN, GENERAL_INDEX).non_chain_part(), + Some(GENERAL_INDEX.into()) + ); + assert_eq!( + MultiLocation::X2(PARACHAIN, GENERAL_INDEX).non_chain_part(), + Some(GENERAL_INDEX.into()) + ); + } +} diff --git a/xtokens/src/reserve_location.rs b/xtokens/src/reserve_location.rs deleted file mode 100644 index c7dfd0a27..000000000 --- a/xtokens/src/reserve_location.rs +++ /dev/null @@ -1,59 +0,0 @@ -use xcm::v0::{Junction, MultiAsset, MultiLocation}; - -pub trait ReserveLocation { - fn reserve(&self) -> Option; -} - -impl ReserveLocation for MultiAsset { - fn reserve(&self) -> Option { - if let MultiAsset::ConcreteFungible { id, .. } = self { - match (id.first(), id.at(1)) { - (Some(Junction::Parent), Some(Junction::Parachain { id: para_id })) => { - Some((Junction::Parent, Junction::Parachain { id: *para_id }).into()) - } - (Some(Junction::Parent), _) => Some(Junction::Parent.into()), - _ => None, - } - } else { - None - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn concrete_fungible(id: MultiLocation) -> MultiAsset { - MultiAsset::ConcreteFungible { id, amount: 1 } - } - - #[test] - fn parent_as_reserve_chain() { - assert_eq!( - concrete_fungible(MultiLocation::X2(Junction::Parent, Junction::GeneralIndex { id: 1 })).reserve(), - Some(Junction::Parent.into()) - ); - } - - #[test] - fn sibling_parachain_as_reserve_chain() { - assert_eq!( - concrete_fungible(MultiLocation::X3( - Junction::Parent, - Junction::Parachain { id: 1 }, - Junction::GeneralIndex { id: 1 } - )) - .reserve(), - Some((Junction::Parent, Junction::Parachain { id: 1 }).into()) - ); - } - - #[test] - fn no_reserve_chain() { - assert_eq!( - concrete_fungible(MultiLocation::X1(Junction::GeneralKey("DOT".into()))).reserve(), - None - ); - } -} From fdad4e46ad910e0b4a92f239b5f40e902eab5d77 Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Thu, 1 Apr 2021 17:11:14 +1300 Subject: [PATCH 05/16] Change transfer_multiasset fn signature. --- xtokens/src/lib.rs | 34 +++++++++++++++++++++++++--------- xtokens/src/location.rs | 6 +----- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs index 4093f7f7a..b27d9b676 100644 --- a/xtokens/src/lib.rs +++ b/xtokens/src/lib.rs @@ -29,11 +29,14 @@ pub use module::*; #[frame_support::pallet] pub mod module { use super::*; - use location::Reserve; + use location::*; use frame_support::{pallet_prelude::*, traits::Get, transactional, Parameter}; use frame_system::{ensure_signed, pallet_prelude::*}; - use sp_runtime::traits::{AtLeast32BitUnsigned, Convert, MaybeSerializeDeserialize, Member}; + use sp_runtime::{ + traits::{AtLeast32BitUnsigned, Convert, MaybeSerializeDeserialize, Member}, + DispatchError, + }; use sp_std::prelude::*; use xcm::v0::{ @@ -71,8 +74,8 @@ pub mod module { #[pallet::event] #[pallet::generate_deposit(fn deposit_event)] pub enum Event { - /// Transferred `MultiAsset`. \[sender, asset, dest, recipient\] - TransferredMultiAsset(T::AccountId, MultiAsset, MultiLocation, MultiLocation), + /// Transferred `MultiAsset`. \[sender, asset, dest\] + TransferredMultiAsset(T::AccountId, MultiAsset, MultiLocation), } #[pallet::error] @@ -81,6 +84,8 @@ pub mod module { AssetHasNoReserve, /// Not cross-chain transfer. NotCrossChainTransfer, + /// Invalid transfer destination. + InvalidDest, } #[pallet::hooks] @@ -98,11 +103,10 @@ pub mod module { origin: OriginFor, asset: MultiAsset, dest: MultiLocation, - recipient: MultiLocation, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - Self::do_transfer_multiasset(who.clone(), asset.clone(), dest.clone(), recipient.clone())?; - Self::deposit_event(Event::::TransferredMultiAsset(who, asset, dest, recipient)); + Self::do_transfer_multiasset(who.clone(), asset.clone(), dest.clone())?; + Self::deposit_event(Event::::TransferredMultiAsset(who, asset, dest)); Ok(().into()) } } @@ -113,8 +117,9 @@ pub mod module { who: T::AccountId, asset: MultiAsset, dest: MultiLocation, - recipient: MultiLocation, ) -> DispatchResultWithPostInfo { + let (dest, recipient) = Self::ensure_valid_dest(dest)?; + let self_location = T::SelfLocation::get(); ensure!(dest != self_location, Error::::NotCrossChainTransfer); @@ -127,7 +132,7 @@ pub mod module { Self::transfer_to_non_reserve(asset, reserve, dest, recipient) }; - T::XcmHandler::execute_xcm(who.clone(), xcm)?; + T::XcmHandler::execute_xcm(who, xcm)?; Ok(().into()) } @@ -180,5 +185,16 @@ pub mod module { dest: recipient, }] } + + /// Ensure has the `dest` has chain part and recipient part. + fn ensure_valid_dest( + 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)) + } else { + Err(Error::::InvalidDest.into()) + } + } } } diff --git a/xtokens/src/location.rs b/xtokens/src/location.rs index fa56a254c..a0b9bdd0b 100644 --- a/xtokens/src/location.rs +++ b/xtokens/src/location.rs @@ -12,11 +12,7 @@ pub trait Parse { } fn is_chain_junction(junction: Option<&Junction>) -> bool { - match junction { - Some(Parent) => true, - Some(Parachain { id: _ }) => true, - _ => false, - } + matches!(junction, Some(Parent) | Some(Parachain { id: _ })) } impl Parse for MultiLocation { From 89dedad0bd2215e4adcfe04cbbb72100f0ee874f Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Thu, 1 Apr 2021 17:23:11 +1300 Subject: [PATCH 06/16] Add transfer dispatchable call. --- xtokens/src/lib.rs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs index b27d9b676..cde8d9689 100644 --- a/xtokens/src/lib.rs +++ b/xtokens/src/lib.rs @@ -60,6 +60,9 @@ pub mod module { + MaybeSerializeDeserialize + Into; + /// Currency. + type CurrencyId: Parameter + Member + Clone + Into; + /// Convert `Self::Account` to `AccountId32` type AccountId32Convert: Convert; @@ -73,7 +76,10 @@ pub mod module { #[pallet::event] #[pallet::generate_deposit(fn deposit_event)] + #[pallet::metadata(T::AccountId = "AccountId", T::CurrencyId = "CurrencyId", T::Balance = "Balance")] pub enum Event { + /// Transferred. \[sender, currency_id, amount, dest\] + Transferred(T::AccountId, T::CurrencyId, T::Balance, MultiLocation), /// Transferred `MultiAsset`. \[sender, asset, dest\] TransferredMultiAsset(T::AccountId, MultiAsset, MultiLocation), } @@ -96,7 +102,26 @@ pub mod module { #[pallet::call] impl Pallet { - /// Transfer `asset` to `recipient` in `dest` chain. + /// Transfer native currencies. + #[transactional] + #[pallet::weight(1000)] + pub fn transfer( + origin: OriginFor, + currency_id: T::CurrencyId, + amount: T::Balance, + dest: MultiLocation, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + let asset = MultiAsset::ConcreteFungible { + id: currency_id.clone().into(), + amount: amount.into(), + }; + Self::do_transfer_multiasset(who.clone(), asset, dest.clone())?; + Self::deposit_event(Event::::Transferred(who, currency_id, amount, dest)); + Ok(().into()) + } + + /// Transfer `MultiAsset`. #[transactional] #[pallet::weight(1000)] pub fn transfer_multiasset( From 6db546a873e7e25ebd0f2b6d254b3bb073975448 Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Thu, 1 Apr 2021 17:26:57 +1300 Subject: [PATCH 07/16] Update doc. --- xtokens/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs index cde8d9689..a0ca4e45f 100644 --- a/xtokens/src/lib.rs +++ b/xtokens/src/lib.rs @@ -60,7 +60,7 @@ pub mod module { + MaybeSerializeDeserialize + Into; - /// Currency. + /// Currency Id. type CurrencyId: Parameter + Member + Clone + Into; /// Convert `Self::Account` to `AccountId32` From 11ae60bbc72a6b0cdf44a0268c7550d431948209 Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Mon, 5 Apr 2021 23:22:35 +1200 Subject: [PATCH 08/16] Use xcm-simulator to mock network. --- xtokens/Cargo.toml | 9 ++ xtokens/src/lib.rs | 37 ++--- xtokens/src/mock.rs | 348 +++++++++++++++++++++++++++++++++++++++++++ xtokens/src/tests.rs | 46 ++++++ 4 files changed, 423 insertions(+), 17 deletions(-) create mode 100644 xtokens/src/mock.rs create mode 100644 xtokens/src/tests.rs diff --git a/xtokens/Cargo.toml b/xtokens/Cargo.toml index 3340fbacc..cab8e98d2 100644 --- a/xtokens/Cargo.toml +++ b/xtokens/Cargo.toml @@ -26,7 +26,16 @@ orml-xcm-support = { path = "../xcm-support", default-features = false } [dev-dependencies] sp-core = { git = "https://github.com/paritytech/substrate", branch = "rococo-v1" } polkadot-core-primitives = { git = "https://github.com/paritytech/polkadot", branch = "rococo-v1" } +polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "rococo-v1" } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "rococo-v1" } +xcm-simulator = { git = "https://github.com/shaunxw/xcm-simulator", branch = "master" } +cumulus-pallet-xcm-handler = { git = "https://github.com/paritytech/cumulus", branch = "rococo-v1" } +parachain-info = { git = "https://github.com/paritytech/cumulus", branch = "rococo-v1" } +xcm-executor = { git = "https://github.com/paritytech/polkadot", branch = "rococo-v1" } +xcm-builder = { git = "https://github.com/paritytech/polkadot", branch = "rococo-v1" } + orml-tokens = { path = "../tokens", version = "0.4.1-dev" } +orml-traits = { path = "../traits", version = "0.4.1-dev" } [features] default = ["std"] diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs index a0ca4e45f..84b006f6c 100644 --- a/xtokens/src/lib.rs +++ b/xtokens/src/lib.rs @@ -22,30 +22,33 @@ #![allow(clippy::unused_unit)] #![allow(clippy::large_enum_variant)] +use frame_support::{pallet_prelude::*, traits::Get, transactional, Parameter}; +use frame_system::{ensure_signed, pallet_prelude::*}; +use sp_runtime::{ + traits::{AtLeast32BitUnsigned, Convert, MaybeSerializeDeserialize, Member}, + DispatchError, +}; +use sp_std::prelude::*; + +use xcm::v0::{ + MultiAsset, MultiLocation, Order, + Order::*, + Xcm::{self, *}, +}; + +use orml_xcm_support::XcmHandler; + pub mod location; +use location::*; + +mod mock; +mod tests; pub use module::*; #[frame_support::pallet] pub mod module { use super::*; - use location::*; - - use frame_support::{pallet_prelude::*, traits::Get, transactional, Parameter}; - use frame_system::{ensure_signed, pallet_prelude::*}; - use sp_runtime::{ - traits::{AtLeast32BitUnsigned, Convert, MaybeSerializeDeserialize, Member}, - DispatchError, - }; - use sp_std::prelude::*; - - use xcm::v0::{ - MultiAsset, MultiLocation, Order, - Order::*, - Xcm::{self, *}, - }; - - use orml_xcm_support::XcmHandler; #[pallet::config] pub trait Config: frame_system::Config { diff --git a/xtokens/src/mock.rs b/xtokens/src/mock.rs new file mode 100644 index 000000000..1a31b21e1 --- /dev/null +++ b/xtokens/src/mock.rs @@ -0,0 +1,348 @@ +#![cfg(test)] + +use super::*; +use crate as orml_xtokens; + +use frame_support::parameter_types; +use orml_traits::parameter_type_with_key; +use orml_xcm_support::{ + CurrencyIdConverter, IsConcreteWithGeneralKey, MultiCurrencyAdapter, XcmHandler as XcmHandlerT, +}; +use polkadot_parachain::primitives::Sibling; +use serde::{Deserialize, Serialize}; +use sp_io::TestExternalities; +use sp_runtime::{traits::Identity, AccountId32}; +use sp_std::convert::TryFrom; +use xcm::v0::{Junction, NetworkId}; +use xcm_builder::{ + AccountId32Aliases, LocationInverter, ParentIsDefault, RelayChainAsNative, SiblingParachainAsNative, + SiblingParachainConvertsVia, SignedAccountId32AsNative, SovereignSignedViaLocation, +}; +use xcm_executor::{traits::NativeAsset, Config as XcmConfigT}; +use xcm_simulator::{decl_test_network, decl_test_parachain, prelude::*}; + +pub const ALICE: AccountId32 = AccountId32::new([0u8; 32]); +pub const BOB: AccountId32 = AccountId32::new([1u8; 32]); + +#[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, RuntimeDebug, PartialOrd, Ord)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub enum CurrencyId { + /// Relay chain token. + R, + /// Parachain A token. + A, + /// Parachain B token. + B, +} + +impl TryFrom> for CurrencyId { + type Error = (); + fn try_from(v: Vec) -> Result { + match v.as_slice() { + b"R" => Ok(CurrencyId::R), + b"A" => Ok(CurrencyId::A), + b"B" => Ok(CurrencyId::B), + _ => Err(()), + } + } +} + +impl From for MultiLocation { + fn from(id: CurrencyId) -> Self { + match id { + CurrencyId::R => Junction::Parent.into(), + CurrencyId::A => ( + Junction::Parent, + Junction::Parachain { id: 1 }, + Junction::GeneralKey("A".into()), + ) + .into(), + CurrencyId::B => ( + Junction::Parent, + Junction::Parachain { id: 2 }, + Junction::GeneralKey("B".into()), + ) + .into(), + } + } +} + +pub type Balance = u128; +pub type Amount = i128; + +decl_test_parachain! { + pub struct ParaA { + new_ext = parachain_ext::(1), + para_id = 1, + } + pub mod para_a { + test_network = super::TestNetwork, + xcm_config = { + use super::*; + + parameter_types! { + pub ParaANetwork: NetworkId = NetworkId::Any; + pub RelayChainOrigin: Origin = cumulus_pallet_xcm_handler::Origin::Relay.into(); + pub Ancestry: MultiLocation = MultiLocation::X1(Junction::Parachain { + id: ParachainInfo::get().into(), + }); + pub const RelayChainCurrencyId: CurrencyId = CurrencyId::R; + } + + pub type LocationConverter = ( + ParentIsDefault, + SiblingParachainConvertsVia, + AccountId32Aliases, + ); + + pub type LocalAssetTransactor = MultiCurrencyAdapter< + Tokens, + (), + IsConcreteWithGeneralKey, + LocationConverter, + AccountId, + CurrencyIdConverter, + CurrencyId, + >; + + pub type LocalOriginConverter = ( + SovereignSignedViaLocation, + RelayChainAsNative, + SiblingParachainAsNative, + SignedAccountId32AsNative, + ); + + pub struct XcmConfig; + impl XcmConfigT for XcmConfig { + type Call = Call; + type XcmSender = XcmHandler; + type AssetTransactor = LocalAssetTransactor; + type OriginConverter = LocalOriginConverter; + type IsReserve = NativeAsset; + type IsTeleporter = (); + type LocationInverter = LocationInverter; + } + }, + extra_config = { + parameter_type_with_key! { + pub ExistentialDeposits: |_currency_id: super::CurrencyId| -> Balance { + Default::default() + }; + } + + impl orml_tokens::Config for Runtime { + type Event = Event; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = super::CurrencyId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type OnDust = (); + } + + pub struct HandleXcm; + impl XcmHandlerT for HandleXcm { + fn execute_xcm(origin: AccountId, xcm: Xcm) -> DispatchResult { + XcmHandler::execute_xcm(origin, xcm) + } + } + + pub struct AccountId32Convert; + impl Convert for AccountId32Convert { + fn convert(account_id: AccountId) -> [u8; 32] { + account_id.into() + } + } + + parameter_types! { + pub SelfLocation: MultiLocation = (Junction::Parent, Junction::Parachain { id: ParachainInfo::get().into() }).into(); + } + + impl orml_xtokens::Config for Runtime { + type Event = Event; + type Balance = Balance; + type CurrencyId = CurrencyId; + type AccountId32Convert = AccountId32Convert; + type SelfLocation = SelfLocation; + type XcmHandler = HandleXcm; + } + }, + extra_modules = { + Tokens: orml_tokens::{Pallet, Storage, Event, Config}, + XTokens: orml_xtokens::{Pallet, Storage, Call, Event}, + }, + } +} + +decl_test_parachain! { + pub struct ParaB { + new_ext = parachain_ext::(2), + para_id = 2, + } + pub mod para_b { + test_network = super::TestNetwork, + xcm_config = { + use super::*; + + parameter_types! { + pub ParaANetwork: NetworkId = NetworkId::Any; + pub RelayChainOrigin: Origin = cumulus_pallet_xcm_handler::Origin::Relay.into(); + pub Ancestry: MultiLocation = MultiLocation::X1(Junction::Parachain { + id: ParachainInfo::get().into(), + }); + pub const RelayChainCurrencyId: CurrencyId = CurrencyId::R; + } + + pub type LocationConverter = ( + ParentIsDefault, + SiblingParachainConvertsVia, + AccountId32Aliases, + ); + + pub type LocalAssetTransactor = MultiCurrencyAdapter< + Tokens, + (), + IsConcreteWithGeneralKey, + LocationConverter, + AccountId, + CurrencyIdConverter, + CurrencyId, + >; + + pub type LocalOriginConverter = ( + SovereignSignedViaLocation, + RelayChainAsNative, + SiblingParachainAsNative, + SignedAccountId32AsNative, + ); + + pub struct XcmConfig; + impl XcmConfigT for XcmConfig { + type Call = Call; + type XcmSender = XcmHandler; + type AssetTransactor = LocalAssetTransactor; + type OriginConverter = LocalOriginConverter; + type IsReserve = NativeAsset; + type IsTeleporter = (); + type LocationInverter = LocationInverter; + } + }, + extra_config = { + parameter_type_with_key! { + pub ExistentialDeposits: |_currency_id: super::CurrencyId| -> Balance { + Default::default() + }; + } + + impl orml_tokens::Config for Runtime { + type Event = Event; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = super::CurrencyId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type OnDust = (); + } + + pub struct HandleXcm; + impl XcmHandlerT for HandleXcm { + fn execute_xcm(origin: AccountId, xcm: Xcm) -> DispatchResult { + XcmHandler::execute_xcm(origin, xcm) + } + } + + pub struct AccountId32Convert; + impl Convert for AccountId32Convert { + fn convert(account_id: AccountId) -> [u8; 32] { + account_id.into() + } + } + + parameter_types! { + pub SelfLocation: MultiLocation = (Junction::Parent, Junction::Parachain { id: ParachainInfo::get().into() }).into(); + } + + impl orml_xtokens::Config for Runtime { + type Event = Event; + type Balance = Balance; + type CurrencyId = CurrencyId; + type AccountId32Convert = AccountId32Convert; + type SelfLocation = SelfLocation; + type XcmHandler = HandleXcm; + } + }, + extra_modules = { + Tokens: orml_tokens::{Pallet, Storage, Event, Config}, + XTokens: orml_xtokens::{Pallet, Storage, Call, Event}, + }, + } +} + +decl_test_network! { + pub struct TestNetwork { + relay_chain = default, + parachains = vec![ + (1, ParaA), + (2, ParaB), + ], + } +} + +pub type ParaATokens = orml_tokens::Pallet; +pub type ParaAXtokens = orml_xtokens::Pallet; + +pub type ParaBTokens = orml_tokens::Pallet; +pub type ParaBXtokens = orml_xtokens::Pallet; + +pub type RelayBalances = pallet_balances::Pallet; + +pub struct ParaExtBuilder; + +impl Default for ParaExtBuilder { + fn default() -> Self { + ParaExtBuilder + } +} + +impl ParaExtBuilder { + pub fn build< + Runtime: frame_system::Config + orml_tokens::Config, + >( + self, + para_id: u32, + ) -> TestExternalities + where + ::BlockNumber: From, + { + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + parachain_info::GenesisConfig { + parachain_id: para_id.into(), + } + .assimilate_storage(&mut t) + .unwrap(); + + orml_tokens::GenesisConfig:: { + endowed_accounts: vec![(ALICE, CurrencyId::R, 100)], + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = TestExternalities::new(t); + ext.execute_with(|| frame_system::Pallet::::set_block_number(1.into())); + ext + } +} + +pub fn parachain_ext< + Runtime: frame_system::Config + orml_tokens::Config, +>( + para_id: u32, +) -> TestExternalities +where + ::BlockNumber: From, +{ + ParaExtBuilder::default().build::(para_id) +} diff --git a/xtokens/src/tests.rs b/xtokens/src/tests.rs new file mode 100644 index 000000000..fe55591e8 --- /dev/null +++ b/xtokens/src/tests.rs @@ -0,0 +1,46 @@ +#![cfg(test)] + +use super::*; +use cumulus_primitives_core::ParaId; +use frame_support::{assert_ok, traits::Currency}; +use mock::*; +use orml_traits::MultiCurrency; +use polkadot_parachain::primitives::AccountIdConversion; +use sp_runtime::AccountId32; +use xcm::v0::{Junction, NetworkId}; +use xcm_simulator::TestExt; + +fn para_a_account() -> AccountId32 { + ParaId::from(1).into_account() +} + +#[test] +fn send_relay_chain_asset_to_relay_chain() { + TestNetwork::reset(); + + MockRelay::execute_with(|| { + let _ = RelayBalances::deposit_creating(¶_a_account(), 100); + }); + + ParaA::execute_with(|| { + assert_ok!(ParaAXtokens::transfer( + Some(ALICE).into(), + CurrencyId::R, + 30, + ( + Junction::Parent, + Junction::AccountId32 { + network: NetworkId::Polkadot, + id: BOB.into(), + }, + ) + .into(), + )); + assert_eq!(ParaATokens::free_balance(CurrencyId::R, &ALICE), 70); + }); + + MockRelay::execute_with(|| { + assert_eq!(RelayBalances::free_balance(¶_a_account()), 70); + assert_eq!(RelayBalances::free_balance(&BOB), 30); + }); +} From cb2532f4c572942c046f2e636a83e2b15267555f Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Tue, 6 Apr 2021 01:19:01 +1200 Subject: [PATCH 09/16] Send relay chain asset to sibling unit test. --- xtokens/src/lib.rs | 10 +++++++++- xtokens/src/mock.rs | 2 +- xtokens/src/tests.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs index 84b006f6c..7f4559b1a 100644 --- a/xtokens/src/lib.rs +++ b/xtokens/src/lib.rs @@ -31,6 +31,7 @@ use sp_runtime::{ use sp_std::prelude::*; use xcm::v0::{ + Junction::*, MultiAsset, MultiLocation, Order, Order::*, Xcm::{self, *}, @@ -193,6 +194,13 @@ pub mod module { dest: MultiLocation, recipient: MultiLocation, ) -> Xcm { + let mut reanchored_dest = dest.clone(); + if reserve == Parent.into() { + if let MultiLocation::X2(Parent, Parachain { id }) = dest { + reanchored_dest = Parachain { id }.into(); + } + } + WithdrawAsset { assets: vec![asset], effects: vec![InitiateReserveWithdraw { @@ -200,7 +208,7 @@ pub mod module { reserve, effects: vec![DepositReserveAsset { assets: vec![MultiAsset::All], - dest, + dest: reanchored_dest, effects: Self::deposit_asset(recipient), }], }], diff --git a/xtokens/src/mock.rs b/xtokens/src/mock.rs index 1a31b21e1..3320a89bb 100644 --- a/xtokens/src/mock.rs +++ b/xtokens/src/mock.rs @@ -294,7 +294,7 @@ pub type ParaAXtokens = orml_xtokens::Pallet; pub type ParaBTokens = orml_tokens::Pallet; pub type ParaBXtokens = orml_xtokens::Pallet; -pub type RelayBalances = pallet_balances::Pallet; +pub type RelayBalances = pallet_balances::Pallet; pub struct ParaExtBuilder; diff --git a/xtokens/src/tests.rs b/xtokens/src/tests.rs index fe55591e8..8c5588ffb 100644 --- a/xtokens/src/tests.rs +++ b/xtokens/src/tests.rs @@ -14,6 +14,10 @@ fn para_a_account() -> AccountId32 { ParaId::from(1).into_account() } +fn para_b_account() -> AccountId32 { + ParaId::from(2).into_account() +} + #[test] fn send_relay_chain_asset_to_relay_chain() { TestNetwork::reset(); @@ -44,3 +48,41 @@ fn send_relay_chain_asset_to_relay_chain() { assert_eq!(RelayBalances::free_balance(&BOB), 30); }); } + +#[test] +fn send_relay_chain_asset_to_sibling() { + TestNetwork::reset(); + + MockRelay::execute_with(|| { + let _ = RelayBalances::deposit_creating(¶_a_account(), 100); + }); + + ParaA::execute_with(|| { + assert_ok!(ParaAXtokens::transfer( + Some(ALICE).into(), + CurrencyId::R, + 30, + ( + Junction::Parent, + Junction::Parachain { id: 2 }, + Junction::AccountId32 { + network: NetworkId::Any, + id: BOB.into(), + }, + ) + .into(), + )); + assert_eq!(ParaATokens::free_balance(CurrencyId::R, &ALICE), 70); + }); + + use xcm_simulator::relay_chain; + + MockRelay::execute_with(|| { + assert_eq!(RelayBalances::free_balance(¶_a_account()), 70); + assert_eq!(RelayBalances::free_balance(¶_b_account()), 30); + }); + + ParaB::execute_with(|| { + assert_eq!(ParaBTokens::free_balance(CurrencyId::R, &BOB), 30); + }); +} From d527ad3b4a7edabb2e975b8046a6e52c89a610ec Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Tue, 6 Apr 2021 10:23:35 +1200 Subject: [PATCH 10/16] Move location traits into orml-traits. --- traits/Cargo.toml | 2 + traits/src/lib.rs | 1 + {xtokens => traits}/src/location.rs | 0 xtokens/Cargo.toml | 2 + xtokens/src/lib.rs | 4 +- xtokens/src/tests.rs | 103 +++++++++++++++++++++++++--- 6 files changed, 101 insertions(+), 11 deletions(-) rename {xtokens => traits}/src/location.rs (100%) diff --git a/traits/Cargo.toml b/traits/Cargo.toml index 99f411be7..2c92c0f16 100644 --- a/traits/Cargo.toml +++ b/traits/Cargo.toml @@ -17,6 +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 = "rococo-v1", default-features = false } orml-utilities = { path = "../utilities", version = "0.4.1-dev", default-features = false } +xcm = { git = "https://github.com/paritytech/polkadot", branch = "rococo-v1", default-features = false } funty = { version = "=1.1.0", default-features = false } # https://github.com/bitvecto-rs/bitvec/issues/105 @@ -31,4 +32,5 @@ std = [ "num-traits/std", "frame-support/std", "orml-utilities/std", + "xcm/std", ] diff --git a/traits/src/lib.rs b/traits/src/lib.rs index d8dfbab39..3c1e16de3 100644 --- a/traits/src/lib.rs +++ b/traits/src/lib.rs @@ -27,6 +27,7 @@ pub mod auction; pub mod currency; pub mod data_provider; pub mod get_by_key; +pub mod location; pub mod nft; pub mod price; pub mod rewards; diff --git a/xtokens/src/location.rs b/traits/src/location.rs similarity index 100% rename from xtokens/src/location.rs rename to traits/src/location.rs diff --git a/xtokens/Cargo.toml b/xtokens/Cargo.toml index cab8e98d2..6f45a4462 100644 --- a/xtokens/Cargo.toml +++ b/xtokens/Cargo.toml @@ -22,6 +22,7 @@ cumulus-primitives-core = { git = "https://github.com/paritytech/cumulus", branc xcm = { git = "https://github.com/paritytech/polkadot", branch = "rococo-v1", 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 = "rococo-v1" } @@ -50,4 +51,5 @@ std = [ "cumulus-primitives-core/std", "xcm/std", "orml-xcm-support/std", + "orml-traits/std", ] diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs index 7f4559b1a..b516ccfc0 100644 --- a/xtokens/src/lib.rs +++ b/xtokens/src/lib.rs @@ -37,11 +37,9 @@ use xcm::v0::{ Xcm::{self, *}, }; +use orml_traits::location::{Parse, Reserve}; use orml_xcm_support::XcmHandler; -pub mod location; -use location::*; - mod mock; mod tests; diff --git a/xtokens/src/tests.rs b/xtokens/src/tests.rs index 8c5588ffb..1782ae110 100644 --- a/xtokens/src/tests.rs +++ b/xtokens/src/tests.rs @@ -5,25 +5,35 @@ use cumulus_primitives_core::ParaId; use frame_support::{assert_ok, traits::Currency}; use mock::*; use orml_traits::MultiCurrency; -use polkadot_parachain::primitives::AccountIdConversion; +use polkadot_parachain::primitives::{AccountIdConversion, Sibling}; use sp_runtime::AccountId32; use xcm::v0::{Junction, NetworkId}; use xcm_simulator::TestExt; -fn para_a_account() -> AccountId32 { +fn relay_chain_para_a_account() -> AccountId32 { ParaId::from(1).into_account() } -fn para_b_account() -> AccountId32 { +fn relay_chain_para_b_account() -> AccountId32 { ParaId::from(2).into_account() } +fn para_a_account() -> AccountId32 { + use sp_runtime::traits::AccountIdConversion; + Sibling::from(1).into_account() +} + +fn para_b_account() -> AccountId32 { + use sp_runtime::traits::AccountIdConversion; + Sibling::from(2).into_account() +} + #[test] fn send_relay_chain_asset_to_relay_chain() { TestNetwork::reset(); MockRelay::execute_with(|| { - let _ = RelayBalances::deposit_creating(¶_a_account(), 100); + let _ = RelayBalances::deposit_creating(&relay_chain_para_a_account(), 100); }); ParaA::execute_with(|| { @@ -44,7 +54,7 @@ fn send_relay_chain_asset_to_relay_chain() { }); MockRelay::execute_with(|| { - assert_eq!(RelayBalances::free_balance(¶_a_account()), 70); + assert_eq!(RelayBalances::free_balance(&relay_chain_para_a_account()), 70); assert_eq!(RelayBalances::free_balance(&BOB), 30); }); } @@ -54,7 +64,7 @@ fn send_relay_chain_asset_to_sibling() { TestNetwork::reset(); MockRelay::execute_with(|| { - let _ = RelayBalances::deposit_creating(¶_a_account(), 100); + let _ = RelayBalances::deposit_creating(&relay_chain_para_a_account(), 100); }); ParaA::execute_with(|| { @@ -78,11 +88,88 @@ fn send_relay_chain_asset_to_sibling() { use xcm_simulator::relay_chain; MockRelay::execute_with(|| { - assert_eq!(RelayBalances::free_balance(¶_a_account()), 70); - assert_eq!(RelayBalances::free_balance(¶_b_account()), 30); + assert_eq!(RelayBalances::free_balance(&relay_chain_para_a_account()), 70); + assert_eq!(RelayBalances::free_balance(&relay_chain_para_b_account()), 30); }); ParaB::execute_with(|| { assert_eq!(ParaBTokens::free_balance(CurrencyId::R, &BOB), 30); }); } + +#[test] +fn send_sibling_asset_to_reserve_sibling() { + TestNetwork::reset(); + + ParaA::execute_with(|| { + assert_ok!(ParaATokens::deposit(CurrencyId::B, &ALICE, 100)); + }); + + ParaB::execute_with(|| { + assert_ok!(ParaBTokens::deposit(CurrencyId::B, ¶_a_account(), 100)); + }); + + ParaA::execute_with(|| { + assert_ok!(ParaAXtokens::transfer( + Some(ALICE).into(), + CurrencyId::B, + 30, + ( + Junction::Parent, + Junction::Parachain { id: 2 }, + Junction::AccountId32 { + network: NetworkId::Any, + id: BOB.into(), + }, + ) + .into(), + )); + + assert_eq!(ParaATokens::free_balance(CurrencyId::B, &ALICE), 70); + }); + + ParaB::execute_with(|| { + assert_eq!(ParaBTokens::free_balance(CurrencyId::B, ¶_a_account()), 70); + assert_eq!(ParaBTokens::free_balance(CurrencyId::B, &BOB), 30); + }); +} + +#[test] +fn send_sibling_asset_to_non_reserve_sibling() { + //TODO: add another parachain and test +} + +#[test] +fn send_self_parachain_asset_to_sibling() { + TestNetwork::reset(); + + ParaA::execute_with(|| { + assert_ok!(ParaATokens::deposit(CurrencyId::A, &ALICE, 100)); + + assert_ok!(ParaAXtokens::transfer( + Some(ALICE).into(), + CurrencyId::A, + 30, + ( + Junction::Parent, + Junction::Parachain { id: 2 }, + Junction::AccountId32 { + network: NetworkId::Any, + id: BOB.into(), + }, + ) + .into(), + )); + + assert_eq!(ParaATokens::free_balance(CurrencyId::A, &ALICE), 70); + assert_eq!(ParaATokens::free_balance(CurrencyId::A, ¶_b_account()), 30); + }); + + // fix: untrusted reserve location + ParaB::execute_with(|| { + para_b::System::events().iter().for_each(|r| { + println!(">>> {:?}", r.event); + }); + assert_eq!(ParaBTokens::free_balance(CurrencyId::A, &BOB), 30); + }); +} From 1b3c2d5ef65b29044f53556930f3d70d92a7c4ef Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Tue, 6 Apr 2021 10:35:42 +1200 Subject: [PATCH 11/16] Add MultiNativeAsset filter for is reserve check. --- xcm-support/src/lib.rs | 16 ++++++++++++++++ xtokens/src/mock.rs | 8 ++++---- xtokens/src/tests.rs | 2 -- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/xcm-support/src/lib.rs b/xcm-support/src/lib.rs index 5b419a71d..6963f0d5b 100644 --- a/xcm-support/src/lib.rs +++ b/xcm-support/src/lib.rs @@ -24,6 +24,8 @@ use sp_std::{ use xcm::v0::{Junction, MultiAsset, MultiLocation, Xcm}; use xcm_executor::traits::{FilterAssetLocation, MatchesFungible, NativeAsset}; +use orml_traits::location::Reserve; + pub use currency_adapter::MultiCurrencyAdapter; mod currency_adapter; @@ -90,6 +92,20 @@ impl, MultiLocation)>>> FilterAssetLocation for Nat } } +/// A `FilterAssetLocation` implementation. Filters multi native assets whose +/// reserve is same with `origin`. +pub struct MultiNativeAsset; +impl FilterAssetLocation for MultiNativeAsset { + fn filter_asset_location(asset: &MultiAsset, origin: &MultiLocation) -> bool { + if let Some(ref reserve) = asset.reserve() { + if reserve == origin { + return true; + } + } + false + } +} + /// `CurrencyIdConversion` implementation. Converts relay chain tokens, or /// parachain tokens that could be decoded from a general key. pub struct CurrencyIdConverter( diff --git a/xtokens/src/mock.rs b/xtokens/src/mock.rs index 3320a89bb..a20173478 100644 --- a/xtokens/src/mock.rs +++ b/xtokens/src/mock.rs @@ -6,7 +6,7 @@ use crate as orml_xtokens; use frame_support::parameter_types; use orml_traits::parameter_type_with_key; use orml_xcm_support::{ - CurrencyIdConverter, IsConcreteWithGeneralKey, MultiCurrencyAdapter, XcmHandler as XcmHandlerT, + CurrencyIdConverter, IsConcreteWithGeneralKey, MultiCurrencyAdapter, MultiNativeAsset, XcmHandler as XcmHandlerT, }; use polkadot_parachain::primitives::Sibling; use serde::{Deserialize, Serialize}; @@ -18,7 +18,7 @@ use xcm_builder::{ AccountId32Aliases, LocationInverter, ParentIsDefault, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SovereignSignedViaLocation, }; -use xcm_executor::{traits::NativeAsset, Config as XcmConfigT}; +use xcm_executor::Config as XcmConfigT; use xcm_simulator::{decl_test_network, decl_test_parachain, prelude::*}; pub const ALICE: AccountId32 = AccountId32::new([0u8; 32]); @@ -118,7 +118,7 @@ decl_test_parachain! { type XcmSender = XcmHandler; type AssetTransactor = LocalAssetTransactor; type OriginConverter = LocalOriginConverter; - type IsReserve = NativeAsset; + type IsReserve = MultiNativeAsset; type IsTeleporter = (); type LocationInverter = LocationInverter; } @@ -222,7 +222,7 @@ decl_test_parachain! { type XcmSender = XcmHandler; type AssetTransactor = LocalAssetTransactor; type OriginConverter = LocalOriginConverter; - type IsReserve = NativeAsset; + type IsReserve = MultiNativeAsset; type IsTeleporter = (); type LocationInverter = LocationInverter; } diff --git a/xtokens/src/tests.rs b/xtokens/src/tests.rs index 1782ae110..bc06bb9fc 100644 --- a/xtokens/src/tests.rs +++ b/xtokens/src/tests.rs @@ -85,8 +85,6 @@ fn send_relay_chain_asset_to_sibling() { assert_eq!(ParaATokens::free_balance(CurrencyId::R, &ALICE), 70); }); - use xcm_simulator::relay_chain; - MockRelay::execute_with(|| { assert_eq!(RelayBalances::free_balance(&relay_chain_para_a_account()), 70); assert_eq!(RelayBalances::free_balance(&relay_chain_para_b_account()), 30); From 1330f16c3be69baa7c3279afe6bb5ef80f471742 Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Tue, 6 Apr 2021 10:45:51 +1200 Subject: [PATCH 12/16] More unit tests. --- xtokens/src/mock.rs | 110 +++++++++++++++++++++++++++++++++++++++++-- xtokens/src/tests.rs | 44 ++++++++++++++++- 2 files changed, 149 insertions(+), 5 deletions(-) diff --git a/xtokens/src/mock.rs b/xtokens/src/mock.rs index a20173478..c23f28436 100644 --- a/xtokens/src/mock.rs +++ b/xtokens/src/mock.rs @@ -179,6 +179,110 @@ decl_test_parachain! { new_ext = parachain_ext::(2), para_id = 2, } + pub mod para_c { + test_network = super::TestNetwork, + xcm_config = { + use super::*; + + parameter_types! { + pub ParaANetwork: NetworkId = NetworkId::Any; + pub RelayChainOrigin: Origin = cumulus_pallet_xcm_handler::Origin::Relay.into(); + pub Ancestry: MultiLocation = MultiLocation::X1(Junction::Parachain { + id: ParachainInfo::get().into(), + }); + pub const RelayChainCurrencyId: CurrencyId = CurrencyId::R; + } + + pub type LocationConverter = ( + ParentIsDefault, + SiblingParachainConvertsVia, + AccountId32Aliases, + ); + + pub type LocalAssetTransactor = MultiCurrencyAdapter< + Tokens, + (), + IsConcreteWithGeneralKey, + LocationConverter, + AccountId, + CurrencyIdConverter, + CurrencyId, + >; + + pub type LocalOriginConverter = ( + SovereignSignedViaLocation, + RelayChainAsNative, + SiblingParachainAsNative, + SignedAccountId32AsNative, + ); + + pub struct XcmConfig; + impl XcmConfigT for XcmConfig { + type Call = Call; + type XcmSender = XcmHandler; + type AssetTransactor = LocalAssetTransactor; + type OriginConverter = LocalOriginConverter; + type IsReserve = MultiNativeAsset; + type IsTeleporter = (); + type LocationInverter = LocationInverter; + } + }, + extra_config = { + parameter_type_with_key! { + pub ExistentialDeposits: |_currency_id: super::CurrencyId| -> Balance { + Default::default() + }; + } + + impl orml_tokens::Config for Runtime { + type Event = Event; + type Balance = Balance; + type Amount = Amount; + type CurrencyId = super::CurrencyId; + type WeightInfo = (); + type ExistentialDeposits = ExistentialDeposits; + type OnDust = (); + } + + pub struct HandleXcm; + impl XcmHandlerT for HandleXcm { + fn execute_xcm(origin: AccountId, xcm: Xcm) -> DispatchResult { + XcmHandler::execute_xcm(origin, xcm) + } + } + + pub struct AccountId32Convert; + impl Convert for AccountId32Convert { + fn convert(account_id: AccountId) -> [u8; 32] { + account_id.into() + } + } + + parameter_types! { + pub SelfLocation: MultiLocation = (Junction::Parent, Junction::Parachain { id: ParachainInfo::get().into() }).into(); + } + + impl orml_xtokens::Config for Runtime { + type Event = Event; + type Balance = Balance; + type CurrencyId = CurrencyId; + type AccountId32Convert = AccountId32Convert; + type SelfLocation = SelfLocation; + type XcmHandler = HandleXcm; + } + }, + extra_modules = { + Tokens: orml_tokens::{Pallet, Storage, Event, Config}, + XTokens: orml_xtokens::{Pallet, Storage, Call, Event}, + }, + } +} + +decl_test_parachain! { + pub struct ParaC { + new_ext = parachain_ext::(3), + para_id = 3, + } pub mod para_b { test_network = super::TestNetwork, xcm_config = { @@ -284,15 +388,15 @@ decl_test_network! { parachains = vec![ (1, ParaA), (2, ParaB), + (3, ParaC), ], } } -pub type ParaATokens = orml_tokens::Pallet; pub type ParaAXtokens = orml_xtokens::Pallet; - +pub type ParaATokens = orml_tokens::Pallet; pub type ParaBTokens = orml_tokens::Pallet; -pub type ParaBXtokens = orml_xtokens::Pallet; +pub type ParaCTokens = orml_tokens::Pallet; pub type RelayBalances = pallet_balances::Pallet; diff --git a/xtokens/src/tests.rs b/xtokens/src/tests.rs index bc06bb9fc..1e27ddd39 100644 --- a/xtokens/src/tests.rs +++ b/xtokens/src/tests.rs @@ -28,6 +28,11 @@ fn para_b_account() -> AccountId32 { Sibling::from(2).into_account() } +fn para_c_account() -> AccountId32 { + use sp_runtime::traits::AccountIdConversion; + Sibling::from(3).into_account() +} + #[test] fn send_relay_chain_asset_to_relay_chain() { TestNetwork::reset(); @@ -134,7 +139,43 @@ fn send_sibling_asset_to_reserve_sibling() { #[test] fn send_sibling_asset_to_non_reserve_sibling() { - //TODO: add another parachain and test + TestNetwork::reset(); + + ParaA::execute_with(|| { + assert_ok!(ParaATokens::deposit(CurrencyId::B, &ALICE, 100)); + }); + + ParaB::execute_with(|| { + assert_ok!(ParaBTokens::deposit(CurrencyId::B, ¶_a_account(), 100)); + }); + + ParaA::execute_with(|| { + assert_ok!(ParaAXtokens::transfer( + Some(ALICE).into(), + CurrencyId::B, + 30, + ( + Junction::Parent, + Junction::Parachain { id: 3 }, + Junction::AccountId32 { + network: NetworkId::Any, + id: BOB.into(), + }, + ) + .into(), + )); + assert_eq!(ParaATokens::free_balance(CurrencyId::B, &ALICE), 70); + }); + + // check reserve accounts + ParaB::execute_with(|| { + assert_eq!(ParaBTokens::free_balance(CurrencyId::B, ¶_a_account()), 70); + assert_eq!(ParaBTokens::free_balance(CurrencyId::B, ¶_c_account()), 30); + }); + + ParaC::execute_with(|| { + assert_eq!(ParaCTokens::free_balance(CurrencyId::B, &BOB), 30); + }); } #[test] @@ -163,7 +204,6 @@ fn send_self_parachain_asset_to_sibling() { assert_eq!(ParaATokens::free_balance(CurrencyId::A, ¶_b_account()), 30); }); - // fix: untrusted reserve location ParaB::execute_with(|| { para_b::System::events().iter().for_each(|r| { println!(">>> {:?}", r.event); From d9bf73e56c45362be884a9a5d8702eddbb8f6c6b Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Tue, 6 Apr 2021 11:01:13 +1200 Subject: [PATCH 13/16] Failing edge case unit tests. --- xtokens/src/tests.rs | 97 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 87 insertions(+), 10 deletions(-) diff --git a/xtokens/src/tests.rs b/xtokens/src/tests.rs index 1e27ddd39..b184df4ca 100644 --- a/xtokens/src/tests.rs +++ b/xtokens/src/tests.rs @@ -2,7 +2,7 @@ use super::*; use cumulus_primitives_core::ParaId; -use frame_support::{assert_ok, traits::Currency}; +use frame_support::{assert_noop, assert_ok, traits::Currency}; use mock::*; use orml_traits::MultiCurrency; use polkadot_parachain::primitives::{AccountIdConversion, Sibling}; @@ -47,7 +47,7 @@ fn send_relay_chain_asset_to_relay_chain() { CurrencyId::R, 30, ( - Junction::Parent, + Parent, Junction::AccountId32 { network: NetworkId::Polkadot, id: BOB.into(), @@ -78,8 +78,8 @@ fn send_relay_chain_asset_to_sibling() { CurrencyId::R, 30, ( - Junction::Parent, - Junction::Parachain { id: 2 }, + Parent, + Parachain { id: 2 }, Junction::AccountId32 { network: NetworkId::Any, id: BOB.into(), @@ -118,8 +118,8 @@ fn send_sibling_asset_to_reserve_sibling() { CurrencyId::B, 30, ( - Junction::Parent, - Junction::Parachain { id: 2 }, + Parent, + Parachain { id: 2 }, Junction::AccountId32 { network: NetworkId::Any, id: BOB.into(), @@ -155,8 +155,8 @@ fn send_sibling_asset_to_non_reserve_sibling() { CurrencyId::B, 30, ( - Junction::Parent, - Junction::Parachain { id: 3 }, + Parent, + Parachain { id: 3 }, Junction::AccountId32 { network: NetworkId::Any, id: BOB.into(), @@ -190,8 +190,8 @@ fn send_self_parachain_asset_to_sibling() { CurrencyId::A, 30, ( - Junction::Parent, - Junction::Parachain { id: 2 }, + Parent, + Parachain { id: 2 }, Junction::AccountId32 { network: NetworkId::Any, id: BOB.into(), @@ -211,3 +211,80 @@ fn send_self_parachain_asset_to_sibling() { assert_eq!(ParaBTokens::free_balance(CurrencyId::A, &BOB), 30); }); } + +#[test] +fn transfer_no_reserve_assets_fails() { + TestNetwork::reset(); + + ParaA::execute_with(|| { + assert_noop!( + ParaAXtokens::transfer_multiasset( + Some(ALICE).into(), + MultiAsset::ConcreteFungible { + id: GeneralKey("B".into()).into(), + amount: 1 + }, + ( + Parent, + Parachain { id: 2 }, + Junction::AccountId32 { + network: NetworkId::Any, + id: BOB.into() + } + ) + .into() + ), + Error::::AssetHasNoReserve + ); + }); +} + +#[test] +fn transfer_to_self_chain_fails() { + TestNetwork::reset(); + + ParaA::execute_with(|| { + assert_noop!( + ParaAXtokens::transfer_multiasset( + Some(ALICE).into(), + MultiAsset::ConcreteFungible { + id: (Parent, Parachain { id: 1 }, GeneralKey("A".into())).into(), + amount: 1 + }, + ( + Parent, + Parachain { id: 1 }, + Junction::AccountId32 { + network: NetworkId::Any, + id: BOB.into() + } + ) + .into() + ), + Error::::NotCrossChainTransfer + ); + }); +} + +#[test] +fn transfer_to_invalid_dest_fails() { + TestNetwork::reset(); + + ParaA::execute_with(|| { + assert_noop!( + ParaAXtokens::transfer_multiasset( + Some(ALICE).into(), + MultiAsset::ConcreteFungible { + id: (Parent, Parachain { id: 1 }, GeneralKey("A".into())).into(), + amount: 1 + }, + (Junction::AccountId32 { + network: NetworkId::Any, + id: BOB.into() + }) + .into() + ), + Error::::InvalidDest + ); + }); +} From 4e9b0041ded5b935bac0f26d79571b96cb7b126f Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Tue, 6 Apr 2021 11:15:25 +1200 Subject: [PATCH 14/16] Handle zero amount asset case. --- xtokens/src/lib.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs index b516ccfc0..5ba280bf4 100644 --- a/xtokens/src/lib.rs +++ b/xtokens/src/lib.rs @@ -25,7 +25,7 @@ use frame_support::{pallet_prelude::*, traits::Get, transactional, Parameter}; use frame_system::{ensure_signed, pallet_prelude::*}; use sp_runtime::{ - traits::{AtLeast32BitUnsigned, Convert, MaybeSerializeDeserialize, Member}, + traits::{AtLeast32BitUnsigned, Convert, MaybeSerializeDeserialize, Member, Zero}, DispatchError, }; use sp_std::prelude::*; @@ -114,6 +114,11 @@ pub mod module { dest: MultiLocation, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; + + if amount == Zero::zero() { + return Ok(().into()); + } + let asset = MultiAsset::ConcreteFungible { id: currency_id.clone().into(), amount: amount.into(), @@ -132,6 +137,11 @@ pub mod module { dest: MultiLocation, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; + + if Self::is_zero_amount(&asset) { + return Ok(().into()); + } + Self::do_transfer_multiasset(who.clone(), asset.clone(), dest.clone())?; Self::deposit_event(Event::::TransferredMultiAsset(who, asset, dest)); Ok(().into()) @@ -220,6 +230,22 @@ pub mod module { }] } + fn is_zero_amount(asset: &MultiAsset) -> bool { + if let MultiAsset::ConcreteFungible { id: _, amount } = asset { + if *amount == Zero::zero() { + return true; + } + } + + if let MultiAsset::AbstractFungible { id: _, amount } = asset { + if *amount == Zero::zero() { + return true; + } + } + + false + } + /// Ensure has the `dest` has chain part and recipient part. fn ensure_valid_dest( dest: MultiLocation, From e0783ac5804f7e367a1ffe86c47dce8322c9bf82 Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Tue, 6 Apr 2021 11:16:04 +1200 Subject: [PATCH 15/16] Fix mocks. --- auction/src/mock.rs | 1 + authority/src/mock.rs | 1 + benchmarking/src/tests.rs | 1 + currencies/src/mock.rs | 1 + gradually-update/src/mock.rs | 1 + nft/src/mock.rs | 1 + oracle/src/mock.rs | 1 + rewards/src/mock.rs | 1 + tokens/src/mock.rs | 1 + unknown-tokens/src/mock.rs | 1 + unknown-tokens/src/tests.rs | 2 -- vesting/src/mock.rs | 1 + 12 files changed, 11 insertions(+), 2 deletions(-) diff --git a/auction/src/mock.rs b/auction/src/mock.rs index 25558c2eb..718c25164 100644 --- a/auction/src/mock.rs +++ b/auction/src/mock.rs @@ -42,6 +42,7 @@ impl frame_system::Config for Runtime { type BaseCallFilter = (); type SystemWeightInfo = (); type SS58Prefix = (); + type OnSetCode = (); } pub struct Handler; diff --git a/authority/src/mock.rs b/authority/src/mock.rs index 069f994a8..593710bc6 100644 --- a/authority/src/mock.rs +++ b/authority/src/mock.rs @@ -51,6 +51,7 @@ impl frame_system::Config for Runtime { type BaseCallFilter = (); type SystemWeightInfo = (); type SS58Prefix = (); + type OnSetCode = (); } parameter_types! { diff --git a/benchmarking/src/tests.rs b/benchmarking/src/tests.rs index 5068c8146..333a776cb 100644 --- a/benchmarking/src/tests.rs +++ b/benchmarking/src/tests.rs @@ -76,6 +76,7 @@ impl frame_system::Config for Test { type BaseCallFilter = (); type SystemWeightInfo = (); type SS58Prefix = (); + type OnSetCode = (); } impl tests::test::Config for Test { diff --git a/currencies/src/mock.rs b/currencies/src/mock.rs index a71a81f23..6eb8d883e 100644 --- a/currencies/src/mock.rs +++ b/currencies/src/mock.rs @@ -42,6 +42,7 @@ impl frame_system::Config for Runtime { type BaseCallFilter = (); type SystemWeightInfo = (); type SS58Prefix = (); + type OnSetCode = (); } type CurrencyId = u32; diff --git a/gradually-update/src/mock.rs b/gradually-update/src/mock.rs index d837e6367..410cb935d 100644 --- a/gradually-update/src/mock.rs +++ b/gradually-update/src/mock.rs @@ -39,6 +39,7 @@ impl frame_system::Config for Runtime { type BaseCallFilter = (); type SystemWeightInfo = (); type SS58Prefix = (); + type OnSetCode = (); } parameter_types! { diff --git a/nft/src/mock.rs b/nft/src/mock.rs index cf4bac4f2..915f67561 100644 --- a/nft/src/mock.rs +++ b/nft/src/mock.rs @@ -40,6 +40,7 @@ impl frame_system::Config for Runtime { type BaseCallFilter = (); type SystemWeightInfo = (); type SS58Prefix = (); + type OnSetCode = (); } impl Config for Runtime { diff --git a/oracle/src/mock.rs b/oracle/src/mock.rs index c7ea7487f..569dcdc6a 100644 --- a/oracle/src/mock.rs +++ b/oracle/src/mock.rs @@ -45,6 +45,7 @@ impl frame_system::Config for Test { type BaseCallFilter = (); type SystemWeightInfo = (); type SS58Prefix = (); + type OnSetCode = (); } thread_local! { diff --git a/rewards/src/mock.rs b/rewards/src/mock.rs index d088983b2..2a34337b3 100644 --- a/rewards/src/mock.rs +++ b/rewards/src/mock.rs @@ -49,6 +49,7 @@ impl frame_system::Config for Runtime { type BaseCallFilter = (); type SystemWeightInfo = (); type SS58Prefix = (); + type OnSetCode = (); } thread_local! { diff --git a/tokens/src/mock.rs b/tokens/src/mock.rs index bc430b16a..607693940 100644 --- a/tokens/src/mock.rs +++ b/tokens/src/mock.rs @@ -54,6 +54,7 @@ impl frame_system::Config for Runtime { type BaseCallFilter = (); type SystemWeightInfo = (); type SS58Prefix = (); + type OnSetCode = (); } thread_local! { diff --git a/unknown-tokens/src/mock.rs b/unknown-tokens/src/mock.rs index 1a68d01c4..b8e297a15 100644 --- a/unknown-tokens/src/mock.rs +++ b/unknown-tokens/src/mock.rs @@ -38,6 +38,7 @@ impl frame_system::Config for Runtime { type BaseCallFilter = (); type SystemWeightInfo = (); type SS58Prefix = (); + type OnSetCode = (); } impl Config for Runtime { diff --git a/unknown-tokens/src/tests.rs b/unknown-tokens/src/tests.rs index 63ad7a2d4..b2cb39627 100644 --- a/unknown-tokens/src/tests.rs +++ b/unknown-tokens/src/tests.rs @@ -5,8 +5,6 @@ use super::*; use mock::{Event, *}; -use codec::{Decode, Encode}; - use frame_support::{assert_err, assert_ok}; use xcm::v0::Junction; diff --git a/vesting/src/mock.rs b/vesting/src/mock.rs index c99f50059..e3b250191 100644 --- a/vesting/src/mock.rs +++ b/vesting/src/mock.rs @@ -38,6 +38,7 @@ impl frame_system::Config for Runtime { type BaseCallFilter = (); type SystemWeightInfo = (); type SS58Prefix = (); + type OnSetCode = (); } type Balance = u64; From 3a629f90a1765bbb49f240cf73d629e657b59b4e Mon Sep 17 00:00:00 2001 From: Shaun Wang Date: Tue, 6 Apr 2021 11:18:43 +1200 Subject: [PATCH 16/16] Renaming. --- xtokens/src/tests.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/xtokens/src/tests.rs b/xtokens/src/tests.rs index b184df4ca..a90ca0b32 100644 --- a/xtokens/src/tests.rs +++ b/xtokens/src/tests.rs @@ -10,25 +10,25 @@ use sp_runtime::AccountId32; use xcm::v0::{Junction, NetworkId}; use xcm_simulator::TestExt; -fn relay_chain_para_a_account() -> AccountId32 { +fn para_a_account() -> AccountId32 { ParaId::from(1).into_account() } -fn relay_chain_para_b_account() -> AccountId32 { +fn para_b_account() -> AccountId32 { ParaId::from(2).into_account() } -fn para_a_account() -> AccountId32 { +fn sibling_a_account() -> AccountId32 { use sp_runtime::traits::AccountIdConversion; Sibling::from(1).into_account() } -fn para_b_account() -> AccountId32 { +fn sibling_b_account() -> AccountId32 { use sp_runtime::traits::AccountIdConversion; Sibling::from(2).into_account() } -fn para_c_account() -> AccountId32 { +fn sibling_c_account() -> AccountId32 { use sp_runtime::traits::AccountIdConversion; Sibling::from(3).into_account() } @@ -38,7 +38,7 @@ fn send_relay_chain_asset_to_relay_chain() { TestNetwork::reset(); MockRelay::execute_with(|| { - let _ = RelayBalances::deposit_creating(&relay_chain_para_a_account(), 100); + let _ = RelayBalances::deposit_creating(¶_a_account(), 100); }); ParaA::execute_with(|| { @@ -59,7 +59,7 @@ fn send_relay_chain_asset_to_relay_chain() { }); MockRelay::execute_with(|| { - assert_eq!(RelayBalances::free_balance(&relay_chain_para_a_account()), 70); + assert_eq!(RelayBalances::free_balance(¶_a_account()), 70); assert_eq!(RelayBalances::free_balance(&BOB), 30); }); } @@ -69,7 +69,7 @@ fn send_relay_chain_asset_to_sibling() { TestNetwork::reset(); MockRelay::execute_with(|| { - let _ = RelayBalances::deposit_creating(&relay_chain_para_a_account(), 100); + let _ = RelayBalances::deposit_creating(¶_a_account(), 100); }); ParaA::execute_with(|| { @@ -91,8 +91,8 @@ fn send_relay_chain_asset_to_sibling() { }); MockRelay::execute_with(|| { - assert_eq!(RelayBalances::free_balance(&relay_chain_para_a_account()), 70); - assert_eq!(RelayBalances::free_balance(&relay_chain_para_b_account()), 30); + assert_eq!(RelayBalances::free_balance(¶_a_account()), 70); + assert_eq!(RelayBalances::free_balance(¶_b_account()), 30); }); ParaB::execute_with(|| { @@ -109,7 +109,7 @@ fn send_sibling_asset_to_reserve_sibling() { }); ParaB::execute_with(|| { - assert_ok!(ParaBTokens::deposit(CurrencyId::B, ¶_a_account(), 100)); + assert_ok!(ParaBTokens::deposit(CurrencyId::B, &sibling_a_account(), 100)); }); ParaA::execute_with(|| { @@ -132,7 +132,7 @@ fn send_sibling_asset_to_reserve_sibling() { }); ParaB::execute_with(|| { - assert_eq!(ParaBTokens::free_balance(CurrencyId::B, ¶_a_account()), 70); + assert_eq!(ParaBTokens::free_balance(CurrencyId::B, &sibling_a_account()), 70); assert_eq!(ParaBTokens::free_balance(CurrencyId::B, &BOB), 30); }); } @@ -146,7 +146,7 @@ fn send_sibling_asset_to_non_reserve_sibling() { }); ParaB::execute_with(|| { - assert_ok!(ParaBTokens::deposit(CurrencyId::B, ¶_a_account(), 100)); + assert_ok!(ParaBTokens::deposit(CurrencyId::B, &sibling_a_account(), 100)); }); ParaA::execute_with(|| { @@ -169,8 +169,8 @@ fn send_sibling_asset_to_non_reserve_sibling() { // check reserve accounts ParaB::execute_with(|| { - assert_eq!(ParaBTokens::free_balance(CurrencyId::B, ¶_a_account()), 70); - assert_eq!(ParaBTokens::free_balance(CurrencyId::B, ¶_c_account()), 30); + assert_eq!(ParaBTokens::free_balance(CurrencyId::B, &sibling_a_account()), 70); + assert_eq!(ParaBTokens::free_balance(CurrencyId::B, &sibling_c_account()), 30); }); ParaC::execute_with(|| { @@ -201,7 +201,7 @@ fn send_self_parachain_asset_to_sibling() { )); assert_eq!(ParaATokens::free_balance(CurrencyId::A, &ALICE), 70); - assert_eq!(ParaATokens::free_balance(CurrencyId::A, ¶_b_account()), 30); + assert_eq!(ParaATokens::free_balance(CurrencyId::A, &sibling_b_account()), 30); }); ParaB::execute_with(|| {