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 moonbase_runtime::{
26
	currency::UNIT, AccountId, AsyncBacking, AuthorInherent, Balance, Ethereum, EvmForeignAssets,
27
	InflationInfo, ParachainStaking, Range, Runtime, RuntimeCall, RuntimeEvent, System,
28
	TransactionConverter, 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 moonbase_runtime::XcmWeightTrader;
41
use pallet_transaction_payment::Multiplier;
42
use std::collections::BTreeMap;
43

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

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

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

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

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

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

            
109
6598
		increase_last_relay_slot_number(1);
110

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
307
63
		let mut ext = sp_io::TestExternalities::new(t);
308

            
309
63
		let xcm_assets = self.xcm_assets.clone();
310

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

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

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

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

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

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

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

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

            
395
25
pub fn root_origin() -> <Runtime as frame_system::Config>::RuntimeOrigin {
396
25
	<Runtime as frame_system::Config>::RuntimeOrigin::root()
397
25
}
398

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

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

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

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

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

            
425
8
	let additional_key_values = vec![];
426

            
427
8
	relay_sproof.additional_key_values = additional_key_values;
428

            
429
8
	let (relay_parent_storage_root, relay_chain_state) = relay_sproof.into_state_root_and_proof();
430

            
431
8
	let vfp = PersistedValidationData {
432
8
		relay_parent_number: 1u32,
433
8
		relay_parent_storage_root,
434
8
		..Default::default()
435
8
	};
436
8
	let parachain_inherent_data = ParachainInherentData {
437
8
		validation_data: vfp,
438
8
		relay_chain_state: relay_chain_state,
439
8
		downward_messages: Default::default(),
440
8
		horizontal_messages: Default::default(),
441
8
		collator_peer_id: Default::default(),
442
8
		relay_parent_descendants: Default::default(),
443
8
	};
444
8
	assert_ok!(RuntimeCall::ParachainSystem(
445
8
		cumulus_pallet_parachain_system::Call::<Runtime>::set_validation_data {
446
8
			data: parachain_inherent_data
447
8
		}
448
8
	)
449
8
	.dispatch(inherent_origin()));
450
8
}
451

            
452
6600
pub(crate) fn increase_last_relay_slot_number(amount: u64) {
453
6600
	let last_relay_slot = u64::from(AsyncBacking::slot_info().unwrap_or_default().0);
454
6600
	frame_support::storage::unhashed::put(
455
6600
		&frame_support::storage::storage_prefix(b"AsyncBacking", b"SlotInfo"),
456
6600
		&((Slot::from(last_relay_slot + amount), 0)),
457
	);
458
6600
}