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

            
19
use fp_evm::PrecompileHandle;
20
use frame_support::{
21
	dispatch::{GetDispatchInfo, PostDispatchInfo},
22
	traits::ConstU32,
23
};
24
use pallet_evm::AddressMapping;
25
use pallet_xcm_transactor::{
26
	Currency, CurrencyPayment, RemoteTransactInfoWithMaxWeight, TransactWeights,
27
};
28
use precompile_utils::prelude::*;
29
use sp_core::{MaxEncodedLen, H160, U256};
30
use sp_runtime::traits::Dispatchable;
31
use sp_std::{
32
	boxed::Box,
33
	convert::{TryFrom, TryInto},
34
	marker::PhantomData,
35
	vec::Vec,
36
};
37
use sp_weights::Weight;
38
use xcm::latest::prelude::*;
39
use xcm::latest::Location;
40
use xcm_primitives::{
41
	AccountIdToCurrencyId, UtilityAvailableCalls, UtilityEncodeCall, DEFAULT_PROOF_SIZE,
42
};
43

            
44
/// A precompile to wrap the functionality from xcm transactor
45
pub struct XcmTransactorWrapper<Runtime>(PhantomData<Runtime>);
46

            
47
pub type TransactorOf<Runtime> = <Runtime as pallet_xcm_transactor::Config>::Transactor;
48
pub type CurrencyIdOf<Runtime> = <Runtime as pallet_xcm_transactor::Config>::CurrencyId;
49

            
50
pub const CALL_DATA_LIMIT: u32 = 2u32.pow(16);
51
pub type GetDataLimit = ConstU32<CALL_DATA_LIMIT>;
52

            
53
impl<Runtime> XcmTransactorWrapper<Runtime>
54
where
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
2
	pub(crate) fn account_index(
64
2
		handle: &mut impl PrecompileHandle,
65
2
		index: u16,
66
2
	) -> EvmResult<Address> {
67
		// storage item: IndexToAccount: Blake2_128(16) + u16(2) + AccountId(20)
68
2
		handle.record_db_read::<Runtime>(38)?;
69

            
70
		// fetch data from pallet
71
2
		let account: H160 = pallet_xcm_transactor::Pallet::<Runtime>::index_to_account(index)
72
2
			.ok_or(revert("No index assigned"))?
73
1
			.into();
74

            
75
1
		Ok(account.into())
76
2
	}
77

            
78
2
	pub(crate) fn transact_info(
79
2
		handle: &mut impl PrecompileHandle,
80
2
		multilocation: Location,
81
2
	) -> EvmResult<(u64, U256, u64)> {
82
		// fetch data from pallet
83
		// storage item: TransactInfoWithWeightLimit: Blake2_128(16) + Location
84
		// + RemoteTransactInfoWithMaxWeight
85
2
		handle.record_db_read::<Runtime>(
86
2
			16 + Location::max_encoded_len() + RemoteTransactInfoWithMaxWeight::max_encoded_len(),
87
		)?;
88
1
		let remote_transact_info: RemoteTransactInfoWithMaxWeight =
89
2
			pallet_xcm_transactor::Pallet::<Runtime>::transact_info(&multilocation)
90
2
				.ok_or(revert("Transact Info not set"))?;
91

            
92
		// fetch data from pallet
93
		// storage item: AssetTypeUnitsPerSecond: Blake2_128(16) + Location + u128(16)
94
1
		handle.record_db_read::<Runtime>(32 + Location::max_encoded_len())?;
95
1
		let fee_per_second: u128 =
96
1
			pallet_xcm_transactor::Pallet::<Runtime>::dest_asset_fee_per_second(&multilocation)
97
1
				.ok_or(revert("Fee Per Second not set"))?;
98

            
99
1
		Ok((
100
1
			remote_transact_info.transact_extra_weight.ref_time(),
101
1
			fee_per_second.into(),
102
1
			remote_transact_info.max_weight.ref_time(),
103
1
		))
104
2
	}
105

            
106
2
	pub(crate) fn transact_info_with_signed(
107
2
		handle: &mut impl PrecompileHandle,
108
2
		multilocation: Location,
109
2
	) -> EvmResult<(u64, u64, u64)> {
110
		// fetch data from pallet
111
		// storage item: TransactInfoWithWeightLimit: Blake2_128(16) + Location
112
		// + RemoteTransactInfoWithMaxWeight
113
2
		handle.record_db_read::<Runtime>(
114
2
			16 + Location::max_encoded_len() + RemoteTransactInfoWithMaxWeight::max_encoded_len(),
115
		)?;
116
1
		let remote_transact_info: RemoteTransactInfoWithMaxWeight =
117
2
			pallet_xcm_transactor::Pallet::<Runtime>::transact_info(multilocation)
118
2
				.ok_or(revert("Transact Info not set"))?;
119

            
120
1
		let transact_extra_weight_signed = remote_transact_info
121
1
			.transact_extra_weight_signed
122
1
			.unwrap_or(Weight::zero());
123

            
124
1
		Ok((
125
1
			remote_transact_info.transact_extra_weight.ref_time(),
126
1
			transact_extra_weight_signed.ref_time(),
127
1
			remote_transact_info.max_weight.ref_time(),
128
1
		))
129
2
	}
130

            
131
2
	pub(crate) fn fee_per_second(
132
2
		handle: &mut impl PrecompileHandle,
133
2
		location: Location,
134
2
	) -> EvmResult<U256> {
135
		// fetch data from pallet
136
		// storage item: AssetTypeUnitsPerSecond: Blake2_128(16) + Location + u128(16)
137
2
		handle.record_db_read::<Runtime>(32 + Location::max_encoded_len())?;
138
1
		let fee_per_second: u128 =
139
2
			pallet_xcm_transactor::Pallet::<Runtime>::dest_asset_fee_per_second(location)
140
2
				.ok_or(revert("Fee Per Second not set"))?;
141

            
142
1
		Ok(fee_per_second.into())
143
2
	}
144

            
145
1
	pub(crate) fn transact_through_derivative_multilocation(
146
1
		handle: &mut impl PrecompileHandle,
147
1
		transactor: u8,
148
1
		index: u16,
149
1
		fee_asset: Location,
150
1
		weight: u64,
151
1
		inner_call: BoundedBytes<GetDataLimit>,
152
1
	) -> EvmResult {
153
1
		let transactor = transactor
154
1
			.try_into()
155
1
			.map_err(|_| RevertReason::custom("Non-existent transactor").in_field("transactor"))?;
156
1
		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
1
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
161
1
		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_derivative {
162
1
			dest: transactor,
163
1
			index,
164
1
			fee: CurrencyPayment {
165
1
				currency: Currency::AsMultiLocation(Box::new(xcm::VersionedLocation::from(
166
1
					fee_asset,
167
1
				))),
168
1
				fee_amount: None,
169
1
			},
170
1
			inner_call,
171
1
			weight_info: TransactWeights {
172
1
				// TODO overall weight None means we will retrieve the allowed proof size from storage.
173
1
				// In the v2 to v3 migration we set this value to DEFAULT_PROOF_SIZE, so setting
174
1
				// require_weight_at_most to DEFAULT_PROOF_SIZE/2 makes sense. Although we might
175
1
				// want to revisit this to use whatever storage value there is and divide it by 2.
176
1
				transact_required_weight_at_most: Weight::from_parts(
177
1
					weight,
178
1
					DEFAULT_PROOF_SIZE.saturating_div(2),
179
1
				),
180
1
				overall_weight: None,
181
1
			},
182
1
			refund: false,
183
1
		};
184

            
185
1
		RuntimeHelper::<Runtime>::try_dispatch(
186
1
			handle,
187
1
			frame_system::RawOrigin::Signed(origin).into(),
188
1
			call,
189
			0,
190
		)?;
191

            
192
1
		Ok(())
193
1
	}
194

            
195
1
	pub(crate) fn transact_through_derivative_multilocation_fee_weight(
196
1
		handle: &mut impl PrecompileHandle,
197
1
		transactor: u8,
198
1
		index: u16,
199
1
		fee_asset: Location,
200
1
		weight: u64,
201
1
		inner_call: BoundedBytes<GetDataLimit>,
202
1
		fee_amount: u128,
203
1
		overall_weight: u64,
204
1
	) -> EvmResult {
205
1
		let transactor = transactor
206
1
			.try_into()
207
1
			.map_err(|_| RevertReason::custom("Non-existent transactor").in_field("transactor"))?;
208

            
209
1
		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
1
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
214
1
		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_derivative {
215
1
			dest: transactor,
216
1
			index,
217
1
			fee: CurrencyPayment {
218
1
				currency: Currency::AsMultiLocation(Box::new(xcm::VersionedLocation::from(
219
1
					fee_asset,
220
1
				))),
221
1
				fee_amount: Some(fee_amount),
222
1
			},
223
1
			inner_call,
224
1
			weight_info: TransactWeights {
225
1
				transact_required_weight_at_most: Weight::from_parts(
226
1
					weight,
227
1
					DEFAULT_PROOF_SIZE.saturating_div(2),
228
1
				),
229
1
				overall_weight: Some(Limited(Weight::from_parts(
230
1
					overall_weight,
231
1
					DEFAULT_PROOF_SIZE,
232
1
				))),
233
1
			},
234
1
			refund: false,
235
1
		};
236

            
237
1
		RuntimeHelper::<Runtime>::try_dispatch(
238
1
			handle,
239
1
			frame_system::RawOrigin::Signed(origin).into(),
240
1
			call,
241
			0,
242
		)?;
243

            
244
1
		Ok(())
245
1
	}
246

            
247
1
	pub(crate) fn transact_through_derivative(
248
1
		handle: &mut impl PrecompileHandle,
249
1
		transactor: u8,
250
1
		index: u16,
251
1
		currency_id: Address,
252
1
		weight: u64,
253
1
		inner_call: BoundedBytes<GetDataLimit>,
254
1
	) -> 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
1
		handle.record_cost(1000)?;
258

            
259
1
		let transactor = transactor
260
1
			.try_into()
261
1
			.map_err(|_| RevertReason::custom("Non-existent transactor").in_field("transactor"))?;
262
1
		let inner_call: Vec<_> = inner_call.into();
263

            
264
1
		let to_account = Runtime::AddressMapping::into_account_id(currency_id.0);
265

            
266
		// We convert the address into a currency
267
1
		let currency_id: <Runtime as pallet_xcm_transactor::Config>::CurrencyId =
268
1
			Runtime::account_to_currency_id(to_account)
269
1
				.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
1
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
274
1
		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_derivative {
275
1
			dest: transactor,
276
1
			index,
277
1
			fee: CurrencyPayment {
278
1
				currency: Currency::AsCurrencyId(currency_id),
279
1
				fee_amount: None,
280
1
			},
281
1
			weight_info: TransactWeights {
282
1
				transact_required_weight_at_most: Weight::from_parts(
283
1
					weight,
284
1
					DEFAULT_PROOF_SIZE.saturating_div(2),
285
1
				),
286
1
				overall_weight: None,
287
1
			},
288
1
			inner_call,
289
1
			refund: false,
290
1
		};
291

            
292
1
		RuntimeHelper::<Runtime>::try_dispatch(
293
1
			handle,
294
1
			frame_system::RawOrigin::Signed(origin).into(),
295
1
			call,
296
			0,
297
		)?;
298

            
299
1
		Ok(())
300
1
	}
301

            
302
1
	pub(crate) fn transact_through_derivative_fee_weight(
303
1
		handle: &mut impl PrecompileHandle,
304
1
		transactor: u8,
305
1
		index: u16,
306
1
		fee_asset: Address,
307
1
		weight: u64,
308
1
		inner_call: BoundedBytes<GetDataLimit>,
309
1
		fee_amount: u128,
310
1
		overall_weight: u64,
311
1
	) -> 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
1
		handle.record_cost(1000)?;
315

            
316
1
		let transactor = transactor
317
1
			.try_into()
318
1
			.map_err(|_| RevertReason::custom("Non-existent transactor").in_field("transactor"))?;
319
1
		let inner_call: Vec<_> = inner_call.into();
320

            
321
1
		let to_address: H160 = fee_asset.into();
322
1
		let to_account = Runtime::AddressMapping::into_account_id(to_address);
323

            
324
		// We convert the address into a currency
325
1
		let currency_id: <Runtime as pallet_xcm_transactor::Config>::CurrencyId =
326
1
			Runtime::account_to_currency_id(to_account)
327
1
				.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
1
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
332
1
		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_derivative {
333
1
			dest: transactor,
334
1
			index,
335
1
			fee: CurrencyPayment {
336
1
				currency: Currency::AsCurrencyId(currency_id),
337
1
				fee_amount: Some(fee_amount),
338
1
			},
339
1
			weight_info: TransactWeights {
340
1
				transact_required_weight_at_most: Weight::from_parts(
341
1
					weight,
342
1
					DEFAULT_PROOF_SIZE.saturating_div(2),
343
1
				),
344
1
				overall_weight: Some(Limited(Weight::from_parts(
345
1
					overall_weight,
346
1
					DEFAULT_PROOF_SIZE,
347
1
				))),
348
1
			},
349
1
			inner_call,
350
1
			refund: false,
351
1
		};
352

            
353
1
		RuntimeHelper::<Runtime>::try_dispatch(
354
1
			handle,
355
1
			frame_system::RawOrigin::Signed(origin).into(),
356
1
			call,
357
			0,
358
		)?;
359

            
360
1
		Ok(())
361
1
	}
362

            
363
2
	pub(crate) fn transact_through_signed_multilocation(
364
2
		handle: &mut impl PrecompileHandle,
365
2
		dest: Location,
366
2
		fee_asset: Location,
367
2
		weight: u64,
368
2
		call: BoundedBytes<GetDataLimit>,
369
2
	) -> EvmResult {
370
2
		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
2
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
375
2
		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_signed {
376
2
			dest: Box::new(xcm::VersionedLocation::from(dest)),
377
2
			fee: CurrencyPayment {
378
2
				currency: Currency::AsMultiLocation(Box::new(xcm::VersionedLocation::from(
379
2
					fee_asset,
380
2
				))),
381
2
				fee_amount: None,
382
2
			},
383
2
			weight_info: TransactWeights {
384
2
				transact_required_weight_at_most: Weight::from_parts(
385
2
					weight,
386
2
					DEFAULT_PROOF_SIZE.saturating_div(2),
387
2
				),
388
2
				overall_weight: None,
389
2
			},
390
2
			refund: false,
391
2
			call,
392
2
		};
393

            
394
2
		RuntimeHelper::<Runtime>::try_dispatch(
395
2
			handle,
396
2
			frame_system::RawOrigin::Signed(origin).into(),
397
2
			call,
398
			0,
399
		)?;
400

            
401
2
		Ok(())
402
2
	}
403

            
404
7
	pub(crate) fn transact_through_signed_multilocation_fee_weight(
405
7
		handle: &mut impl PrecompileHandle,
406
7
		dest: Location,
407
7
		fee_asset: Location,
408
7
		weight: u64,
409
7
		call: BoundedBytes<GetDataLimit>,
410
7
		fee_amount: u128,
411
7
		overall_weight: u64,
412
7
	) -> EvmResult {
413
7
		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
7
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
418
7
		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_signed {
419
7
			dest: Box::new(xcm::VersionedLocation::from(dest)),
420
7
			fee: CurrencyPayment {
421
7
				currency: Currency::AsMultiLocation(Box::new(xcm::VersionedLocation::from(
422
7
					fee_asset,
423
7
				))),
424
7
				fee_amount: Some(fee_amount),
425
7
			},
426
7
			weight_info: TransactWeights {
427
7
				transact_required_weight_at_most: Weight::from_parts(
428
7
					weight,
429
7
					DEFAULT_PROOF_SIZE.saturating_div(2),
430
7
				),
431
7
				overall_weight: Some(Limited(Weight::from_parts(
432
7
					overall_weight,
433
7
					DEFAULT_PROOF_SIZE,
434
7
				))),
435
7
			},
436
7
			refund: false,
437
7
			call,
438
7
		};
439

            
440
7
		RuntimeHelper::<Runtime>::try_dispatch(
441
7
			handle,
442
7
			frame_system::RawOrigin::Signed(origin).into(),
443
7
			call,
444
			0,
445
3
		)?;
446

            
447
4
		Ok(())
448
7
	}
449

            
450
1
	pub(crate) fn transact_through_signed(
451
1
		handle: &mut impl PrecompileHandle,
452
1
		dest: Location,
453
1
		fee_asset: Address,
454
1
		weight: u64,
455
1
		call: BoundedBytes<GetDataLimit>,
456
1
	) -> 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
1
		handle.record_cost(1000)?;
460

            
461
1
		let to_address: H160 = fee_asset.into();
462
1
		let to_account = Runtime::AddressMapping::into_account_id(to_address);
463

            
464
1
		let call: Vec<_> = call.into();
465

            
466
		// We convert the address into a currency
467
1
		let currency_id: <Runtime as pallet_xcm_transactor::Config>::CurrencyId =
468
1
			Runtime::account_to_currency_id(to_account)
469
1
				.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
1
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
474
1
		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_signed {
475
1
			dest: Box::new(xcm::VersionedLocation::from(dest)),
476
1
			fee: CurrencyPayment {
477
1
				currency: Currency::AsCurrencyId(currency_id),
478
1
				fee_amount: None,
479
1
			},
480
1
			weight_info: TransactWeights {
481
1
				transact_required_weight_at_most: Weight::from_parts(
482
1
					weight,
483
1
					DEFAULT_PROOF_SIZE.saturating_div(2),
484
1
				),
485
1
				overall_weight: None,
486
1
			},
487
1
			refund: false,
488
1
			call,
489
1
		};
490

            
491
1
		RuntimeHelper::<Runtime>::try_dispatch(
492
1
			handle,
493
1
			frame_system::RawOrigin::Signed(origin).into(),
494
1
			call,
495
			0,
496
		)?;
497

            
498
1
		Ok(())
499
1
	}
500

            
501
1
	pub(crate) fn transact_through_signed_fee_weight(
502
1
		handle: &mut impl PrecompileHandle,
503
1
		dest: Location,
504
1
		fee_asset: Address,
505
1
		weight: u64,
506
1
		call: BoundedBytes<GetDataLimit>,
507
1
		fee_amount: u128,
508
1
		overall_weight: u64,
509
1
	) -> 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
1
		handle.record_cost(1000)?;
513

            
514
1
		let to_address: H160 = fee_asset.into();
515
1
		let to_account = Runtime::AddressMapping::into_account_id(to_address);
516

            
517
1
		let call: Vec<_> = call.into();
518

            
519
		// We convert the address into a currency
520
1
		let currency_id: <Runtime as pallet_xcm_transactor::Config>::CurrencyId =
521
1
			Runtime::account_to_currency_id(to_account)
522
1
				.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
1
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
527
1
		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_signed {
528
1
			dest: Box::new(xcm::VersionedLocation::from(dest)),
529
1
			fee: CurrencyPayment {
530
1
				currency: Currency::AsCurrencyId(currency_id),
531
1
				fee_amount: Some(fee_amount),
532
1
			},
533
1
			weight_info: TransactWeights {
534
1
				transact_required_weight_at_most: Weight::from_parts(
535
1
					weight,
536
1
					DEFAULT_PROOF_SIZE.saturating_div(2),
537
1
				),
538
1
				overall_weight: Some(Limited(Weight::from_parts(
539
1
					overall_weight,
540
1
					DEFAULT_PROOF_SIZE,
541
1
				))),
542
1
			},
543
1
			refund: false,
544
1
			call,
545
1
		};
546

            
547
1
		RuntimeHelper::<Runtime>::try_dispatch(
548
1
			handle,
549
1
			frame_system::RawOrigin::Signed(origin).into(),
550
1
			call,
551
			0,
552
		)?;
553

            
554
1
		Ok(())
555
1
	}
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
2
	pub(crate) fn transact_info_with_signed_v3(
582
2
		handle: &mut impl PrecompileHandle,
583
2
		multilocation: Location,
584
2
	) -> EvmResult<(Weight, Weight, Weight)> {
585
		// fetch data from pallet
586
		// storage item: TransactInfoWithWeightLimit: Blake2_128(16) + Location
587
		// + RemoteTransactInfoWithMaxWeight
588
2
		handle.record_db_read::<Runtime>(
589
2
			16 + Location::max_encoded_len() + RemoteTransactInfoWithMaxWeight::max_encoded_len(),
590
		)?;
591

            
592
1
		let remote_transact_info: RemoteTransactInfoWithMaxWeight =
593
2
			pallet_xcm_transactor::Pallet::<Runtime>::transact_info(multilocation)
594
2
				.ok_or(revert("Transact Info not set"))?;
595

            
596
1
		let transact_extra_weight_signed = remote_transact_info
597
1
			.transact_extra_weight_signed
598
1
			.unwrap_or(Weight::zero());
599

            
600
1
		Ok((
601
1
			remote_transact_info.transact_extra_weight,
602
1
			transact_extra_weight_signed,
603
1
			remote_transact_info.max_weight,
604
1
		))
605
2
	}
606

            
607
1
	pub(crate) fn transact_through_derivative_multilocation_v3(
608
1
		handle: &mut impl PrecompileHandle,
609
1
		transactor: u8,
610
1
		index: u16,
611
1
		fee_asset: Location,
612
1
		weight: Weight,
613
1
		inner_call: BoundedBytes<GetDataLimit>,
614
1
		fee_amount: u128,
615
1
		overall_weight: Weight,
616
1
		refund: bool,
617
1
	) -> EvmResult {
618
1
		let transactor = transactor
619
1
			.try_into()
620
1
			.map_err(|_| RevertReason::custom("Non-existent transactor").in_field("transactor"))?;
621

            
622
1
		let inner_call: Vec<_> = inner_call.into();
623

            
624
1
		let overall_weight_limit = match overall_weight.ref_time() {
625
			u64::MAX => Unlimited,
626
1
			_ => 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
1
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
632
1
		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_derivative {
633
1
			dest: transactor,
634
1
			index,
635
1
			fee: CurrencyPayment {
636
1
				currency: Currency::AsMultiLocation(Box::new(xcm::VersionedLocation::from(
637
1
					fee_asset,
638
1
				))),
639
1
				fee_amount: Some(fee_amount),
640
1
			},
641
1
			inner_call,
642
1
			weight_info: TransactWeights {
643
1
				transact_required_weight_at_most: weight,
644
1
				overall_weight: Some(overall_weight_limit),
645
1
			},
646
1
			refund,
647
1
		};
648

            
649
1
		RuntimeHelper::<Runtime>::try_dispatch(
650
1
			handle,
651
1
			frame_system::RawOrigin::Signed(origin).into(),
652
1
			call,
653
			0,
654
		)?;
655

            
656
1
		Ok(())
657
1
	}
658

            
659
1
	pub(crate) fn transact_through_derivative_v3(
660
1
		handle: &mut impl PrecompileHandle,
661
1
		transactor: u8,
662
1
		index: u16,
663
1
		fee_asset: Address,
664
1
		weight: Weight,
665
1
		inner_call: BoundedBytes<GetDataLimit>,
666
1
		fee_amount: u128,
667
1
		overall_weight: Weight,
668
1
		refund: bool,
669
1
	) -> 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
1
		handle.record_cost(1000)?;
673

            
674
1
		let transactor = transactor
675
1
			.try_into()
676
1
			.map_err(|_| RevertReason::custom("Non-existent transactor").in_field("transactor"))?;
677
1
		let inner_call: Vec<_> = inner_call.into();
678

            
679
1
		let to_address: H160 = fee_asset.into();
680
1
		let to_account = Runtime::AddressMapping::into_account_id(to_address);
681

            
682
		// We convert the address into a currency
683
1
		let currency_id: <Runtime as pallet_xcm_transactor::Config>::CurrencyId =
684
1
			Runtime::account_to_currency_id(to_account)
685
1
				.ok_or(revert("cannot convert into currency id"))?;
686

            
687
1
		let overall_weight_limit = match overall_weight.ref_time() {
688
			u64::MAX => Unlimited,
689
1
			_ => 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
1
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
695
1
		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_derivative {
696
1
			dest: transactor,
697
1
			index,
698
1
			fee: CurrencyPayment {
699
1
				currency: Currency::AsCurrencyId(currency_id),
700
1
				fee_amount: Some(fee_amount),
701
1
			},
702
1
			weight_info: TransactWeights {
703
1
				transact_required_weight_at_most: weight,
704
1
				overall_weight: Some(overall_weight_limit),
705
1
			},
706
1
			inner_call,
707
1
			refund,
708
1
		};
709

            
710
1
		RuntimeHelper::<Runtime>::try_dispatch(
711
1
			handle,
712
1
			frame_system::RawOrigin::Signed(origin).into(),
713
1
			call,
714
			0,
715
		)?;
716

            
717
1
		Ok(())
718
1
	}
719

            
720
1
	pub(crate) fn transact_through_signed_multilocation_v3(
721
1
		handle: &mut impl PrecompileHandle,
722
1
		dest: Location,
723
1
		fee_asset: Location,
724
1
		weight: Weight,
725
1
		call: BoundedBytes<GetDataLimit>,
726
1
		fee_amount: u128,
727
1
		overall_weight: Weight,
728
1
		refund: bool,
729
1
	) -> EvmResult {
730
1
		let call: Vec<_> = call.into();
731

            
732
1
		let overall_weight_limit = match overall_weight.ref_time() {
733
			u64::MAX => Unlimited,
734
1
			_ => 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
1
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
740
1
		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_signed {
741
1
			dest: Box::new(xcm::VersionedLocation::from(dest)),
742
1
			fee: CurrencyPayment {
743
1
				currency: Currency::AsMultiLocation(Box::new(xcm::VersionedLocation::from(
744
1
					fee_asset,
745
1
				))),
746
1
				fee_amount: Some(fee_amount),
747
1
			},
748
1
			weight_info: TransactWeights {
749
1
				transact_required_weight_at_most: weight,
750
1
				overall_weight: Some(overall_weight_limit),
751
1
			},
752
1
			refund,
753
1
			call,
754
1
		};
755

            
756
1
		RuntimeHelper::<Runtime>::try_dispatch(
757
1
			handle,
758
1
			frame_system::RawOrigin::Signed(origin).into(),
759
1
			call,
760
			0,
761
		)?;
762

            
763
1
		Ok(())
764
1
	}
765

            
766
1
	pub(crate) fn transact_through_signed_v3(
767
1
		handle: &mut impl PrecompileHandle,
768
1
		dest: Location,
769
1
		fee_asset: Address,
770
1
		weight: Weight,
771
1
		call: BoundedBytes<GetDataLimit>,
772
1
		fee_amount: u128,
773
1
		overall_weight: Weight,
774
1
		refund: bool,
775
1
	) -> 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
1
		handle.record_cost(1000)?;
779

            
780
1
		let to_address: H160 = fee_asset.into();
781
1
		let to_account = Runtime::AddressMapping::into_account_id(to_address);
782

            
783
1
		let call: Vec<_> = call.into();
784

            
785
		// We convert the address into a currency
786
1
		let currency_id: <Runtime as pallet_xcm_transactor::Config>::CurrencyId =
787
1
			Runtime::account_to_currency_id(to_account)
788
1
				.ok_or(revert("cannot convert into currency id"))?;
789

            
790
1
		let overall_weight_limit = match overall_weight.ref_time() {
791
			u64::MAX => Unlimited,
792
1
			_ => 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
1
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
798
1
		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_signed {
799
1
			dest: Box::new(xcm::VersionedLocation::from(dest)),
800
1
			fee: CurrencyPayment {
801
1
				currency: Currency::AsCurrencyId(currency_id),
802
1
				fee_amount: Some(fee_amount),
803
1
			},
804
1
			weight_info: TransactWeights {
805
1
				transact_required_weight_at_most: weight,
806
1
				overall_weight: Some(overall_weight_limit),
807
1
			},
808
1
			refund,
809
1
			call,
810
1
		};
811

            
812
1
		RuntimeHelper::<Runtime>::try_dispatch(
813
1
			handle,
814
1
			frame_system::RawOrigin::Signed(origin).into(),
815
1
			call,
816
			0,
817
		)?;
818

            
819
1
		Ok(())
820
1
	}
821
}