pallet_evm_precompile_xcm_transactor/
functions.rs

1// Copyright 2019-2025 PureStake Inc.
2// This file is part of Moonbeam.
3
4// Moonbeam is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Moonbeam is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Moonbeam.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Common functions to access xcm-transactor pallet dispatchables
18
19use fp_evm::PrecompileHandle;
20use frame_support::{
21	dispatch::{GetDispatchInfo, PostDispatchInfo},
22	traits::ConstU32,
23};
24use pallet_evm::AddressMapping;
25use pallet_xcm_transactor::{
26	Currency, CurrencyPayment, RemoteTransactInfoWithMaxWeight, TransactWeights,
27};
28use precompile_utils::prelude::*;
29use sp_core::{MaxEncodedLen, H160, U256};
30use sp_runtime::traits::Dispatchable;
31use sp_std::{
32	boxed::Box,
33	convert::{TryFrom, TryInto},
34	marker::PhantomData,
35	vec::Vec,
36};
37use sp_weights::Weight;
38use xcm::latest::prelude::*;
39use xcm::latest::Location;
40use xcm_primitives::{
41	AccountIdToCurrencyId, UtilityAvailableCalls, UtilityEncodeCall, DEFAULT_PROOF_SIZE,
42};
43
44/// A precompile to wrap the functionality from xcm transactor
45pub struct XcmTransactorWrapper<Runtime>(PhantomData<Runtime>);
46
47pub type TransactorOf<Runtime> = <Runtime as pallet_xcm_transactor::Config>::Transactor;
48pub type CurrencyIdOf<Runtime> = <Runtime as pallet_xcm_transactor::Config>::CurrencyId;
49
50pub const CALL_DATA_LIMIT: u32 = 2u32.pow(16);
51pub type GetDataLimit = ConstU32<CALL_DATA_LIMIT>;
52
53impl<Runtime> XcmTransactorWrapper<Runtime>
54where
55	Runtime: pallet_xcm_transactor::Config + pallet_evm::Config + frame_system::Config,
56	Runtime::RuntimeCall: Dispatchable<PostInfo = PostDispatchInfo> + GetDispatchInfo,
57	Runtime::RuntimeCall: From<pallet_xcm_transactor::Call<Runtime>>,
58	TransactorOf<Runtime>: TryFrom<u8>,
59	Runtime::AccountId: Into<H160>,
60	Runtime: AccountIdToCurrencyId<Runtime::AccountId, CurrencyIdOf<Runtime>>,
61	<Runtime as pallet_evm::Config>::AddressMapping: AddressMapping<Runtime::AccountId>,
62{
63	pub(crate) fn account_index(
64		handle: &mut impl PrecompileHandle,
65		index: u16,
66	) -> EvmResult<Address> {
67		// storage item: IndexToAccount: Blake2_128(16) + u16(2) + AccountId(20)
68		handle.record_db_read::<Runtime>(38)?;
69
70		// fetch data from pallet
71		let account: H160 = pallet_xcm_transactor::Pallet::<Runtime>::index_to_account(index)
72			.ok_or(revert("No index assigned"))?
73			.into();
74
75		Ok(account.into())
76	}
77
78	pub(crate) fn transact_info(
79		handle: &mut impl PrecompileHandle,
80		multilocation: Location,
81	) -> EvmResult<(u64, U256, u64)> {
82		// fetch data from pallet
83		// storage item: TransactInfoWithWeightLimit: Blake2_128(16) + Location
84		// + RemoteTransactInfoWithMaxWeight
85		handle.record_db_read::<Runtime>(
86			16 + Location::max_encoded_len() + RemoteTransactInfoWithMaxWeight::max_encoded_len(),
87		)?;
88		let remote_transact_info: RemoteTransactInfoWithMaxWeight =
89			pallet_xcm_transactor::Pallet::<Runtime>::transact_info(&multilocation)
90				.ok_or(revert("Transact Info not set"))?;
91
92		// fetch data from pallet
93		// storage item: AssetTypeUnitsPerSecond: Blake2_128(16) + Location + u128(16)
94		handle.record_db_read::<Runtime>(32 + Location::max_encoded_len())?;
95		let fee_per_second: u128 =
96			pallet_xcm_transactor::Pallet::<Runtime>::dest_asset_fee_per_second(&multilocation)
97				.ok_or(revert("Fee Per Second not set"))?;
98
99		Ok((
100			remote_transact_info.transact_extra_weight.ref_time(),
101			fee_per_second.into(),
102			remote_transact_info.max_weight.ref_time(),
103		))
104	}
105
106	pub(crate) fn transact_info_with_signed(
107		handle: &mut impl PrecompileHandle,
108		multilocation: Location,
109	) -> EvmResult<(u64, u64, u64)> {
110		// fetch data from pallet
111		// storage item: TransactInfoWithWeightLimit: Blake2_128(16) + Location
112		// + RemoteTransactInfoWithMaxWeight
113		handle.record_db_read::<Runtime>(
114			16 + Location::max_encoded_len() + RemoteTransactInfoWithMaxWeight::max_encoded_len(),
115		)?;
116		let remote_transact_info: RemoteTransactInfoWithMaxWeight =
117			pallet_xcm_transactor::Pallet::<Runtime>::transact_info(multilocation)
118				.ok_or(revert("Transact Info not set"))?;
119
120		let transact_extra_weight_signed = remote_transact_info
121			.transact_extra_weight_signed
122			.unwrap_or(Weight::zero());
123
124		Ok((
125			remote_transact_info.transact_extra_weight.ref_time(),
126			transact_extra_weight_signed.ref_time(),
127			remote_transact_info.max_weight.ref_time(),
128		))
129	}
130
131	pub(crate) fn fee_per_second(
132		handle: &mut impl PrecompileHandle,
133		location: Location,
134	) -> EvmResult<U256> {
135		// fetch data from pallet
136		// storage item: AssetTypeUnitsPerSecond: Blake2_128(16) + Location + u128(16)
137		handle.record_db_read::<Runtime>(32 + Location::max_encoded_len())?;
138		let fee_per_second: u128 =
139			pallet_xcm_transactor::Pallet::<Runtime>::dest_asset_fee_per_second(location)
140				.ok_or(revert("Fee Per Second not set"))?;
141
142		Ok(fee_per_second.into())
143	}
144
145	pub(crate) fn transact_through_derivative_multilocation(
146		handle: &mut impl PrecompileHandle,
147		transactor: u8,
148		index: u16,
149		fee_asset: Location,
150		weight: u64,
151		inner_call: BoundedBytes<GetDataLimit>,
152	) -> EvmResult {
153		let transactor = transactor
154			.try_into()
155			.map_err(|_| RevertReason::custom("Non-existent transactor").in_field("transactor"))?;
156		let inner_call: Vec<_> = inner_call.into();
157
158		// Depending on the Runtime, this might involve a DB read. This is not the case in
159		// moonbeam, as we are using IdentityMapping
160		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
161		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_derivative {
162			dest: transactor,
163			index,
164			fee: CurrencyPayment {
165				currency: Currency::AsMultiLocation(Box::new(xcm::VersionedLocation::from(
166					fee_asset,
167				))),
168				fee_amount: None,
169			},
170			inner_call,
171			weight_info: TransactWeights {
172				// TODO overall weight None means we will retrieve the allowed proof size from storage.
173				// In the v2 to v3 migration we set this value to DEFAULT_PROOF_SIZE, so setting
174				// require_weight_at_most to DEFAULT_PROOF_SIZE/2 makes sense. Although we might
175				// want to revisit this to use whatever storage value there is and divide it by 2.
176				transact_required_weight_at_most: Weight::from_parts(
177					weight,
178					DEFAULT_PROOF_SIZE.saturating_div(2),
179				),
180				overall_weight: None,
181			},
182			refund: false,
183		};
184
185		RuntimeHelper::<Runtime>::try_dispatch(
186			handle,
187			frame_system::RawOrigin::Signed(origin).into(),
188			call,
189			0,
190		)?;
191
192		Ok(())
193	}
194
195	pub(crate) fn transact_through_derivative_multilocation_fee_weight(
196		handle: &mut impl PrecompileHandle,
197		transactor: u8,
198		index: u16,
199		fee_asset: Location,
200		weight: u64,
201		inner_call: BoundedBytes<GetDataLimit>,
202		fee_amount: u128,
203		overall_weight: u64,
204	) -> EvmResult {
205		let transactor = transactor
206			.try_into()
207			.map_err(|_| RevertReason::custom("Non-existent transactor").in_field("transactor"))?;
208
209		let inner_call: Vec<_> = inner_call.into();
210
211		// Depending on the Runtime, this might involve a DB read. This is not the case in
212		// moonbeam, as we are using IdentityMapping
213		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
214		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_derivative {
215			dest: transactor,
216			index,
217			fee: CurrencyPayment {
218				currency: Currency::AsMultiLocation(Box::new(xcm::VersionedLocation::from(
219					fee_asset,
220				))),
221				fee_amount: Some(fee_amount),
222			},
223			inner_call,
224			weight_info: TransactWeights {
225				transact_required_weight_at_most: Weight::from_parts(
226					weight,
227					DEFAULT_PROOF_SIZE.saturating_div(2),
228				),
229				overall_weight: Some(Limited(Weight::from_parts(
230					overall_weight,
231					DEFAULT_PROOF_SIZE,
232				))),
233			},
234			refund: false,
235		};
236
237		RuntimeHelper::<Runtime>::try_dispatch(
238			handle,
239			frame_system::RawOrigin::Signed(origin).into(),
240			call,
241			0,
242		)?;
243
244		Ok(())
245	}
246
247	pub(crate) fn transact_through_derivative(
248		handle: &mut impl PrecompileHandle,
249		transactor: u8,
250		index: u16,
251		currency_id: Address,
252		weight: u64,
253		inner_call: BoundedBytes<GetDataLimit>,
254	) -> EvmResult {
255		// No DB access before try_dispatch but lot of logical stuff
256		// To prevent spam, we charge an arbitrary amoun of gas
257		handle.record_cost(1000)?;
258
259		let transactor = transactor
260			.try_into()
261			.map_err(|_| RevertReason::custom("Non-existent transactor").in_field("transactor"))?;
262		let inner_call: Vec<_> = inner_call.into();
263
264		let to_account = Runtime::AddressMapping::into_account_id(currency_id.0);
265
266		// We convert the address into a currency
267		let currency_id: <Runtime as pallet_xcm_transactor::Config>::CurrencyId =
268			Runtime::account_to_currency_id(to_account)
269				.ok_or(revert("cannot convert into currency id"))?;
270
271		// Depending on the Runtime, this might involve a DB read. This is not the case in
272		// moonbeam, as we are using IdentityMapping
273		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
274		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_derivative {
275			dest: transactor,
276			index,
277			fee: CurrencyPayment {
278				currency: Currency::AsCurrencyId(currency_id),
279				fee_amount: None,
280			},
281			weight_info: TransactWeights {
282				transact_required_weight_at_most: Weight::from_parts(
283					weight,
284					DEFAULT_PROOF_SIZE.saturating_div(2),
285				),
286				overall_weight: None,
287			},
288			inner_call,
289			refund: false,
290		};
291
292		RuntimeHelper::<Runtime>::try_dispatch(
293			handle,
294			frame_system::RawOrigin::Signed(origin).into(),
295			call,
296			0,
297		)?;
298
299		Ok(())
300	}
301
302	pub(crate) fn transact_through_derivative_fee_weight(
303		handle: &mut impl PrecompileHandle,
304		transactor: u8,
305		index: u16,
306		fee_asset: Address,
307		weight: u64,
308		inner_call: BoundedBytes<GetDataLimit>,
309		fee_amount: u128,
310		overall_weight: u64,
311	) -> EvmResult {
312		// No DB access before try_dispatch but lot of logical stuff
313		// To prevent spam, we charge an arbitrary amoun of gas
314		handle.record_cost(1000)?;
315
316		let transactor = transactor
317			.try_into()
318			.map_err(|_| RevertReason::custom("Non-existent transactor").in_field("transactor"))?;
319		let inner_call: Vec<_> = inner_call.into();
320
321		let to_address: H160 = fee_asset.into();
322		let to_account = Runtime::AddressMapping::into_account_id(to_address);
323
324		// We convert the address into a currency
325		let currency_id: <Runtime as pallet_xcm_transactor::Config>::CurrencyId =
326			Runtime::account_to_currency_id(to_account)
327				.ok_or(revert("cannot convert into currency id"))?;
328
329		// Depending on the Runtime, this might involve a DB read. This is not the case in
330		// moonbeam, as we are using IdentityMapping
331		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
332		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_derivative {
333			dest: transactor,
334			index,
335			fee: CurrencyPayment {
336				currency: Currency::AsCurrencyId(currency_id),
337				fee_amount: Some(fee_amount),
338			},
339			weight_info: TransactWeights {
340				transact_required_weight_at_most: Weight::from_parts(
341					weight,
342					DEFAULT_PROOF_SIZE.saturating_div(2),
343				),
344				overall_weight: Some(Limited(Weight::from_parts(
345					overall_weight,
346					DEFAULT_PROOF_SIZE,
347				))),
348			},
349			inner_call,
350			refund: false,
351		};
352
353		RuntimeHelper::<Runtime>::try_dispatch(
354			handle,
355			frame_system::RawOrigin::Signed(origin).into(),
356			call,
357			0,
358		)?;
359
360		Ok(())
361	}
362
363	pub(crate) fn transact_through_signed_multilocation(
364		handle: &mut impl PrecompileHandle,
365		dest: Location,
366		fee_asset: Location,
367		weight: u64,
368		call: BoundedBytes<GetDataLimit>,
369	) -> EvmResult {
370		let call: Vec<_> = call.into();
371
372		// Depending on the Runtime, this might involve a DB read. This is not the case in
373		// moonbeam, as we are using IdentityMapping
374		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
375		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_signed {
376			dest: Box::new(xcm::VersionedLocation::from(dest)),
377			fee: CurrencyPayment {
378				currency: Currency::AsMultiLocation(Box::new(xcm::VersionedLocation::from(
379					fee_asset,
380				))),
381				fee_amount: None,
382			},
383			weight_info: TransactWeights {
384				transact_required_weight_at_most: Weight::from_parts(
385					weight,
386					DEFAULT_PROOF_SIZE.saturating_div(2),
387				),
388				overall_weight: None,
389			},
390			refund: false,
391			call,
392		};
393
394		RuntimeHelper::<Runtime>::try_dispatch(
395			handle,
396			frame_system::RawOrigin::Signed(origin).into(),
397			call,
398			0,
399		)?;
400
401		Ok(())
402	}
403
404	pub(crate) fn transact_through_signed_multilocation_fee_weight(
405		handle: &mut impl PrecompileHandle,
406		dest: Location,
407		fee_asset: Location,
408		weight: u64,
409		call: BoundedBytes<GetDataLimit>,
410		fee_amount: u128,
411		overall_weight: u64,
412	) -> EvmResult {
413		let call: Vec<_> = call.into();
414
415		// Depending on the Runtime, this might involve a DB read. This is not the case in
416		// moonbeam, as we are using IdentityMapping
417		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
418		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_signed {
419			dest: Box::new(xcm::VersionedLocation::from(dest)),
420			fee: CurrencyPayment {
421				currency: Currency::AsMultiLocation(Box::new(xcm::VersionedLocation::from(
422					fee_asset,
423				))),
424				fee_amount: Some(fee_amount),
425			},
426			weight_info: TransactWeights {
427				transact_required_weight_at_most: Weight::from_parts(
428					weight,
429					DEFAULT_PROOF_SIZE.saturating_div(2),
430				),
431				overall_weight: Some(Limited(Weight::from_parts(
432					overall_weight,
433					DEFAULT_PROOF_SIZE,
434				))),
435			},
436			refund: false,
437			call,
438		};
439
440		RuntimeHelper::<Runtime>::try_dispatch(
441			handle,
442			frame_system::RawOrigin::Signed(origin).into(),
443			call,
444			0,
445		)?;
446
447		Ok(())
448	}
449
450	pub(crate) fn transact_through_signed(
451		handle: &mut impl PrecompileHandle,
452		dest: Location,
453		fee_asset: Address,
454		weight: u64,
455		call: BoundedBytes<GetDataLimit>,
456	) -> EvmResult {
457		// No DB access before try_dispatch but lot of logical stuff
458		// To prevent spam, we charge an arbitrary amoun of gas
459		handle.record_cost(1000)?;
460
461		let to_address: H160 = fee_asset.into();
462		let to_account = Runtime::AddressMapping::into_account_id(to_address);
463
464		let call: Vec<_> = call.into();
465
466		// We convert the address into a currency
467		let currency_id: <Runtime as pallet_xcm_transactor::Config>::CurrencyId =
468			Runtime::account_to_currency_id(to_account)
469				.ok_or(revert("cannot convert into currency id"))?;
470
471		// Depending on the Runtime, this might involve a DB read. This is not the case in
472		// moonbeam, as we are using IdentityMapping
473		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
474		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_signed {
475			dest: Box::new(xcm::VersionedLocation::from(dest)),
476			fee: CurrencyPayment {
477				currency: Currency::AsCurrencyId(currency_id),
478				fee_amount: None,
479			},
480			weight_info: TransactWeights {
481				transact_required_weight_at_most: Weight::from_parts(
482					weight,
483					DEFAULT_PROOF_SIZE.saturating_div(2),
484				),
485				overall_weight: None,
486			},
487			refund: false,
488			call,
489		};
490
491		RuntimeHelper::<Runtime>::try_dispatch(
492			handle,
493			frame_system::RawOrigin::Signed(origin).into(),
494			call,
495			0,
496		)?;
497
498		Ok(())
499	}
500
501	pub(crate) fn transact_through_signed_fee_weight(
502		handle: &mut impl PrecompileHandle,
503		dest: Location,
504		fee_asset: Address,
505		weight: u64,
506		call: BoundedBytes<GetDataLimit>,
507		fee_amount: u128,
508		overall_weight: u64,
509	) -> EvmResult {
510		// No DB access before try_dispatch but lot of logical stuff
511		// To prevent spam, we charge an arbitrary amoun of gas
512		handle.record_cost(1000)?;
513
514		let to_address: H160 = fee_asset.into();
515		let to_account = Runtime::AddressMapping::into_account_id(to_address);
516
517		let call: Vec<_> = call.into();
518
519		// We convert the address into a currency
520		let currency_id: <Runtime as pallet_xcm_transactor::Config>::CurrencyId =
521			Runtime::account_to_currency_id(to_account)
522				.ok_or(revert("cannot convert into currency id"))?;
523
524		// Depending on the Runtime, this might involve a DB read. This is not the case in
525		// moonbeam, as we are using IdentityMapping
526		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
527		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_signed {
528			dest: Box::new(xcm::VersionedLocation::from(dest)),
529			fee: CurrencyPayment {
530				currency: Currency::AsCurrencyId(currency_id),
531				fee_amount: Some(fee_amount),
532			},
533			weight_info: TransactWeights {
534				transact_required_weight_at_most: Weight::from_parts(
535					weight,
536					DEFAULT_PROOF_SIZE.saturating_div(2),
537				),
538				overall_weight: Some(Limited(Weight::from_parts(
539					overall_weight,
540					DEFAULT_PROOF_SIZE,
541				))),
542			},
543			refund: false,
544			call,
545		};
546
547		RuntimeHelper::<Runtime>::try_dispatch(
548			handle,
549			frame_system::RawOrigin::Signed(origin).into(),
550			call,
551			0,
552		)?;
553
554		Ok(())
555	}
556
557	pub(crate) fn encode_utility_as_derivative(
558		handle: &mut impl PrecompileHandle,
559		transactor: u8,
560		index: u16,
561		inner_call: BoundedBytes<GetDataLimit>,
562	) -> EvmResult<UnboundedBytes> {
563		// There is no DB read in this function,
564		// we just account an arbitrary amount of gas to prevent spam
565		// TODO replace by proper benchmarks
566		handle.record_cost(1000)?;
567
568		let transactor: TransactorOf<Runtime> = transactor
569			.try_into()
570			.map_err(|_| RevertReason::custom("Non-existent transactor").in_field("transactor"))?;
571
572		let encoded = <pallet_xcm_transactor::Pallet<Runtime> as UtilityEncodeCall>::encode_call(
573			transactor,
574			UtilityAvailableCalls::AsDerivative(index, inner_call.into()),
575		)
576		.as_slice()
577		.into();
578		Ok(encoded)
579	}
580
581	pub(crate) fn transact_info_with_signed_v3(
582		handle: &mut impl PrecompileHandle,
583		multilocation: Location,
584	) -> EvmResult<(Weight, Weight, Weight)> {
585		// fetch data from pallet
586		// storage item: TransactInfoWithWeightLimit: Blake2_128(16) + Location
587		// + RemoteTransactInfoWithMaxWeight
588		handle.record_db_read::<Runtime>(
589			16 + Location::max_encoded_len() + RemoteTransactInfoWithMaxWeight::max_encoded_len(),
590		)?;
591
592		let remote_transact_info: RemoteTransactInfoWithMaxWeight =
593			pallet_xcm_transactor::Pallet::<Runtime>::transact_info(multilocation)
594				.ok_or(revert("Transact Info not set"))?;
595
596		let transact_extra_weight_signed = remote_transact_info
597			.transact_extra_weight_signed
598			.unwrap_or(Weight::zero());
599
600		Ok((
601			remote_transact_info.transact_extra_weight,
602			transact_extra_weight_signed,
603			remote_transact_info.max_weight,
604		))
605	}
606
607	pub(crate) fn transact_through_derivative_multilocation_v3(
608		handle: &mut impl PrecompileHandle,
609		transactor: u8,
610		index: u16,
611		fee_asset: Location,
612		weight: Weight,
613		inner_call: BoundedBytes<GetDataLimit>,
614		fee_amount: u128,
615		overall_weight: Weight,
616		refund: bool,
617	) -> EvmResult {
618		let transactor = transactor
619			.try_into()
620			.map_err(|_| RevertReason::custom("Non-existent transactor").in_field("transactor"))?;
621
622		let inner_call: Vec<_> = inner_call.into();
623
624		let overall_weight_limit = match overall_weight.ref_time() {
625			u64::MAX => Unlimited,
626			_ => Limited(overall_weight),
627		};
628
629		// Depending on the Runtime, this might involve a DB read. This is not the case in
630		// moonbeam, as we are using IdentityMapping
631		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
632		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_derivative {
633			dest: transactor,
634			index,
635			fee: CurrencyPayment {
636				currency: Currency::AsMultiLocation(Box::new(xcm::VersionedLocation::from(
637					fee_asset,
638				))),
639				fee_amount: Some(fee_amount),
640			},
641			inner_call,
642			weight_info: TransactWeights {
643				transact_required_weight_at_most: weight,
644				overall_weight: Some(overall_weight_limit),
645			},
646			refund,
647		};
648
649		RuntimeHelper::<Runtime>::try_dispatch(
650			handle,
651			frame_system::RawOrigin::Signed(origin).into(),
652			call,
653			0,
654		)?;
655
656		Ok(())
657	}
658
659	pub(crate) fn transact_through_derivative_v3(
660		handle: &mut impl PrecompileHandle,
661		transactor: u8,
662		index: u16,
663		fee_asset: Address,
664		weight: Weight,
665		inner_call: BoundedBytes<GetDataLimit>,
666		fee_amount: u128,
667		overall_weight: Weight,
668		refund: bool,
669	) -> EvmResult {
670		// No DB access before try_dispatch but lot of logical stuff
671		// To prevent spam, we charge an arbitrary amoun of gas
672		handle.record_cost(1000)?;
673
674		let transactor = transactor
675			.try_into()
676			.map_err(|_| RevertReason::custom("Non-existent transactor").in_field("transactor"))?;
677		let inner_call: Vec<_> = inner_call.into();
678
679		let to_address: H160 = fee_asset.into();
680		let to_account = Runtime::AddressMapping::into_account_id(to_address);
681
682		// We convert the address into a currency
683		let currency_id: <Runtime as pallet_xcm_transactor::Config>::CurrencyId =
684			Runtime::account_to_currency_id(to_account)
685				.ok_or(revert("cannot convert into currency id"))?;
686
687		let overall_weight_limit = match overall_weight.ref_time() {
688			u64::MAX => Unlimited,
689			_ => Limited(overall_weight),
690		};
691
692		// Depending on the Runtime, this might involve a DB read. This is not the case in
693		// moonbeam, as we are using IdentityMapping
694		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
695		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_derivative {
696			dest: transactor,
697			index,
698			fee: CurrencyPayment {
699				currency: Currency::AsCurrencyId(currency_id),
700				fee_amount: Some(fee_amount),
701			},
702			weight_info: TransactWeights {
703				transact_required_weight_at_most: weight,
704				overall_weight: Some(overall_weight_limit),
705			},
706			inner_call,
707			refund,
708		};
709
710		RuntimeHelper::<Runtime>::try_dispatch(
711			handle,
712			frame_system::RawOrigin::Signed(origin).into(),
713			call,
714			0,
715		)?;
716
717		Ok(())
718	}
719
720	pub(crate) fn transact_through_signed_multilocation_v3(
721		handle: &mut impl PrecompileHandle,
722		dest: Location,
723		fee_asset: Location,
724		weight: Weight,
725		call: BoundedBytes<GetDataLimit>,
726		fee_amount: u128,
727		overall_weight: Weight,
728		refund: bool,
729	) -> EvmResult {
730		let call: Vec<_> = call.into();
731
732		let overall_weight_limit = match overall_weight.ref_time() {
733			u64::MAX => Unlimited,
734			_ => Limited(overall_weight),
735		};
736
737		// Depending on the Runtime, this might involve a DB read. This is not the case in
738		// moonbeam, as we are using IdentityMapping
739		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
740		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_signed {
741			dest: Box::new(xcm::VersionedLocation::from(dest)),
742			fee: CurrencyPayment {
743				currency: Currency::AsMultiLocation(Box::new(xcm::VersionedLocation::from(
744					fee_asset,
745				))),
746				fee_amount: Some(fee_amount),
747			},
748			weight_info: TransactWeights {
749				transact_required_weight_at_most: weight,
750				overall_weight: Some(overall_weight_limit),
751			},
752			refund,
753			call,
754		};
755
756		RuntimeHelper::<Runtime>::try_dispatch(
757			handle,
758			frame_system::RawOrigin::Signed(origin).into(),
759			call,
760			0,
761		)?;
762
763		Ok(())
764	}
765
766	pub(crate) fn transact_through_signed_v3(
767		handle: &mut impl PrecompileHandle,
768		dest: Location,
769		fee_asset: Address,
770		weight: Weight,
771		call: BoundedBytes<GetDataLimit>,
772		fee_amount: u128,
773		overall_weight: Weight,
774		refund: bool,
775	) -> EvmResult {
776		// No DB access before try_dispatch but lot of logical stuff
777		// To prevent spam, we charge an arbitrary amoun of gas
778		handle.record_cost(1000)?;
779
780		let to_address: H160 = fee_asset.into();
781		let to_account = Runtime::AddressMapping::into_account_id(to_address);
782
783		let call: Vec<_> = call.into();
784
785		// We convert the address into a currency
786		let currency_id: <Runtime as pallet_xcm_transactor::Config>::CurrencyId =
787			Runtime::account_to_currency_id(to_account)
788				.ok_or(revert("cannot convert into currency id"))?;
789
790		let overall_weight_limit = match overall_weight.ref_time() {
791			u64::MAX => Unlimited,
792			_ => Limited(overall_weight),
793		};
794
795		// Depending on the Runtime, this might involve a DB read. This is not the case in
796		// moonbeam, as we are using IdentityMapping
797		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
798		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_signed {
799			dest: Box::new(xcm::VersionedLocation::from(dest)),
800			fee: CurrencyPayment {
801				currency: Currency::AsCurrencyId(currency_id),
802				fee_amount: Some(fee_amount),
803			},
804			weight_info: TransactWeights {
805				transact_required_weight_at_most: weight,
806				overall_weight: Some(overall_weight_limit),
807			},
808			refund,
809			call,
810		};
811
812		RuntimeHelper::<Runtime>::try_dispatch(
813			handle,
814			frame_system::RawOrigin::Signed(origin).into(),
815			call,
816			0,
817		)?;
818
819		Ok(())
820	}
821}