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 super::*;
19
use frame_support::traits::Disabled;
20
use frame_support::{
21
	construct_runtime, parameter_types,
22
	traits::{ConstU32, Everything, Nothing, OriginTrait, PalletInfo as _},
23
	weights::{RuntimeDbWeight, Weight},
24
};
25
use pallet_evm::{
26
	EnsureAddressNever, EnsureAddressRoot, FrameSystemAccountProvider, GasWeightMapping,
27
};
28
use precompile_utils::{
29
	mock_account,
30
	precompile_set::*,
31
	testing::{AddressInPrefixedSet, MockAccount},
32
};
33
use sp_core::{H256, U256};
34
use sp_io;
35
use sp_runtime::traits::{BlakeTwo256, IdentityLookup, TryConvert};
36
use sp_runtime::BuildStorage;
37
use xcm::latest::Error as XcmError;
38
use xcm_builder::FixedWeightBounds;
39
use xcm_builder::IsConcrete;
40
use xcm_builder::SovereignSignedViaLocation;
41
use xcm_builder::{AllowUnpaidExecutionFrom, Case};
42
use xcm_executor::{
43
	traits::{ConvertLocation, TransactAsset, WeightTrader},
44
	AssetsInHolding,
45
};
46
use Junctions::Here;
47

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

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

            
53
// Configure a mock runtime to test the pallet.
54
construct_runtime!(
55
	pub enum Runtime	{
56
		System: frame_system,
57
		Balances: pallet_balances,
58
		Evm: pallet_evm,
59
		Timestamp: pallet_timestamp,
60
		PolkadotXcm: pallet_xcm,
61
		XcmWeightTrader: pallet_xcm_weight_trader,
62
	}
63
);
64

            
65
mock_account!(SelfReserveAccount, |_| MockAccount::from_u64(2));
66
2
mock_account!(ParentAccount, |_| MockAccount::from_u64(3));
67
// use simple encoding for parachain accounts.
68
mock_account!(
69
	SiblingParachainAccount(u32),
70
2
	|v: SiblingParachainAccount| { AddressInPrefixedSet(0xffffffff, v.0 as u128).into() }
71
);
72

            
73
use frame_system::RawOrigin as SystemRawOrigin;
74
use xcm::latest::Junction;
75
pub struct MockAccountToAccountKey20<Origin, AccountId>(PhantomData<(Origin, AccountId)>);
76

            
77
impl<Origin: OriginTrait + Clone, AccountId: Into<H160>> TryConvert<Origin, Location>
78
	for MockAccountToAccountKey20<Origin, AccountId>
79
where
80
	Origin::PalletsOrigin: From<SystemRawOrigin<AccountId>>
81
		+ TryInto<SystemRawOrigin<AccountId>, Error = Origin::PalletsOrigin>,
82
{
83
4
	fn try_convert(o: Origin) -> Result<Location, Origin> {
84
4
		o.try_with_caller(|caller| match caller.try_into() {
85
4
			Ok(SystemRawOrigin::Signed(who)) => {
86
4
				let account_h160: H160 = who.into();
87
4
				Ok(Junction::AccountKey20 {
88
4
					network: None,
89
4
					key: account_h160.into(),
90
4
				}
91
4
				.into())
92
			}
93
			Ok(other) => Err(other.into()),
94
			Err(other) => Err(other),
95
4
		})
96
4
	}
97
}
98

            
99
pub struct MockParentMultilocationToAccountConverter;
100
impl ConvertLocation<AccountId> for MockParentMultilocationToAccountConverter {
101
2
	fn convert_location(location: &Location) -> Option<AccountId> {
102
1
		match location {
103
			Location {
104
				parents: 1,
105
				interior: Here,
106
1
			} => Some(ParentAccount.into()),
107
1
			_ => None,
108
		}
109
2
	}
110
}
111

            
112
pub struct MockParachainMultilocationToAccountConverter;
113
impl ConvertLocation<AccountId> for MockParachainMultilocationToAccountConverter {
114
3
	fn convert_location(location: &Location) -> Option<AccountId> {
115
3
		match location.unpack() {
116
1
			(1, [Parachain(id)]) => Some(SiblingParachainAccount(*id).into()),
117
2
			_ => None,
118
		}
119
3
	}
120
}
121

            
122
pub type LocationToAccountId = (
123
	MockParachainMultilocationToAccountConverter,
124
	MockParentMultilocationToAccountConverter,
125
	xcm_builder::AccountKey20Aliases<LocalNetworkId, AccountId>,
126
);
127

            
128
parameter_types! {
129
	pub ParachainId: cumulus_primitives_core::ParaId = 100.into();
130
	pub LocalNetworkId: Option<NetworkId> = None;
131
}
132

            
133
parameter_types! {
134
	pub const BlockHashCount: u32 = 250;
135
	pub const SS58Prefix: u8 = 42;
136
	pub const MockDbWeight: RuntimeDbWeight = RuntimeDbWeight {
137
		read: 1,
138
		write: 5,
139
	};
140
}
141

            
142
impl frame_system::Config for Runtime {
143
	type BaseCallFilter = Everything;
144
	type DbWeight = MockDbWeight;
145
	type RuntimeOrigin = RuntimeOrigin;
146
	type RuntimeTask = RuntimeTask;
147
	type Nonce = u64;
148
	type Block = Block;
149
	type RuntimeCall = RuntimeCall;
150
	type Hash = H256;
151
	type Hashing = BlakeTwo256;
152
	type AccountId = AccountId;
153
	type Lookup = IdentityLookup<Self::AccountId>;
154
	type RuntimeEvent = RuntimeEvent;
155
	type BlockHashCount = BlockHashCount;
156
	type Version = ();
157
	type PalletInfo = PalletInfo;
158
	type AccountData = pallet_balances::AccountData<Balance>;
159
	type OnNewAccount = ();
160
	type OnKilledAccount = ();
161
	type SystemWeightInfo = ();
162
	type BlockWeights = ();
163
	type BlockLength = ();
164
	type SS58Prefix = SS58Prefix;
165
	type OnSetCode = ();
166
	type MaxConsumers = frame_support::traits::ConstU32<16>;
167
	type SingleBlockMigrations = ();
168
	type MultiBlockMigrator = ();
169
	type PreInherents = ();
170
	type PostInherents = ();
171
	type PostTransactions = ();
172
	type ExtensionsWeightInfo = ();
173
}
174
parameter_types! {
175
	pub const ExistentialDeposit: u128 = 0;
176
}
177
impl pallet_balances::Config for Runtime {
178
	type MaxReserves = ();
179
	type ReserveIdentifier = ();
180
	type MaxLocks = ();
181
	type Balance = Balance;
182
	type RuntimeEvent = RuntimeEvent;
183
	type DustRemoval = ();
184
	type ExistentialDeposit = ExistentialDeposit;
185
	type AccountStore = System;
186
	type WeightInfo = ();
187
	type RuntimeHoldReason = ();
188
	type FreezeIdentifier = ();
189
	type MaxFreezes = ();
190
	type RuntimeFreezeReason = ();
191
	type DoneSlashHandler = ();
192
}
193

            
194
parameter_types! {
195
	pub MatcherLocation: Location = Location::here();
196
}
197
pub type LocalOriginToLocation = MockAccountToAccountKey20<RuntimeOrigin, AccountId>;
198
impl pallet_xcm::Config for Runtime {
199
	type RuntimeEvent = RuntimeEvent;
200
	type SendXcmOrigin = xcm_builder::EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
201
	type XcmRouter = TestSendXcm;
202
	type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
203
	type XcmExecuteFilter = frame_support::traits::Everything;
204
	type XcmExecutor = xcm_executor::XcmExecutor<XcmConfig>;
205
	// Do not allow teleports
206
	type XcmTeleportFilter = Everything;
207
	type XcmReserveTransferFilter = Everything;
208
	type Weigher = FixedWeightBounds<BaseXcmWeight, RuntimeCall, MaxInstructions>;
209
	type UniversalLocation = Ancestry;
210
	type RuntimeOrigin = RuntimeOrigin;
211
	type RuntimeCall = RuntimeCall;
212
	const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
213
	// We use a custom one to test runtime ugprades
214
	type AdvertisedXcmVersion = ();
215
	type Currency = Balances;
216
	type CurrencyMatcher = IsConcrete<MatcherLocation>;
217
	type TrustedLockers = ();
218
	type SovereignAccountOf = ();
219
	type MaxLockers = ConstU32<8>;
220
	type WeightInfo = pallet_xcm::TestWeightInfo;
221
	type MaxRemoteLockConsumers = ConstU32<0>;
222
	type RemoteLockConsumerIdentifier = ();
223
	type AdminOrigin = frame_system::EnsureRoot<AccountId>;
224
	type AuthorizedAliasConsideration = Disabled;
225
}
226
// AccountIdToLocation converter for mock
227
pub struct MockAccountIdToLocation;
228
impl sp_runtime::traits::Convert<AccountId, Location> for MockAccountIdToLocation {
229
	fn convert(account: AccountId) -> Location {
230
		let account_h160: H160 = account.into();
231
		Location::new(
232
			0,
233
			[Junction::AccountKey20 {
234
				network: None,
235
				key: account_h160.into(),
236
			}],
237
		)
238
	}
239
}
240

            
241
parameter_types! {
242
	pub MockXcmFeesAccount: AccountId = MockAccount::from_u64(99);
243
	pub NativeLocation: Location = Location::new(
244
		0,
245
		[PalletInstance(<Runtime as frame_system::Config>::PalletInfo::index::<Balances>().unwrap() as u8)]
246
	);
247
}
248

            
249
// Simple weight to fee converter
250
pub struct MockWeightToFee;
251
impl frame_support::weights::WeightToFee for MockWeightToFee {
252
	type Balance = Balance;
253
1
	fn weight_to_fee(weight: &Weight) -> Self::Balance {
254
1
		weight.ref_time() as u128
255
1
	}
256
}
257

            
258
impl pallet_xcm_weight_trader::Config for Runtime {
259
	type AccountIdToLocation = MockAccountIdToLocation;
260
	type AddSupportedAssetOrigin = frame_system::EnsureRoot<AccountId>;
261
	type AssetLocationFilter = Everything;
262
	type AssetTransactor = DummyAssetTransactor;
263
	type Balance = Balance;
264
	type EditSupportedAssetOrigin = frame_system::EnsureRoot<AccountId>;
265
	type NativeLocation = NativeLocation;
266
	type PauseSupportedAssetOrigin = frame_system::EnsureRoot<AccountId>;
267
	type ResumeSupportedAssetOrigin = frame_system::EnsureRoot<AccountId>;
268
	type RemoveSupportedAssetOrigin = frame_system::EnsureRoot<AccountId>;
269
	type WeightInfo = ();
270
	type WeightToFee = MockWeightToFee;
271
	type XcmFeesAccount = MockXcmFeesAccount;
272
	#[cfg(feature = "runtime-benchmarks")]
273
	type NotFilteredLocation = RelayLocation;
274
}
275

            
276
pub type Precompiles<R> = PrecompileSetBuilder<
277
	R,
278
	(
279
		PrecompileAt<
280
			AddressU64<1>,
281
			XcmUtilsPrecompile<R, XcmConfig>,
282
			CallableByContract<AllExceptXcmExecute<R, XcmConfig>>,
283
		>,
284
	),
285
>;
286

            
287
pub type PCall = XcmUtilsPrecompileCall<Runtime, XcmConfig>;
288

            
289
const MAX_POV_SIZE: u64 = 5 * 1024 * 1024;
290
/// Block storage limit in bytes. Set to 40 KB.
291
const BLOCK_STORAGE_LIMIT: u64 = 40 * 1024;
292

            
293
parameter_types! {
294
	pub BlockGasLimit: U256 = U256::from(u64::MAX);
295
	pub PrecompilesValue: Precompiles<Runtime> = Precompiles::new();
296
	pub const WeightPerGas: Weight = Weight::from_parts(1, 0);
297
	pub GasLimitPovSizeRatio: u64 = {
298
		let block_gas_limit = BlockGasLimit::get().min(u64::MAX.into()).low_u64();
299
		block_gas_limit.saturating_div(MAX_POV_SIZE)
300
	};
301
	pub GasLimitStorageGrowthRatio: u64 = {
302
		let block_gas_limit = BlockGasLimit::get().min(u64::MAX.into()).low_u64();
303
		block_gas_limit.saturating_div(BLOCK_STORAGE_LIMIT)
304
	};
305
}
306

            
307
/// A mapping function that converts Ethereum gas to Substrate weight
308
/// We are mocking this 1-1 to test db read charges too
309
pub struct MockGasWeightMapping;
310
impl GasWeightMapping for MockGasWeightMapping {
311
	fn gas_to_weight(gas: u64, _without_base_weight: bool) -> Weight {
312
		Weight::from_parts(gas, 1)
313
	}
314
26
	fn weight_to_gas(weight: Weight) -> u64 {
315
26
		weight.ref_time().into()
316
26
	}
317
}
318

            
319
impl pallet_evm::Config for Runtime {
320
	type FeeCalculator = ();
321
	type GasWeightMapping = MockGasWeightMapping;
322
	type WeightPerGas = WeightPerGas;
323
	type CallOrigin = EnsureAddressRoot<AccountId>;
324
	type WithdrawOrigin = EnsureAddressNever<AccountId>;
325
	type AddressMapping = AccountId;
326
	type Currency = Balances;
327
	type Runner = pallet_evm::runner::stack::Runner<Self>;
328
	type PrecompilesValue = PrecompilesValue;
329
	type PrecompilesType = Precompiles<Self>;
330
	type ChainId = ();
331
	type OnChargeTransaction = ();
332
	type BlockGasLimit = BlockGasLimit;
333
	type BlockHashMapping = pallet_evm::SubstrateBlockHashMapping<Self>;
334
	type FindAuthor = ();
335
	type OnCreate = ();
336
	type GasLimitPovSizeRatio = GasLimitPovSizeRatio;
337
	type GasLimitStorageGrowthRatio = GasLimitStorageGrowthRatio;
338
	type Timestamp = Timestamp;
339
	type WeightInfo = pallet_evm::weights::SubstrateWeight<Runtime>;
340
	type AccountProvider = FrameSystemAccountProvider<Runtime>;
341
	type CreateOriginFilter = ();
342
	type CreateInnerOriginFilter = ();
343
}
344

            
345
parameter_types! {
346
	pub const MinimumPeriod: u64 = 5;
347
}
348
impl pallet_timestamp::Config for Runtime {
349
	type Moment = u64;
350
	type OnTimestampSet = ();
351
	type MinimumPeriod = MinimumPeriod;
352
	type WeightInfo = ();
353
}
354
pub type Barrier = AllowUnpaidExecutionFrom<Everything>;
355

            
356
use sp_std::cell::RefCell;
357
use xcm::latest::opaque;
358
// Simulates sending a XCM message
359
thread_local! {
360
	pub static SENT_XCM: RefCell<Vec<(Location, opaque::Xcm)>> = RefCell::new(Vec::new());
361
}
362
2
pub fn sent_xcm() -> Vec<(Location, opaque::Xcm)> {
363
2
	SENT_XCM.with(|q| (*q.borrow()).clone())
364
2
}
365
pub struct TestSendXcm;
366
impl SendXcm for TestSendXcm {
367
	type Ticket = ();
368

            
369
3
	fn validate(
370
3
		destination: &mut Option<Location>,
371
3
		message: &mut Option<opaque::Xcm>,
372
3
	) -> SendResult<Self::Ticket> {
373
3
		SENT_XCM.with(|q| {
374
3
			q.borrow_mut()
375
3
				.push((destination.clone().unwrap(), message.clone().unwrap()))
376
3
		});
377
3
		Ok(((), Assets::new()))
378
3
	}
379

            
380
2
	fn deliver(_: Self::Ticket) -> Result<XcmHash, SendError> {
381
2
		Ok(XcmHash::default())
382
2
	}
383
}
384

            
385
pub struct DummyAssetTransactor;
386
impl TransactAsset for DummyAssetTransactor {
387
	fn deposit_asset(_what: &Asset, _who: &Location, _context: Option<&XcmContext>) -> XcmResult {
388
		Ok(())
389
	}
390

            
391
1
	fn withdraw_asset(
392
1
		_what: &Asset,
393
1
		_who: &Location,
394
1
		_maybe_context: Option<&XcmContext>,
395
1
	) -> Result<AssetsInHolding, XcmError> {
396
1
		Ok(AssetsInHolding::default())
397
1
	}
398
}
399

            
400
pub struct DummyWeightTrader;
401
impl WeightTrader for DummyWeightTrader {
402
3
	fn new() -> Self {
403
3
		DummyWeightTrader
404
3
	}
405

            
406
	fn buy_weight(
407
		&mut self,
408
		weight: Weight,
409
		payment: AssetsInHolding,
410
		_context: &XcmContext,
411
	) -> Result<AssetsInHolding, XcmError> {
412
		let asset_to_charge: Asset = (Location::parent(), weight.ref_time() as u128).into();
413
		let unused = payment
414
			.checked_sub(asset_to_charge)
415
			.map_err(|_| XcmError::TooExpensive)?;
416

            
417
		Ok(unused)
418
	}
419
}
420

            
421
parameter_types! {
422
	pub const BaseXcmWeight: Weight = Weight::from_parts(1000u64, 0u64);
423
	pub const RelayNetwork: NetworkId = NetworkId::Polkadot;
424

            
425
	pub SelfLocation: Location =
426
		Location::new(1, [Parachain(ParachainId::get().into())]);
427

            
428
	pub SelfReserve: Location = Location::new(
429
		1,
430
		[
431
			Parachain(ParachainId::get().into()),
432
			PalletInstance(<Runtime as frame_system::Config>::PalletInfo::index::<Balances>().unwrap() as u8)
433
		]);
434
	pub MaxInstructions: u32 = 100;
435

            
436
	pub UniversalLocation: InteriorLocation = Here;
437
	pub Ancestry: InteriorLocation =
438
		[GlobalConsensus(RelayNetwork::get()), Parachain(ParachainId::get().into())].into();
439

            
440
	pub const MaxAssetsIntoHolding: u32 = 64;
441

            
442
	pub RelayLocation: Location = Location::parent();
443
	pub RelayForeignAsset: (AssetFilter, Location) = (All.into(), RelayLocation::get());
444
}
445

            
446
pub type XcmOriginToTransactDispatchOrigin = (
447
	// Sovereign account converter; this attempts to derive an `AccountId` from the origin location
448
	// using `LocationToAccountId` and then turn that into the usual `Signed` origin. Useful for
449
	// foreign chains who want to have a local sovereign account on this chain which they control.
450
	SovereignSignedViaLocation<LocationToAccountId, RuntimeOrigin>,
451
);
452
pub struct XcmConfig;
453
impl xcm_executor::Config for XcmConfig {
454
	type RuntimeCall = RuntimeCall;
455
	type XcmSender = TestSendXcm;
456
	type AssetTransactor = DummyAssetTransactor;
457
	type OriginConverter = XcmOriginToTransactDispatchOrigin;
458
	type IsReserve = Case<RelayForeignAsset>;
459
	type IsTeleporter = ();
460
	type UniversalLocation = UniversalLocation;
461
	type Barrier = Barrier;
462
	type Weigher = FixedWeightBounds<BaseXcmWeight, RuntimeCall, MaxInstructions>;
463
	type Trader = DummyWeightTrader;
464
	type ResponseHandler = ();
465
	type SubscriptionService = ();
466
	type AssetTrap = ();
467
	type AssetClaims = ();
468
	type CallDispatcher = RuntimeCall;
469
	type AssetLocker = ();
470
	type AssetExchanger = ();
471
	type PalletInstancesInfo = ();
472
	type MaxAssetsIntoHolding = MaxAssetsIntoHolding;
473
	type FeeManager = ();
474
	type MessageExporter = ();
475
	type UniversalAliases = Nothing;
476
	type SafeCallFilter = Everything;
477
	type Aliasers = Nothing;
478
	type TransactionalProcessor = ();
479
	type HrmpNewChannelOpenRequestHandler = ();
480
	type HrmpChannelAcceptedHandler = ();
481
	type HrmpChannelClosingHandler = ();
482
	type XcmRecorder = ();
483
	type XcmEventEmitter = ();
484
}
485

            
486
pub(crate) struct ExtBuilder {
487
	// endowed accounts with balances
488
	balances: Vec<(AccountId, Balance)>,
489
}
490

            
491
impl Default for ExtBuilder {
492
10
	fn default() -> ExtBuilder {
493
10
		ExtBuilder { balances: vec![] }
494
10
	}
495
}
496

            
497
impl ExtBuilder {
498
2
	pub(crate) fn with_balances(mut self, balances: Vec<(AccountId, Balance)>) -> Self {
499
2
		self.balances = balances;
500
2
		self
501
2
	}
502

            
503
10
	pub(crate) fn build(self) -> sp_io::TestExternalities {
504
10
		let mut t = frame_system::GenesisConfig::<Runtime>::default()
505
10
			.build_storage()
506
10
			.expect("Frame system builds valid default genesis config");
507

            
508
10
		pallet_balances::GenesisConfig::<Runtime> {
509
10
			balances: self.balances,
510
10
			dev_accounts: None,
511
10
		}
512
10
		.assimilate_storage(&mut t)
513
10
		.expect("Pallet balances storage can be assimilated");
514

            
515
		// Register Location::parent() as a supported asset in pallet_xcm_weight_trader
516
		// with a relative_price of 10^18 so that units_per_second = weight_per_second
517
		// (since MockWeightToFee is 1:1 and RELATIVE_PRICE_DECIMALS = 18)
518
10
		pallet_xcm_weight_trader::GenesisConfig::<Runtime> {
519
10
			assets: vec![pallet_xcm_weight_trader::XcmWeightTraderAssetInfo {
520
10
				location: Location::parent(),
521
10
				relative_price: 1_000_000_000_000_000_000u128, // 10^18
522
10
			}],
523
10
			_phantom: Default::default(),
524
10
		}
525
10
		.assimilate_storage(&mut t)
526
10
		.expect("XcmWeightTrader storage can be assimilated");
527

            
528
10
		let mut ext = sp_io::TestExternalities::new(t);
529
10
		ext.execute_with(|| System::set_block_number(1));
530
10
		ext
531
10
	}
532
}