Skip to content

Commit 5fb024c

Browse files
authored
Girazoki controlled fee specification (#658)
* Start trying to separate fee from asset * add new funcs * Remove relay events * insert end of line * Remove print * Add new events * refactor deposit asset and make transactional * Remove from trait, as it is not necessary * Better doc * Return non-spent fee to deposit account, plus some fmt changes * More comment fixes * Zero fee should error * Add trap assets tests * Clippy * Add first shortcut for fee in do_transfer_with_fee * Do the check before getting into do_transfer_with_fee and do_transfer_multiasset_with_Fee
1 parent a9c41b5 commit 5fb024c

File tree

3 files changed

+540
-2
lines changed

3 files changed

+540
-2
lines changed

xtokens/src/lib.rs

+253
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,12 @@ pub mod module {
105105
pub enum Event<T: Config> {
106106
/// Transferred. \[sender, currency_id, amount, dest\]
107107
Transferred(T::AccountId, T::CurrencyId, T::Balance, MultiLocation),
108+
/// Transferred with fee. \[sender, currency_id, amount, fee, dest\]
109+
TransferredWithFee(T::AccountId, T::CurrencyId, T::Balance, T::Balance, MultiLocation),
108110
/// Transferred `MultiAsset`. \[sender, asset, dest\]
109111
TransferredMultiAsset(T::AccountId, MultiAsset, MultiLocation),
112+
/// Transferred `MultiAsset` with fee. \[sender, asset, fee, dest\]
113+
TransferredMultiAssetWithFee(T::AccountId, MultiAsset, MultiAsset, MultiLocation),
110114
}
111115

112116
#[pallet::error]
@@ -136,6 +140,11 @@ pub mod module {
136140
/// The version of the `Versioned` value used is not able to be
137141
/// interpreted.
138142
BadVersion,
143+
/// The fee MultiAsset is of different type than the asset to transfer.
144+
DistincAssetAndFeeId,
145+
/// The fee amount was zero when the fee specification extrinsic is
146+
/// being used.
147+
FeeCannotBeZero,
139148
}
140149

141150
#[pallet::hooks]
@@ -197,6 +206,89 @@ pub mod module {
197206
let dest: MultiLocation = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
198207
Self::do_transfer_multiasset(who, asset, dest, dest_weight, true)
199208
}
209+
210+
/// Transfer native currencies specifying the fee and amount as
211+
/// separate.
212+
///
213+
/// `dest_weight` is the weight for XCM execution on the dest chain, and
214+
/// it would be charged from the transferred assets. If set below
215+
/// requirements, the execution may fail and assets wouldn't be
216+
/// received.
217+
///
218+
/// `fee` is the amount to be spent to pay for execution in destination
219+
/// chain. Both fee and amount will be subtracted form the callers
220+
/// balance.
221+
///
222+
/// If `fee` is not high enough to cover for the execution costs in the
223+
/// destination chain, then the assets will be trapped in the
224+
/// destination chain
225+
///
226+
/// It's a no-op if any error on local XCM execution or message sending.
227+
/// Note sending assets out per se doesn't guarantee they would be
228+
/// received. Receiving depends on if the XCM message could be delivered
229+
/// by the network, and if the receiving chain would handle
230+
/// messages correctly.
231+
#[pallet::weight(Pallet::<T>::weight_of_transfer(currency_id.clone(), *amount, dest))]
232+
#[transactional]
233+
pub fn transfer_with_fee(
234+
origin: OriginFor<T>,
235+
currency_id: T::CurrencyId,
236+
amount: T::Balance,
237+
fee: T::Balance,
238+
dest: Box<VersionedMultiLocation>,
239+
dest_weight: Weight,
240+
) -> DispatchResult {
241+
let who = ensure_signed(origin)?;
242+
let dest: MultiLocation = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
243+
// Zero fee is an error
244+
if fee.is_zero() {
245+
return Err(Error::<T>::FeeCannotBeZero.into());
246+
}
247+
248+
Self::do_transfer_with_fee(who, currency_id, amount, fee, dest, dest_weight)
249+
}
250+
251+
/// Transfer `MultiAsset` specifying the fee and amount as separate.
252+
///
253+
/// `dest_weight` is the weight for XCM execution on the dest chain, and
254+
/// it would be charged from the transferred assets. If set below
255+
/// requirements, the execution may fail and assets wouldn't be
256+
/// received.
257+
///
258+
/// `fee` is the multiasset to be spent to pay for execution in
259+
/// destination chain. Both fee and amount will be subtracted form the
260+
/// callers balance For now we only accept fee and asset having the same
261+
/// `MultiLocation` id.
262+
///
263+
/// If `fee` is not high enough to cover for the execution costs in the
264+
/// destination chain, then the assets will be trapped in the
265+
/// destination chain
266+
///
267+
/// It's a no-op if any error on local XCM execution or message sending.
268+
/// Note sending assets out per se doesn't guarantee they would be
269+
/// received. Receiving depends on if the XCM message could be delivered
270+
/// by the network, and if the receiving chain would handle
271+
/// messages correctly.
272+
#[pallet::weight(Pallet::<T>::weight_of_transfer_multiasset(asset, dest))]
273+
#[transactional]
274+
pub fn transfer_multiasset_with_fee(
275+
origin: OriginFor<T>,
276+
asset: Box<VersionedMultiAsset>,
277+
fee: Box<VersionedMultiAsset>,
278+
dest: Box<VersionedMultiLocation>,
279+
dest_weight: Weight,
280+
) -> DispatchResult {
281+
let who = ensure_signed(origin)?;
282+
let asset: MultiAsset = (*asset).try_into().map_err(|()| Error::<T>::BadVersion)?;
283+
let fee: MultiAsset = (*fee).try_into().map_err(|()| Error::<T>::BadVersion)?;
284+
let dest: MultiLocation = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
285+
// Zero fee is an error
286+
if fungible_amount(&fee).is_zero() {
287+
return Err(Error::<T>::FeeCannotBeZero.into());
288+
}
289+
290+
Self::do_transfer_multiasset_with_fee(who, asset, fee, dest, dest_weight, true)
291+
}
200292
}
201293

202294
impl<T: Config> Pallet<T> {
@@ -217,6 +309,25 @@ pub mod module {
217309
Ok(())
218310
}
219311

312+
fn do_transfer_with_fee(
313+
who: T::AccountId,
314+
currency_id: T::CurrencyId,
315+
amount: T::Balance,
316+
fee: T::Balance,
317+
dest: MultiLocation,
318+
dest_weight: Weight,
319+
) -> DispatchResult {
320+
let location: MultiLocation = T::CurrencyIdConvert::convert(currency_id.clone())
321+
.ok_or(Error::<T>::NotCrossChainTransferableCurrency)?;
322+
323+
let asset = (location.clone(), amount.into()).into();
324+
let fee_asset: MultiAsset = (location, fee.into()).into();
325+
Self::do_transfer_multiasset_with_fee(who.clone(), asset, fee_asset, dest.clone(), dest_weight, false)?;
326+
327+
Self::deposit_event(Event::<T>::TransferredWithFee(who, currency_id, amount, fee, dest));
328+
Ok(())
329+
}
330+
220331
fn do_transfer_multiasset(
221332
who: T::AccountId,
222333
asset: MultiAsset,
@@ -259,6 +370,65 @@ pub mod module {
259370
Ok(())
260371
}
261372

373+
fn do_transfer_multiasset_with_fee(
374+
who: T::AccountId,
375+
asset: MultiAsset,
376+
fee: MultiAsset,
377+
dest: MultiLocation,
378+
dest_weight: Weight,
379+
deposit_event: bool,
380+
) -> DispatchResult {
381+
if !asset.is_fungible(None) || !fee.is_fungible(None) {
382+
return Err(Error::<T>::NotFungible.into());
383+
}
384+
385+
if fungible_amount(&asset).is_zero() {
386+
return Ok(());
387+
}
388+
389+
// For now fee and asset id should be identical
390+
// We can relax this assumption in the future
391+
ensure!(fee.id == asset.id, Error::<T>::DistincAssetAndFeeId);
392+
393+
let (transfer_kind, dest, reserve, recipient) = Self::transfer_kind(&asset, &dest)?;
394+
let mut msg = match transfer_kind {
395+
SelfReserveAsset => Self::transfer_self_reserve_asset_with_fee(
396+
asset.clone(),
397+
fee.clone(),
398+
dest.clone(),
399+
recipient,
400+
dest_weight,
401+
)?,
402+
ToReserve => Self::transfer_to_reserve_with_fee(
403+
asset.clone(),
404+
fee.clone(),
405+
dest.clone(),
406+
recipient,
407+
dest_weight,
408+
)?,
409+
ToNonReserve => Self::transfer_to_non_reserve_with_fee(
410+
asset.clone(),
411+
fee.clone(),
412+
reserve,
413+
dest.clone(),
414+
recipient,
415+
dest_weight,
416+
)?,
417+
};
418+
419+
let origin_location = T::AccountIdToMultiLocation::convert(who.clone());
420+
let weight = T::Weigher::weight(&mut msg).map_err(|()| Error::<T>::UnweighableMessage)?;
421+
T::XcmExecutor::execute_xcm_in_credit(origin_location, msg, weight, weight)
422+
.ensure_complete()
423+
.map_err(|_| Error::<T>::XcmExecutionFailed)?;
424+
425+
if deposit_event {
426+
Self::deposit_event(Event::<T>::TransferredMultiAssetWithFee(who, asset, fee, dest));
427+
}
428+
429+
Ok(())
430+
}
431+
262432
fn transfer_self_reserve_asset(
263433
asset: MultiAsset,
264434
dest: MultiLocation,
@@ -279,6 +449,27 @@ pub mod module {
279449
]))
280450
}
281451

452+
fn transfer_self_reserve_asset_with_fee(
453+
asset: MultiAsset,
454+
fee: MultiAsset,
455+
dest: MultiLocation,
456+
recipient: MultiLocation,
457+
dest_weight: Weight,
458+
) -> Result<Xcm<T::Call>, DispatchError> {
459+
Ok(Xcm(vec![
460+
WithdrawAsset(vec![asset, fee.clone()].into()),
461+
DepositReserveAsset {
462+
assets: All.into(),
463+
max_assets: 1,
464+
dest: dest.clone(),
465+
xcm: Xcm(vec![
466+
Self::buy_execution(fee, &dest, dest_weight)?,
467+
Self::deposit_asset(recipient),
468+
]),
469+
},
470+
]))
471+
}
472+
282473
fn transfer_to_reserve(
283474
asset: MultiAsset,
284475
reserve: MultiLocation,
@@ -298,6 +489,26 @@ pub mod module {
298489
]))
299490
}
300491

492+
fn transfer_to_reserve_with_fee(
493+
asset: MultiAsset,
494+
fee: MultiAsset,
495+
reserve: MultiLocation,
496+
recipient: MultiLocation,
497+
dest_weight: Weight,
498+
) -> Result<Xcm<T::Call>, DispatchError> {
499+
Ok(Xcm(vec![
500+
WithdrawAsset(vec![asset, fee.clone()].into()),
501+
InitiateReserveWithdraw {
502+
assets: All.into(),
503+
reserve: reserve.clone(),
504+
xcm: Xcm(vec![
505+
Self::buy_execution(fee, &reserve, dest_weight)?,
506+
Self::deposit_asset(recipient),
507+
]),
508+
},
509+
]))
510+
}
511+
301512
fn transfer_to_non_reserve(
302513
asset: MultiAsset,
303514
reserve: MultiLocation,
@@ -339,6 +550,48 @@ pub mod module {
339550
]))
340551
}
341552

553+
fn transfer_to_non_reserve_with_fee(
554+
asset: MultiAsset,
555+
fee: MultiAsset,
556+
reserve: MultiLocation,
557+
dest: MultiLocation,
558+
recipient: MultiLocation,
559+
dest_weight: Weight,
560+
) -> Result<Xcm<T::Call>, DispatchError> {
561+
let mut reanchored_dest = dest.clone();
562+
if reserve == MultiLocation::parent() {
563+
match dest {
564+
MultiLocation {
565+
parents,
566+
interior: X1(Parachain(id)),
567+
} if parents == 1 => {
568+
reanchored_dest = Parachain(id).into();
569+
}
570+
_ => {}
571+
}
572+
}
573+
574+
Ok(Xcm(vec![
575+
WithdrawAsset(vec![asset, fee.clone()].into()),
576+
InitiateReserveWithdraw {
577+
assets: All.into(),
578+
reserve: reserve.clone(),
579+
xcm: Xcm(vec![
580+
Self::buy_execution(half(&fee), &reserve, dest_weight)?,
581+
DepositReserveAsset {
582+
assets: All.into(),
583+
max_assets: 1,
584+
dest: reanchored_dest,
585+
xcm: Xcm(vec![
586+
Self::buy_execution(half(&fee), &dest, dest_weight)?,
587+
Self::deposit_asset(recipient),
588+
]),
589+
},
590+
]),
591+
},
592+
]))
593+
}
594+
342595
fn deposit_asset(recipient: MultiLocation) -> Instruction<()> {
343596
DepositAsset {
344597
assets: All.into(),

xtokens/src/mock/para.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,8 @@ impl Config for XcmConfig {
198198
type Weigher = FixedWeightBounds<UnitWeightCost, Call, MaxInstructions>;
199199
type Trader = AllTokensAreCreatedEqualToWeight;
200200
type ResponseHandler = ();
201-
type AssetTrap = ();
202-
type AssetClaims = ();
201+
type AssetTrap = PolkadotXcm;
202+
type AssetClaims = PolkadotXcm;
203203
type SubscriptionService = PolkadotXcm;
204204
}
205205

0 commit comments

Comments
 (0)