1
// Copyright 2019-2022 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
{
63
2
	pub(crate) fn account_index(
64
2
		handle: &mut impl PrecompileHandle,
65
2
		index: u16,
66
2
	) -> EvmResult<Address> {
67
2
		// 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
1

            
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
2
		// fetch data from pallet
83
2
		// storage item: TransactInfoWithWeightLimit: Blake2_128(16) + Location
84
2
		// + RemoteTransactInfoWithMaxWeight
85
2
		handle.record_db_read::<Runtime>(
86
2
			16 + Location::max_encoded_len() + RemoteTransactInfoWithMaxWeight::max_encoded_len(),
87
2
		)?;
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
2
		// fetch data from pallet
111
2
		// storage item: TransactInfoWithWeightLimit: Blake2_128(16) + Location
112
2
		// + RemoteTransactInfoWithMaxWeight
113
2
		handle.record_db_read::<Runtime>(
114
2
			16 + Location::max_encoded_len() + RemoteTransactInfoWithMaxWeight::max_encoded_len(),
115
2
		)?;
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
1

            
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
2
		// fetch data from pallet
136
2
		// 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
1

            
158
1
		// Depending on the Runtime, this might involve a DB read. This is not the case in
159
1
		// 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::V4(
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
1

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

            
187
1
		Ok(())
188
1
	}
189

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

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

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

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

            
234
1
		Ok(())
235
1
	}
236

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

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

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

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

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

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

            
284
1
		Ok(())
285
1
	}
286

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

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

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

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

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

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

            
340
1
		Ok(())
341
1
	}
342

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

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

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

            
376
2
		Ok(())
377
2
	}
378

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

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

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

            
417
4
		Ok(())
418
7
	}
419

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

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

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

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

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

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

            
463
1
		Ok(())
464
1
	}
465

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

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

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

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

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

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

            
514
1
		Ok(())
515
1
	}
516

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

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

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

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

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

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

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

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

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

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

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

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

            
611
1
		Ok(())
612
1
	}
613

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

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

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

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

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

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

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

            
667
1
		Ok(())
668
1
	}
669

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

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

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

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

            
708
1
		Ok(())
709
1
	}
710

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

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

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

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

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

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

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

            
759
1
		Ok(())
760
1
	}
761
}