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::{ConstBool, 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
48
construct_runtime!(
55
48
	pub enum Runtime	{
56
48
		System: frame_system,
57
48
		Balances: pallet_balances,
58
48
		Evm: pallet_evm,
59
48
		Timestamp: pallet_timestamp,
60
48
		PolkadotXcm: pallet_xcm,
61
48
	}
62
48
);
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
	type AssetHubMigrationStarted = ConstBool<false>;
225
}
226
pub type Precompiles<R> = PrecompileSetBuilder<
227
	R,
228
	(
229
		PrecompileAt<
230
			AddressU64<1>,
231
			XcmUtilsPrecompile<R, XcmConfig>,
232
			CallableByContract<AllExceptXcmExecute<R, XcmConfig>>,
233
		>,
234
	),
235
>;
236

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

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

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

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

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

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

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

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

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

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

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

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

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

            
368
1
		Ok(unused)
369
1
	}
370
}
371

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

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

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

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

            
391
	pub const MaxAssetsIntoHolding: u32 = 64;
392

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

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

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

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

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

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

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

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