Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
224 changes: 166 additions & 58 deletions xtokens/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ use TransferKind::*;

#[frame_support::pallet]
pub mod module {

use super::*;

#[pallet::config]
Expand Down Expand Up @@ -571,34 +570,121 @@ pub mod module {
assets.len() <= T::MaxAssetsForTransfer::get(),
Error::<T>::TooManyAssetsBeingSent
);
let origin_location = T::AccountIdToMultiLocation::convert(who.clone());

let fee_to_dest = reset_fee(&fee, dest_weight as u128);
Comment thread
zqhxuyuan marked this conversation as resolved.
Outdated

// We check that all assets are valid and share the same reserve
for i in 0..assets.len() {
let mut non_fee_reserve: Option<MultiLocation> = None;
let asset_len = assets.len();
let mut assets_to_dest = MultiAssets::new();
for i in 0..asset_len {
let asset = assets.get(i).ok_or(Error::<T>::AssetIndexNonExistent)?;
if !asset.is_fungible(None) {
return Err(Error::<T>::NotFungible.into());
}
if fungible_amount(asset).is_zero() {
return Ok(());
Comment thread
xlc marked this conversation as resolved.
Outdated
}
ensure!(
fee.reserve() == asset.reserve(),
Error::<T>::DistinctReserveForAssetAndFee
);
if fee != *asset {
assets_to_dest.push(asset.clone());
} else {
assets_to_dest.push(fee_to_dest.clone());
}
// `assets` includes fee, the reserve location is decided by non fee asset
if (fee != *asset && non_fee_reserve.is_none()) || asset_len == 1 {
non_fee_reserve = asset.reserve();
}
// make sure all non fee assets share the same reserve
if non_fee_reserve.is_some() {
ensure!(
non_fee_reserve == asset.reserve(),
Error::<T>::DistinctReserveForAssetAndFee
);
}
}

// In case that fee reserve != asset reserve, there're two xcm sent from sender.
// first xcm send to fee reserve which also route to dest. second xcm directly
// send to dest. the fee amount in fee asset is split into two parts.
// 1. assets send to fee reserve = fee_amount - dest_weight
// 2. assets send to dest reserve = dest_weight
// the first part + second part = fee amount in fee asset
let fee_reserve = fee.reserve();
if fee_reserve != non_fee_reserve {
let mut assets_to_fee_reserve = MultiAssets::new();
let asset_to_fee_reserve = subtract_fee(&fee, dest_weight as u128);
assets_to_fee_reserve.push(asset_to_fee_reserve.clone());

// First xcm send to fee reserve chain and routing to dest chain.
// The `SelfLocation` current is (1, Parachain(id)) refer to sender parachain.
// we use `SelfLocation` to fund fee to sender's parachain account on
// destination chain, which asset is origin from sender account on sender chain.
// Notice: if parachain set `SelfLocation` to (0, Here), then it'll be error!
Self::send_xcm(
origin_location.clone(),
assets_to_fee_reserve,
asset_to_fee_reserve,
fee_reserve,
&dest,
Some(T::SelfLocation::get()),
dest_weight,
)?;

// Second xcm send to dest chain.
Self::send_xcm(
origin_location,
assets_to_dest,
fee_to_dest,
non_fee_reserve,
&dest,
None,
dest_weight,
)?;
} else {
Self::send_xcm(
origin_location,
assets.clone(),
fee,
non_fee_reserve,
&dest,
None,
dest_weight,
)?;
}

let (transfer_kind, dest, reserve, recipient) = Self::transfer_kind(&fee, &dest)?;
if deposit_event {
Self::deposit_event(Event::<T>::TransferredMultiAssets {
sender: who,
assets,
dest,
});
}

Ok(())
}

/// Send xcm with given assets and fee to dest or reserve chain.
fn send_xcm(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need a better naming

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename to execute_and_send_reserve_kind_xcm

origin_location: MultiLocation,
assets: MultiAssets,
fee: MultiAsset,
reserve: Option<MultiLocation>,
dest: &MultiLocation,
manual_recipient: Option<MultiLocation>,
Comment thread
zqhxuyuan marked this conversation as resolved.
Outdated
dest_weight: Weight,
) -> DispatchResult {
let (transfer_kind, dest, reserve, recipient) = Self::transfer_kind(reserve, dest)?;
let recipient = match manual_recipient {
Some(recipient) => recipient,
None => recipient,
};

let mut msg = match transfer_kind {
SelfReserveAsset => {
Self::transfer_self_reserve_asset(assets.clone(), fee, dest.clone(), recipient, dest_weight)?
}
ToReserve => Self::transfer_to_reserve(assets.clone(), fee, dest.clone(), recipient, dest_weight)?,
ToNonReserve => {
Self::transfer_to_non_reserve(assets.clone(), fee, reserve, dest.clone(), recipient, dest_weight)?
}
SelfReserveAsset => Self::transfer_self_reserve_asset(assets, fee, dest, recipient, dest_weight)?,
ToReserve => Self::transfer_to_reserve(assets, fee, dest, recipient, dest_weight)?,
ToNonReserve => Self::transfer_to_non_reserve(assets, fee, reserve, dest, recipient, dest_weight)?,
};

let origin_location = T::AccountIdToMultiLocation::convert(who.clone());
let weight = T::Weigher::weight(&mut msg).map_err(|()| Error::<T>::UnweighableMessage)?;
T::XcmExecutor::execute_xcm_in_credit(origin_location, msg, weight, weight)
.ensure_complete()
Expand All @@ -607,14 +693,6 @@ pub mod module {
Error::<T>::XcmExecutionFailed
})?;

if deposit_event {
Self::deposit_event(Event::<T>::TransferredMultiAssets {
sender: who,
assets,
dest,
});
}

Ok(())
}

Expand Down Expand Up @@ -735,22 +813,22 @@ pub mod module {

/// Get the transfer kind.
///
/// Returns `Err` if `asset` and `dest` combination doesn't make sense,
/// else returns a tuple of:
/// Returns `Err` if `dest` combination doesn't make sense, or `reserve`
/// is none, 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,
reserve: Option<MultiLocation>,
dest: &MultiLocation,
) -> 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 reserve = reserve.ok_or(Error::<T>::AssetHasNoReserve)?;
let transfer_kind = if reserve == self_location {
SelfReserveAsset
} else if reserve == dest {
Expand All @@ -766,13 +844,13 @@ pub mod module {
impl<T: Config> Pallet<T> {
/// Returns weight of `transfer_multiasset` call.
fn weight_of_transfer_multiasset(asset: &VersionedMultiAsset, dest: &VersionedMultiLocation) -> Weight {
let asset = asset.clone().try_into();
let asset: Result<MultiAsset, _> = asset.clone().try_into();
let dest = dest.clone().try_into();
if let (Ok(asset), Ok(dest)) = (asset, dest) {
if let Ok((transfer_kind, dest, _, reserve)) = Self::transfer_kind(&asset, &dest) {
if let Ok((transfer_kind, dest, _, reserve)) = Self::transfer_kind(asset.reserve(), &dest) {
let mut msg = match transfer_kind {
SelfReserveAsset => Xcm(vec![
WithdrawAsset(MultiAssets::from(asset.clone())),
WithdrawAsset(MultiAssets::from(asset)),
DepositReserveAsset {
assets: All.into(),
max_assets: 1,
Expand All @@ -781,7 +859,7 @@ pub mod module {
},
]),
ToReserve | ToNonReserve => Xcm(vec![
WithdrawAsset(MultiAssets::from(asset.clone())),
WithdrawAsset(MultiAssets::from(asset)),
InitiateReserveWithdraw {
assets: All.into(),
// `dest` is always (equal to) `reserve` in both cases
Expand Down Expand Up @@ -833,38 +911,53 @@ pub mod module {
dest: &VersionedMultiLocation,
) -> Weight {
let assets: Result<MultiAssets, ()> = assets.clone().try_into();

let dest = dest.clone().try_into();
if let (Ok(assets), Ok(dest)) = (assets, dest) {
if let Some(fee) = assets.get(*fee_item as usize) {
if let Ok((transfer_kind, dest, _, reserve)) = Self::transfer_kind(fee, &dest) {
let mut msg = match transfer_kind {
SelfReserveAsset => Xcm(vec![
WithdrawAsset(assets.clone()),
DepositReserveAsset {
assets: All.into(),
max_assets: assets.len() as u32,
dest,
xcm: Xcm(vec![]),
},
]),
ToReserve | ToNonReserve => Xcm(vec![
WithdrawAsset(assets),
InitiateReserveWithdraw {
assets: All.into(),
// `dest` is always (equal to) `reserve` in both cases
reserve,
xcm: Xcm(vec![]),
},
]),
};
return T::Weigher::weight(&mut msg)
.map_or(Weight::max_value(), |w| T::BaseXcmWeight::get().saturating_add(w));
}
let reserve_location = Self::get_reserve_location(&assets, fee_item);
// if let Ok(reserve_location) = reserve_location {
if let Ok((transfer_kind, dest, _, reserve)) = Self::transfer_kind(reserve_location, &dest) {
let mut msg = match transfer_kind {
SelfReserveAsset => Xcm(vec![
WithdrawAsset(assets.clone()),
DepositReserveAsset {
assets: All.into(),
max_assets: assets.len() as u32,
dest,
xcm: Xcm(vec![]),
},
]),
ToReserve | ToNonReserve => Xcm(vec![
WithdrawAsset(assets),
InitiateReserveWithdraw {
assets: All.into(),
// `dest` is always (equal to) `reserve` in both cases
reserve,
xcm: Xcm(vec![]),
},
]),
};
return T::Weigher::weight(&mut msg)
.map_or(Weight::max_value(), |w| T::BaseXcmWeight::get().saturating_add(w));
}
// }
}
0
}

/// Get reserve location by `assets` and `fee_item`. the `assets`
/// includes fee asset and non fee asset. make sure assets have ge one
/// asset. all non fee asset should share same reserve location.
fn get_reserve_location(assets: &MultiAssets, fee_item: &u32) -> Option<MultiLocation> {
let reserve_idx = if assets.len() == 1 {
0
} else if *fee_item == 0 {
1
} else {
0
};
let asset = assets.get(reserve_idx);
asset.and_then(|a| a.reserve())
}
}

impl<T: Config> XcmTransfer<T::AccountId, T::Balance, T::CurrencyId> for Pallet<T> {
Expand Down Expand Up @@ -909,3 +1002,18 @@ fn half(asset: &MultiAsset) -> MultiAsset {
id: asset.id.clone(),
}
}

fn subtract_fee(asset: &MultiAsset, amount: u128) -> MultiAsset {
let final_amount = fungible_amount(asset).checked_sub(amount).expect("fee too low; qed");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no fee_amount >= min_xcm_fee check. This could panic no?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we checked before subtract, so it's ok here I think

// min xcm fee should less than user fee
let fee_to_dest: MultiAsset = (fee.id.clone(), min_xcm_fee).into();
ensure!(fee_to_dest < fee, Error::<T>::InvalidAsset);

MultiAsset {
fun: Fungible(final_amount),
id: asset.id.clone(),
}
}
Comment thread
zqhxuyuan marked this conversation as resolved.

fn reset_fee(asset: &MultiAsset, amount: u128) -> MultiAsset {
MultiAsset {
fun: Fungible(amount),
id: asset.id.clone(),
}
}
Comment thread
zqhxuyuan marked this conversation as resolved.
Outdated
6 changes: 6 additions & 0 deletions xtokens/src/mock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ pub enum CurrencyId {
B,
/// Parachain B B1 token
B1,
/// Parachain B B2 token
B2,
}

pub struct CurrencyIdConvert;
Expand All @@ -40,6 +42,7 @@ impl Convert<CurrencyId, Option<MultiLocation>> for CurrencyIdConvert {
CurrencyId::A1 => Some((Parent, Parachain(1), GeneralKey("A1".into())).into()),
CurrencyId::B => Some((Parent, Parachain(2), GeneralKey("B".into())).into()),
CurrencyId::B1 => Some((Parent, Parachain(2), GeneralKey("B1".into())).into()),
CurrencyId::B2 => Some((Parent, Parachain(2), GeneralKey("B2".into())).into()),
}
}
}
Expand All @@ -49,6 +52,7 @@ impl Convert<MultiLocation, Option<CurrencyId>> for CurrencyIdConvert {
let a1: Vec<u8> = "A1".into();
let b: Vec<u8> = "B".into();
let b1: Vec<u8> = "B1".into();
let b2: Vec<u8> = "B2".into();
if l == MultiLocation::parent() {
return Some(CurrencyId::R);
}
Expand All @@ -58,13 +62,15 @@ impl Convert<MultiLocation, Option<CurrencyId>> for CurrencyIdConvert {
X2(Parachain(1), GeneralKey(k)) if k == a1 => Some(CurrencyId::A1),
X2(Parachain(2), GeneralKey(k)) if k == b => Some(CurrencyId::B),
X2(Parachain(2), GeneralKey(k)) if k == b1 => Some(CurrencyId::B1),
X2(Parachain(2), GeneralKey(k)) if k == b1 => Some(CurrencyId::B2),
_ => None,
},
MultiLocation { parents, interior } if parents == 0 => match interior {
X1(GeneralKey(k)) if k == a => Some(CurrencyId::A),
X1(GeneralKey(k)) if k == b => Some(CurrencyId::B),
X1(GeneralKey(k)) if k == a1 => Some(CurrencyId::A1),
X1(GeneralKey(k)) if k == b1 => Some(CurrencyId::B1),
X1(GeneralKey(k)) if k == b2 => Some(CurrencyId::B2),
_ => None,
},
_ => None,
Expand Down
2 changes: 1 addition & 1 deletion xtokens/src/mock/para.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ impl Convert<AccountId, MultiLocation> for AccountIdToMultiLocation {
parameter_types! {
pub SelfLocation: MultiLocation = MultiLocation::new(1, X1(Parachain(ParachainInfo::get().into())));
pub const BaseXcmWeight: Weight = 100_000_000;
pub const MaxAssetsForTransfer: usize = 2;
pub const MaxAssetsForTransfer: usize = 3;
}

impl orml_xtokens::Config for Runtime {
Expand Down
Loading