1#![cfg_attr(not(feature = "std"), no_std)]
21
22extern crate alloc;
23
24use alloc::{vec, vec::Vec};
25use codec::Encode;
26use core::marker::PhantomData;
27use cumulus_primitives_core::{MessageSendError, UpwardMessageSender};
28use frame_support::{
29 defensive,
30 traits::{tokens::fungibles, Get, OnUnbalanced as OnUnbalancedT},
31 weights::{Weight, WeightToFee as WeightToFeeT},
32 CloneNoBound,
33};
34use pallet_asset_conversion::SwapCredit as SwapCreditT;
35use polkadot_runtime_common::xcm_sender::PriceForMessageDelivery;
36use sp_runtime::{
37 traits::{Saturating, Zero},
38 SaturatedConversion,
39};
40use xcm::{latest::prelude::*, VersionedLocation, VersionedXcm, WrapVersion};
41use xcm_builder::{InspectMessageQueues, TakeRevenue};
42use xcm_executor::{
43 traits::{MatchesFungibles, TransactAsset, WeightTrader},
44 AssetsInHolding,
45};
46
47#[cfg(test)]
48mod tests;
49
50pub struct ParentAsUmp<T, W, P>(PhantomData<(T, W, P)>);
58impl<T, W, P> SendXcm for ParentAsUmp<T, W, P>
59where
60 T: UpwardMessageSender,
61 W: WrapVersion,
62 P: PriceForMessageDelivery<Id = ()>,
63{
64 type Ticket = Vec<u8>;
65
66 fn validate(dest: &mut Option<Location>, msg: &mut Option<Xcm<()>>) -> SendResult<Vec<u8>> {
67 let d = dest.take().ok_or(SendError::MissingArgument)?;
68
69 if d.contains_parents_only(1) {
70 let xcm = msg.take().ok_or(SendError::MissingArgument)?;
72 let price = P::price_for_delivery((), &xcm);
73 let versioned_xcm =
74 W::wrap_version(&d, xcm).map_err(|()| SendError::DestinationUnsupported)?;
75 versioned_xcm
76 .check_is_decodable()
77 .map_err(|()| SendError::ExceedsMaxMessageSize)?;
78 let data = versioned_xcm.encode();
79
80 T::can_send_upward_message(&data).map_err(Self::map_upward_sender_err)?;
82
83 Ok((data, price))
84 } else {
85 *dest = Some(d);
88 Err(SendError::NotApplicable)
89 }
90 }
91
92 fn deliver(data: Vec<u8>) -> Result<XcmHash, SendError> {
93 let (_, hash) = T::send_upward_message(data).map_err(Self::map_upward_sender_err)?;
94 Ok(hash)
95 }
96
97 #[cfg(feature = "runtime-benchmarks")]
98 fn ensure_successful_delivery(location: Option<Location>) {
99 if location.as_ref().map_or(false, |l| l.contains_parents_only(1)) {
100 T::ensure_successful_delivery();
101 }
102 }
103}
104
105impl<T, W, P> ParentAsUmp<T, W, P> {
106 fn map_upward_sender_err(message_send_error: MessageSendError) -> SendError {
107 match message_send_error {
108 MessageSendError::TooBig => SendError::ExceedsMaxMessageSize,
109 e => SendError::Transport(e.into()),
110 }
111 }
112}
113
114impl<T: UpwardMessageSender + InspectMessageQueues, W, P> InspectMessageQueues
115 for ParentAsUmp<T, W, P>
116{
117 fn clear_messages() {
118 T::clear_messages();
119 }
120
121 fn get_messages() -> Vec<(VersionedLocation, Vec<VersionedXcm<()>>)> {
122 T::get_messages()
123 }
124}
125
126#[derive(Clone, Eq, PartialEq, Debug)]
128struct AssetTraderRefunder {
129 weight_outstanding: Weight,
131 outstanding_concrete_asset: Asset,
133}
134
135#[derive(CloneNoBound)]
144pub struct TakeFirstAssetTrader<
145 AccountId: Eq,
146 FeeCharger: ChargeWeightInFungibles<AccountId, ConcreteAssets>,
147 Matcher: MatchesFungibles<ConcreteAssets::AssetId, ConcreteAssets::Balance>,
148 ConcreteAssets: fungibles::Mutate<AccountId> + fungibles::Balanced<AccountId>,
149 HandleRefund: TakeRevenue,
150>(
151 Option<AssetTraderRefunder>,
152 PhantomData<(AccountId, FeeCharger, Matcher, ConcreteAssets, HandleRefund)>,
153);
154impl<
155 AccountId: Eq,
156 FeeCharger: ChargeWeightInFungibles<AccountId, ConcreteAssets>,
157 Matcher: MatchesFungibles<ConcreteAssets::AssetId, ConcreteAssets::Balance>,
158 ConcreteAssets: fungibles::Mutate<AccountId> + fungibles::Balanced<AccountId>,
159 HandleRefund: TakeRevenue,
160 > WeightTrader
161 for TakeFirstAssetTrader<AccountId, FeeCharger, Matcher, ConcreteAssets, HandleRefund>
162{
163 fn new() -> Self {
164 Self(None, PhantomData)
165 }
166 fn buy_weight(
170 &mut self,
171 weight: Weight,
172 payment: xcm_executor::AssetsInHolding,
173 context: &XcmContext,
174 ) -> Result<xcm_executor::AssetsInHolding, XcmError> {
175 log::trace!(target: "xcm::weight", "TakeFirstAssetTrader::buy_weight weight: {:?}, payment: {:?}, context: {:?}", weight, payment, context);
176
177 if self.0.is_some() {
179 return Err(XcmError::NotWithdrawable)
180 }
181
182 let assets: Assets = payment.clone().into();
185
186 let first = assets.get(0).ok_or(XcmError::AssetNotFound)?;
188
189 let (local_asset_id, _) =
191 Matcher::matches_fungibles(first).map_err(|_| XcmError::AssetNotFound)?;
192
193 let asset_balance: u128 =
197 FeeCharger::charge_weight_in_fungibles(local_asset_id.clone(), weight)
198 .map(|amount| {
199 let minimum_balance = ConcreteAssets::minimum_balance(local_asset_id);
200 if amount < minimum_balance {
201 minimum_balance
202 } else {
203 amount
204 }
205 })?
206 .try_into()
207 .map_err(|_| XcmError::Overflow)?;
208
209 let required = first.id.clone().into_asset(asset_balance.into());
211
212 let unused = payment.checked_sub(required.clone()).map_err(|_| XcmError::TooExpensive)?;
214
215 self.0 = Some(AssetTraderRefunder {
217 weight_outstanding: weight,
218 outstanding_concrete_asset: required,
219 });
220
221 Ok(unused)
222 }
223
224 fn refund_weight(&mut self, weight: Weight, context: &XcmContext) -> Option<Asset> {
225 log::trace!(target: "xcm::weight", "TakeFirstAssetTrader::refund_weight weight: {:?}, context: {:?}", weight, context);
226 if let Some(AssetTraderRefunder {
227 mut weight_outstanding,
228 outstanding_concrete_asset: Asset { id, fun },
229 }) = self.0.clone()
230 {
231 let (local_asset_id, outstanding_balance) =
233 Matcher::matches_fungibles(&(id.clone(), fun).into()).ok()?;
234
235 let minimum_balance = ConcreteAssets::minimum_balance(local_asset_id.clone());
236
237 let (asset_balance, outstanding_minus_subtracted) =
240 FeeCharger::charge_weight_in_fungibles(local_asset_id, weight).ok().map(
241 |asset_balance| {
242 if outstanding_balance.saturating_sub(asset_balance) > minimum_balance {
245 (asset_balance, outstanding_balance.saturating_sub(asset_balance))
246 }
247 else {
251 (outstanding_balance.saturating_sub(minimum_balance), minimum_balance)
252 }
253 },
254 )?;
255
256 let outstanding_minus_subtracted: u128 = outstanding_minus_subtracted.saturated_into();
258 let asset_balance: u128 = asset_balance.saturated_into();
259
260 let outstanding_concrete_asset: Asset =
263 (id.clone(), outstanding_minus_subtracted).into();
264
265 weight_outstanding = weight_outstanding.saturating_sub(weight);
267
268 self.0 = Some(AssetTraderRefunder { weight_outstanding, outstanding_concrete_asset });
270
271 if asset_balance > 0 {
273 Some((id, asset_balance).into())
274 } else {
275 None
276 }
277 } else {
278 None
279 }
280 }
281}
282
283impl<
284 AccountId: Eq,
285 FeeCharger: ChargeWeightInFungibles<AccountId, ConcreteAssets>,
286 Matcher: MatchesFungibles<ConcreteAssets::AssetId, ConcreteAssets::Balance>,
287 ConcreteAssets: fungibles::Mutate<AccountId> + fungibles::Balanced<AccountId>,
288 HandleRefund: TakeRevenue,
289 > Drop for TakeFirstAssetTrader<AccountId, FeeCharger, Matcher, ConcreteAssets, HandleRefund>
290{
291 fn drop(&mut self) {
292 if let Some(asset_trader) = self.0.clone() {
293 HandleRefund::take_revenue(asset_trader.outstanding_concrete_asset);
294 }
295 }
296}
297
298pub struct XcmFeesTo32ByteAccount<FungiblesMutateAdapter, AccountId, ReceiverAccount>(
303 PhantomData<(FungiblesMutateAdapter, AccountId, ReceiverAccount)>,
304);
305impl<
306 FungiblesMutateAdapter: TransactAsset,
307 AccountId: Clone + Into<[u8; 32]>,
308 ReceiverAccount: Get<Option<AccountId>>,
309 > TakeRevenue for XcmFeesTo32ByteAccount<FungiblesMutateAdapter, AccountId, ReceiverAccount>
310{
311 fn take_revenue(revenue: Asset) {
312 if let Some(receiver) = ReceiverAccount::get() {
313 let ok = FungiblesMutateAdapter::deposit_asset(
314 &revenue,
315 &([AccountId32 { network: None, id: receiver.into() }].into()),
316 None,
317 )
318 .is_ok();
319
320 debug_assert!(ok, "`deposit_asset` cannot generally fail; qed");
321 }
322 }
323}
324
325pub trait ChargeWeightInFungibles<AccountId, Assets: fungibles::Inspect<AccountId>> {
329 fn charge_weight_in_fungibles(
330 asset_id: <Assets as fungibles::Inspect<AccountId>>::AssetId,
331 weight: Weight,
332 ) -> Result<<Assets as fungibles::Inspect<AccountId>>::Balance, XcmError>;
333}
334
335pub struct SwapFirstAssetTrader<
351 Target: Get<Fungibles::AssetId>,
352 SwapCredit: SwapCreditT<
353 AccountId,
354 Balance = Fungibles::Balance,
355 AssetKind = Fungibles::AssetId,
356 Credit = fungibles::Credit<AccountId, Fungibles>,
357 >,
358 WeightToFee: WeightToFeeT<Balance = Fungibles::Balance>,
359 Fungibles: fungibles::Balanced<AccountId>,
360 FungiblesAssetMatcher: MatchesFungibles<Fungibles::AssetId, Fungibles::Balance>,
361 OnUnbalanced: OnUnbalancedT<fungibles::Credit<AccountId, Fungibles>>,
362 AccountId,
363> where
364 Fungibles::Balance: Into<u128>,
365{
366 total_fee: fungibles::Credit<AccountId, Fungibles>,
368 last_fee_asset: Option<AssetId>,
370 _phantom_data: PhantomData<(
371 Target,
372 SwapCredit,
373 WeightToFee,
374 Fungibles,
375 FungiblesAssetMatcher,
376 OnUnbalanced,
377 AccountId,
378 )>,
379}
380
381impl<
382 Target: Get<Fungibles::AssetId>,
383 SwapCredit: SwapCreditT<
384 AccountId,
385 Balance = Fungibles::Balance,
386 AssetKind = Fungibles::AssetId,
387 Credit = fungibles::Credit<AccountId, Fungibles>,
388 >,
389 WeightToFee: WeightToFeeT<Balance = Fungibles::Balance>,
390 Fungibles: fungibles::Balanced<AccountId>,
391 FungiblesAssetMatcher: MatchesFungibles<Fungibles::AssetId, Fungibles::Balance>,
392 OnUnbalanced: OnUnbalancedT<fungibles::Credit<AccountId, Fungibles>>,
393 AccountId,
394 > WeightTrader
395 for SwapFirstAssetTrader<
396 Target,
397 SwapCredit,
398 WeightToFee,
399 Fungibles,
400 FungiblesAssetMatcher,
401 OnUnbalanced,
402 AccountId,
403 >
404where
405 Fungibles::Balance: Into<u128>,
406{
407 fn new() -> Self {
408 Self {
409 total_fee: fungibles::Credit::<AccountId, Fungibles>::zero(Target::get()),
410 last_fee_asset: None,
411 _phantom_data: PhantomData,
412 }
413 }
414
415 fn buy_weight(
416 &mut self,
417 weight: Weight,
418 mut payment: AssetsInHolding,
419 _context: &XcmContext,
420 ) -> Result<AssetsInHolding, XcmError> {
421 log::trace!(
422 target: "xcm::weight",
423 "SwapFirstAssetTrader::buy_weight weight: {:?}, payment: {:?}",
424 weight,
425 payment,
426 );
427 let first_asset: Asset =
428 payment.fungible.pop_first().ok_or(XcmError::AssetNotFound)?.into();
429 let (fungibles_asset, balance) = FungiblesAssetMatcher::matches_fungibles(&first_asset)
430 .map_err(|error| {
431 log::trace!(
432 target: "xcm::weight",
433 "SwapFirstAssetTrader::buy_weight asset {:?} didn't match. Error: {:?}",
434 first_asset,
435 error,
436 );
437 XcmError::AssetNotFound
438 })?;
439
440 let swap_asset = fungibles_asset.clone().into();
441 if Target::get().eq(&swap_asset) {
442 log::trace!(
443 target: "xcm::weight",
444 "SwapFirstAssetTrader::buy_weight Asset was same as Target, swap not needed.",
445 );
446 return Err(XcmError::FeesNotMet)
448 }
449
450 let credit_in = Fungibles::issue(fungibles_asset, balance);
451 let fee = WeightToFee::weight_to_fee(&weight);
452
453 let (credit_out, credit_change) = SwapCredit::swap_tokens_for_exact_tokens(
455 vec![swap_asset, Target::get()],
456 credit_in,
457 fee,
458 )
459 .map_err(|(credit_in, error)| {
460 log::trace!(
461 target: "xcm::weight",
462 "SwapFirstAssetTrader::buy_weight swap couldn't be done. Error was: {:?}",
463 error,
464 );
465 drop(credit_in);
466 XcmError::FeesNotMet
467 })?;
468
469 match self.total_fee.subsume(credit_out) {
470 Err(credit_out) => {
471 defensive!(
474 "`total_fee.asset` must be equal to `credit_out.asset`",
475 (self.total_fee.asset(), credit_out.asset())
476 );
477 return Err(XcmError::FeesNotMet)
478 },
479 _ => (),
480 };
481 self.last_fee_asset = Some(first_asset.id.clone());
482
483 payment.fungible.insert(first_asset.id, credit_change.peek().into());
484 drop(credit_change);
485 Ok(payment)
486 }
487
488 fn refund_weight(&mut self, weight: Weight, _context: &XcmContext) -> Option<Asset> {
489 log::trace!(
490 target: "xcm::weight",
491 "SwapFirstAssetTrader::refund_weight weight: {:?}, self.total_fee: {:?}",
492 weight,
493 self.total_fee,
494 );
495 if self.total_fee.peek().is_zero() {
496 return None
498 }
499 let mut refund_asset = if let Some(asset) = &self.last_fee_asset {
500 (asset.clone(), Fungible(0)).into()
502 } else {
503 return None
504 };
505 let refund_amount = WeightToFee::weight_to_fee(&weight);
506 if refund_amount >= self.total_fee.peek() {
507 return None
509 }
510
511 let refund_swap_asset = FungiblesAssetMatcher::matches_fungibles(&refund_asset)
512 .map(|(a, _)| a.into())
513 .ok()?;
514
515 let refund = self.total_fee.extract(refund_amount);
516 let refund = match SwapCredit::swap_exact_tokens_for_tokens(
517 vec![Target::get(), refund_swap_asset],
518 refund,
519 None,
520 ) {
521 Ok(refund_in_target) => refund_in_target,
522 Err((refund, _)) => {
523 let _ = self.total_fee.subsume(refund).map_err(|refund| {
525 defensive!(
528 "`total_fee.asset` must be equal to `refund.asset`",
529 (self.total_fee.asset(), refund.asset())
530 );
531 });
532 return None
533 },
534 };
535
536 refund_asset.fun = refund.peek().into().into();
537 drop(refund);
538 Some(refund_asset)
539 }
540}
541
542impl<
543 Target: Get<Fungibles::AssetId>,
544 SwapCredit: SwapCreditT<
545 AccountId,
546 Balance = Fungibles::Balance,
547 AssetKind = Fungibles::AssetId,
548 Credit = fungibles::Credit<AccountId, Fungibles>,
549 >,
550 WeightToFee: WeightToFeeT<Balance = Fungibles::Balance>,
551 Fungibles: fungibles::Balanced<AccountId>,
552 FungiblesAssetMatcher: MatchesFungibles<Fungibles::AssetId, Fungibles::Balance>,
553 OnUnbalanced: OnUnbalancedT<fungibles::Credit<AccountId, Fungibles>>,
554 AccountId,
555 > Drop
556 for SwapFirstAssetTrader<
557 Target,
558 SwapCredit,
559 WeightToFee,
560 Fungibles,
561 FungiblesAssetMatcher,
562 OnUnbalanced,
563 AccountId,
564 >
565where
566 Fungibles::Balance: Into<u128>,
567{
568 fn drop(&mut self) {
569 if self.total_fee.peek().is_zero() {
570 return
571 }
572 let total_fee = self.total_fee.extract(self.total_fee.peek());
573 OnUnbalanced::on_unbalanced(total_fee);
574 }
575}
576
577#[cfg(test)]
578mod test_xcm_router {
579 use super::*;
580 use cumulus_primitives_core::UpwardMessage;
581 use frame_support::assert_ok;
582 use xcm::MAX_XCM_DECODE_DEPTH;
583
584 struct OkFixedXcmHashWithAssertingRequiredInputsSender;
586 impl OkFixedXcmHashWithAssertingRequiredInputsSender {
587 const FIXED_XCM_HASH: [u8; 32] = [9; 32];
588
589 fn fixed_delivery_asset() -> Assets {
590 Assets::new()
591 }
592
593 fn expected_delivery_result() -> Result<(XcmHash, Assets), SendError> {
594 Ok((Self::FIXED_XCM_HASH, Self::fixed_delivery_asset()))
595 }
596 }
597 impl SendXcm for OkFixedXcmHashWithAssertingRequiredInputsSender {
598 type Ticket = ();
599
600 fn validate(
601 destination: &mut Option<Location>,
602 message: &mut Option<Xcm<()>>,
603 ) -> SendResult<Self::Ticket> {
604 assert!(destination.is_some());
605 assert!(message.is_some());
606 Ok(((), OkFixedXcmHashWithAssertingRequiredInputsSender::fixed_delivery_asset()))
607 }
608
609 fn deliver(_: Self::Ticket) -> Result<XcmHash, SendError> {
610 Ok(Self::FIXED_XCM_HASH)
611 }
612 }
613
614 struct CanSendUpwardMessageSender;
616 impl UpwardMessageSender for CanSendUpwardMessageSender {
617 fn send_upward_message(_: UpwardMessage) -> Result<(u32, XcmHash), MessageSendError> {
618 Err(MessageSendError::Other)
619 }
620
621 fn can_send_upward_message(_: &UpwardMessage) -> Result<(), MessageSendError> {
622 Ok(())
623 }
624 }
625
626 #[test]
627 fn parent_as_ump_does_not_consume_dest_or_msg_on_not_applicable() {
628 let message = Xcm(vec![Trap(5)]);
630
631 let dest = (Parent, Parent, Parent);
633 let mut dest_wrapper = Some(dest.into());
634 let mut msg_wrapper = Some(message.clone());
635 assert_eq!(
636 Err(SendError::NotApplicable),
637 <ParentAsUmp<(), (), ()> as SendXcm>::validate(&mut dest_wrapper, &mut msg_wrapper)
638 );
639
640 assert_eq!(Some(dest.into()), dest_wrapper.take());
642 assert_eq!(Some(message.clone()), msg_wrapper.take());
643
644 assert_eq!(
646 OkFixedXcmHashWithAssertingRequiredInputsSender::expected_delivery_result(),
647 send_xcm::<(ParentAsUmp<(), (), ()>, OkFixedXcmHashWithAssertingRequiredInputsSender)>(
648 dest.into(),
649 message
650 )
651 );
652 }
653
654 #[test]
655 fn parent_as_ump_consumes_dest_and_msg_on_ok_validate() {
656 let message = Xcm(vec![Trap(5)]);
658
659 let dest = (Parent, Here);
661 let mut dest_wrapper = Some(dest.clone().into());
662 let mut msg_wrapper = Some(message.clone());
663 assert!(<ParentAsUmp<CanSendUpwardMessageSender, (), ()> as SendXcm>::validate(
664 &mut dest_wrapper,
665 &mut msg_wrapper
666 )
667 .is_ok());
668
669 assert_eq!(None, dest_wrapper.take());
671 assert_eq!(None, msg_wrapper.take());
672
673 assert_eq!(
675 Err(SendError::Transport("Other")),
676 send_xcm::<(
677 ParentAsUmp<CanSendUpwardMessageSender, (), ()>,
678 OkFixedXcmHashWithAssertingRequiredInputsSender
679 )>(dest.into(), message)
680 );
681 }
682
683 #[test]
684 fn parent_as_ump_validate_nested_xcm_works() {
685 let dest = Parent;
686
687 type Router = ParentAsUmp<CanSendUpwardMessageSender, (), ()>;
688
689 let mut good = Xcm(vec![ClearOrigin]);
691 for _ in 0..MAX_XCM_DECODE_DEPTH - 1 {
692 good = Xcm(vec![SetAppendix(good)]);
693 }
694
695 assert_ok!(<Router as SendXcm>::validate(&mut Some(dest.into()), &mut Some(good.clone())));
697
698 let bad = Xcm(vec![SetAppendix(good)]);
700 assert_eq!(
701 Err(SendError::ExceedsMaxMessageSize),
702 <Router as SendXcm>::validate(&mut Some(dest.into()), &mut Some(bad))
703 );
704 }
705}
706#[cfg(test)]
707mod test_trader {
708 use super::*;
709 use frame_support::{
710 assert_ok,
711 traits::tokens::{
712 DepositConsequence, Fortitude, Preservation, Provenance, WithdrawConsequence,
713 },
714 };
715 use sp_runtime::DispatchError;
716 use xcm_executor::{traits::Error, AssetsInHolding};
717
718 #[test]
719 fn take_first_asset_trader_buy_weight_called_twice_throws_error() {
720 const AMOUNT: u128 = 100;
721
722 type TestAccountId = u32;
724 type TestAssetId = u32;
725 type TestBalance = u128;
726 struct TestAssets;
727 impl MatchesFungibles<TestAssetId, TestBalance> for TestAssets {
728 fn matches_fungibles(a: &Asset) -> Result<(TestAssetId, TestBalance), Error> {
729 match a {
730 Asset { fun: Fungible(amount), id: AssetId(_id) } => Ok((1, *amount)),
731 _ => Err(Error::AssetNotHandled),
732 }
733 }
734 }
735 impl fungibles::Inspect<TestAccountId> for TestAssets {
736 type AssetId = TestAssetId;
737 type Balance = TestBalance;
738
739 fn total_issuance(_: Self::AssetId) -> Self::Balance {
740 todo!()
741 }
742
743 fn minimum_balance(_: Self::AssetId) -> Self::Balance {
744 0
745 }
746
747 fn balance(_: Self::AssetId, _: &TestAccountId) -> Self::Balance {
748 todo!()
749 }
750
751 fn total_balance(_: Self::AssetId, _: &TestAccountId) -> Self::Balance {
752 todo!()
753 }
754
755 fn reducible_balance(
756 _: Self::AssetId,
757 _: &TestAccountId,
758 _: Preservation,
759 _: Fortitude,
760 ) -> Self::Balance {
761 todo!()
762 }
763
764 fn can_deposit(
765 _: Self::AssetId,
766 _: &TestAccountId,
767 _: Self::Balance,
768 _: Provenance,
769 ) -> DepositConsequence {
770 todo!()
771 }
772
773 fn can_withdraw(
774 _: Self::AssetId,
775 _: &TestAccountId,
776 _: Self::Balance,
777 ) -> WithdrawConsequence<Self::Balance> {
778 todo!()
779 }
780
781 fn asset_exists(_: Self::AssetId) -> bool {
782 todo!()
783 }
784 }
785 impl fungibles::Mutate<TestAccountId> for TestAssets {}
786 impl fungibles::Balanced<TestAccountId> for TestAssets {
787 type OnDropCredit = fungibles::DecreaseIssuance<TestAccountId, Self>;
788 type OnDropDebt = fungibles::IncreaseIssuance<TestAccountId, Self>;
789 }
790 impl fungibles::Unbalanced<TestAccountId> for TestAssets {
791 fn handle_dust(_: fungibles::Dust<TestAccountId, Self>) {
792 todo!()
793 }
794 fn write_balance(
795 _: Self::AssetId,
796 _: &TestAccountId,
797 _: Self::Balance,
798 ) -> Result<Option<Self::Balance>, DispatchError> {
799 todo!()
800 }
801
802 fn set_total_issuance(_: Self::AssetId, _: Self::Balance) {
803 todo!()
804 }
805 }
806
807 struct FeeChargerAssetsHandleRefund;
808 impl ChargeWeightInFungibles<TestAccountId, TestAssets> for FeeChargerAssetsHandleRefund {
809 fn charge_weight_in_fungibles(
810 _: <TestAssets as fungibles::Inspect<TestAccountId>>::AssetId,
811 _: Weight,
812 ) -> Result<<TestAssets as fungibles::Inspect<TestAccountId>>::Balance, XcmError> {
813 Ok(AMOUNT)
814 }
815 }
816 impl TakeRevenue for FeeChargerAssetsHandleRefund {
817 fn take_revenue(_: Asset) {}
818 }
819
820 type Trader = TakeFirstAssetTrader<
822 TestAccountId,
823 FeeChargerAssetsHandleRefund,
824 TestAssets,
825 TestAssets,
826 FeeChargerAssetsHandleRefund,
827 >;
828 let mut trader = <Trader as WeightTrader>::new();
829 let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
830
831 let asset: Asset = (Here, AMOUNT).into();
833 let payment = AssetsInHolding::from(asset);
834 let weight_to_buy = Weight::from_parts(1_000, 1_000);
835
836 assert_ok!(trader.buy_weight(weight_to_buy, payment.clone(), &ctx));
838
839 assert_eq!(trader.buy_weight(weight_to_buy, payment, &ctx), Err(XcmError::NotWithdrawable));
841 }
842}
843
844#[cfg(feature = "runtime-benchmarks")]
850pub struct ToParentDeliveryHelper<XcmConfig, ExistentialDeposit, PriceForDelivery>(
851 core::marker::PhantomData<(XcmConfig, ExistentialDeposit, PriceForDelivery)>,
852);
853
854#[cfg(feature = "runtime-benchmarks")]
855impl<
856 XcmConfig: xcm_executor::Config,
857 ExistentialDeposit: Get<Option<Asset>>,
858 PriceForDelivery: PriceForMessageDelivery<Id = ()>,
859 > xcm_builder::EnsureDelivery
860 for ToParentDeliveryHelper<XcmConfig, ExistentialDeposit, PriceForDelivery>
861{
862 fn ensure_successful_delivery(
863 origin_ref: &Location,
864 dest: &Location,
865 fee_reason: xcm_executor::traits::FeeReason,
866 ) -> (Option<xcm_executor::FeesMode>, Option<Assets>) {
867 use xcm::{latest::MAX_ITEMS_IN_ASSETS, MAX_INSTRUCTIONS_TO_DECODE};
868 use xcm_executor::{traits::FeeManager, FeesMode};
869
870 if dest.ne(&Location::parent()) {
872 return (None, None);
873 }
874
875 XcmConfig::XcmSender::ensure_successful_delivery(Some(Location::parent()));
877
878 let mut fees_mode = None;
879 if !XcmConfig::FeeManager::is_waived(Some(origin_ref), fee_reason) {
880 if let Some(ed) = ExistentialDeposit::get() {
884 XcmConfig::AssetTransactor::deposit_asset(&ed, &origin_ref, None).unwrap();
885 }
886
887 let mut max_assets: Vec<Asset> = Vec::new();
889 for i in 0..MAX_ITEMS_IN_ASSETS {
890 max_assets.push((GeneralIndex(i as u128), 100u128).into());
891 }
892 let overestimated_xcm =
893 vec![WithdrawAsset(max_assets.into()); MAX_INSTRUCTIONS_TO_DECODE as usize].into();
894 let overestimated_fees = PriceForDelivery::price_for_delivery((), &overestimated_xcm);
895
896 for fee in overestimated_fees.inner() {
898 XcmConfig::AssetTransactor::deposit_asset(&fee, &origin_ref, None).unwrap();
899 }
900
901 fees_mode = Some(FeesMode { jit_withdraw: true });
903 }
904 (fees_mode, None)
905 }
906}