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 fp_evm::GenesisAccount;
20
use frame_support::{
21
	assert_ok,
22
	traits::{OnFinalize, OnInitialize},
23
};
24
pub use moonbase_runtime::{
25
	currency::UNIT, AccountId, AsyncBacking, AuthorInherent, Balance, Ethereum, EvmForeignAssets,
26
	InflationInfo, ParachainStaking, Range, Runtime, RuntimeCall, RuntimeEvent, System,
27
	TransactionConverter, UncheckedExtrinsic, HOURS,
28
};
29
use nimbus_primitives::{NimbusId, NIMBUS_ENGINE_ID};
30
use polkadot_parachain::primitives::HeadData;
31
use sp_consensus_slots::Slot;
32
use sp_core::{Encode, H160};
33
use sp_runtime::{traits::Dispatchable, BuildStorage, Digest, DigestItem, Perbill, Percent};
34

            
35
use cumulus_pallet_parachain_system::{
36
	parachain_inherent::{BasicParachainInherentData, InboundMessagesData},
37
	MessagingStateSnapshot,
38
};
39
use cumulus_primitives_core::relay_chain::{AbridgedHostConfiguration, AsyncBackingParams};
40
use cumulus_primitives_core::AbridgedHrmpChannel;
41
use fp_rpc::ConvertTransaction;
42
use moonbase_runtime::XcmWeightTrader;
43
use pallet_transaction_payment::Multiplier;
44
use std::collections::BTreeMap;
45

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

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

            
69
// A valid signed Alice transfer.
70
pub const VALID_ETH_TX: &str =
71
	"02f86d8205018085174876e80085e8d4a5100082520894f24ff3a9cf04c71dbc94d0b566f7a27b9456\
72
	6cac8080c001a0e1094e1a52520a75c0255db96132076dd0f1263089f838bea548cbdbfc64a4d19f031c\
73
	92a8cb04e2d68d20a6158d542a07ac440cc8d07b6e36af02db046d92df";
74

            
75
// An invalid signed Alice transfer with a gas limit artifically set to 0.
76
pub const INVALID_ETH_TX: &str =
77
	"f86180843b9aca00809412cb274aad8251c875c0bf6872b67d9983e53fdd01801ca00e28ba2dd3c5a\
78
	3fd467d4afd7aefb4a34b373314fff470bb9db743a84d674a0aa06e5994f2d07eafe1c37b4ce5471ca\
79
	ecec29011f6f5bf0b1a552c55ea348df35f";
80

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

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

            
111
6598
		increase_last_relay_slot_number(1);
112

            
113
		// Initialize the new block
114
6598
		AuthorInherent::on_initialize(System::block_number());
115
6598
		ParachainStaking::on_initialize(System::block_number());
116
6598
		Ethereum::on_initialize(System::block_number());
117

            
118
		// Finalize the block
119
6598
		ParachainStaking::on_finalize(System::block_number());
120
	}
121
9
}
122

            
123
6
pub fn last_event() -> RuntimeEvent {
124
6
	System::events().pop().expect("Event expected").event
125
6
}
126

            
127
// Test struct with the purpose of initializing xcm assets
128
#[derive(Clone)]
129
pub struct XcmAssetInitialization {
130
	pub asset_id: u128,
131
	pub xcm_location: xcm::v5::Location,
132
	pub decimals: u8,
133
	pub name: &'static str,
134
	pub symbol: &'static str,
135
	pub balances: Vec<(AccountId, Balance)>,
136
}
137

            
138
pub struct ExtBuilder {
139
	// endowed accounts with balances
140
	balances: Vec<(AccountId, Balance)>,
141
	// [collator, amount]
142
	collators: Vec<(AccountId, Balance)>,
143
	// [delegator, collator, nomination_amount]
144
	delegations: Vec<(AccountId, AccountId, Balance, Percent)>,
145
	// per-round inflation config
146
	inflation: InflationInfo<Balance>,
147
	// AuthorId -> AccoutId mappings
148
	mappings: Vec<(NimbusId, AccountId)>,
149
	// Crowdloan fund
150
	crowdloan_fund: Balance,
151
	// Chain id
152
	chain_id: u64,
153
	// EVM genesis accounts
154
	evm_accounts: BTreeMap<H160, GenesisAccount>,
155
	// [assettype, metadata, Vec<Account, Balance>]
156
	xcm_assets: Vec<XcmAssetInitialization>,
157
	safe_xcm_version: Option<u32>,
158
}
159

            
160
impl Default for ExtBuilder {
161
64
	fn default() -> ExtBuilder {
162
64
		ExtBuilder {
163
64
			balances: vec![],
164
64
			delegations: vec![],
165
64
			collators: vec![],
166
64
			inflation: InflationInfo {
167
64
				expect: Range {
168
64
					min: 100_000 * UNIT,
169
64
					ideal: 200_000 * UNIT,
170
64
					max: 500_000 * UNIT,
171
64
				},
172
64
				// not used
173
64
				annual: Range {
174
64
					min: Perbill::from_percent(50),
175
64
					ideal: Perbill::from_percent(50),
176
64
					max: Perbill::from_percent(50),
177
64
				},
178
64
				// unrealistically high parameterization, only for testing
179
64
				round: Range {
180
64
					min: Perbill::from_percent(5),
181
64
					ideal: Perbill::from_percent(5),
182
64
					max: Perbill::from_percent(5),
183
64
				},
184
64
			},
185
64
			mappings: vec![],
186
64
			crowdloan_fund: 0,
187
64
			chain_id: CHAIN_ID,
188
64
			evm_accounts: BTreeMap::new(),
189
64
			xcm_assets: vec![],
190
64
			safe_xcm_version: None,
191
64
		}
192
64
	}
193
}
194

            
195
impl ExtBuilder {
196
2
	pub fn with_evm_accounts(mut self, accounts: BTreeMap<H160, GenesisAccount>) -> Self {
197
2
		self.evm_accounts = accounts;
198
2
		self
199
2
	}
200

            
201
40
	pub fn with_balances(mut self, balances: Vec<(AccountId, Balance)>) -> Self {
202
40
		self.balances = balances;
203
40
		self
204
40
	}
205

            
206
	pub fn with_trace_logs(self) -> Self {
207
		frame_support::__private::sp_tracing::init_for_tests();
208
		self
209
	}
210

            
211
8
	pub fn with_collators(mut self, collators: Vec<(AccountId, Balance)>) -> Self {
212
8
		self.collators = collators;
213
8
		self
214
8
	}
215

            
216
7
	pub fn with_delegations(mut self, delegations: Vec<(AccountId, AccountId, Balance)>) -> Self {
217
7
		self.delegations = delegations
218
7
			.into_iter()
219
8
			.map(|d| (d.0, d.1, d.2, Percent::zero()))
220
7
			.collect();
221
7
		self
222
7
	}
223

            
224
4
	pub fn with_xcm_assets(mut self, xcm_assets: Vec<XcmAssetInitialization>) -> Self {
225
4
		self.xcm_assets = xcm_assets;
226
4
		self
227
4
	}
228

            
229
	pub fn with_crowdloan_fund(mut self, crowdloan_fund: Balance) -> Self {
230
		self.crowdloan_fund = crowdloan_fund;
231
		self
232
	}
233

            
234
7
	pub fn with_mappings(mut self, mappings: Vec<(NimbusId, AccountId)>) -> Self {
235
7
		self.mappings = mappings;
236
7
		self
237
7
	}
238

            
239
6
	pub fn with_safe_xcm_version(mut self, safe_xcm_version: u32) -> Self {
240
6
		self.safe_xcm_version = Some(safe_xcm_version);
241
6
		self
242
6
	}
243

            
244
	#[allow(dead_code)]
245
	pub fn with_inflation(mut self, inflation: InflationInfo<Balance>) -> Self {
246
		self.inflation = inflation;
247
		self
248
	}
249

            
250
64
	pub fn build(self) -> sp_io::TestExternalities {
251
64
		let mut t = frame_system::GenesisConfig::<Runtime>::default()
252
64
			.build_storage()
253
64
			.unwrap();
254

            
255
64
		pallet_balances::GenesisConfig::<Runtime> {
256
64
			balances: self.balances,
257
64
			dev_accounts: None,
258
64
		}
259
64
		.assimilate_storage(&mut t)
260
64
		.unwrap();
261

            
262
64
		pallet_parachain_staking::GenesisConfig::<Runtime> {
263
64
			candidates: self.collators,
264
64
			delegations: self.delegations,
265
64
			inflation_config: self.inflation,
266
64
			collator_commission: Perbill::from_percent(20),
267
64
			parachain_bond_reserve_percent: Percent::from_percent(30),
268
64
			blocks_per_round: 2 * HOURS,
269
64
			num_selected_candidates: 8,
270
64
		}
271
64
		.assimilate_storage(&mut t)
272
64
		.unwrap();
273

            
274
64
		pallet_author_mapping::GenesisConfig::<Runtime> {
275
64
			mappings: self.mappings,
276
64
		}
277
64
		.assimilate_storage(&mut t)
278
64
		.unwrap();
279

            
280
64
		let genesis_config = pallet_evm_chain_id::GenesisConfig::<Runtime> {
281
64
			chain_id: self.chain_id,
282
64
			..Default::default()
283
64
		};
284
64
		genesis_config.assimilate_storage(&mut t).unwrap();
285

            
286
64
		let genesis_config = pallet_evm::GenesisConfig::<Runtime> {
287
64
			accounts: self.evm_accounts,
288
64
			..Default::default()
289
64
		};
290
64
		genesis_config.assimilate_storage(&mut t).unwrap();
291

            
292
64
		let genesis_config = pallet_ethereum::GenesisConfig::<Runtime> {
293
64
			..Default::default()
294
64
		};
295
64
		genesis_config.assimilate_storage(&mut t).unwrap();
296

            
297
64
		let genesis_config = pallet_xcm::GenesisConfig::<Runtime> {
298
64
			safe_xcm_version: self.safe_xcm_version,
299
64
			..Default::default()
300
64
		};
301
64
		genesis_config.assimilate_storage(&mut t).unwrap();
302

            
303
64
		let genesis_config = pallet_transaction_payment::GenesisConfig::<Runtime> {
304
64
			multiplier: Multiplier::from(8u128),
305
64
			..Default::default()
306
64
		};
307
64
		genesis_config.assimilate_storage(&mut t).unwrap();
308

            
309
64
		let mut ext = sp_io::TestExternalities::new(t);
310

            
311
64
		let xcm_assets = self.xcm_assets.clone();
312

            
313
64
		ext.execute_with(|| {
314
			// Mock host configuration for ParachainSystem
315
64
			cumulus_pallet_parachain_system::HostConfiguration::<Runtime>::put(
316
64
				mock_abridged_host_config(),
317
			);
318

            
319
			// Mock hrmp egress_channels
320
64
			cumulus_pallet_parachain_system::RelevantMessagingState::<Runtime>::put(
321
64
				MessagingStateSnapshot {
322
64
					dmq_mqc_head: Default::default(),
323
64
					relay_dispatch_queue_remaining_capacity: Default::default(),
324
64
					ingress_channels: vec![],
325
64
					egress_channels: vec![(
326
64
						1_001.into(),
327
64
						AbridgedHrmpChannel {
328
64
							max_capacity: u32::MAX,
329
64
							max_total_size: u32::MAX,
330
64
							max_message_size: u32::MAX,
331
64
							msg_count: 0,
332
64
							total_size: 0,
333
64
							mqc_head: None,
334
64
						},
335
64
					)],
336
64
				},
337
			);
338

            
339
			// If any xcm assets specified, we register them here
340
68
			for xcm_asset_initialization in xcm_assets {
341
4
				let asset_id = xcm_asset_initialization.asset_id;
342
4
				EvmForeignAssets::create_foreign_asset(
343
4
					root_origin(),
344
4
					asset_id,
345
4
					xcm_asset_initialization.xcm_location.clone(),
346
4
					xcm_asset_initialization.decimals,
347
4
					xcm_asset_initialization
348
4
						.symbol
349
4
						.as_bytes()
350
4
						.to_vec()
351
4
						.try_into()
352
4
						.expect("too long"),
353
4
					xcm_asset_initialization
354
4
						.name
355
4
						.as_bytes()
356
4
						.to_vec()
357
4
						.try_into()
358
4
						.expect("too long"),
359
				)
360
4
				.expect("fail to create foreign asset");
361

            
362
4
				XcmWeightTrader::add_asset(
363
4
					root_origin(),
364
4
					xcm_asset_initialization.xcm_location,
365
					UNIT,
366
				)
367
4
				.expect("register evm native foreign asset as sufficient");
368

            
369
8
				for (account, balance) in xcm_asset_initialization.balances {
370
4
					if EvmForeignAssets::mint_into(asset_id, account, balance.into()).is_err() {
371
						panic!("fail to mint foreign asset");
372
4
					}
373
				}
374
			}
375
64
			System::set_block_number(1);
376
64
		});
377
64
		ext
378
64
	}
379
}
380

            
381
pub const CHAIN_ID: u64 = 1281;
382
pub const ALICE: [u8; 20] = [4u8; 20];
383
pub const ALICE_NIMBUS: [u8; 32] = [4u8; 32];
384
pub const BOB: [u8; 20] = [5u8; 20];
385
pub const CHARLIE: [u8; 20] = [6u8; 20];
386
pub const DAVE: [u8; 20] = [7u8; 20];
387
pub const EVM_CONTRACT: [u8; 20] = [8u8; 20];
388

            
389
18
pub fn origin_of(account_id: AccountId) -> <Runtime as frame_system::Config>::RuntimeOrigin {
390
18
	<Runtime as frame_system::Config>::RuntimeOrigin::signed(account_id)
391
18
}
392

            
393
8
pub fn inherent_origin() -> <Runtime as frame_system::Config>::RuntimeOrigin {
394
8
	<Runtime as frame_system::Config>::RuntimeOrigin::none()
395
8
}
396

            
397
23
pub fn root_origin() -> <Runtime as frame_system::Config>::RuntimeOrigin {
398
23
	<Runtime as frame_system::Config>::RuntimeOrigin::root()
399
23
}
400

            
401
7
pub fn unchecked_eth_tx(raw_hex_tx: &str) -> UncheckedExtrinsic {
402
7
	let converter = TransactionConverter;
403
7
	converter.convert_transaction(ethereum_transaction(raw_hex_tx))
404
7
}
405

            
406
9
pub fn ethereum_transaction(raw_hex_tx: &str) -> pallet_ethereum::Transaction {
407
9
	let bytes = hex::decode(raw_hex_tx).expect("Transaction bytes.");
408
9
	let transaction = ethereum::EnvelopedDecodable::decode(&bytes[..]);
409
9
	assert!(transaction.is_ok());
410
9
	transaction.unwrap()
411
9
}
412

            
413
/// Mock the inherent that sets validation data in ParachainSystem, which
414
/// contains the `relay_chain_block_number`, which is used in `author-filter` as a
415
/// source of randomness to filter valid authors at each block.
416
8
pub fn set_parachain_inherent_data() {
417
	use cumulus_primitives_core::PersistedValidationData;
418
	use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder;
419

            
420
8
	let author = AccountId::from(<pallet_evm::Pallet<Runtime>>::find_author());
421
8
	pallet_author_inherent::Author::<Runtime>::put(author);
422

            
423
8
	let mut relay_sproof = RelayStateSproofBuilder::default();
424
8
	relay_sproof.para_id = 100u32.into();
425
8
	relay_sproof.included_para_head = Some(HeadData(vec![1, 2, 3]));
426

            
427
8
	let additional_key_values = vec![];
428

            
429
8
	relay_sproof.additional_key_values = additional_key_values;
430

            
431
8
	let (relay_parent_storage_root, relay_chain_state) = relay_sproof.into_state_root_and_proof();
432

            
433
8
	let vfp = PersistedValidationData {
434
8
		relay_parent_number: 1u32,
435
8
		relay_parent_storage_root,
436
8
		..Default::default()
437
8
	};
438

            
439
8
	let data = BasicParachainInherentData {
440
8
		validation_data: vfp,
441
8
		relay_chain_state,
442
8
		collator_peer_id: Default::default(),
443
8
		relay_parent_descendants: Default::default(),
444
8
	};
445

            
446
8
	let inbound_messages_data = InboundMessagesData {
447
8
		downward_messages: Default::default(),
448
8
		horizontal_messages: Default::default(),
449
8
	};
450

            
451
8
	assert_ok!(RuntimeCall::ParachainSystem(
452
8
		cumulus_pallet_parachain_system::Call::<Runtime>::set_validation_data {
453
8
			data,
454
8
			inbound_messages_data,
455
8
		}
456
8
	)
457
8
	.dispatch(inherent_origin()));
458
8
}
459

            
460
6600
pub(crate) fn increase_last_relay_slot_number(amount: u64) {
461
6600
	let last_relay_slot = u64::from(AsyncBacking::slot_info().unwrap_or_default().0);
462
6600
	frame_support::storage::unhashed::put(
463
6600
		&frame_support::storage::storage_prefix(b"AsyncBacking", b"SlotInfo"),
464
6600
		&((Slot::from(last_relay_slot + amount), 0)),
465
	);
466
6600
}