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
//! Test utilities
18
use crate::v1::{XcmTransactorPrecompileV1, XcmTransactorPrecompileV1Call};
19
use crate::v2::{XcmTransactorPrecompileV2, XcmTransactorPrecompileV2Call};
20
use crate::v3::{XcmTransactorPrecompileV3, XcmTransactorPrecompileV3Call};
21
use frame_support::{
22
	construct_runtime, parameter_types,
23
	traits::{EnsureOrigin, Everything, OriginTrait, PalletInfo as PalletInfoTrait},
24
	weights::{RuntimeDbWeight, Weight},
25
};
26
use pallet_evm::{
27
	EnsureAddressNever, EnsureAddressRoot, FrameSystemAccountProvider, GasWeightMapping,
28
};
29
use parity_scale_codec::{Decode, Encode};
30
use precompile_utils::{
31
	mock_account,
32
	precompile_set::*,
33
	testing::{AddressInPrefixedSet, MockAccount},
34
};
35
use scale_info::TypeInfo;
36
use sp_core::{ConstU32, H160, H256, U256};
37
use sp_runtime::traits::{BlakeTwo256, IdentityLookup};
38
use sp_runtime::BuildStorage;
39
use xcm::latest::{prelude::*, Error as XcmError};
40
use xcm_builder::FixedWeightBounds;
41
use xcm_executor::{
42
	traits::{TransactAsset, WeightTrader},
43
	AssetsInHolding,
44
};
45
use xcm_primitives::AccountIdToCurrencyId;
46

            
47
pub type AccountId = MockAccount;
48
pub type Balance = u128;
49

            
50
type Block = frame_system::mocking::MockBlockU32<Runtime>;
51

            
52
// Configure a mock runtime to test the pallet.
53
629
construct_runtime!(
54
	pub enum Runtime	{
55
		System: frame_system,
56
		Balances: pallet_balances,
57
		Evm: pallet_evm,
58
		Timestamp: pallet_timestamp,
59
		XcmTransactor: pallet_xcm_transactor,
60
	}
61
1085
);
62

            
63
pub struct AccountIdToLocation;
64
impl sp_runtime::traits::Convert<AccountId, Location> for AccountIdToLocation {
65
12
	fn convert(account: AccountId) -> Location {
66
12
		let as_h160: H160 = account.into();
67
12
		Location::new(
68
12
			0,
69
12
			[AccountKey20 {
70
12
				network: None,
71
12
				key: as_h160.as_fixed_bytes().clone(),
72
12
			}],
73
12
		)
74
12
	}
75
}
76

            
77
pub type AssetId = u128;
78

            
79
parameter_types! {
80
	pub ParachainId: cumulus_primitives_core::ParaId = 100.into();
81
}
82

            
83
parameter_types! {
84
	pub const BlockHashCount: u32 = 250;
85
	pub const SS58Prefix: u8 = 42;
86
	pub const MockDbWeight: RuntimeDbWeight = RuntimeDbWeight {
87
		read: 1,
88
		write: 5,
89
	};
90
}
91

            
92
impl frame_system::Config for Runtime {
93
	type BaseCallFilter = Everything;
94
	type DbWeight = MockDbWeight;
95
	type RuntimeOrigin = RuntimeOrigin;
96
	type RuntimeTask = RuntimeTask;
97
	type Nonce = u64;
98
	type Block = Block;
99
	type RuntimeCall = RuntimeCall;
100
	type Hash = H256;
101
	type Hashing = BlakeTwo256;
102
	type AccountId = AccountId;
103
	type Lookup = IdentityLookup<Self::AccountId>;
104
	type RuntimeEvent = RuntimeEvent;
105
	type BlockHashCount = BlockHashCount;
106
	type Version = ();
107
	type PalletInfo = PalletInfo;
108
	type AccountData = pallet_balances::AccountData<Balance>;
109
	type OnNewAccount = ();
110
	type OnKilledAccount = ();
111
	type SystemWeightInfo = ();
112
	type BlockWeights = ();
113
	type BlockLength = ();
114
	type SS58Prefix = SS58Prefix;
115
	type OnSetCode = ();
116
	type MaxConsumers = frame_support::traits::ConstU32<16>;
117
	type SingleBlockMigrations = ();
118
	type MultiBlockMigrator = ();
119
	type PreInherents = ();
120
	type PostInherents = ();
121
	type PostTransactions = ();
122
}
123
parameter_types! {
124
	pub const ExistentialDeposit: u128 = 0;
125
}
126
impl pallet_balances::Config for Runtime {
127
	type MaxReserves = ();
128
	type ReserveIdentifier = ();
129
	type MaxLocks = ();
130
	type Balance = Balance;
131
	type RuntimeEvent = RuntimeEvent;
132
	type DustRemoval = ();
133
	type ExistentialDeposit = ExistentialDeposit;
134
	type AccountStore = System;
135
	type WeightInfo = ();
136
	type RuntimeHoldReason = ();
137
	type FreezeIdentifier = ();
138
	type MaxFreezes = ();
139
	type RuntimeFreezeReason = ();
140
}
141

            
142
// These parameters dont matter much as this will only be called by root with the forced arguments
143
// No deposit is substracted with those methods
144
parameter_types! {
145
	pub const AssetDeposit: Balance = 0;
146
	pub const ApprovalDeposit: Balance = 0;
147
	pub const AssetsStringLimit: u32 = 50;
148
	pub const MetadataDepositBase: Balance = 0;
149
	pub const MetadataDepositPerByte: Balance = 0;
150
}
151

            
152
pub type Precompiles<R> = PrecompileSetBuilder<
153
	R,
154
	(
155
		PrecompileAt<AddressU64<1>, XcmTransactorPrecompileV1<R>, CallableByContract>,
156
		PrecompileAt<AddressU64<2>, XcmTransactorPrecompileV2<R>, CallableByContract>,
157
		PrecompileAt<AddressU64<4>, XcmTransactorPrecompileV3<R>, CallableByContract>,
158
	),
159
>;
160

            
161
15
mock_account!(TransactorV1, |_| MockAccount::from_u64(1));
162
5
mock_account!(TransactorV2, |_| MockAccount::from_u64(2));
163
6
mock_account!(TransactorV3, |_| MockAccount::from_u64(4));
164
mock_account!(SelfReserveAddress, |_| MockAccount::from_u64(3));
165
6
mock_account!(AssetAddress(u128), |value: AssetAddress| {
166
6
	AddressInPrefixedSet(0xffffffff, value.0).into()
167
6
});
168

            
169
pub type PCallV1 = XcmTransactorPrecompileV1Call<Runtime>;
170
pub type PCallV2 = XcmTransactorPrecompileV2Call<Runtime>;
171
pub type PCallV3 = XcmTransactorPrecompileV3Call<Runtime>;
172

            
173
const MAX_POV_SIZE: u64 = 5 * 1024 * 1024;
174
/// Block storage limit in bytes. Set to 40 KB.
175
const BLOCK_STORAGE_LIMIT: u64 = 40 * 1024;
176

            
177
parameter_types! {
178
	pub BlockGasLimit: U256 = U256::from(u64::MAX);
179
	pub PrecompilesValue: Precompiles<Runtime> = Precompiles::new();
180
	pub const WeightPerGas: Weight = Weight::from_parts(1, 0);
181
	pub GasLimitPovSizeRatio: u64 = {
182
		let block_gas_limit = BlockGasLimit::get().min(u64::MAX.into()).low_u64();
183
		block_gas_limit.saturating_div(MAX_POV_SIZE)
184
	};
185
	pub GasLimitStorageGrowthRatio: u64 = {
186
		let block_gas_limit = BlockGasLimit::get().min(u64::MAX.into()).low_u64();
187
		block_gas_limit.saturating_div(BLOCK_STORAGE_LIMIT)
188
	};
189
}
190

            
191
/// A mapping function that converts Ethereum gas to Substrate weight
192
/// We are mocking this 1-1 to test db read charges too
193
pub struct MockGasWeightMapping;
194
impl GasWeightMapping for MockGasWeightMapping {
195
	fn gas_to_weight(gas: u64, _without_base_weight: bool) -> Weight {
196
		Weight::from_parts(gas, 1)
197
	}
198
105
	fn weight_to_gas(weight: Weight) -> u64 {
199
105
		weight.ref_time().into()
200
105
	}
201
}
202

            
203
impl pallet_evm::Config for Runtime {
204
	type FeeCalculator = ();
205
	type GasWeightMapping = MockGasWeightMapping;
206
	type WeightPerGas = WeightPerGas;
207
	type CallOrigin = EnsureAddressRoot<AccountId>;
208
	type WithdrawOrigin = EnsureAddressNever<AccountId>;
209
	type AddressMapping = AccountId;
210
	type Currency = Balances;
211
	type RuntimeEvent = RuntimeEvent;
212
	type Runner = pallet_evm::runner::stack::Runner<Self>;
213
	type PrecompilesValue = PrecompilesValue;
214
	type PrecompilesType = Precompiles<Self>;
215
	type ChainId = ();
216
	type OnChargeTransaction = ();
217
	type BlockGasLimit = BlockGasLimit;
218
	type BlockHashMapping = pallet_evm::SubstrateBlockHashMapping<Self>;
219
	type FindAuthor = ();
220
	type OnCreate = ();
221
	type GasLimitPovSizeRatio = GasLimitPovSizeRatio;
222
	type SuicideQuickClearLimit = ConstU32<0>;
223
	type GasLimitStorageGrowthRatio = GasLimitStorageGrowthRatio;
224
	type Timestamp = Timestamp;
225
	type WeightInfo = pallet_evm::weights::SubstrateWeight<Runtime>;
226
	type AccountProvider = FrameSystemAccountProvider<Runtime>;
227
}
228

            
229
parameter_types! {
230
	pub const MinimumPeriod: u64 = 5;
231
}
232
impl pallet_timestamp::Config for Runtime {
233
	type Moment = u64;
234
	type OnTimestampSet = ();
235
	type MinimumPeriod = MinimumPeriod;
236
	type WeightInfo = ();
237
}
238
pub struct ConvertOriginToLocal;
239
impl<Origin: OriginTrait> EnsureOrigin<Origin> for ConvertOriginToLocal {
240
	type Success = Location;
241

            
242
	fn try_origin(_: Origin) -> Result<Location, Origin> {
243
		Ok(Location::here())
244
	}
245

            
246
	#[cfg(feature = "runtime-benchmarks")]
247
	fn try_successful_origin() -> Result<Origin, ()> {
248
		Ok(Origin::root())
249
	}
250
}
251

            
252
pub struct DoNothingRouter;
253
impl SendXcm for DoNothingRouter {
254
	type Ticket = ();
255

            
256
12
	fn validate(
257
12
		_destination: &mut Option<Location>,
258
12
		_message: &mut Option<Xcm<()>>,
259
12
	) -> SendResult<Self::Ticket> {
260
12
		Ok(((), Assets::new()))
261
12
	}
262

            
263
12
	fn deliver(_: Self::Ticket) -> Result<XcmHash, SendError> {
264
12
		Ok(XcmHash::default())
265
12
	}
266
}
267

            
268
pub struct DummyAssetTransactor;
269
impl TransactAsset for DummyAssetTransactor {
270
	fn deposit_asset(_what: &Asset, _who: &Location, _context: Option<&XcmContext>) -> XcmResult {
271
		Ok(())
272
	}
273

            
274
6
	fn withdraw_asset(
275
6
		_what: &Asset,
276
6
		_who: &Location,
277
6
		_maybe_context: Option<&XcmContext>,
278
6
	) -> Result<AssetsInHolding, XcmError> {
279
6
		Ok(AssetsInHolding::default())
280
6
	}
281
}
282

            
283
pub struct DummyWeightTrader;
284
impl WeightTrader for DummyWeightTrader {
285
	fn new() -> Self {
286
		DummyWeightTrader
287
	}
288

            
289
	fn buy_weight(
290
		&mut self,
291
		_weight: Weight,
292
		_payment: AssetsInHolding,
293
		_context: &XcmContext,
294
	) -> Result<AssetsInHolding, XcmError> {
295
		Ok(AssetsInHolding::default())
296
	}
297
}
298

            
299
#[derive(Clone, Eq, Debug, PartialEq, Ord, PartialOrd, Encode, Decode, scale_info::TypeInfo)]
300
pub enum CurrencyId {
301
	SelfReserve,
302
	OtherReserve(AssetId),
303
}
304

            
305
parameter_types! {
306
	pub Ancestry: Location = Parachain(ParachainId::get().into()).into();
307

            
308
	pub const BaseXcmWeight: Weight = Weight::from_parts(1000u64, 1000u64);
309
	pub const RelayNetwork: NetworkId = NetworkId::Polkadot;
310

            
311
	pub SelfLocation: Location =
312
		Location::new(1, [Parachain(ParachainId::get().into())]);
313

            
314
	pub SelfReserve: Location = Location::new(
315
		1,
316
		[
317
			Parachain(ParachainId::get().into()),
318
			PalletInstance(
319
				<Runtime as frame_system::Config>::PalletInfo::index::<Balances>().unwrap() as u8
320
			)
321
		]);
322
	pub MaxInstructions: u32 = 100;
323

            
324
	pub UniversalLocation: InteriorLocation = Here;
325
	pub SelfLocationAbsolute: Location = Location {
326
		parents: 1,
327
		interior: [Parachain(ParachainId::get().into())].into(),
328
	};
329
}
330

            
331
impl pallet_xcm_transactor::Config for Runtime {
332
	type RuntimeEvent = RuntimeEvent;
333
	type Balance = Balance;
334
	type Transactor = MockTransactors;
335
	type DerivativeAddressRegistrationOrigin = frame_system::EnsureRoot<AccountId>;
336
	type SovereignAccountDispatcherOrigin = frame_system::EnsureRoot<AccountId>;
337
	type CurrencyId = CurrencyId;
338
	type AccountIdToLocation = AccountIdToLocation;
339
	type CurrencyIdToLocation = CurrencyIdToLocation;
340
	type SelfLocation = SelfLocation;
341
	type Weigher = FixedWeightBounds<BaseXcmWeight, RuntimeCall, MaxInstructions>;
342
	type UniversalLocation = UniversalLocation;
343
	type BaseXcmWeight = BaseXcmWeight;
344
	type XcmSender = DoNothingRouter;
345
	type AssetTransactor = DummyAssetTransactor;
346
	type ReserveProvider = xcm_primitives::AbsoluteAndRelativeReserve<SelfLocationAbsolute>;
347
	type WeightInfo = ();
348
	type HrmpManipulatorOrigin = frame_system::EnsureRoot<AccountId>;
349
	type HrmpOpenOrigin = frame_system::EnsureRoot<AccountId>;
350
	type MaxHrmpFee = ();
351
}
352

            
353
// We need to use the encoding from the relay mock runtime
354
#[derive(Encode, Decode)]
355
pub enum RelayCall {
356
	#[codec(index = 5u8)]
357
	// the index should match the position of the module in `construct_runtime!`
358
	Utility(UtilityCall),
359
}
360

            
361
#[derive(Encode, Decode)]
362
pub enum UtilityCall {
363
	#[codec(index = 1u8)]
364
	AsDerivative(u16),
365
}
366

            
367
#[derive(Clone, Eq, Debug, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
368
pub enum MockTransactors {
369
	Relay,
370
}
371

            
372
impl TryFrom<u8> for MockTransactors {
373
	type Error = ();
374

            
375
6
	fn try_from(value: u8) -> Result<Self, Self::Error> {
376
6
		match value {
377
6
			0x0 => Ok(MockTransactors::Relay),
378
			_ => Err(()),
379
		}
380
6
	}
381
}
382

            
383
impl xcm_primitives::XcmTransact for MockTransactors {
384
6
	fn destination(self) -> Location {
385
6
		match self {
386
6
			MockTransactors::Relay => Location::parent(),
387
6
		}
388
6
	}
389
}
390

            
391
impl xcm_primitives::UtilityEncodeCall for MockTransactors {
392
6
	fn encode_call(self, call: xcm_primitives::UtilityAvailableCalls) -> Vec<u8> {
393
6
		match self {
394
6
			MockTransactors::Relay => match call {
395
6
				xcm_primitives::UtilityAvailableCalls::AsDerivative(a, b) => {
396
6
					let mut call =
397
6
						RelayCall::Utility(UtilityCall::AsDerivative(a.clone())).encode();
398
6
					call.append(&mut b.clone());
399
6
					call
400
6
				}
401
6
			},
402
6
		}
403
6
	}
404
}
405

            
406
// Implement the trait, where we convert AccountId to AssetID
407
impl AccountIdToCurrencyId<AccountId, CurrencyId> for Runtime {
408
	/// The way to convert an account to assetId is by ensuring that the prefix is 0XFFFFFFFF
409
	/// and by taking the lowest 128 bits as the assetId
410
6
	fn account_to_currency_id(account: AccountId) -> Option<CurrencyId> {
411
6
		match account {
412
6
			a if a.has_prefix_u32(0xffffffff) => Some(CurrencyId::OtherReserve(a.without_prefix())),
413
			a if a == SelfReserveAddress.into() => Some(CurrencyId::SelfReserve),
414
			_ => None,
415
		}
416
6
	}
417
}
418

            
419
pub struct CurrencyIdToLocation;
420

            
421
impl sp_runtime::traits::Convert<CurrencyId, Option<Location>> for CurrencyIdToLocation {
422
6
	fn convert(currency: CurrencyId) -> Option<Location> {
423
6
		match currency {
424
			CurrencyId::SelfReserve => {
425
				let multi: Location = SelfReserve::get();
426
				Some(multi)
427
			}
428
			// To distinguish between relay and others, specially for reserve asset
429
6
			CurrencyId::OtherReserve(asset) => {
430
6
				if asset == 0 {
431
6
					Some(Location::parent())
432
				} else {
433
					Some(Location::new(1, [Parachain(2), GeneralIndex(asset)]))
434
				}
435
			}
436
		}
437
6
	}
438
}
439

            
440
pub(crate) struct ExtBuilder {
441
	// endowed accounts with balances
442
	balances: Vec<(AccountId, Balance)>,
443
}
444

            
445
impl Default for ExtBuilder {
446
20
	fn default() -> ExtBuilder {
447
20
		ExtBuilder { balances: vec![] }
448
20
	}
449
}
450

            
451
impl ExtBuilder {
452
17
	pub(crate) fn with_balances(mut self, balances: Vec<(AccountId, Balance)>) -> Self {
453
17
		self.balances = balances;
454
17
		self
455
17
	}
456
20
	pub(crate) fn build(self) -> sp_io::TestExternalities {
457
20
		let mut t = frame_system::GenesisConfig::<Runtime>::default()
458
20
			.build_storage()
459
20
			.expect("Frame system builds valid default genesis config");
460
20

            
461
20
		pallet_balances::GenesisConfig::<Runtime> {
462
20
			balances: self.balances,
463
20
		}
464
20
		.assimilate_storage(&mut t)
465
20
		.expect("Pallet balances storage can be assimilated");
466
20

            
467
20
		let mut ext = sp_io::TestExternalities::new(t);
468
20
		ext.execute_with(|| System::set_block_number(1));
469
20
		ext
470
20
	}
471
}