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::{Everything, 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::{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
236
	pub enum Runtime	{
55
236
		System: frame_system,
56
236
		Balances: pallet_balances,
57
236
		Evm: pallet_evm,
58
236
		Timestamp: pallet_timestamp,
59
236
		XcmTransactor: pallet_xcm_transactor,
60
236
	}
61
666
);
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
	type ExtensionsWeightInfo = ();
123
}
124
parameter_types! {
125
	pub const ExistentialDeposit: u128 = 0;
126
}
127
impl pallet_balances::Config for Runtime {
128
	type MaxReserves = ();
129
	type ReserveIdentifier = ();
130
	type MaxLocks = ();
131
	type Balance = Balance;
132
	type RuntimeEvent = RuntimeEvent;
133
	type DustRemoval = ();
134
	type ExistentialDeposit = ExistentialDeposit;
135
	type AccountStore = System;
136
	type WeightInfo = ();
137
	type RuntimeHoldReason = ();
138
	type FreezeIdentifier = ();
139
	type MaxFreezes = ();
140
	type RuntimeFreezeReason = ();
141
	type DoneSlashHandler = ();
142
}
143

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

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

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

            
171
pub type PCallV1 = XcmTransactorPrecompileV1Call<Runtime>;
172
pub type PCallV2 = XcmTransactorPrecompileV2Call<Runtime>;
173
pub type PCallV3 = XcmTransactorPrecompileV3Call<Runtime>;
174

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

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

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

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

            
230
parameter_types! {
231
	pub const MinimumPeriod: u64 = 5;
232
}
233
impl pallet_timestamp::Config for Runtime {
234
	type Moment = u64;
235
	type OnTimestampSet = ();
236
	type MinimumPeriod = MinimumPeriod;
237
	type WeightInfo = ();
238
}
239

            
240
pub struct DoNothingRouter;
241
impl SendXcm for DoNothingRouter {
242
	type Ticket = ();
243

            
244
12
	fn validate(
245
12
		_destination: &mut Option<Location>,
246
12
		_message: &mut Option<Xcm<()>>,
247
12
	) -> SendResult<Self::Ticket> {
248
12
		Ok(((), Assets::new()))
249
12
	}
250

            
251
12
	fn deliver(_: Self::Ticket) -> Result<XcmHash, SendError> {
252
12
		Ok(XcmHash::default())
253
12
	}
254
}
255

            
256
pub struct DummyAssetTransactor;
257
impl TransactAsset for DummyAssetTransactor {
258
	fn deposit_asset(_what: &Asset, _who: &Location, _context: Option<&XcmContext>) -> XcmResult {
259
		Ok(())
260
	}
261

            
262
6
	fn withdraw_asset(
263
6
		_what: &Asset,
264
6
		_who: &Location,
265
6
		_maybe_context: Option<&XcmContext>,
266
6
	) -> Result<AssetsInHolding, XcmError> {
267
6
		Ok(AssetsInHolding::default())
268
6
	}
269
}
270

            
271
pub struct DummyWeightTrader;
272
impl WeightTrader for DummyWeightTrader {
273
	fn new() -> Self {
274
		DummyWeightTrader
275
	}
276

            
277
	fn buy_weight(
278
		&mut self,
279
		_weight: Weight,
280
		_payment: AssetsInHolding,
281
		_context: &XcmContext,
282
	) -> Result<AssetsInHolding, XcmError> {
283
		Ok(AssetsInHolding::default())
284
	}
285
}
286

            
287
#[derive(Clone, Eq, Debug, PartialEq, Ord, PartialOrd, Encode, Decode, scale_info::TypeInfo)]
288
pub enum CurrencyId {
289
	SelfReserve,
290
	OtherReserve(AssetId),
291
}
292

            
293
parameter_types! {
294
	pub Ancestry: Location = Parachain(ParachainId::get().into()).into();
295

            
296
	pub const BaseXcmWeight: Weight = Weight::from_parts(1000u64, 1000u64);
297
	pub const RelayNetwork: NetworkId = NetworkId::Polkadot;
298

            
299
	pub SelfLocation: Location =
300
		Location::new(1, [Parachain(ParachainId::get().into())]);
301

            
302
	pub SelfReserve: Location = Location::new(
303
		1,
304
		[
305
			Parachain(ParachainId::get().into()),
306
			PalletInstance(
307
				<Runtime as frame_system::Config>::PalletInfo::index::<Balances>().unwrap() as u8
308
			)
309
		]);
310
	pub MaxInstructions: u32 = 100;
311

            
312
	pub UniversalLocation: InteriorLocation = Here;
313
	pub SelfLocationAbsolute: Location = Location {
314
		parents: 1,
315
		interior: [Parachain(ParachainId::get().into())].into(),
316
	};
317
}
318

            
319
impl pallet_xcm_transactor::Config for Runtime {
320
	type RuntimeEvent = RuntimeEvent;
321
	type Balance = Balance;
322
	type Transactor = MockTransactors;
323
	type DerivativeAddressRegistrationOrigin = frame_system::EnsureRoot<AccountId>;
324
	type SovereignAccountDispatcherOrigin = frame_system::EnsureRoot<AccountId>;
325
	type CurrencyId = CurrencyId;
326
	type AccountIdToLocation = AccountIdToLocation;
327
	type CurrencyIdToLocation = CurrencyIdToLocation;
328
	type SelfLocation = SelfLocation;
329
	type Weigher = FixedWeightBounds<BaseXcmWeight, RuntimeCall, MaxInstructions>;
330
	type UniversalLocation = UniversalLocation;
331
	type BaseXcmWeight = BaseXcmWeight;
332
	type XcmSender = DoNothingRouter;
333
	type AssetTransactor = DummyAssetTransactor;
334
	type ReserveProvider = xcm_primitives::AbsoluteAndRelativeReserve<SelfLocationAbsolute>;
335
	type WeightInfo = ();
336
	type HrmpManipulatorOrigin = frame_system::EnsureRoot<AccountId>;
337
	type HrmpOpenOrigin = frame_system::EnsureRoot<AccountId>;
338
	type MaxHrmpFee = ();
339
}
340

            
341
// We need to use the encoding from the relay mock runtime
342
#[derive(Encode, Decode)]
343
pub enum RelayCall {
344
	#[codec(index = 5u8)]
345
	// the index should match the position of the module in `construct_runtime!`
346
	Utility(UtilityCall),
347
}
348

            
349
#[derive(Encode, Decode)]
350
pub enum UtilityCall {
351
	#[codec(index = 1u8)]
352
	AsDerivative(u16),
353
}
354

            
355
#[derive(Clone, Eq, Debug, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
356
pub enum MockTransactors {
357
	Relay,
358
}
359

            
360
impl TryFrom<u8> for MockTransactors {
361
	type Error = ();
362

            
363
6
	fn try_from(value: u8) -> Result<Self, Self::Error> {
364
6
		match value {
365
6
			0x0 => Ok(MockTransactors::Relay),
366
			_ => Err(()),
367
		}
368
6
	}
369
}
370

            
371
impl xcm_primitives::XcmTransact for MockTransactors {
372
6
	fn destination(self) -> Location {
373
6
		match self {
374
6
			MockTransactors::Relay => Location::parent(),
375
6
		}
376
6
	}
377
}
378

            
379
impl xcm_primitives::UtilityEncodeCall for MockTransactors {
380
6
	fn encode_call(self, call: xcm_primitives::UtilityAvailableCalls) -> Vec<u8> {
381
6
		match self {
382
6
			MockTransactors::Relay => match call {
383
6
				xcm_primitives::UtilityAvailableCalls::AsDerivative(a, b) => {
384
6
					let mut call =
385
6
						RelayCall::Utility(UtilityCall::AsDerivative(a.clone())).encode();
386
6
					call.append(&mut b.clone());
387
6
					call
388
6
				}
389
6
			},
390
6
		}
391
6
	}
392
}
393

            
394
// Implement the trait, where we convert AccountId to AssetID
395
impl AccountIdToCurrencyId<AccountId, CurrencyId> for Runtime {
396
	/// The way to convert an account to assetId is by ensuring that the prefix is 0XFFFFFFFF
397
	/// and by taking the lowest 128 bits as the assetId
398
6
	fn account_to_currency_id(account: AccountId) -> Option<CurrencyId> {
399
6
		match account {
400
6
			a if a.has_prefix_u32(0xffffffff) => Some(CurrencyId::OtherReserve(a.without_prefix())),
401
			a if a == SelfReserveAddress.into() => Some(CurrencyId::SelfReserve),
402
			_ => None,
403
		}
404
6
	}
405
}
406

            
407
pub struct CurrencyIdToLocation;
408

            
409
impl sp_runtime::traits::Convert<CurrencyId, Option<Location>> for CurrencyIdToLocation {
410
6
	fn convert(currency: CurrencyId) -> Option<Location> {
411
6
		match currency {
412
			CurrencyId::SelfReserve => {
413
				let multi: Location = SelfReserve::get();
414
				Some(multi)
415
			}
416
			// To distinguish between relay and others, specially for reserve asset
417
6
			CurrencyId::OtherReserve(asset) => {
418
6
				if asset == 0 {
419
6
					Some(Location::parent())
420
				} else {
421
					Some(Location::new(1, [Parachain(2), GeneralIndex(asset)]))
422
				}
423
			}
424
		}
425
6
	}
426
}
427

            
428
pub(crate) struct ExtBuilder {
429
	// endowed accounts with balances
430
	balances: Vec<(AccountId, Balance)>,
431
}
432

            
433
impl Default for ExtBuilder {
434
20
	fn default() -> ExtBuilder {
435
20
		ExtBuilder { balances: vec![] }
436
20
	}
437
}
438

            
439
impl ExtBuilder {
440
17
	pub(crate) fn with_balances(mut self, balances: Vec<(AccountId, Balance)>) -> Self {
441
17
		self.balances = balances;
442
17
		self
443
17
	}
444
20
	pub(crate) fn build(self) -> sp_io::TestExternalities {
445
20
		let mut t = frame_system::GenesisConfig::<Runtime>::default()
446
20
			.build_storage()
447
20
			.expect("Frame system builds valid default genesis config");
448
20

            
449
20
		pallet_balances::GenesisConfig::<Runtime> {
450
20
			balances: self.balances,
451
20
		}
452
20
		.assimilate_storage(&mut t)
453
20
		.expect("Pallet balances storage can be assimilated");
454
20

            
455
20
		let mut ext = sp_io::TestExternalities::new(t);
456
20
		ext.execute_with(|| System::set_block_number(1));
457
20
		ext
458
20
	}
459
}