-
Notifications
You must be signed in to change notification settings - Fork 1.6k
pallet-xcm: XCM assets reserve withdraw feature #5975
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -550,6 +550,51 @@ pub mod pallet { | |
) | ||
} | ||
|
||
/// Transfer some assets from sovereign account to reserve holder chain. | ||
/// | ||
/// Fee payment on the destination side is made from the asset in the `assets` vector of | ||
/// index `fee_asset_item`. The weight limit for fees is not provided and thus is unlimited, | ||
/// with all fees taken as needed from the asset. | ||
/// | ||
/// - `origin`: Must be capable of withdrawing the `assets` and executing XCM. | ||
/// - `dest`: Destination context for the assets. Will typically be `X2(Parent, Parachain(..))` to send | ||
/// from parachain to parachain, or `X1(Parachain(..))` to send from relay to parachain. | ||
/// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will generally be | ||
/// an `AccountId32` value. | ||
/// - `assets`: The assets to be withdrawn. This should include the assets used to pay the fee on the | ||
/// `dest` side. | ||
/// - `fee_asset_item`: The index into `assets` of the item which should be used to pay | ||
/// fees. | ||
#[pallet::weight({ | ||
match ((*assets.clone()).try_into(), (*dest.clone()).try_into()) { | ||
(Ok(assets), Ok(dest)) => { | ||
use sp_std::vec; | ||
let mut message = Xcm(vec![ | ||
WithdrawAsset(assets), | ||
InitiateReserveWithdraw { assets: Wild(All), reserve: dest, xcm: Xcm(vec![]) } | ||
]); | ||
T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| Weight::from_ref_time(100_000_000.saturating_add(w))) | ||
}, | ||
_ => Weight::MAX, | ||
} | ||
})] | ||
pub fn reserve_withdraw_assets( | ||
origin: OriginFor<T>, | ||
dest: Box<VersionedMultiLocation>, | ||
beneficiary: Box<VersionedMultiLocation>, | ||
assets: Box<VersionedMultiAssets>, | ||
fee_asset_item: u32, | ||
) -> DispatchResult { | ||
Self::do_reserve_withdraw_assets( | ||
origin, | ||
dest, | ||
beneficiary, | ||
assets, | ||
fee_asset_item, | ||
None, | ||
) | ||
} | ||
|
||
/// Execute an XCM message from a local, signed, origin. | ||
/// | ||
/// An event is deposited indicating whether `msg` could be executed completely or only | ||
|
@@ -762,6 +807,52 @@ pub mod pallet { | |
Some(weight_limit), | ||
) | ||
} | ||
|
||
/// Transfer some assets from sovereign account to reserve holder chain. | ||
/// | ||
/// Fee payment on the destination side is made from the asset in the `assets` vector of | ||
/// index `fee_asset_item`. The weight limit for fees is not provided and thus is unlimited, | ||
/// with all fees taken as needed from the asset. | ||
/// | ||
/// - `origin`: Must be capable of withdrawing the `assets` and executing XCM. | ||
/// - `dest`: Destination context for the assets. Will typically be `(Parent, Parachain(..))` to claim | ||
/// reserve assets from a sibling parachain, or `(Parent,)` to claim the reserve assets from the Relay chain. | ||
/// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will generally be | ||
/// an `AccountId32` value. | ||
/// - `assets`: The assets to be withdrawn. This should include the assets used to pay the fee on the | ||
/// `dest` side. | ||
/// - `fee_asset_item`: The index into `assets` of the item which should be used to pay | ||
/// fees. | ||
/// - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase. | ||
#[pallet::weight({ | ||
match ((*assets.clone()).try_into(), (*dest.clone()).try_into()) { | ||
(Ok(assets), Ok(dest)) => { | ||
use sp_std::vec; | ||
let mut message = Xcm(vec![ | ||
TransferReserveAsset { assets, dest, xcm: Xcm(vec![]) } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. wrong instruction here, should be the same as for
|
||
]); | ||
T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| Weight::from_ref_time(100_000_000.saturating_add(w))) | ||
}, | ||
_ => Weight::MAX, | ||
} | ||
})] | ||
pub fn limited_reserve_withdraw_assets( | ||
origin: OriginFor<T>, | ||
dest: Box<VersionedMultiLocation>, | ||
beneficiary: Box<VersionedMultiLocation>, | ||
assets: Box<VersionedMultiAssets>, | ||
fee_asset_item: u32, | ||
weight_limit: WeightLimit, | ||
) -> DispatchResult { | ||
Self::do_reserve_withdraw_assets( | ||
origin, | ||
dest, | ||
beneficiary, | ||
assets, | ||
fee_asset_item, | ||
Some(weight_limit), | ||
) | ||
} | ||
} | ||
|
||
impl<T: Config> Pallet<T> { | ||
|
@@ -822,6 +913,88 @@ pub mod pallet { | |
Ok(()) | ||
} | ||
|
||
fn do_reserve_withdraw_assets( | ||
origin: OriginFor<T>, | ||
dest: Box<VersionedMultiLocation>, | ||
beneficiary: Box<VersionedMultiLocation>, | ||
assets: Box<VersionedMultiAssets>, | ||
fee_asset_item: u32, | ||
maybe_weight_limit: Option<WeightLimit>, | ||
) -> DispatchResult { | ||
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; | ||
let dest = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?; | ||
let beneficiary: MultiLocation = (*beneficiary) | ||
.try_into() | ||
.map_err(|()| Error::<T>::BadVersion)?; | ||
let assets: MultiAssets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?; | ||
|
||
ensure!( | ||
assets.len() <= MAX_ASSETS_FOR_TRANSFER, | ||
Error::<T>::TooManyAssets | ||
); | ||
let value = (origin_location, assets.drain()); | ||
ensure!( | ||
T::XcmReserveTransferFilter::contains(&value), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that this is not a good filter,
for opposite "reserve withdraw assets", there should be another dedicated filter like
|
||
Error::<T>::Filtered | ||
); | ||
let (origin_location, assets) = value; | ||
let ancestry = T::LocationInverter::ancestry(); | ||
let fees = assets | ||
.get(fee_asset_item as usize) | ||
.ok_or(Error::<T>::Empty)? | ||
.clone() | ||
.reanchored(&dest, &ancestry) | ||
.map_err(|_| Error::<T>::CannotReanchor)?; | ||
let max_assets = assets.len() as u32; | ||
let assets: MultiAssets = assets.into(); | ||
let weight_limit = match maybe_weight_limit { | ||
Some(weight_limit) => weight_limit, | ||
None => { | ||
let beneficiary = beneficiary.clone(); | ||
let fees = fees.clone(); | ||
let mut remote_message = Xcm(vec![ | ||
WithdrawAsset(assets.clone()), | ||
ClearOrigin, | ||
BuyExecution { | ||
fees, | ||
weight_limit: Limited(0), | ||
}, | ||
DepositAsset { | ||
assets: Wild(All), | ||
max_assets, | ||
beneficiary, | ||
}, | ||
]); | ||
// use local weight for remote message and hope for the best. | ||
let remote_weight = T::Weigher::weight(&mut remote_message) | ||
.map_err(|()| Error::<T>::UnweighableMessage)?; | ||
Limited(remote_weight) | ||
} | ||
}; | ||
let xcm = Xcm(vec![ | ||
BuyExecution { fees, weight_limit }, | ||
DepositAsset { | ||
assets: Wild(All), | ||
max_assets, | ||
beneficiary, | ||
}, | ||
]); | ||
let mut message = Xcm(vec![ | ||
WithdrawAsset(assets), | ||
InitiateReserveWithdraw { | ||
assets: Wild(All), | ||
reserve: dest, | ||
xcm, | ||
}, | ||
]); | ||
Comment on lines
+982
to
+989
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This XCM here doesn't actually include any instruction that switches the derivative asset back to the reserve asset. In fact, it looks to me that you're in fact sending the derivative asset back to the reserve. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Of course, that’s the point, it should withdraw asset from reserve. Shortly user story is:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, and this XCM here is step 3, and there isn't any instruction here that burns xDOT. Neither In fact, what it actually does is that it sends xDOT onwards to the reserve chain, and expecting it to withdraw xDOT locally. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In fact it burns assets as you can see here, for example: https://astar.subscan.io/extrinsic/1683493-9 But yes, it's not clear how There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The asset you sent over isn't a derivative; it's the native token of the parent chain. It does not prove to me that any of the instructions here had exchanged a derivative asset with its corresponding reserve asset. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The aim of this PR is to make possible return native token of parent chain back. Currently it's impossible using pallet-xcm, you known. What's about is it derivative or not, I believe it becomes derivative when comes to parachain, technically parachain runtime mints synthetic asset according to locked amount of native asset on relay chain side. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, I just dug through Astar's code and found out where the miscommunication is happening -- Astar is using the same This is very unexpected, as my understanding of Your PR exploits this "location punning" in order to work, and honestly the "location punning" issue is a much bigger issue that we need to discuss as a community. I'll discuss internally about the issue we found here, and will come back with a proper action plan later. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see, thank you very much for highlight this. Honestly that's the problem we had, for now is no way to understand just by There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, after some thought, I think this is what we should do: Your code in this PR absolutely cannot contain the assumption that both the reserve and derivative asset shares the same Thus, a proper implementation should then require the extrinsic to also take in a In the meantime, I'm going to figure out just how and when exactly a derivative asset should be converted to its corresponding reserve asset. I had always assumed that Does that make sense? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it, but I prefer to have clear design before starting to change code that works well for now. I feel that proposed changes will require to change XCM message arguments and come with XCMv{N} standard later. So, probably we should suspend this PR until XCM support will come. |
||
let weight = | ||
T::Weigher::weight(&mut message).map_err(|()| Error::<T>::UnweighableMessage)?; | ||
let outcome = | ||
T::XcmExecutor::execute_xcm_in_credit(origin_location, message, weight, weight); | ||
Self::deposit_event(Event::Attempted(outcome)); | ||
Ok(()) | ||
} | ||
|
||
fn do_teleport_assets( | ||
origin: OriginFor<T>, | ||
dest: Box<VersionedMultiLocation>, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.