referrerpolicy=no-referrer-when-downgrade

cumulus_primitives_utility/
lib.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Cumulus.
3// SPDX-License-Identifier: Apache-2.0
4
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// 	http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17//! Helper datatypes for cumulus. This includes the [`ParentAsUmp`] routing type which will route
18//! messages into an [`UpwardMessageSender`] if the destination is `Parent`.
19
20#![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
50/// Xcm router which recognises the `Parent` destination and handles it by sending the message into
51/// the given UMP `UpwardMessageSender` implementation. Thus this essentially adapts an
52/// `UpwardMessageSender` trait impl into a `SendXcm` trait impl.
53///
54/// NOTE: This is a pretty dumb "just send it" router; we will probably want to introduce queuing
55/// to UMP eventually and when we do, the pallet which implements the queuing will be responsible
56/// for the `SendXcm` implementation.
57pub 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			// An upward message for the relay chain.
71			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			// Pre-check with our message sender if everything else is okay.
81			T::can_send_upward_message(&data).map_err(Self::map_upward_sender_err)?;
82
83			Ok((data, price))
84		} else {
85			// Anything else is unhandled. This includes a message that is not meant for us.
86			// We need to make sure that dest/msg is not consumed here.
87			*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/// Contains information to handle refund/payment for xcm-execution
127#[derive(Clone, Eq, PartialEq, Debug)]
128struct AssetTraderRefunder {
129	// The amount of weight bought minus the weigh already refunded
130	weight_outstanding: Weight,
131	// The concrete asset containing the asset location and outstanding balance
132	outstanding_concrete_asset: Asset,
133}
134
135/// Charges for execution in the first asset of those selected for fee payment
136/// Only succeeds for Concrete Fungible Assets
137/// First tries to convert the this Asset into a local assetId
138/// Then charges for this assetId as described by FeeCharger
139/// Weight, paid balance, local asset Id and the location is stored for
140/// later refund purposes
141/// Important: Errors if the Trader is being called twice by 2 BuyExecution instructions
142/// Alternatively we could just return payment in the aforementioned case
143#[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	// We take first asset
167	// Check whether we can convert fee to asset_fee (is_sufficient, min_deposit)
168	// If everything goes well, we charge.
169	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		// Make sure we don't enter twice
178		if self.0.is_some() {
179			return Err(XcmError::NotWithdrawable)
180		}
181
182		// We take the very first asset from payment
183		// (assets are sorted by fungibility/amount after this conversion)
184		let assets: Assets = payment.clone().into();
185
186		// Take the first asset from the selected Assets
187		let first = assets.get(0).ok_or(XcmError::AssetNotFound)?;
188
189		// Get the local asset id in which we can pay for fees
190		let (local_asset_id, _) =
191			Matcher::matches_fungibles(first).map_err(|_| XcmError::AssetNotFound)?;
192
193		// Calculate how much we should charge in the asset_id for such amount of weight
194		// Require at least a payment of minimum_balance
195		// Necessary for fully collateral-backed assets
196		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		// Convert to the same kind of asset, with the required fungible balance
210		let required = first.id.clone().into_asset(asset_balance.into());
211
212		// Subtract payment
213		let unused = payment.checked_sub(required.clone()).map_err(|_| XcmError::TooExpensive)?;
214
215		// record weight and asset
216		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			// Get the local asset id in which we can refund fees
232			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			// Calculate asset_balance
238			// This read should have already be cached in buy_weight
239			let (asset_balance, outstanding_minus_subtracted) =
240				FeeCharger::charge_weight_in_fungibles(local_asset_id, weight).ok().map(
241					|asset_balance| {
242						// Require at least a drop of minimum_balance
243						// Necessary for fully collateral-backed assets
244						if outstanding_balance.saturating_sub(asset_balance) > minimum_balance {
245							(asset_balance, outstanding_balance.saturating_sub(asset_balance))
246						}
247						// If the amount to be refunded leaves the remaining balance below ED,
248						// we just refund the exact amount that guarantees at least ED will be
249						// dropped
250						else {
251							(outstanding_balance.saturating_sub(minimum_balance), minimum_balance)
252						}
253					},
254				)?;
255
256			// Convert balances into u128
257			let outstanding_minus_subtracted: u128 = outstanding_minus_subtracted.saturated_into();
258			let asset_balance: u128 = asset_balance.saturated_into();
259
260			// Construct outstanding_concrete_asset with the same location id and subtracted
261			// balance
262			let outstanding_concrete_asset: Asset =
263				(id.clone(), outstanding_minus_subtracted).into();
264
265			// Subtract from existing weight and balance
266			weight_outstanding = weight_outstanding.saturating_sub(weight);
267
268			// Override AssetTraderRefunder
269			self.0 = Some(AssetTraderRefunder { weight_outstanding, outstanding_concrete_asset });
270
271			// Only refund if positive
272			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
298/// XCM fee depositor to which we implement the `TakeRevenue` trait.
299/// It receives a `Transact` implemented argument and a 32 byte convertible `AccountId`, and the fee
300/// receiver account's `FungiblesMutateAdapter` should be identical to that implemented by
301/// `WithdrawAsset`.
302pub 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
325/// ChargeWeightInFungibles trait, which converts a given amount of weight
326/// and an assetId, and it returns the balance amount that should be charged
327/// in such assetId for that amount of weight
328pub 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
335/// Provides an implementation of [`WeightTrader`] to charge for weight using the first asset
336/// specified in the `payment` argument.
337///
338/// The asset used to pay for the weight must differ from the `Target` asset and be exchangeable for
339/// the same `Target` asset through `SwapCredit`.
340///
341/// ### Parameters:
342/// - `Target`: the asset into which the user's payment will be exchanged using `SwapCredit`.
343/// - `SwapCredit`: mechanism used for the exchange of the user's payment asset into the `Target`.
344/// - `WeightToFee`: weight to the `Target` asset fee calculator.
345/// - `Fungibles`: registry of fungible assets.
346/// - `FungiblesAssetMatcher`: utility for mapping [`Asset`] to `Fungibles::AssetId` and
347///   `Fungibles::Balance`.
348/// - `OnUnbalanced`: handler for the fee payment.
349/// - `AccountId`: the account identifier type.
350pub 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	/// Accumulated fee paid for XCM execution.
367	total_fee: fungibles::Credit<AccountId, Fungibles>,
368	/// Last asset utilized by a client to settle a fee.
369	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			// current trader is not applicable.
447			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		// swap the user's asset for the `Target` asset.
454		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				// error may occur if `total_fee.asset` differs from `credit_out.asset`, which does
472				// not apply in this context.
473				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			// noting yet paid to refund.
497			return None
498		}
499		let mut refund_asset = if let Some(asset) = &self.last_fee_asset {
500			// create an initial zero refund in the asset used in the last `buy_weight`.
501			(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			// not enough was paid to refund the `weight`.
508			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				// return an attempted refund back to the `total_fee`.
524				let _ = self.total_fee.subsume(refund).map_err(|refund| {
525					// error may occur if `total_fee.asset` differs from `refund.asset`, which does
526					// not apply in this context.
527					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	/// Validates [`validate`] for required Some(destination) and Some(message)
585	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	/// Impl [`UpwardMessageSender`] that return `Ok` for `can_send_upward_message`.
615	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		// dummy message
629		let message = Xcm(vec![Trap(5)]);
630
631		// ParentAsUmp - check dest is really not applicable
632		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		// check wrapper were not consumed
641		assert_eq!(Some(dest.into()), dest_wrapper.take());
642		assert_eq!(Some(message.clone()), msg_wrapper.take());
643
644		// another try with router chain with asserting sender
645		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		// dummy message
657		let message = Xcm(vec![Trap(5)]);
658
659		// ParentAsUmp - check dest/msg is valid
660		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		// check wrapper were consumed
670		assert_eq!(None, dest_wrapper.take());
671		assert_eq!(None, msg_wrapper.take());
672
673		// another try with router chain with asserting sender
674		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		// Message that is not too deeply nested:
690		let mut good = Xcm(vec![ClearOrigin]);
691		for _ in 0..MAX_XCM_DECODE_DEPTH - 1 {
692			good = Xcm(vec![SetAppendix(good)]);
693		}
694
695		// Check that the good message is validated:
696		assert_ok!(<Router as SendXcm>::validate(&mut Some(dest.into()), &mut Some(good.clone())));
697
698		// Nesting the message one more time should reject it:
699		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		// prepare prerequisites to instantiate `TakeFirstAssetTrader`
723		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		// create new instance
821		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		// prepare test data
832		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		// lets do first call (success)
837		assert_ok!(trader.buy_weight(weight_to_buy, payment.clone(), &ctx));
838
839		// lets do second call (error)
840		assert_eq!(trader.buy_weight(weight_to_buy, payment, &ctx), Err(XcmError::NotWithdrawable));
841	}
842}
843
844/// Implementation of `xcm_builder::EnsureDelivery` which helps to ensure delivery to the
845/// parent relay chain. Deposits existential deposit for origin (if needed).
846/// Deposits estimated fee to the origin account (if needed).
847/// Allows triggering of additional logic for a specific `ParaId` (e.g. to open an HRMP channel) if
848/// needed.
849#[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		// check if the destination is relay/parent
871		if dest.ne(&Location::parent()) {
872			return (None, None);
873		}
874
875		// Ensure routers
876		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 not waived, we need to set up accounts for paying and receiving fees
881
882			// mint ED to origin if needed
883			if let Some(ed) = ExistentialDeposit::get() {
884				XcmConfig::AssetTransactor::deposit_asset(&ed, &origin_ref, None).unwrap();
885			}
886
887			// overestimate delivery fee
888			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			// mint overestimated fee to origin
897			for fee in overestimated_fees.inner() {
898				XcmConfig::AssetTransactor::deposit_asset(&fee, &origin_ref, None).unwrap();
899			}
900
901			// expected worst case - direct withdraw
902			fees_mode = Some(FeesMode { jit_withdraw: true });
903		}
904		(fees_mode, None)
905	}
906}