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
	<Runtime::RuntimeCall as Dispatchable>::RuntimeOrigin: From<Option<Runtime::AccountId>>,
59
	TransactorOf<Runtime>: TryFrom<u8>,
60
	Runtime::AccountId: Into<H160>,
61
	Runtime: AccountIdToCurrencyId<Runtime::AccountId, CurrencyIdOf<Runtime>>,
62
	<Runtime as pallet_evm::Config>::AddressMapping: AddressMapping<Runtime::AccountId>,
63
{
64
2
	pub(crate) fn account_index(
65
2
		handle: &mut impl PrecompileHandle,
66
2
		index: u16,
67
2
	) -> EvmResult<Address> {
68
2
		// storage item: IndexToAccount: Blake2_128(16) + u16(2) + AccountId(20)
69
2
		handle.record_db_read::<Runtime>(38)?;
70

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

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

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

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

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

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

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

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

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

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

            
146
1
	pub(crate) fn transact_through_derivative_multilocation(
147
1
		handle: &mut impl PrecompileHandle,
148
1
		transactor: u8,
149
1
		index: u16,
150
1
		fee_asset: Location,
151
1
		weight: u64,
152
1
		inner_call: BoundedBytes<GetDataLimit>,
153
1
	) -> EvmResult {
154
1
		let transactor = transactor
155
1
			.try_into()
156
1
			.map_err(|_| RevertReason::custom("Non-existent transactor").in_field("transactor"))?;
157
1
		let inner_call: Vec<_> = inner_call.into();
158
1

            
159
1
		// Depending on the Runtime, this might involve a DB read. This is not the case in
160
1
		// moonbeam, as we are using IdentityMapping
161
1
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
162
1
		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_derivative {
163
1
			dest: transactor,
164
1
			index,
165
1
			fee: CurrencyPayment {
166
1
				currency: Currency::AsMultiLocation(Box::new(xcm::VersionedLocation::from(
167
1
					fee_asset,
168
1
				))),
169
1
				fee_amount: None,
170
1
			},
171
1
			inner_call,
172
1
			weight_info: TransactWeights {
173
1
				// TODO overall weight None means we will retrieve the allowed proof size from storage.
174
1
				// In the v2 to v3 migration we set this value to DEFAULT_PROOF_SIZE, so setting
175
1
				// require_weight_at_most to DEFAULT_PROOF_SIZE/2 makes sense. Although we might
176
1
				// want to revisit this to use whatever storage value there is and divide it by 2.
177
1
				transact_required_weight_at_most: Weight::from_parts(
178
1
					weight,
179
1
					DEFAULT_PROOF_SIZE.saturating_div(2),
180
1
				),
181
1
				overall_weight: None,
182
1
			},
183
1
			refund: false,
184
1
		};
185
1

            
186
1
		RuntimeHelper::<Runtime>::try_dispatch(handle, Some(origin).into(), call, 0)?;
187

            
188
1
		Ok(())
189
1
	}
190

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

            
205
1
		let inner_call: Vec<_> = inner_call.into();
206
1

            
207
1
		// Depending on the Runtime, this might involve a DB read. This is not the case in
208
1
		// moonbeam, as we are using IdentityMapping
209
1
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
210
1
		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_derivative {
211
1
			dest: transactor,
212
1
			index,
213
1
			fee: CurrencyPayment {
214
1
				currency: Currency::AsMultiLocation(Box::new(xcm::VersionedLocation::from(
215
1
					fee_asset,
216
1
				))),
217
1
				fee_amount: Some(fee_amount),
218
1
			},
219
1
			inner_call,
220
1
			weight_info: TransactWeights {
221
1
				transact_required_weight_at_most: Weight::from_parts(
222
1
					weight,
223
1
					DEFAULT_PROOF_SIZE.saturating_div(2),
224
1
				),
225
1
				overall_weight: Some(Limited(Weight::from_parts(
226
1
					overall_weight,
227
1
					DEFAULT_PROOF_SIZE,
228
1
				))),
229
1
			},
230
1
			refund: false,
231
1
		};
232
1

            
233
1
		RuntimeHelper::<Runtime>::try_dispatch(handle, Some(origin).into(), call, 0)?;
234

            
235
1
		Ok(())
236
1
	}
237

            
238
1
	pub(crate) fn transact_through_derivative(
239
1
		handle: &mut impl PrecompileHandle,
240
1
		transactor: u8,
241
1
		index: u16,
242
1
		currency_id: Address,
243
1
		weight: u64,
244
1
		inner_call: BoundedBytes<GetDataLimit>,
245
1
	) -> EvmResult {
246
1
		// No DB access before try_dispatch but lot of logical stuff
247
1
		// To prevent spam, we charge an arbitrary amoun of gas
248
1
		handle.record_cost(1000)?;
249

            
250
1
		let transactor = transactor
251
1
			.try_into()
252
1
			.map_err(|_| RevertReason::custom("Non-existent transactor").in_field("transactor"))?;
253
1
		let inner_call: Vec<_> = inner_call.into();
254
1

            
255
1
		let to_account = Runtime::AddressMapping::into_account_id(currency_id.0);
256

            
257
		// We convert the address into a currency
258
1
		let currency_id: <Runtime as pallet_xcm_transactor::Config>::CurrencyId =
259
1
			Runtime::account_to_currency_id(to_account)
260
1
				.ok_or(revert("cannot convert into currency id"))?;
261

            
262
		// Depending on the Runtime, this might involve a DB read. This is not the case in
263
		// moonbeam, as we are using IdentityMapping
264
1
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
265
1
		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_derivative {
266
1
			dest: transactor,
267
1
			index,
268
1
			fee: CurrencyPayment {
269
1
				currency: Currency::AsCurrencyId(currency_id),
270
1
				fee_amount: None,
271
1
			},
272
1
			weight_info: TransactWeights {
273
1
				transact_required_weight_at_most: Weight::from_parts(
274
1
					weight,
275
1
					DEFAULT_PROOF_SIZE.saturating_div(2),
276
1
				),
277
1
				overall_weight: None,
278
1
			},
279
1
			inner_call,
280
1
			refund: false,
281
1
		};
282
1

            
283
1
		RuntimeHelper::<Runtime>::try_dispatch(handle, Some(origin).into(), call, 0)?;
284

            
285
1
		Ok(())
286
1
	}
287

            
288
1
	pub(crate) fn transact_through_derivative_fee_weight(
289
1
		handle: &mut impl PrecompileHandle,
290
1
		transactor: u8,
291
1
		index: u16,
292
1
		fee_asset: Address,
293
1
		weight: u64,
294
1
		inner_call: BoundedBytes<GetDataLimit>,
295
1
		fee_amount: u128,
296
1
		overall_weight: u64,
297
1
	) -> EvmResult {
298
1
		// No DB access before try_dispatch but lot of logical stuff
299
1
		// To prevent spam, we charge an arbitrary amoun of gas
300
1
		handle.record_cost(1000)?;
301

            
302
1
		let transactor = transactor
303
1
			.try_into()
304
1
			.map_err(|_| RevertReason::custom("Non-existent transactor").in_field("transactor"))?;
305
1
		let inner_call: Vec<_> = inner_call.into();
306
1

            
307
1
		let to_address: H160 = fee_asset.into();
308
1
		let to_account = Runtime::AddressMapping::into_account_id(to_address);
309

            
310
		// We convert the address into a currency
311
1
		let currency_id: <Runtime as pallet_xcm_transactor::Config>::CurrencyId =
312
1
			Runtime::account_to_currency_id(to_account)
313
1
				.ok_or(revert("cannot convert into currency id"))?;
314

            
315
		// Depending on the Runtime, this might involve a DB read. This is not the case in
316
		// moonbeam, as we are using IdentityMapping
317
1
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
318
1
		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_derivative {
319
1
			dest: transactor,
320
1
			index,
321
1
			fee: CurrencyPayment {
322
1
				currency: Currency::AsCurrencyId(currency_id),
323
1
				fee_amount: Some(fee_amount),
324
1
			},
325
1
			weight_info: TransactWeights {
326
1
				transact_required_weight_at_most: Weight::from_parts(
327
1
					weight,
328
1
					DEFAULT_PROOF_SIZE.saturating_div(2),
329
1
				),
330
1
				overall_weight: Some(Limited(Weight::from_parts(
331
1
					overall_weight,
332
1
					DEFAULT_PROOF_SIZE,
333
1
				))),
334
1
			},
335
1
			inner_call,
336
1
			refund: false,
337
1
		};
338
1

            
339
1
		RuntimeHelper::<Runtime>::try_dispatch(handle, Some(origin).into(), call, 0)?;
340

            
341
1
		Ok(())
342
1
	}
343

            
344
2
	pub(crate) fn transact_through_signed_multilocation(
345
2
		handle: &mut impl PrecompileHandle,
346
2
		dest: Location,
347
2
		fee_asset: Location,
348
2
		weight: u64,
349
2
		call: BoundedBytes<GetDataLimit>,
350
2
	) -> EvmResult {
351
2
		let call: Vec<_> = call.into();
352
2

            
353
2
		// Depending on the Runtime, this might involve a DB read. This is not the case in
354
2
		// moonbeam, as we are using IdentityMapping
355
2
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
356
2
		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_signed {
357
2
			dest: Box::new(xcm::VersionedLocation::from(dest)),
358
2
			fee: CurrencyPayment {
359
2
				currency: Currency::AsMultiLocation(Box::new(xcm::VersionedLocation::from(
360
2
					fee_asset,
361
2
				))),
362
2
				fee_amount: None,
363
2
			},
364
2
			weight_info: TransactWeights {
365
2
				transact_required_weight_at_most: Weight::from_parts(
366
2
					weight,
367
2
					DEFAULT_PROOF_SIZE.saturating_div(2),
368
2
				),
369
2
				overall_weight: None,
370
2
			},
371
2
			refund: false,
372
2
			call,
373
2
		};
374
2

            
375
2
		RuntimeHelper::<Runtime>::try_dispatch(handle, Some(origin).into(), call, 0)?;
376

            
377
2
		Ok(())
378
2
	}
379

            
380
7
	pub(crate) fn transact_through_signed_multilocation_fee_weight(
381
7
		handle: &mut impl PrecompileHandle,
382
7
		dest: Location,
383
7
		fee_asset: Location,
384
7
		weight: u64,
385
7
		call: BoundedBytes<GetDataLimit>,
386
7
		fee_amount: u128,
387
7
		overall_weight: u64,
388
7
	) -> EvmResult {
389
7
		let call: Vec<_> = call.into();
390
7

            
391
7
		// Depending on the Runtime, this might involve a DB read. This is not the case in
392
7
		// moonbeam, as we are using IdentityMapping
393
7
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
394
7
		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_signed {
395
7
			dest: Box::new(xcm::VersionedLocation::from(dest)),
396
7
			fee: CurrencyPayment {
397
7
				currency: Currency::AsMultiLocation(Box::new(xcm::VersionedLocation::from(
398
7
					fee_asset,
399
7
				))),
400
7
				fee_amount: Some(fee_amount),
401
7
			},
402
7
			weight_info: TransactWeights {
403
7
				transact_required_weight_at_most: Weight::from_parts(
404
7
					weight,
405
7
					DEFAULT_PROOF_SIZE.saturating_div(2),
406
7
				),
407
7
				overall_weight: Some(Limited(Weight::from_parts(
408
7
					overall_weight,
409
7
					DEFAULT_PROOF_SIZE,
410
7
				))),
411
7
			},
412
7
			refund: false,
413
7
			call,
414
7
		};
415
7

            
416
7
		RuntimeHelper::<Runtime>::try_dispatch(handle, Some(origin).into(), call, 0)?;
417

            
418
4
		Ok(())
419
7
	}
420

            
421
1
	pub(crate) fn transact_through_signed(
422
1
		handle: &mut impl PrecompileHandle,
423
1
		dest: Location,
424
1
		fee_asset: Address,
425
1
		weight: u64,
426
1
		call: BoundedBytes<GetDataLimit>,
427
1
	) -> EvmResult {
428
1
		// No DB access before try_dispatch but lot of logical stuff
429
1
		// To prevent spam, we charge an arbitrary amoun of gas
430
1
		handle.record_cost(1000)?;
431

            
432
1
		let to_address: H160 = fee_asset.into();
433
1
		let to_account = Runtime::AddressMapping::into_account_id(to_address);
434
1

            
435
1
		let call: Vec<_> = call.into();
436

            
437
		// We convert the address into a currency
438
1
		let currency_id: <Runtime as pallet_xcm_transactor::Config>::CurrencyId =
439
1
			Runtime::account_to_currency_id(to_account)
440
1
				.ok_or(revert("cannot convert into currency id"))?;
441

            
442
		// Depending on the Runtime, this might involve a DB read. This is not the case in
443
		// moonbeam, as we are using IdentityMapping
444
1
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
445
1
		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_signed {
446
1
			dest: Box::new(xcm::VersionedLocation::from(dest)),
447
1
			fee: CurrencyPayment {
448
1
				currency: Currency::AsCurrencyId(currency_id),
449
1
				fee_amount: None,
450
1
			},
451
1
			weight_info: TransactWeights {
452
1
				transact_required_weight_at_most: Weight::from_parts(
453
1
					weight,
454
1
					DEFAULT_PROOF_SIZE.saturating_div(2),
455
1
				),
456
1
				overall_weight: None,
457
1
			},
458
1
			refund: false,
459
1
			call,
460
1
		};
461
1

            
462
1
		RuntimeHelper::<Runtime>::try_dispatch(handle, Some(origin).into(), call, 0)?;
463

            
464
1
		Ok(())
465
1
	}
466

            
467
1
	pub(crate) fn transact_through_signed_fee_weight(
468
1
		handle: &mut impl PrecompileHandle,
469
1
		dest: Location,
470
1
		fee_asset: Address,
471
1
		weight: u64,
472
1
		call: BoundedBytes<GetDataLimit>,
473
1
		fee_amount: u128,
474
1
		overall_weight: u64,
475
1
	) -> EvmResult {
476
1
		// No DB access before try_dispatch but lot of logical stuff
477
1
		// To prevent spam, we charge an arbitrary amoun of gas
478
1
		handle.record_cost(1000)?;
479

            
480
1
		let to_address: H160 = fee_asset.into();
481
1
		let to_account = Runtime::AddressMapping::into_account_id(to_address);
482
1

            
483
1
		let call: Vec<_> = call.into();
484

            
485
		// We convert the address into a currency
486
1
		let currency_id: <Runtime as pallet_xcm_transactor::Config>::CurrencyId =
487
1
			Runtime::account_to_currency_id(to_account)
488
1
				.ok_or(revert("cannot convert into currency id"))?;
489

            
490
		// Depending on the Runtime, this might involve a DB read. This is not the case in
491
		// moonbeam, as we are using IdentityMapping
492
1
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
493
1
		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_signed {
494
1
			dest: Box::new(xcm::VersionedLocation::from(dest)),
495
1
			fee: CurrencyPayment {
496
1
				currency: Currency::AsCurrencyId(currency_id),
497
1
				fee_amount: Some(fee_amount),
498
1
			},
499
1
			weight_info: TransactWeights {
500
1
				transact_required_weight_at_most: Weight::from_parts(
501
1
					weight,
502
1
					DEFAULT_PROOF_SIZE.saturating_div(2),
503
1
				),
504
1
				overall_weight: Some(Limited(Weight::from_parts(
505
1
					overall_weight,
506
1
					DEFAULT_PROOF_SIZE,
507
1
				))),
508
1
			},
509
1
			refund: false,
510
1
			call,
511
1
		};
512
1

            
513
1
		RuntimeHelper::<Runtime>::try_dispatch(handle, Some(origin).into(), call, 0)?;
514

            
515
1
		Ok(())
516
1
	}
517

            
518
	pub(crate) fn encode_utility_as_derivative(
519
		handle: &mut impl PrecompileHandle,
520
		transactor: u8,
521
		index: u16,
522
		inner_call: BoundedBytes<GetDataLimit>,
523
	) -> EvmResult<UnboundedBytes> {
524
		// There is no DB read in this function,
525
		// we just account an arbitrary amount of gas to prevent spam
526
		// TODO replace by proper benchmarks
527
		handle.record_cost(1000)?;
528

            
529
		let transactor: TransactorOf<Runtime> = transactor
530
			.try_into()
531
			.map_err(|_| RevertReason::custom("Non-existent transactor").in_field("transactor"))?;
532

            
533
		let encoded = UtilityEncodeCall::encode_call(
534
			transactor,
535
			UtilityAvailableCalls::AsDerivative(index, inner_call.into()),
536
		)
537
		.as_slice()
538
		.into();
539
		Ok(encoded)
540
	}
541

            
542
2
	pub(crate) fn transact_info_with_signed_v3(
543
2
		handle: &mut impl PrecompileHandle,
544
2
		multilocation: Location,
545
2
	) -> EvmResult<(Weight, Weight, Weight)> {
546
2
		// fetch data from pallet
547
2
		// storage item: TransactInfoWithWeightLimit: Blake2_128(16) + Location
548
2
		// + RemoteTransactInfoWithMaxWeight
549
2
		handle.record_db_read::<Runtime>(
550
2
			16 + Location::max_encoded_len() + RemoteTransactInfoWithMaxWeight::max_encoded_len(),
551
2
		)?;
552

            
553
1
		let remote_transact_info: RemoteTransactInfoWithMaxWeight =
554
2
			pallet_xcm_transactor::Pallet::<Runtime>::transact_info(multilocation)
555
2
				.ok_or(revert("Transact Info not set"))?;
556

            
557
1
		let transact_extra_weight_signed = remote_transact_info
558
1
			.transact_extra_weight_signed
559
1
			.unwrap_or(Weight::zero());
560
1

            
561
1
		Ok((
562
1
			remote_transact_info.transact_extra_weight,
563
1
			transact_extra_weight_signed,
564
1
			remote_transact_info.max_weight,
565
1
		))
566
2
	}
567

            
568
1
	pub(crate) fn transact_through_derivative_multilocation_v3(
569
1
		handle: &mut impl PrecompileHandle,
570
1
		transactor: u8,
571
1
		index: u16,
572
1
		fee_asset: Location,
573
1
		weight: Weight,
574
1
		inner_call: BoundedBytes<GetDataLimit>,
575
1
		fee_amount: u128,
576
1
		overall_weight: Weight,
577
1
		refund: bool,
578
1
	) -> EvmResult {
579
1
		let transactor = transactor
580
1
			.try_into()
581
1
			.map_err(|_| RevertReason::custom("Non-existent transactor").in_field("transactor"))?;
582

            
583
1
		let inner_call: Vec<_> = inner_call.into();
584

            
585
1
		let overall_weight_limit = match overall_weight.ref_time() {
586
			u64::MAX => Unlimited,
587
1
			_ => Limited(overall_weight),
588
		};
589

            
590
		// Depending on the Runtime, this might involve a DB read. This is not the case in
591
		// moonbeam, as we are using IdentityMapping
592
1
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
593
1
		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_derivative {
594
1
			dest: transactor,
595
1
			index,
596
1
			fee: CurrencyPayment {
597
1
				currency: Currency::AsMultiLocation(Box::new(xcm::VersionedLocation::from(
598
1
					fee_asset,
599
1
				))),
600
1
				fee_amount: Some(fee_amount),
601
1
			},
602
1
			inner_call,
603
1
			weight_info: TransactWeights {
604
1
				transact_required_weight_at_most: weight,
605
1
				overall_weight: Some(overall_weight_limit),
606
1
			},
607
1
			refund,
608
1
		};
609
1

            
610
1
		RuntimeHelper::<Runtime>::try_dispatch(handle, Some(origin).into(), call, 0)?;
611

            
612
1
		Ok(())
613
1
	}
614

            
615
1
	pub(crate) fn transact_through_derivative_v3(
616
1
		handle: &mut impl PrecompileHandle,
617
1
		transactor: u8,
618
1
		index: u16,
619
1
		fee_asset: Address,
620
1
		weight: Weight,
621
1
		inner_call: BoundedBytes<GetDataLimit>,
622
1
		fee_amount: u128,
623
1
		overall_weight: Weight,
624
1
		refund: bool,
625
1
	) -> EvmResult {
626
1
		// No DB access before try_dispatch but lot of logical stuff
627
1
		// To prevent spam, we charge an arbitrary amoun of gas
628
1
		handle.record_cost(1000)?;
629

            
630
1
		let transactor = transactor
631
1
			.try_into()
632
1
			.map_err(|_| RevertReason::custom("Non-existent transactor").in_field("transactor"))?;
633
1
		let inner_call: Vec<_> = inner_call.into();
634
1

            
635
1
		let to_address: H160 = fee_asset.into();
636
1
		let to_account = Runtime::AddressMapping::into_account_id(to_address);
637

            
638
		// We convert the address into a currency
639
1
		let currency_id: <Runtime as pallet_xcm_transactor::Config>::CurrencyId =
640
1
			Runtime::account_to_currency_id(to_account)
641
1
				.ok_or(revert("cannot convert into currency id"))?;
642

            
643
1
		let overall_weight_limit = match overall_weight.ref_time() {
644
			u64::MAX => Unlimited,
645
1
			_ => Limited(overall_weight),
646
		};
647

            
648
		// Depending on the Runtime, this might involve a DB read. This is not the case in
649
		// moonbeam, as we are using IdentityMapping
650
1
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
651
1
		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_derivative {
652
1
			dest: transactor,
653
1
			index,
654
1
			fee: CurrencyPayment {
655
1
				currency: Currency::AsCurrencyId(currency_id),
656
1
				fee_amount: Some(fee_amount),
657
1
			},
658
1
			weight_info: TransactWeights {
659
1
				transact_required_weight_at_most: weight,
660
1
				overall_weight: Some(overall_weight_limit),
661
1
			},
662
1
			inner_call,
663
1
			refund,
664
1
		};
665
1

            
666
1
		RuntimeHelper::<Runtime>::try_dispatch(handle, Some(origin).into(), call, 0)?;
667

            
668
1
		Ok(())
669
1
	}
670

            
671
1
	pub(crate) fn transact_through_signed_multilocation_v3(
672
1
		handle: &mut impl PrecompileHandle,
673
1
		dest: Location,
674
1
		fee_asset: Location,
675
1
		weight: Weight,
676
1
		call: BoundedBytes<GetDataLimit>,
677
1
		fee_amount: u128,
678
1
		overall_weight: Weight,
679
1
		refund: bool,
680
1
	) -> EvmResult {
681
1
		let call: Vec<_> = call.into();
682

            
683
1
		let overall_weight_limit = match overall_weight.ref_time() {
684
			u64::MAX => Unlimited,
685
1
			_ => Limited(overall_weight),
686
		};
687

            
688
		// Depending on the Runtime, this might involve a DB read. This is not the case in
689
		// moonbeam, as we are using IdentityMapping
690
1
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
691
1
		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_signed {
692
1
			dest: Box::new(xcm::VersionedLocation::from(dest)),
693
1
			fee: CurrencyPayment {
694
1
				currency: Currency::AsMultiLocation(Box::new(xcm::VersionedLocation::from(
695
1
					fee_asset,
696
1
				))),
697
1
				fee_amount: Some(fee_amount),
698
1
			},
699
1
			weight_info: TransactWeights {
700
1
				transact_required_weight_at_most: weight,
701
1
				overall_weight: Some(overall_weight_limit),
702
1
			},
703
1
			refund,
704
1
			call,
705
1
		};
706
1

            
707
1
		RuntimeHelper::<Runtime>::try_dispatch(handle, Some(origin).into(), call, 0)?;
708

            
709
1
		Ok(())
710
1
	}
711

            
712
1
	pub(crate) fn transact_through_signed_v3(
713
1
		handle: &mut impl PrecompileHandle,
714
1
		dest: Location,
715
1
		fee_asset: Address,
716
1
		weight: Weight,
717
1
		call: BoundedBytes<GetDataLimit>,
718
1
		fee_amount: u128,
719
1
		overall_weight: Weight,
720
1
		refund: bool,
721
1
	) -> EvmResult {
722
1
		// No DB access before try_dispatch but lot of logical stuff
723
1
		// To prevent spam, we charge an arbitrary amoun of gas
724
1
		handle.record_cost(1000)?;
725

            
726
1
		let to_address: H160 = fee_asset.into();
727
1
		let to_account = Runtime::AddressMapping::into_account_id(to_address);
728
1

            
729
1
		let call: Vec<_> = call.into();
730

            
731
		// We convert the address into a currency
732
1
		let currency_id: <Runtime as pallet_xcm_transactor::Config>::CurrencyId =
733
1
			Runtime::account_to_currency_id(to_account)
734
1
				.ok_or(revert("cannot convert into currency id"))?;
735

            
736
1
		let overall_weight_limit = match overall_weight.ref_time() {
737
			u64::MAX => Unlimited,
738
1
			_ => Limited(overall_weight),
739
		};
740

            
741
		// Depending on the Runtime, this might involve a DB read. This is not the case in
742
		// moonbeam, as we are using IdentityMapping
743
1
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
744
1
		let call = pallet_xcm_transactor::Call::<Runtime>::transact_through_signed {
745
1
			dest: Box::new(xcm::VersionedLocation::from(dest)),
746
1
			fee: CurrencyPayment {
747
1
				currency: Currency::AsCurrencyId(currency_id),
748
1
				fee_amount: Some(fee_amount),
749
1
			},
750
1
			weight_info: TransactWeights {
751
1
				transact_required_weight_at_most: weight,
752
1
				overall_weight: Some(overall_weight_limit),
753
1
			},
754
1
			refund,
755
1
			call,
756
1
		};
757
1

            
758
1
		RuntimeHelper::<Runtime>::try_dispatch(handle, Some(origin).into(), call, 0)?;
759

            
760
1
		Ok(())
761
1
	}
762
}