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
	}
62
);
63

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

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

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

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

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

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

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

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

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

            
193
parameter_types! {
194
	pub MatcherLocation: Location = Location::here();
195
}
196
pub type LocalOriginToLocation = MockAccountToAccountKey20<RuntimeOrigin, AccountId>;
197
impl pallet_xcm::Config for Runtime {
198
	type RuntimeEvent = RuntimeEvent;
199
	type SendXcmOrigin = xcm_builder::EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
200
	type XcmRouter = TestSendXcm;
201
	type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
202
	type XcmExecuteFilter = frame_support::traits::Everything;
203
	type XcmExecutor = xcm_executor::XcmExecutor<XcmConfig>;
204
	// Do not allow teleports
205
	type XcmTeleportFilter = Everything;
206
	type XcmReserveTransferFilter = Everything;
207
	type Weigher = FixedWeightBounds<BaseXcmWeight, RuntimeCall, MaxInstructions>;
208
	type UniversalLocation = Ancestry;
209
	type RuntimeOrigin = RuntimeOrigin;
210
	type RuntimeCall = RuntimeCall;
211
	const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
212
	// We use a custom one to test runtime ugprades
213
	type AdvertisedXcmVersion = ();
214
	type Currency = Balances;
215
	type CurrencyMatcher = IsConcrete<MatcherLocation>;
216
	type TrustedLockers = ();
217
	type SovereignAccountOf = ();
218
	type MaxLockers = ConstU32<8>;
219
	type WeightInfo = pallet_xcm::TestWeightInfo;
220
	type MaxRemoteLockConsumers = ConstU32<0>;
221
	type RemoteLockConsumerIdentifier = ();
222
	type AdminOrigin = frame_system::EnsureRoot<AccountId>;
223
	type AuthorizedAliasConsideration = Disabled;
224
}
225
pub type Precompiles<R> = PrecompileSetBuilder<
226
	R,
227
	(
228
		PrecompileAt<
229
			AddressU64<1>,
230
			XcmUtilsPrecompile<R, XcmConfig>,
231
			CallableByContract<AllExceptXcmExecute<R, XcmConfig>>,
232
		>,
233
	),
234
>;
235

            
236
pub type PCall = XcmUtilsPrecompileCall<Runtime, XcmConfig>;
237

            
238
const MAX_POV_SIZE: u64 = 5 * 1024 * 1024;
239
/// Block storage limit in bytes. Set to 40 KB.
240
const BLOCK_STORAGE_LIMIT: u64 = 40 * 1024;
241

            
242
parameter_types! {
243
	pub BlockGasLimit: U256 = U256::from(u64::MAX);
244
	pub PrecompilesValue: Precompiles<Runtime> = Precompiles::new();
245
	pub const WeightPerGas: Weight = Weight::from_parts(1, 0);
246
	pub GasLimitPovSizeRatio: u64 = {
247
		let block_gas_limit = BlockGasLimit::get().min(u64::MAX.into()).low_u64();
248
		block_gas_limit.saturating_div(MAX_POV_SIZE)
249
	};
250
	pub GasLimitStorageGrowthRatio: u64 = {
251
		let block_gas_limit = BlockGasLimit::get().min(u64::MAX.into()).low_u64();
252
		block_gas_limit.saturating_div(BLOCK_STORAGE_LIMIT)
253
	};
254
}
255

            
256
/// A mapping function that converts Ethereum gas to Substrate weight
257
/// We are mocking this 1-1 to test db read charges too
258
pub struct MockGasWeightMapping;
259
impl GasWeightMapping for MockGasWeightMapping {
260
	fn gas_to_weight(gas: u64, _without_base_weight: bool) -> Weight {
261
		Weight::from_parts(gas, 1)
262
	}
263
26
	fn weight_to_gas(weight: Weight) -> u64 {
264
26
		weight.ref_time().into()
265
26
	}
266
}
267

            
268
impl pallet_evm::Config for Runtime {
269
	type FeeCalculator = ();
270
	type GasWeightMapping = MockGasWeightMapping;
271
	type WeightPerGas = WeightPerGas;
272
	type CallOrigin = EnsureAddressRoot<AccountId>;
273
	type WithdrawOrigin = EnsureAddressNever<AccountId>;
274
	type AddressMapping = AccountId;
275
	type Currency = Balances;
276
	type Runner = pallet_evm::runner::stack::Runner<Self>;
277
	type PrecompilesValue = PrecompilesValue;
278
	type PrecompilesType = Precompiles<Self>;
279
	type ChainId = ();
280
	type OnChargeTransaction = ();
281
	type BlockGasLimit = BlockGasLimit;
282
	type BlockHashMapping = pallet_evm::SubstrateBlockHashMapping<Self>;
283
	type FindAuthor = ();
284
	type OnCreate = ();
285
	type GasLimitPovSizeRatio = GasLimitPovSizeRatio;
286
	type GasLimitStorageGrowthRatio = GasLimitStorageGrowthRatio;
287
	type Timestamp = Timestamp;
288
	type WeightInfo = pallet_evm::weights::SubstrateWeight<Runtime>;
289
	type AccountProvider = FrameSystemAccountProvider<Runtime>;
290
	type CreateOriginFilter = ();
291
	type CreateInnerOriginFilter = ();
292
}
293

            
294
parameter_types! {
295
	pub const MinimumPeriod: u64 = 5;
296
}
297
impl pallet_timestamp::Config for Runtime {
298
	type Moment = u64;
299
	type OnTimestampSet = ();
300
	type MinimumPeriod = MinimumPeriod;
301
	type WeightInfo = ();
302
}
303
pub type Barrier = AllowUnpaidExecutionFrom<Everything>;
304

            
305
use sp_std::cell::RefCell;
306
use xcm::latest::opaque;
307
// Simulates sending a XCM message
308
thread_local! {
309
	pub static SENT_XCM: RefCell<Vec<(Location, opaque::Xcm)>> = RefCell::new(Vec::new());
310
}
311
2
pub fn sent_xcm() -> Vec<(Location, opaque::Xcm)> {
312
2
	SENT_XCM.with(|q| (*q.borrow()).clone())
313
2
}
314
pub struct TestSendXcm;
315
impl SendXcm for TestSendXcm {
316
	type Ticket = ();
317

            
318
3
	fn validate(
319
3
		destination: &mut Option<Location>,
320
3
		message: &mut Option<opaque::Xcm>,
321
3
	) -> SendResult<Self::Ticket> {
322
3
		SENT_XCM.with(|q| {
323
3
			q.borrow_mut()
324
3
				.push((destination.clone().unwrap(), message.clone().unwrap()))
325
3
		});
326
3
		Ok(((), Assets::new()))
327
3
	}
328

            
329
2
	fn deliver(_: Self::Ticket) -> Result<XcmHash, SendError> {
330
2
		Ok(XcmHash::default())
331
2
	}
332
}
333

            
334
pub struct DummyAssetTransactor;
335
impl TransactAsset for DummyAssetTransactor {
336
	fn deposit_asset(_what: &Asset, _who: &Location, _context: Option<&XcmContext>) -> XcmResult {
337
		Ok(())
338
	}
339

            
340
1
	fn withdraw_asset(
341
1
		_what: &Asset,
342
1
		_who: &Location,
343
1
		_maybe_context: Option<&XcmContext>,
344
1
	) -> Result<AssetsInHolding, XcmError> {
345
1
		Ok(AssetsInHolding::default())
346
1
	}
347
}
348

            
349
pub struct DummyWeightTrader;
350
impl WeightTrader for DummyWeightTrader {
351
4
	fn new() -> Self {
352
4
		DummyWeightTrader
353
4
	}
354

            
355
1
	fn buy_weight(
356
1
		&mut self,
357
1
		weight: Weight,
358
1
		payment: AssetsInHolding,
359
1
		_context: &XcmContext,
360
1
	) -> Result<AssetsInHolding, XcmError> {
361
1
		let asset_to_charge: Asset = (Location::parent(), weight.ref_time() as u128).into();
362
1
		let unused = payment
363
1
			.checked_sub(asset_to_charge)
364
1
			.map_err(|_| XcmError::TooExpensive)?;
365

            
366
1
		Ok(unused)
367
1
	}
368
}
369

            
370
parameter_types! {
371
	pub const BaseXcmWeight: Weight = Weight::from_parts(1000u64, 0u64);
372
	pub const RelayNetwork: NetworkId = NetworkId::Polkadot;
373

            
374
	pub SelfLocation: Location =
375
		Location::new(1, [Parachain(ParachainId::get().into())]);
376

            
377
	pub SelfReserve: Location = Location::new(
378
		1,
379
		[
380
			Parachain(ParachainId::get().into()),
381
			PalletInstance(<Runtime as frame_system::Config>::PalletInfo::index::<Balances>().unwrap() as u8)
382
		]);
383
	pub MaxInstructions: u32 = 100;
384

            
385
	pub UniversalLocation: InteriorLocation = Here;
386
	pub Ancestry: InteriorLocation =
387
		[GlobalConsensus(RelayNetwork::get()), Parachain(ParachainId::get().into())].into();
388

            
389
	pub const MaxAssetsIntoHolding: u32 = 64;
390

            
391
	pub RelayLocation: Location = Location::parent();
392
	pub RelayForeignAsset: (AssetFilter, Location) = (All.into(), RelayLocation::get());
393
}
394

            
395
pub type XcmOriginToTransactDispatchOrigin = (
396
	// Sovereign account converter; this attempts to derive an `AccountId` from the origin location
397
	// using `LocationToAccountId` and then turn that into the usual `Signed` origin. Useful for
398
	// foreign chains who want to have a local sovereign account on this chain which they control.
399
	SovereignSignedViaLocation<LocationToAccountId, RuntimeOrigin>,
400
);
401
pub struct XcmConfig;
402
impl xcm_executor::Config for XcmConfig {
403
	type RuntimeCall = RuntimeCall;
404
	type XcmSender = TestSendXcm;
405
	type AssetTransactor = DummyAssetTransactor;
406
	type OriginConverter = XcmOriginToTransactDispatchOrigin;
407
	type IsReserve = Case<RelayForeignAsset>;
408
	type IsTeleporter = ();
409
	type UniversalLocation = UniversalLocation;
410
	type Barrier = Barrier;
411
	type Weigher = FixedWeightBounds<BaseXcmWeight, RuntimeCall, MaxInstructions>;
412
	type Trader = DummyWeightTrader;
413
	type ResponseHandler = ();
414
	type SubscriptionService = ();
415
	type AssetTrap = ();
416
	type AssetClaims = ();
417
	type CallDispatcher = RuntimeCall;
418
	type AssetLocker = ();
419
	type AssetExchanger = ();
420
	type PalletInstancesInfo = ();
421
	type MaxAssetsIntoHolding = MaxAssetsIntoHolding;
422
	type FeeManager = ();
423
	type MessageExporter = ();
424
	type UniversalAliases = Nothing;
425
	type SafeCallFilter = Everything;
426
	type Aliasers = Nothing;
427
	type TransactionalProcessor = ();
428
	type HrmpNewChannelOpenRequestHandler = ();
429
	type HrmpChannelAcceptedHandler = ();
430
	type HrmpChannelClosingHandler = ();
431
	type XcmRecorder = ();
432
	type XcmEventEmitter = ();
433
}
434

            
435
pub(crate) struct ExtBuilder {
436
	// endowed accounts with balances
437
	balances: Vec<(AccountId, Balance)>,
438
}
439

            
440
impl Default for ExtBuilder {
441
10
	fn default() -> ExtBuilder {
442
10
		ExtBuilder { balances: vec![] }
443
10
	}
444
}
445

            
446
impl ExtBuilder {
447
2
	pub(crate) fn with_balances(mut self, balances: Vec<(AccountId, Balance)>) -> Self {
448
2
		self.balances = balances;
449
2
		self
450
2
	}
451

            
452
10
	pub(crate) fn build(self) -> sp_io::TestExternalities {
453
10
		let mut t = frame_system::GenesisConfig::<Runtime>::default()
454
10
			.build_storage()
455
10
			.expect("Frame system builds valid default genesis config");
456

            
457
10
		pallet_balances::GenesisConfig::<Runtime> {
458
10
			balances: self.balances,
459
10
			dev_accounts: None,
460
10
		}
461
10
		.assimilate_storage(&mut t)
462
10
		.expect("Pallet balances storage can be assimilated");
463

            
464
10
		let mut ext = sp_io::TestExternalities::new(t);
465
10
		ext.execute_with(|| System::set_block_number(1));
466
10
		ext
467
10
	}
468
}