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
#![allow(dead_code)]
18

            
19
use cumulus_primitives_parachain_inherent::ParachainInherentData;
20
use fp_evm::GenesisAccount;
21
use frame_support::{
22
	assert_ok,
23
	traits::{OnFinalize, OnInitialize},
24
};
25
pub use moonbeam_runtime::{
26
	currency::GLMR, AccountId, AsyncBacking, AuthorInherent, Balance, Ethereum, InflationInfo,
27
	ParachainStaking, Range, Runtime, RuntimeCall, RuntimeEvent, System, TransactionConverter,
28
	UncheckedExtrinsic, HOURS,
29
};
30
use nimbus_primitives::{NimbusId, NIMBUS_ENGINE_ID};
31
use polkadot_parachain::primitives::HeadData;
32
use sp_consensus_slots::Slot;
33
use sp_core::{Encode, H160};
34
use sp_runtime::{traits::Dispatchable, BuildStorage, Digest, DigestItem, Perbill, Percent};
35

            
36
use cumulus_pallet_parachain_system::MessagingStateSnapshot;
37
use cumulus_primitives_core::relay_chain::{AbridgedHostConfiguration, AsyncBackingParams};
38
use cumulus_primitives_core::AbridgedHrmpChannel;
39
use fp_rpc::ConvertTransaction;
40
use moonbeam_runtime::bridge_config::XcmOverKusamaInstance;
41
use moonbeam_runtime::{EvmForeignAssets, XcmWeightTrader};
42
use std::collections::BTreeMap;
43
use xcm::latest::{InteriorLocation, Location};
44

            
45
10
pub fn existential_deposit() -> u128 {
46
10
	<Runtime as pallet_balances::Config>::ExistentialDeposit::get()
47
10
}
48

            
49
/// Returns mock AbridgedHostConfiguration for ParachainSystem tests
50
58
pub fn mock_abridged_host_config() -> AbridgedHostConfiguration {
51
58
	AbridgedHostConfiguration {
52
58
		max_code_size: 3_145_728,
53
58
		max_head_data_size: 20_480,
54
58
		max_upward_queue_count: 174_762,
55
58
		max_upward_queue_size: 1_048_576,
56
58
		max_upward_message_size: 65_531,
57
58
		max_upward_message_num_per_candidate: 16,
58
58
		hrmp_max_message_num_per_candidate: 10,
59
58
		validation_upgrade_cooldown: 6,
60
58
		validation_upgrade_delay: 6,
61
58
		async_backing_params: AsyncBackingParams {
62
58
			max_candidate_depth: 3,
63
58
			allowed_ancestry_len: 2,
64
58
		},
65
58
	}
66
58
}
67

            
68
// A valid signed Alice transfer.
69
pub const VALID_ETH_TX: &str =
70
	"02f869820501808085e8d4a51000825208943cd0a705a2dc65e5b1e1205896baa2be8a07c6e00180c\
71
	001a061087911e877a5802142a89a40d231d50913db399eb50839bb2d04e612b22ec8a01aa313efdf2\
72
	793bea76da6813bda611444af16a6207a8cfef2d9c8aa8f8012f7";
73

            
74
// An invalid signed Alice transfer with a gas limit artifically set to 0.
75
pub const INVALID_ETH_TX: &str =
76
	"f8628085174876e800809412cb274aad8251c875c0bf6872b67d9983e53fdd01801ba011110796057\
77
	0e2d49fcc2afbc582e1abd3eeb027242b92abcebcec7cdefab63ea001732f6fac84acdd5b096554230\
78
	75003e7f07430652c3d6722e18f50b3d34e29";
79

            
80
3
pub fn rpc_run_to_block(n: u32) {
81
6
	while System::block_number() < n {
82
3
		Ethereum::on_finalize(System::block_number());
83
3
		System::set_block_number(System::block_number() + 1);
84
3
		Ethereum::on_initialize(System::block_number());
85
3
	}
86
3
}
87

            
88
/// Utility function that advances the chain to the desired block number.
89
/// If an author is provided, that author information is injected to all the blocks in the meantime.
90
8
pub fn run_to_block(n: u32, author: Option<NimbusId>) {
91
16206
	while System::block_number() < n {
92
		// Set the new block number and author
93
16198
		match author {
94
16198
			Some(ref author) => {
95
16198
				let pre_digest = Digest {
96
16198
					logs: vec![DigestItem::PreRuntime(NIMBUS_ENGINE_ID, author.encode())],
97
16198
				};
98
16198
				System::reset_events();
99
16198
				System::initialize(
100
16198
					&(System::block_number() + 1),
101
16198
					&System::parent_hash(),
102
16198
					&pre_digest,
103
16198
				);
104
16198
			}
105
			None => {
106
				System::set_block_number(System::block_number() + 1);
107
			}
108
		}
109

            
110
16198
		increase_last_relay_slot_number(1);
111

            
112
		// Initialize the new block
113
16198
		AuthorInherent::on_initialize(System::block_number());
114
16198
		ParachainStaking::on_initialize(System::block_number());
115

            
116
		// Finalize the block
117
16198
		ParachainStaking::on_finalize(System::block_number());
118
	}
119
8
}
120

            
121
1
pub fn last_event() -> RuntimeEvent {
122
1
	System::events().pop().expect("Event expected").event
123
1
}
124

            
125
// Helper function to give a simple evm context suitable for tests.
126
// We can remove this once https://github.com/rust-blockchain/evm/pull/35
127
// is in our dependency graph.
128
pub fn evm_test_context() -> fp_evm::Context {
129
	fp_evm::Context {
130
		address: Default::default(),
131
		caller: Default::default(),
132
		apparent_value: From::from(0),
133
	}
134
}
135

            
136
// Test struct with the purpose of initializing x─cm assets
137
#[derive(Clone)]
138
pub struct XcmAssetInitialization {
139
	pub asset_id: u128,
140
	pub xcm_location: xcm::v5::Location,
141
	pub decimals: u8,
142
	pub name: &'static str,
143
	pub symbol: &'static str,
144
	pub balances: Vec<(AccountId, Balance)>,
145
}
146

            
147
pub struct ExtBuilder {
148
	// endowed accounts with balances
149
	balances: Vec<(AccountId, Balance)>,
150
	// [collator, amount]
151
	collators: Vec<(AccountId, Balance)>,
152
	// [delegator, collator, nomination_amount]
153
	delegations: Vec<(AccountId, AccountId, Balance, Percent)>,
154
	// per-round inflation config
155
	inflation: InflationInfo<Balance>,
156
	// AuthorId -> AccountId mappings
157
	mappings: Vec<(NimbusId, AccountId)>,
158
	// Crowdloan fund
159
	crowdloan_fund: Balance,
160
	// Chain id
161
	chain_id: u64,
162
	// EVM genesis accounts
163
	evm_accounts: BTreeMap<H160, GenesisAccount>,
164
	// [assettype, metadata, Vec<Account, Balance,>, is_sufficient]
165
	xcm_assets: Vec<XcmAssetInitialization>,
166
	evm_native_foreign_assets: bool,
167
	safe_xcm_version: Option<u32>,
168
	opened_bridges: Vec<(Location, InteriorLocation, Option<bp_moonbeam::LaneId>)>,
169
}
170

            
171
impl Default for ExtBuilder {
172
58
	fn default() -> ExtBuilder {
173
58
		ExtBuilder {
174
58
			balances: vec![],
175
58
			delegations: vec![],
176
58
			collators: vec![],
177
58
			inflation: InflationInfo {
178
58
				expect: Range {
179
58
					min: 100_000 * GLMR,
180
58
					ideal: 200_000 * GLMR,
181
58
					max: 500_000 * GLMR,
182
58
				},
183
58
				// not used
184
58
				annual: Range {
185
58
					min: Perbill::from_percent(50),
186
58
					ideal: Perbill::from_percent(50),
187
58
					max: Perbill::from_percent(50),
188
58
				},
189
58
				// unrealistically high parameterization, only for testing
190
58
				round: Range {
191
58
					min: Perbill::from_percent(5),
192
58
					ideal: Perbill::from_percent(5),
193
58
					max: Perbill::from_percent(5),
194
58
				},
195
58
			},
196
58
			mappings: vec![],
197
58
			crowdloan_fund: 0,
198
58
			chain_id: CHAIN_ID,
199
58
			evm_accounts: BTreeMap::new(),
200
58
			xcm_assets: vec![],
201
58
			evm_native_foreign_assets: false,
202
58
			safe_xcm_version: None,
203
58
			opened_bridges: vec![],
204
58
		}
205
58
	}
206
}
207

            
208
impl ExtBuilder {
209
2
	pub fn with_evm_accounts(mut self, accounts: BTreeMap<H160, GenesisAccount>) -> Self {
210
2
		self.evm_accounts = accounts;
211
2
		self
212
2
	}
213

            
214
37
	pub fn with_balances(mut self, balances: Vec<(AccountId, Balance)>) -> Self {
215
37
		self.balances = balances;
216
37
		self
217
37
	}
218

            
219
11
	pub fn with_collators(mut self, collators: Vec<(AccountId, Balance)>) -> Self {
220
11
		self.collators = collators;
221
11
		self
222
11
	}
223

            
224
7
	pub fn with_delegations(mut self, delegations: Vec<(AccountId, AccountId, Balance)>) -> Self {
225
7
		self.delegations = delegations
226
7
			.into_iter()
227
8
			.map(|d| (d.0, d.1, d.2, Percent::zero()))
228
7
			.collect();
229
7
		self
230
7
	}
231

            
232
	pub fn with_crowdloan_fund(mut self, crowdloan_fund: Balance) -> Self {
233
		self.crowdloan_fund = crowdloan_fund;
234
		self
235
	}
236

            
237
10
	pub fn with_mappings(mut self, mappings: Vec<(NimbusId, AccountId)>) -> Self {
238
10
		self.mappings = mappings;
239
10
		self
240
10
	}
241

            
242
4
	pub fn with_xcm_assets(mut self, xcm_assets: Vec<XcmAssetInitialization>) -> Self {
243
4
		self.xcm_assets = xcm_assets;
244
4
		self
245
4
	}
246

            
247
	pub fn with_evm_native_foreign_assets(mut self) -> Self {
248
		self.evm_native_foreign_assets = true;
249
		self
250
	}
251

            
252
7
	pub fn with_safe_xcm_version(mut self, safe_xcm_version: u32) -> Self {
253
7
		self.safe_xcm_version = Some(safe_xcm_version);
254
7
		self
255
7
	}
256

            
257
2
	pub fn with_open_bridges(
258
2
		mut self,
259
2
		opened_bridges: Vec<(Location, InteriorLocation, Option<bp_moonbeam::LaneId>)>,
260
2
	) -> Self {
261
2
		self.opened_bridges = opened_bridges;
262
2
		self
263
2
	}
264

            
265
	#[allow(dead_code)]
266
	pub fn with_inflation(mut self, inflation: InflationInfo<Balance>) -> Self {
267
		self.inflation = inflation;
268
		self
269
	}
270

            
271
58
	pub fn build(self) -> sp_io::TestExternalities {
272
58
		let mut t = frame_system::GenesisConfig::<Runtime>::default()
273
58
			.build_storage()
274
58
			.unwrap();
275

            
276
58
		parachain_info::GenesisConfig::<Runtime> {
277
58
			parachain_id: <bp_moonbeam::Moonbeam as bp_runtime::Parachain>::PARACHAIN_ID.into(),
278
58
			_config: Default::default(),
279
58
		}
280
58
		.assimilate_storage(&mut t)
281
58
		.unwrap();
282

            
283
58
		pallet_balances::GenesisConfig::<Runtime> {
284
58
			balances: self.balances,
285
58
			dev_accounts: None,
286
58
		}
287
58
		.assimilate_storage(&mut t)
288
58
		.unwrap();
289

            
290
58
		pallet_parachain_staking::GenesisConfig::<Runtime> {
291
58
			candidates: self.collators,
292
58
			delegations: self.delegations,
293
58
			inflation_config: self.inflation,
294
58
			collator_commission: Perbill::from_percent(20),
295
58
			parachain_bond_reserve_percent: Percent::from_percent(30),
296
58
			blocks_per_round: 6 * HOURS,
297
58
			num_selected_candidates: 8,
298
58
		}
299
58
		.assimilate_storage(&mut t)
300
58
		.unwrap();
301

            
302
58
		pallet_author_mapping::GenesisConfig::<Runtime> {
303
58
			mappings: self.mappings,
304
58
		}
305
58
		.assimilate_storage(&mut t)
306
58
		.unwrap();
307

            
308
58
		let genesis_config = pallet_evm_chain_id::GenesisConfig::<Runtime> {
309
58
			chain_id: self.chain_id,
310
58
			..Default::default()
311
58
		};
312
58
		genesis_config.assimilate_storage(&mut t).unwrap();
313

            
314
58
		let genesis_config = pallet_evm::GenesisConfig::<Runtime> {
315
58
			accounts: self.evm_accounts,
316
58
			..Default::default()
317
58
		};
318
58
		genesis_config.assimilate_storage(&mut t).unwrap();
319

            
320
58
		let genesis_config = pallet_ethereum::GenesisConfig::<Runtime> {
321
58
			..Default::default()
322
58
		};
323
58
		genesis_config.assimilate_storage(&mut t).unwrap();
324

            
325
58
		let genesis_config = pallet_xcm::GenesisConfig::<Runtime> {
326
58
			safe_xcm_version: self.safe_xcm_version,
327
58
			..Default::default()
328
58
		};
329
58
		genesis_config.assimilate_storage(&mut t).unwrap();
330

            
331
58
		let genesis_config = pallet_xcm_bridge::GenesisConfig::<Runtime, XcmOverKusamaInstance> {
332
58
			opened_bridges: self.opened_bridges,
333
58
			_phantom: Default::default(),
334
58
		};
335
58
		genesis_config.assimilate_storage(&mut t).unwrap();
336

            
337
58
		let mut ext = sp_io::TestExternalities::new(t);
338
58
		let xcm_assets = self.xcm_assets.clone();
339
58
		ext.execute_with(|| {
340
			// Mock host configuration for ParachainSystem
341
58
			cumulus_pallet_parachain_system::HostConfiguration::<Runtime>::put(
342
58
				mock_abridged_host_config(),
343
			);
344

            
345
			// Mock hrmp egress_channels
346
58
			cumulus_pallet_parachain_system::RelevantMessagingState::<Runtime>::put(
347
58
				MessagingStateSnapshot {
348
58
					dmq_mqc_head: Default::default(),
349
58
					relay_dispatch_queue_remaining_capacity: Default::default(),
350
58
					ingress_channels: vec![],
351
58
					egress_channels: vec![(
352
58
						1_000.into(),
353
58
						AbridgedHrmpChannel {
354
58
							max_capacity: u32::MAX,
355
58
							max_total_size: u32::MAX,
356
58
							max_message_size: u32::MAX,
357
58
							msg_count: 0,
358
58
							total_size: 0,
359
58
							mqc_head: None,
360
58
						},
361
58
					)],
362
58
				},
363
			);
364

            
365
			// If any xcm assets specified, we register them here
366
62
			for xcm_asset_initialization in xcm_assets {
367
4
				let asset_id = xcm_asset_initialization.asset_id;
368
4
				EvmForeignAssets::create_foreign_asset(
369
4
					root_origin(),
370
4
					asset_id,
371
4
					xcm_asset_initialization.xcm_location.clone(),
372
4
					xcm_asset_initialization.decimals,
373
4
					xcm_asset_initialization
374
4
						.symbol
375
4
						.as_bytes()
376
4
						.to_vec()
377
4
						.try_into()
378
4
						.expect("too long"),
379
4
					xcm_asset_initialization
380
4
						.name
381
4
						.as_bytes()
382
4
						.to_vec()
383
4
						.try_into()
384
4
						.expect("too long"),
385
				)
386
4
				.expect("failed to create foreign asset");
387

            
388
4
				XcmWeightTrader::add_asset(
389
4
					root_origin(),
390
4
					xcm_asset_initialization.xcm_location,
391
					GLMR,
392
				)
393
4
				.expect("failed to register asset in weight trader");
394

            
395
8
				for (account, balance) in xcm_asset_initialization.balances {
396
4
					if EvmForeignAssets::mint_into(asset_id, account, balance.into()).is_err() {
397
						panic!("failed to mint foreign asset");
398
4
					}
399
				}
400
			}
401
58
			System::set_block_number(1);
402
58
		});
403
58
		ext
404
58
	}
405
}
406

            
407
pub const CHAIN_ID: u64 = 1281;
408
pub const ALICE: [u8; 20] = [4u8; 20];
409
pub const ALICE_NIMBUS: [u8; 32] = [4u8; 32];
410
pub const BOB: [u8; 20] = [5u8; 20];
411
pub const CHARLIE: [u8; 20] = [6u8; 20];
412
pub const DAVE: [u8; 20] = [7u8; 20];
413
pub const EVM_CONTRACT: [u8; 20] = [8u8; 20];
414

            
415
20
pub fn origin_of(account_id: AccountId) -> <Runtime as frame_system::Config>::RuntimeOrigin {
416
20
	<Runtime as frame_system::Config>::RuntimeOrigin::signed(account_id)
417
20
}
418

            
419
12
pub fn inherent_origin() -> <Runtime as frame_system::Config>::RuntimeOrigin {
420
12
	<Runtime as frame_system::Config>::RuntimeOrigin::none()
421
12
}
422

            
423
20
pub fn root_origin() -> <Runtime as frame_system::Config>::RuntimeOrigin {
424
20
	<Runtime as frame_system::Config>::RuntimeOrigin::root()
425
20
}
426

            
427
/// Mock the inherent that sets validation data in ParachainSystem, which
428
/// contains the `relay_chain_block_number`, which is used in `author-filter` as a
429
/// source of randomness to filter valid authors at each block.
430
12
pub fn set_parachain_inherent_data() {
431
	use cumulus_primitives_core::PersistedValidationData;
432
	use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder;
433

            
434
12
	let author = AccountId::from(<pallet_evm::Pallet<Runtime>>::find_author());
435
12
	pallet_author_inherent::Author::<Runtime>::put(author);
436

            
437
12
	let mut relay_sproof = RelayStateSproofBuilder::default();
438
12
	relay_sproof.para_id = bp_moonbeam::PARACHAIN_ID.into();
439
12
	relay_sproof.included_para_head = Some(HeadData(vec![1, 2, 3]));
440

            
441
12
	let additional_key_values = vec![];
442

            
443
12
	relay_sproof.additional_key_values = additional_key_values;
444

            
445
12
	let (relay_parent_storage_root, relay_chain_state) = relay_sproof.into_state_root_and_proof();
446

            
447
12
	let vfp = PersistedValidationData {
448
12
		relay_parent_number: 1u32,
449
12
		relay_parent_storage_root,
450
12
		..Default::default()
451
12
	};
452
12
	let parachain_inherent_data = ParachainInherentData {
453
12
		validation_data: vfp,
454
12
		relay_chain_state: relay_chain_state,
455
12
		downward_messages: Default::default(),
456
12
		horizontal_messages: Default::default(),
457
12
		collator_peer_id: Default::default(),
458
12
		relay_parent_descendants: Default::default(),
459
12
	};
460
12
	assert_ok!(RuntimeCall::ParachainSystem(
461
12
		cumulus_pallet_parachain_system::Call::<Runtime>::set_validation_data {
462
12
			data: parachain_inherent_data
463
12
		}
464
12
	)
465
12
	.dispatch(inherent_origin()));
466
12
}
467

            
468
7
pub fn unchecked_eth_tx(raw_hex_tx: &str) -> UncheckedExtrinsic {
469
7
	let converter = TransactionConverter;
470
7
	converter.convert_transaction(ethereum_transaction(raw_hex_tx))
471
7
}
472

            
473
9
pub fn ethereum_transaction(raw_hex_tx: &str) -> pallet_ethereum::Transaction {
474
9
	let bytes = hex::decode(raw_hex_tx).expect("Transaction bytes.");
475
9
	let transaction = ethereum::EnvelopedDecodable::decode(&bytes[..]);
476
9
	assert!(transaction.is_ok());
477
9
	transaction.unwrap()
478
9
}
479

            
480
16200
pub fn increase_last_relay_slot_number(amount: u64) {
481
16200
	let last_relay_slot = u64::from(AsyncBacking::slot_info().unwrap_or_default().0);
482
16200
	frame_support::storage::unhashed::put(
483
16200
		&frame_support::storage::storage_prefix(b"AsyncBacking", b"SlotInfo"),
484
16200
		&((Slot::from(last_relay_slot + amount), 0)),
485
	);
486
16200
}