@@ -105,8 +105,12 @@ pub mod module {
105
105
pub enum Event < T : Config > {
106
106
/// Transferred. \[sender, currency_id, amount, dest\]
107
107
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 ) ,
108
110
/// Transferred `MultiAsset`. \[sender, asset, dest\]
109
111
TransferredMultiAsset ( T :: AccountId , MultiAsset , MultiLocation ) ,
112
+ /// Transferred `MultiAsset` with fee. \[sender, asset, fee, dest\]
113
+ TransferredMultiAssetWithFee ( T :: AccountId , MultiAsset , MultiAsset , MultiLocation ) ,
110
114
}
111
115
112
116
#[ pallet:: error]
@@ -136,6 +140,11 @@ pub mod module {
136
140
/// The version of the `Versioned` value used is not able to be
137
141
/// interpreted.
138
142
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 ,
139
148
}
140
149
141
150
#[ pallet:: hooks]
@@ -197,6 +206,89 @@ pub mod module {
197
206
let dest: MultiLocation = ( * dest) . try_into ( ) . map_err ( |( ) | Error :: < T > :: BadVersion ) ?;
198
207
Self :: do_transfer_multiasset ( who, asset, dest, dest_weight, true )
199
208
}
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
+ }
200
292
}
201
293
202
294
impl < T : Config > Pallet < T > {
@@ -217,6 +309,25 @@ pub mod module {
217
309
Ok ( ( ) )
218
310
}
219
311
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
+
220
331
fn do_transfer_multiasset (
221
332
who : T :: AccountId ,
222
333
asset : MultiAsset ,
@@ -259,6 +370,65 @@ pub mod module {
259
370
Ok ( ( ) )
260
371
}
261
372
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
+
262
432
fn transfer_self_reserve_asset (
263
433
asset : MultiAsset ,
264
434
dest : MultiLocation ,
@@ -279,6 +449,27 @@ pub mod module {
279
449
] ) )
280
450
}
281
451
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
+
282
473
fn transfer_to_reserve (
283
474
asset : MultiAsset ,
284
475
reserve : MultiLocation ,
@@ -298,6 +489,26 @@ pub mod module {
298
489
] ) )
299
490
}
300
491
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
+
301
512
fn transfer_to_non_reserve (
302
513
asset : MultiAsset ,
303
514
reserve : MultiLocation ,
@@ -339,6 +550,48 @@ pub mod module {
339
550
] ) )
340
551
}
341
552
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
+
342
595
fn deposit_asset ( recipient : MultiLocation ) -> Instruction < ( ) > {
343
596
DepositAsset {
344
597
assets : All . into ( ) ,
0 commit comments