1#![cfg_attr(not(feature = "std"), no_std)]
21
22extern crate alloc;
23
24use alloc::{boxed::Box, vec, vec::Vec};
25use codec::Encode;
26use core::marker::PhantomData;
27use cumulus_primitives_core::{MessageSendError, UpwardMessageSender};
28use frame_support::{
29 defensive,
30 traits::{
31 tokens::{fungibles, imbalance::UnsafeManualAccounting},
32 Get, OnUnbalanced as OnUnbalancedT,
33 },
34 weights::{Weight, WeightToFee as WeightToFeeT},
35};
36use pallet_asset_conversion::{QuotePrice, SwapCredit as SwapCreditT};
37use polkadot_runtime_common::xcm_sender::PriceForMessageDelivery;
38use sp_runtime::traits::Zero;
39use xcm::{latest::prelude::*, VersionedLocation, VersionedXcm, WrapVersion};
40use xcm_builder::InspectMessageQueues;
41use xcm_executor::{
42 traits::{MatchesFungibles, WeightTrader},
43 AssetsInHolding,
44};
45
46#[cfg(test)]
47mod tests;
48
49#[cfg(test)]
50mod test_helpers {
51 pub use xcm_executor::test_helpers::mock_asset_to_holding as asset_to_holding;
52}
53
54pub struct ParentAsUmp<T, W, P>(PhantomData<(T, W, P)>);
62
63impl<T, W, P> SendXcm for ParentAsUmp<T, W, P>
64where
65 T: UpwardMessageSender,
66 W: WrapVersion,
67 P: PriceForMessageDelivery<Id = ()>,
68{
69 type Ticket = Vec<u8>;
70
71 fn validate(dest: &mut Option<Location>, msg: &mut Option<Xcm<()>>) -> SendResult<Vec<u8>> {
72 let d = dest.take().ok_or(SendError::MissingArgument)?;
73
74 if d.contains_parents_only(1) {
75 let xcm = msg.take().ok_or(SendError::MissingArgument)?;
77 let price = P::price_for_delivery((), &xcm);
78 let versioned_xcm =
79 W::wrap_version(&d, xcm).map_err(|()| SendError::DestinationUnsupported)?;
80 versioned_xcm
81 .check_is_decodable()
82 .map_err(|()| SendError::ExceedsMaxMessageSize)?;
83 let data = versioned_xcm.encode();
84
85 T::can_send_upward_message(&data).map_err(Self::map_upward_sender_err)?;
87
88 Ok((data, price))
89 } else {
90 *dest = Some(d);
93 Err(SendError::NotApplicable)
94 }
95 }
96
97 fn deliver(data: Vec<u8>) -> Result<XcmHash, SendError> {
98 let (_, hash) = T::send_upward_message(data).map_err(Self::map_upward_sender_err)?;
99 Ok(hash)
100 }
101
102 #[cfg(feature = "runtime-benchmarks")]
103 fn ensure_successful_delivery(location: Option<Location>) {
104 if location.as_ref().map_or(false, |l| l.contains_parents_only(1)) {
105 T::ensure_successful_delivery();
106 }
107 }
108}
109
110impl<T, W, P> ParentAsUmp<T, W, P> {
111 fn map_upward_sender_err(message_send_error: MessageSendError) -> SendError {
112 match message_send_error {
113 MessageSendError::TooBig => SendError::ExceedsMaxMessageSize,
114 e => SendError::Transport(e.into()),
115 }
116 }
117}
118
119impl<T: UpwardMessageSender + InspectMessageQueues, W, P> InspectMessageQueues
120 for ParentAsUmp<T, W, P>
121{
122 fn clear_messages() {
123 T::clear_messages();
124 }
125
126 fn get_messages() -> Vec<(VersionedLocation, Vec<VersionedXcm<()>>)> {
127 T::get_messages()
128 }
129}
130
131pub struct TakeFirstAssetTrader<
140 AccountId: Eq,
141 FeeCharger: ChargeWeightInFungibles<AccountId, Fungibles>,
142 Matcher: MatchesFungibles<Fungibles::AssetId, Fungibles::Balance>,
143 Fungibles: fungibles::Balanced<AccountId>,
144 OnUnbalanced: OnUnbalancedT<fungibles::Credit<AccountId, Fungibles>>,
145> {
146 outstanding_credit: Option<fungibles::Credit<AccountId, Fungibles>>,
148 weight_outstanding: Weight,
150 _phantom_data: PhantomData<(AccountId, FeeCharger, Matcher, Fungibles, OnUnbalanced)>,
151}
152
153impl<
154 AccountId: Eq,
155 FeeCharger: ChargeWeightInFungibles<AccountId, Fungibles>,
156 Matcher: MatchesFungibles<Fungibles::AssetId, Fungibles::Balance>,
157 Fungibles: fungibles::Inspect<AccountId, Balance = u128, AssetId: Into<Location> + 'static>
158 + fungibles::Balanced<AccountId, OnDropCredit: 'static, OnDropDebt: 'static>,
159 OnUnbalanced: OnUnbalancedT<fungibles::Credit<AccountId, Fungibles>>,
160 > WeightTrader for TakeFirstAssetTrader<AccountId, FeeCharger, Matcher, Fungibles, OnUnbalanced>
161{
162 fn new() -> Self {
163 Self {
164 outstanding_credit: None,
165 weight_outstanding: Weight::zero(),
166 _phantom_data: PhantomData,
167 }
168 }
169 fn buy_weight(
173 &mut self,
174 weight: Weight,
175 mut payment: AssetsInHolding,
176 context: &XcmContext,
177 ) -> Result<AssetsInHolding, (AssetsInHolding, XcmError)> {
178 log::trace!(target: "xcm::weight", "TakeFirstAssetTrader::buy_weight weight: {:?}, payment: {:?}, context: {:?}", weight, payment, context);
179
180 if self.outstanding_credit.is_some() {
182 return Err((payment, XcmError::NotWithdrawable));
183 }
184
185 let Some(used) = payment.fungible_assets_iter().next() else {
187 return Err((payment, XcmError::AssetNotFound));
188 };
189
190 let Ok((fungibles_asset_id, _)) = Matcher::matches_fungibles(&used) else {
192 return Err((payment, XcmError::AssetNotFound));
193 };
194
195 let required_amount: u128 =
199 match FeeCharger::charge_weight_in_fungibles(fungibles_asset_id.clone(), weight)
200 .map(|amount| amount.max(Fungibles::minimum_balance(fungibles_asset_id.clone())))
201 {
202 Ok(a) => a,
203 Err(_) => return Err((payment, XcmError::Overflow)),
204 };
205
206 let required = used.id.into_asset(required_amount.into());
208
209 let Some(imbalance) = payment
213 .try_take(required.into())
214 .ok()
215 .and_then(|taken| taken.fungible.into_iter().next().map(|(_, v)| v))
216 else {
217 return Err((payment, XcmError::TooExpensive));
218 };
219 let mut credit = fungibles::Credit::<AccountId, Fungibles>::zero(fungibles_asset_id);
221 credit.saturating_subsume(imbalance);
222
223 self.outstanding_credit = Some(credit);
225 self.weight_outstanding = weight;
226
227 Ok(payment)
229 }
230
231 fn refund_weight(&mut self, weight: Weight, context: &XcmContext) -> Option<AssetsInHolding> {
239 log::trace!(target: "xcm::weight", "TakeFirstAssetTrader::refund_weight weight: {:?}, context: {:?}", weight, context);
240 if weight.is_zero() {
241 return None;
242 }
243 let outstanding_credit = self.outstanding_credit.as_mut()?;
244 let id = outstanding_credit.asset();
245 let fun = Fungible(outstanding_credit.peek());
246 let asset = (id.clone(), fun).into();
247
248 let (fungibles_asset_id, _) = Matcher::matches_fungibles(&asset).ok()?;
250 let minimum_balance = Fungibles::minimum_balance(fungibles_asset_id.clone());
251
252 let refund_credit = FeeCharger::charge_weight_in_fungibles(fungibles_asset_id, weight)
255 .ok()
256 .map(|refund_balance| {
257 if outstanding_credit.peek().saturating_sub(refund_balance) >= minimum_balance {
260 outstanding_credit.extract(refund_balance)
261 } else {
262 outstanding_credit.extract(minimum_balance)
266 }
267 })?;
268 self.weight_outstanding = self.weight_outstanding.saturating_sub(weight);
270
271 if refund_credit.peek() != Zero::zero() {
273 Some(AssetsInHolding::new_from_fungible_credit(asset.id, Box::new(refund_credit)))
274 } else {
275 None
276 }
277 }
278
279 fn quote_weight(
280 &mut self,
281 weight: Weight,
282 given_id: AssetId,
283 context: &XcmContext,
284 ) -> Result<Asset, XcmError> {
285 log::trace!(
286 target: "xcm::weight",
287 "TakeFirstAssetTrader::quote_weight weight: {:?}, given_id: {:?}, context: {:?}",
288 weight, given_id, context
289 );
290 if weight.is_zero() {
291 return Err(XcmError::NoDeal);
292 }
293
294 let give_matcher: Asset = (given_id.clone(), 1).into();
295 let (give_fungibles_id, _) =
297 Matcher::matches_fungibles(&give_matcher).map_err(|_| XcmError::AssetNotFound)?;
298
299 let required_amount: u128 =
303 FeeCharger::charge_weight_in_fungibles(give_fungibles_id.clone(), weight)
304 .map(|amount| amount.max(Fungibles::minimum_balance(give_fungibles_id.clone())))
305 .map_err(|_| XcmError::Overflow)?;
306
307 let required = given_id.into_asset(required_amount.into());
309 Ok(required)
310 }
311}
312
313impl<
314 AccountId: Eq,
315 FeeCharger: ChargeWeightInFungibles<AccountId, Fungibles>,
316 Matcher: MatchesFungibles<Fungibles::AssetId, Fungibles::Balance>,
317 Fungibles: fungibles::Balanced<AccountId>,
318 OnUnbalanced: OnUnbalancedT<fungibles::Credit<AccountId, Fungibles>>,
319 > Drop for TakeFirstAssetTrader<AccountId, FeeCharger, Matcher, Fungibles, OnUnbalanced>
320{
321 fn drop(&mut self) {
322 self.outstanding_credit
323 .take()
324 .filter(|credit| !credit.peek().is_zero())
325 .map(OnUnbalanced::on_unbalanced);
326 }
327}
328
329pub trait ChargeWeightInFungibles<AccountId, Assets: fungibles::Inspect<AccountId>> {
333 fn charge_weight_in_fungibles(
334 asset_id: <Assets as fungibles::Inspect<AccountId>>::AssetId,
335 weight: Weight,
336 ) -> Result<<Assets as fungibles::Inspect<AccountId>>::Balance, XcmError>;
337}
338
339pub struct SwapFirstAssetTrader<
355 Target: Get<Fungibles::AssetId>,
356 SwapCredit: SwapCreditT<
357 AccountId,
358 Balance = Fungibles::Balance,
359 AssetKind = Fungibles::AssetId,
360 Credit = fungibles::Credit<AccountId, Fungibles>,
361 > + QuotePrice,
362 WeightToFee: WeightToFeeT<Balance = Fungibles::Balance>,
363 Fungibles: fungibles::Balanced<AccountId>,
364 FungiblesAssetMatcher: MatchesFungibles<Fungibles::AssetId, Fungibles::Balance>,
365 OnUnbalanced: OnUnbalancedT<fungibles::Credit<AccountId, Fungibles>>,
366 AccountId,
367> where
368 Fungibles::Balance: From<u128> + Into<u128>,
369{
370 total_fee: fungibles::Credit<AccountId, Fungibles>,
372 last_fee_asset: Option<AssetId>,
374 _phantom_data: PhantomData<(
375 Target,
376 SwapCredit,
377 WeightToFee,
378 Fungibles,
379 FungiblesAssetMatcher,
380 OnUnbalanced,
381 AccountId,
382 )>,
383}
384
385impl<
386 Target: Get<Fungibles::AssetId>,
387 SwapCredit: SwapCreditT<
388 AccountId,
389 Balance = Fungibles::Balance,
390 AssetKind = Fungibles::AssetId,
391 Credit = fungibles::Credit<AccountId, Fungibles>,
392 > + QuotePrice<AssetKind = Fungibles::AssetId, Balance = Fungibles::Balance>,
393 WeightToFee: WeightToFeeT<Balance = Fungibles::Balance>,
394 Fungibles: fungibles::Balanced<
395 AccountId,
396 AssetId: 'static,
397 OnDropCredit: 'static,
398 OnDropDebt: 'static,
399 >,
400 FungiblesAssetMatcher: MatchesFungibles<Fungibles::AssetId, Fungibles::Balance>,
401 OnUnbalanced: OnUnbalancedT<fungibles::Credit<AccountId, Fungibles>>,
402 AccountId,
403 > WeightTrader
404 for SwapFirstAssetTrader<
405 Target,
406 SwapCredit,
407 WeightToFee,
408 Fungibles,
409 FungiblesAssetMatcher,
410 OnUnbalanced,
411 AccountId,
412 >
413where
414 Fungibles::Balance: From<u128> + Into<u128>,
415{
416 fn new() -> Self {
417 Self {
418 total_fee: fungibles::Credit::<AccountId, Fungibles>::zero(Target::get()),
419 last_fee_asset: None,
420 _phantom_data: PhantomData,
421 }
422 }
423
424 fn buy_weight(
425 &mut self,
426 weight: Weight,
427 mut payment: AssetsInHolding,
428 _context: &XcmContext,
429 ) -> Result<AssetsInHolding, (AssetsInHolding, XcmError)> {
430 log::trace!(
431 target: "xcm::weight",
432 "SwapFirstAssetTrader::buy_weight weight: {:?}, payment: {:?}",
433 weight,
434 payment,
435 );
436 let Some((id, given_credit)) = payment.fungible.first_key_value() else {
437 return Err((payment, XcmError::AssetNotFound));
438 };
439 let id = id.clone();
440 let given_credit_amount = given_credit.amount();
441 let first_asset: Asset = (id.clone(), given_credit_amount).into();
442 let Ok((fungibles_id, _)) = FungiblesAssetMatcher::matches_fungibles(&first_asset) else {
443 log::trace!(
444 target: "xcm::weight",
445 "SwapFirstAssetTrader::buy_weight asset {:?} didn't match",
446 first_asset,
447 );
448 return Err((payment, XcmError::AssetNotFound));
449 };
450
451 let swap_asset = fungibles_id.clone().into();
452 if Target::get().eq(&swap_asset) {
453 log::trace!(
454 target: "xcm::weight",
455 "SwapFirstAssetTrader::buy_weight Asset was same as Target, swap not needed.",
456 );
457 return Err((payment, XcmError::FeesNotMet));
459 }
460 let Some(imbalance) = payment.fungible.remove(&first_asset.id) else {
462 return Err((payment, XcmError::TooExpensive));
463 };
464 let mut credit_in = fungibles::Credit::<AccountId, Fungibles>::zero(fungibles_id);
466 credit_in.saturating_subsume(imbalance);
467
468 let fee = WeightToFee::weight_to_fee(&weight);
469 let (credit_out, credit_change) = match SwapCredit::swap_tokens_for_exact_tokens(
471 vec![swap_asset, Target::get()],
472 credit_in,
473 fee,
474 ) {
475 Ok(a) => a,
476 Err((credit_in, error)) => {
477 log::trace!(
478 target: "xcm::weight",
479 "SwapFirstAssetTrader::buy_weight swap couldn't be done. Error was: {:?}",
480 error,
481 );
482 let taken =
484 AssetsInHolding::new_from_fungible_credit(id.clone(), Box::new(credit_in));
485 payment.subsume_assets(taken);
486 return Err((payment, XcmError::FeesNotMet));
487 },
488 };
489
490 match self.total_fee.subsume(credit_out) {
491 Err(credit_out) => {
492 defensive!(
495 "`total_fee.asset` must be equal to `credit_out.asset`",
496 (self.total_fee.asset(), credit_out.asset())
497 );
498 return Err((payment, XcmError::FeesNotMet));
499 },
500 _ => (),
501 };
502 self.last_fee_asset = Some(id.clone());
503
504 if credit_change.peek() != Zero::zero() {
505 let unspent = AssetsInHolding::new_from_fungible_credit(id, Box::new(credit_change));
506 payment.subsume_assets(unspent);
507 }
508 Ok(payment)
509 }
510
511 fn refund_weight(&mut self, weight: Weight, _context: &XcmContext) -> Option<AssetsInHolding> {
512 log::trace!(
513 target: "xcm::weight",
514 "SwapFirstAssetTrader::refund_weight weight: {:?}, self.total_fee: {:?}",
515 weight,
516 self.total_fee,
517 );
518 if weight.is_zero() || self.total_fee.peek().is_zero() {
519 return None;
521 }
522 let refund_asset = if let Some(asset) = &self.last_fee_asset {
523 (asset.clone(), Fungible(0)).into()
525 } else {
526 return None;
527 };
528 let refund_amount = WeightToFee::weight_to_fee(&weight);
529 if refund_amount >= self.total_fee.peek() {
530 return None;
532 }
533
534 let refund_swap_asset = FungiblesAssetMatcher::matches_fungibles(&refund_asset)
535 .map(|(a, _)| a.into())
536 .ok()?;
537
538 let refund = self.total_fee.extract(refund_amount);
539 let refund = match SwapCredit::swap_exact_tokens_for_tokens(
540 vec![Target::get(), refund_swap_asset],
541 refund,
542 None,
543 ) {
544 Ok(refund_in_target) => refund_in_target,
545 Err((refund, _)) => {
546 let _ = self.total_fee.subsume(refund).map_err(|refund| {
548 defensive!(
551 "`total_fee.asset` must be equal to `refund.asset`",
552 (self.total_fee.asset(), refund.asset())
553 );
554 });
555 return None;
556 },
557 };
558
559 let refund = AssetsInHolding::new_from_fungible_credit(refund_asset.id, Box::new(refund));
560 Some(refund)
561 }
562
563 fn quote_weight(
564 &mut self,
565 weight: Weight,
566 given_id: AssetId,
567 _context: &XcmContext,
568 ) -> Result<Asset, XcmError> {
569 log::trace!(
570 target: "xcm::weight",
571 "SwapFirstAssetTrader::quote_weight weight: {:?}, given_id: {:?}",
572 weight,
573 given_id,
574 );
575 if weight.is_zero() {
576 return Err(XcmError::NoDeal);
577 }
578
579 let give_matcher: Asset = (given_id.clone(), 1).into();
580 let (give_fungibles_id, _) = FungiblesAssetMatcher::matches_fungibles(&give_matcher)
581 .map_err(|_| XcmError::AssetNotFound)?;
582 let want_fungibles_id = Target::get();
583 if give_fungibles_id.eq(&want_fungibles_id.clone().into()) {
584 return Err(XcmError::FeesNotMet);
585 }
586
587 let want_amount = WeightToFee::weight_to_fee(&weight);
588 let necessary_give: u128 = <SwapCredit as QuotePrice>::quote_price_tokens_for_exact_tokens(
590 give_fungibles_id,
591 want_fungibles_id,
592 want_amount,
593 true, )
595 .filter(|amount| *amount > 0u128.into())
596 .ok_or(XcmError::FeesNotMet)?
597 .into();
598 Ok((given_id, necessary_give).into())
599 }
600}
601
602impl<
603 Target: Get<Fungibles::AssetId>,
604 SwapCredit: SwapCreditT<
605 AccountId,
606 Balance = Fungibles::Balance,
607 AssetKind = Fungibles::AssetId,
608 Credit = fungibles::Credit<AccountId, Fungibles>,
609 > + QuotePrice,
610 WeightToFee: WeightToFeeT<Balance = Fungibles::Balance>,
611 Fungibles: fungibles::Balanced<AccountId>,
612 FungiblesAssetMatcher: MatchesFungibles<Fungibles::AssetId, Fungibles::Balance>,
613 OnUnbalanced: OnUnbalancedT<fungibles::Credit<AccountId, Fungibles>>,
614 AccountId,
615 > Drop
616 for SwapFirstAssetTrader<
617 Target,
618 SwapCredit,
619 WeightToFee,
620 Fungibles,
621 FungiblesAssetMatcher,
622 OnUnbalanced,
623 AccountId,
624 >
625where
626 Fungibles::Balance: From<u128> + Into<u128>,
627{
628 fn drop(&mut self) {
629 if self.total_fee.peek().is_zero() {
630 return;
631 }
632 let total_fee = self.total_fee.extract(self.total_fee.peek());
633 OnUnbalanced::on_unbalanced(total_fee);
634 }
635}
636
637#[cfg(test)]
638mod test_xcm_router {
639 use super::*;
640 use cumulus_primitives_core::UpwardMessage;
641 use frame_support::assert_ok;
642 use xcm::MAX_XCM_DECODE_DEPTH;
643
644 struct OkFixedXcmHashWithAssertingRequiredInputsSender;
646
647 impl OkFixedXcmHashWithAssertingRequiredInputsSender {
648 const FIXED_XCM_HASH: [u8; 32] = [9; 32];
649
650 fn fixed_delivery_asset() -> Assets {
651 Assets::new()
652 }
653
654 fn expected_delivery_result() -> Result<(XcmHash, Assets), SendError> {
655 Ok((Self::FIXED_XCM_HASH, Self::fixed_delivery_asset()))
656 }
657 }
658
659 impl SendXcm for OkFixedXcmHashWithAssertingRequiredInputsSender {
660 type Ticket = ();
661
662 fn validate(
663 destination: &mut Option<Location>,
664 message: &mut Option<Xcm<()>>,
665 ) -> SendResult<Self::Ticket> {
666 assert!(destination.is_some());
667 assert!(message.is_some());
668 Ok(((), OkFixedXcmHashWithAssertingRequiredInputsSender::fixed_delivery_asset()))
669 }
670
671 fn deliver(_: Self::Ticket) -> Result<XcmHash, SendError> {
672 Ok(Self::FIXED_XCM_HASH)
673 }
674 }
675
676 struct CanSendUpwardMessageSender;
678
679 impl UpwardMessageSender for CanSendUpwardMessageSender {
680 fn send_upward_message(_: UpwardMessage) -> Result<(u32, XcmHash), MessageSendError> {
681 Err(MessageSendError::Other)
682 }
683
684 fn can_send_upward_message(_: &UpwardMessage) -> Result<(), MessageSendError> {
685 Ok(())
686 }
687 }
688
689 #[test]
690 fn parent_as_ump_does_not_consume_dest_or_msg_on_not_applicable() {
691 let message = Xcm(vec![Trap(5)]);
693
694 let dest = (Parent, Parent, Parent);
696 let mut dest_wrapper = Some(dest.into());
697 let mut msg_wrapper = Some(message.clone());
698 assert_eq!(
699 Err(SendError::NotApplicable),
700 <ParentAsUmp<(), (), ()> as SendXcm>::validate(&mut dest_wrapper, &mut msg_wrapper)
701 );
702
703 assert_eq!(Some(dest.into()), dest_wrapper.take());
705 assert_eq!(Some(message.clone()), msg_wrapper.take());
706
707 assert_eq!(
709 OkFixedXcmHashWithAssertingRequiredInputsSender::expected_delivery_result(),
710 send_xcm::<(ParentAsUmp<(), (), ()>, OkFixedXcmHashWithAssertingRequiredInputsSender)>(
711 dest.into(),
712 message,
713 )
714 );
715 }
716
717 #[test]
718 fn parent_as_ump_consumes_dest_and_msg_on_ok_validate() {
719 let message = Xcm(vec![Trap(5)]);
721
722 let dest = (Parent, Here);
724 let mut dest_wrapper = Some(dest.clone().into());
725 let mut msg_wrapper = Some(message.clone());
726 assert!(<ParentAsUmp<CanSendUpwardMessageSender, (), ()> as SendXcm>::validate(
727 &mut dest_wrapper,
728 &mut msg_wrapper,
729 )
730 .is_ok());
731
732 assert_eq!(None, dest_wrapper.take());
734 assert_eq!(None, msg_wrapper.take());
735
736 assert_eq!(
738 Err(SendError::Transport("Other")),
739 send_xcm::<(
740 ParentAsUmp<CanSendUpwardMessageSender, (), ()>,
741 OkFixedXcmHashWithAssertingRequiredInputsSender
742 )>(dest.into(), message)
743 );
744 }
745
746 #[test]
747 fn parent_as_ump_validate_nested_xcm_works() {
748 let dest = Parent;
749
750 type Router = ParentAsUmp<CanSendUpwardMessageSender, (), ()>;
751
752 let mut good = Xcm(vec![ClearOrigin]);
754 for _ in 0..MAX_XCM_DECODE_DEPTH - 1 {
755 good = Xcm(vec![SetAppendix(good)]);
756 }
757
758 assert_ok!(<Router as SendXcm>::validate(&mut Some(dest.into()), &mut Some(good.clone())));
760
761 let bad = Xcm(vec![SetAppendix(good)]);
763 assert_eq!(
764 Err(SendError::ExceedsMaxMessageSize),
765 <Router as SendXcm>::validate(&mut Some(dest.into()), &mut Some(bad))
766 );
767 }
768}
769
770#[cfg(test)]
771mod test_trader {
772 use super::{test_helpers::asset_to_holding, *};
773 use frame_support::{
774 assert_ok,
775 traits::tokens::{
776 DepositConsequence, Fortitude, Preservation, Provenance, WithdrawConsequence,
777 },
778 };
779 use sp_runtime::DispatchError;
780 use xcm_builder::TakeRevenue;
781 use xcm_executor::traits::Error;
782
783 #[test]
784 fn take_first_asset_trader_buy_weight_called_twice_throws_error() {
785 const AMOUNT: u128 = 100;
786
787 type TestAccountId = u32;
789 type TestAssetId = Location; type TestBalance = u128;
791
792 struct TestAssets;
793 impl MatchesFungibles<TestAssetId, TestBalance> for TestAssets {
794 fn matches_fungibles(a: &Asset) -> Result<(TestAssetId, TestBalance), Error> {
795 match a {
796 Asset { fun: Fungible(amount), id: AssetId(_id) } => {
797 Ok((Location::new(0, [GeneralIndex(1)]), *amount))
798 },
799 _ => Err(Error::AssetNotHandled),
800 }
801 }
802 }
803 impl fungibles::Inspect<TestAccountId> for TestAssets {
804 type AssetId = TestAssetId;
805 type Balance = TestBalance;
806
807 fn total_issuance(_: Self::AssetId) -> Self::Balance {
808 0
809 }
810
811 fn minimum_balance(_: Self::AssetId) -> Self::Balance {
812 0
813 }
814
815 fn balance(_: Self::AssetId, _: &TestAccountId) -> Self::Balance {
816 0
817 }
818
819 fn total_balance(_: Self::AssetId, _: &TestAccountId) -> Self::Balance {
820 0
821 }
822
823 fn reducible_balance(
824 _: Self::AssetId,
825 _: &TestAccountId,
826 _: Preservation,
827 _: Fortitude,
828 ) -> Self::Balance {
829 0
830 }
831
832 fn can_deposit(
833 _: Self::AssetId,
834 _: &TestAccountId,
835 _: Self::Balance,
836 _: Provenance,
837 ) -> DepositConsequence {
838 DepositConsequence::Success
839 }
840
841 fn can_withdraw(
842 _: Self::AssetId,
843 _: &TestAccountId,
844 _: Self::Balance,
845 ) -> WithdrawConsequence<Self::Balance> {
846 WithdrawConsequence::Success
847 }
848
849 fn asset_exists(_: Self::AssetId) -> bool {
850 true
851 }
852 }
853 impl fungibles::Mutate<TestAccountId> for TestAssets {}
854 impl fungibles::Balanced<TestAccountId> for TestAssets {
855 type OnDropCredit = fungibles::DecreaseIssuance<TestAccountId, Self>;
856 type OnDropDebt = fungibles::IncreaseIssuance<TestAccountId, Self>;
857 }
858 impl fungibles::Unbalanced<TestAccountId> for TestAssets {
859 fn handle_dust(_: fungibles::Dust<TestAccountId, Self>) {}
860 fn write_balance(
861 _: Self::AssetId,
862 _: &TestAccountId,
863 _: Self::Balance,
864 ) -> Result<Option<Self::Balance>, DispatchError> {
865 Ok(None)
866 }
867
868 fn set_total_issuance(_: Self::AssetId, _: Self::Balance) {}
869 }
870
871 struct FeeChargerAssetsHandleRefund;
872 impl ChargeWeightInFungibles<TestAccountId, TestAssets> for FeeChargerAssetsHandleRefund {
873 fn charge_weight_in_fungibles(
874 _: <TestAssets as fungibles::Inspect<TestAccountId>>::AssetId,
875 _: Weight,
876 ) -> Result<<TestAssets as fungibles::Inspect<TestAccountId>>::Balance, XcmError> {
877 Ok(AMOUNT)
878 }
879 }
880 impl TakeRevenue for FeeChargerAssetsHandleRefund {
881 fn take_revenue(_: AssetsInHolding) {}
882 }
883
884 struct HandleFees;
886 impl OnUnbalancedT<fungibles::Credit<TestAccountId, TestAssets>> for HandleFees {
887 fn on_unbalanced(_: fungibles::Credit<TestAccountId, TestAssets>) {
888 }
890 }
891
892 type Trader = TakeFirstAssetTrader<
894 TestAccountId,
895 FeeChargerAssetsHandleRefund,
896 TestAssets,
897 TestAssets,
898 HandleFees,
899 >;
900 let mut trader = <Trader as WeightTrader>::new();
901 let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
902
903 let asset: Asset = (Here, AMOUNT).into();
905 let payment1 = asset_to_holding(asset.clone());
906 let payment2 = asset_to_holding(asset);
907 let weight_to_buy = Weight::from_parts(1_000, 1_000);
908
909 assert_ok!(trader.buy_weight(weight_to_buy, payment1, &ctx));
911
912 let (_, error) = trader.buy_weight(weight_to_buy, payment2, &ctx).unwrap_err();
914 assert_eq!(error, XcmError::NotWithdrawable);
915 }
916
917 #[test]
918 fn take_first_asset_trader_returns_unused_amount() {
919 const REQUIRED_AMOUNT: u128 = 100;
922 const TOTAL_AMOUNT: u128 = 500; type TestAccountId = u32;
926 type TestAssetId = Location;
927 type TestBalance = u128;
928
929 struct TestAssets;
930 impl MatchesFungibles<TestAssetId, TestBalance> for TestAssets {
931 fn matches_fungibles(a: &Asset) -> Result<(TestAssetId, TestBalance), Error> {
932 match a {
933 Asset { fun: Fungible(amount), id: AssetId(_id) } => {
934 Ok((Location::new(0, [GeneralIndex(1)]), *amount))
935 },
936 _ => Err(Error::AssetNotHandled),
937 }
938 }
939 }
940 impl fungibles::Inspect<TestAccountId> for TestAssets {
941 type AssetId = TestAssetId;
942 type Balance = TestBalance;
943
944 fn total_issuance(_: Self::AssetId) -> Self::Balance {
945 0
946 }
947
948 fn minimum_balance(_: Self::AssetId) -> Self::Balance {
949 0
950 }
951
952 fn balance(_: Self::AssetId, _: &TestAccountId) -> Self::Balance {
953 0
954 }
955
956 fn total_balance(_: Self::AssetId, _: &TestAccountId) -> Self::Balance {
957 0
958 }
959
960 fn reducible_balance(
961 _: Self::AssetId,
962 _: &TestAccountId,
963 _: Preservation,
964 _: Fortitude,
965 ) -> Self::Balance {
966 0
967 }
968
969 fn can_deposit(
970 _: Self::AssetId,
971 _: &TestAccountId,
972 _: Self::Balance,
973 _: Provenance,
974 ) -> DepositConsequence {
975 DepositConsequence::Success
976 }
977
978 fn can_withdraw(
979 _: Self::AssetId,
980 _: &TestAccountId,
981 _: Self::Balance,
982 ) -> WithdrawConsequence<Self::Balance> {
983 WithdrawConsequence::Success
984 }
985
986 fn asset_exists(_: Self::AssetId) -> bool {
987 true
988 }
989 }
990 impl fungibles::Mutate<TestAccountId> for TestAssets {}
991 impl fungibles::Balanced<TestAccountId> for TestAssets {
992 type OnDropCredit = fungibles::DecreaseIssuance<TestAccountId, Self>;
993 type OnDropDebt = fungibles::IncreaseIssuance<TestAccountId, Self>;
994 }
995 impl fungibles::Unbalanced<TestAccountId> for TestAssets {
996 fn handle_dust(_: fungibles::Dust<TestAccountId, Self>) {}
997 fn write_balance(
998 _: Self::AssetId,
999 _: &TestAccountId,
1000 _: Self::Balance,
1001 ) -> Result<Option<Self::Balance>, DispatchError> {
1002 Ok(None)
1003 }
1004
1005 fn set_total_issuance(_: Self::AssetId, _: Self::Balance) {}
1006 }
1007
1008 struct FeeChargerAssetsHandleRefund;
1009 impl ChargeWeightInFungibles<TestAccountId, TestAssets> for FeeChargerAssetsHandleRefund {
1010 fn charge_weight_in_fungibles(
1011 _: <TestAssets as fungibles::Inspect<TestAccountId>>::AssetId,
1012 _: Weight,
1013 ) -> Result<<TestAssets as fungibles::Inspect<TestAccountId>>::Balance, XcmError> {
1014 Ok(REQUIRED_AMOUNT)
1015 }
1016 }
1017 impl TakeRevenue for FeeChargerAssetsHandleRefund {
1018 fn take_revenue(_: AssetsInHolding) {}
1019 }
1020
1021 struct HandleFees;
1022 impl OnUnbalancedT<fungibles::Credit<TestAccountId, TestAssets>> for HandleFees {
1023 fn on_unbalanced(_: fungibles::Credit<TestAccountId, TestAssets>) {}
1024 }
1025
1026 type Trader = TakeFirstAssetTrader<
1028 TestAccountId,
1029 FeeChargerAssetsHandleRefund,
1030 TestAssets,
1031 TestAssets,
1032 HandleFees,
1033 >;
1034 let mut trader = <Trader as WeightTrader>::new();
1035 let ctx = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
1036
1037 let asset: Asset = (Here, TOTAL_AMOUNT).into();
1039 let payment = asset_to_holding(asset.clone());
1040 let weight_to_buy = Weight::from_parts(1_000, 1_000);
1041
1042 let result = trader.buy_weight(weight_to_buy, payment, &ctx);
1044 assert_ok!(&result);
1045
1046 let unused_payment = result.unwrap();
1047
1048 let expected_excess = TOTAL_AMOUNT - REQUIRED_AMOUNT;
1050 let unused_assets: Vec<Asset> = unused_payment.fungible_assets_iter().collect();
1051
1052 assert_eq!(unused_assets.len(), 1);
1054
1055 match &unused_assets[0] {
1057 Asset { fun: Fungible(amount), .. } => {
1058 assert_eq!(*amount, expected_excess, "Expected excess amount to be returned");
1059 },
1060 _ => panic!("Expected fungible asset"),
1061 }
1062 }
1063}
1064
1065#[cfg(feature = "runtime-benchmarks")]
1071pub struct ToParentDeliveryHelper<XcmConfig, ExistentialDeposit, PriceForDelivery>(
1072 core::marker::PhantomData<(XcmConfig, ExistentialDeposit, PriceForDelivery)>,
1073);
1074
1075#[cfg(feature = "runtime-benchmarks")]
1076impl<
1077 XcmConfig: xcm_executor::Config,
1078 ExistentialDeposit: Get<Option<Asset>>,
1079 PriceForDelivery: PriceForMessageDelivery<Id = ()>,
1080 > xcm_builder::EnsureDelivery
1081 for ToParentDeliveryHelper<XcmConfig, ExistentialDeposit, PriceForDelivery>
1082{
1083 fn ensure_successful_delivery(
1084 origin_ref: &Location,
1085 dest: &Location,
1086 fee_reason: xcm_executor::traits::FeeReason,
1087 ) -> (Option<xcm_executor::FeesMode>, Option<Assets>) {
1088 use xcm::{latest::MAX_ITEMS_IN_ASSETS, MAX_INSTRUCTIONS_TO_DECODE};
1089 use xcm_executor::{
1090 traits::{FeeManager, TransactAsset},
1091 FeesMode,
1092 };
1093
1094 if dest.ne(&Location::parent()) {
1096 return (None, None);
1097 }
1098
1099 XcmConfig::XcmSender::ensure_successful_delivery(Some(Location::parent()));
1101
1102 let mut fees_mode = None;
1103 if !XcmConfig::FeeManager::is_waived(Some(origin_ref), fee_reason) {
1104 let context = XcmContext { origin: None, message_id: XcmHash::default(), topic: None };
1106
1107 if let Some(ed) = ExistentialDeposit::get() {
1109 let holdings = XcmConfig::AssetTransactor::mint_asset(&ed, &context).unwrap();
1110 XcmConfig::AssetTransactor::deposit_asset(holdings, &origin_ref, Some(&context))
1111 .unwrap();
1112 }
1113
1114 let mut max_assets: Vec<Asset> = Vec::new();
1116 for i in 0..MAX_ITEMS_IN_ASSETS {
1117 max_assets.push((GeneralIndex(i as u128), 100u128).into());
1118 }
1119 let overestimated_xcm =
1120 vec![WithdrawAsset(max_assets.into()); MAX_INSTRUCTIONS_TO_DECODE as usize].into();
1121 let overestimated_fees = PriceForDelivery::price_for_delivery((), &overestimated_xcm);
1122
1123 for fee in overestimated_fees.inner() {
1125 let holdings = XcmConfig::AssetTransactor::mint_asset(fee, &context).unwrap();
1126 XcmConfig::AssetTransactor::deposit_asset(holdings, &origin_ref, Some(&context))
1127 .unwrap();
1128 }
1129
1130 fees_mode = Some(FeesMode { jit_withdraw: true });
1132 }
1133 (fees_mode, None)
1134 }
1135}