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 moonriver_runtime::{
25
	currency::MOVR, AccountId, AsyncBacking, AuthorInherent, Balance, Ethereum, InflationInfo,
26
	ParachainStaking, Range, Runtime, RuntimeCall, RuntimeEvent, System, TransactionConverter,
27
	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 moonriver_runtime::bridge_config::XcmOverPolkadotInstance;
43
use moonriver_runtime::{EvmForeignAssets, XcmWeightTrader};
44
use pallet_transaction_payment::Multiplier;
45
use std::collections::BTreeMap;
46
use xcm::latest::{InteriorLocation, Location};
47

            
48
10
pub fn existential_deposit() -> u128 {
49
10
	<Runtime as pallet_balances::Config>::ExistentialDeposit::get()
50
10
}
51

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

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

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

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

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

            
113
6598
		increase_last_relay_slot_number(1);
114

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
249
4
	pub fn with_xcm_assets(mut self, xcm_assets: Vec<XcmAssetInitialization>) -> Self {
250
4
		self.xcm_assets = xcm_assets;
251
4
		self
252
4
	}
253

            
254
5
	pub fn with_safe_xcm_version(mut self, safe_xcm_version: u32) -> Self {
255
5
		self.safe_xcm_version = Some(safe_xcm_version);
256
5
		self
257
5
	}
258

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

            
267
59
	pub fn build(self) -> sp_io::TestExternalities {
268
59
		let mut t = frame_system::GenesisConfig::<Runtime>::default()
269
59
			.build_storage()
270
59
			.unwrap();
271

            
272
59
		parachain_info::GenesisConfig::<Runtime> {
273
59
			parachain_id: <bp_moonriver::Moonriver as bp_runtime::Parachain>::PARACHAIN_ID.into(),
274
59
			_config: Default::default(),
275
59
		}
276
59
		.assimilate_storage(&mut t)
277
59
		.unwrap();
278

            
279
59
		pallet_balances::GenesisConfig::<Runtime> {
280
59
			balances: self.balances,
281
59
			dev_accounts: None,
282
59
		}
283
59
		.assimilate_storage(&mut t)
284
59
		.unwrap();
285

            
286
59
		pallet_parachain_staking::GenesisConfig::<Runtime> {
287
59
			candidates: self.collators,
288
59
			delegations: self.delegations,
289
59
			inflation_config: self.inflation,
290
59
			collator_commission: Perbill::from_percent(20),
291
59
			parachain_bond_reserve_percent: Percent::from_percent(30),
292
59
			blocks_per_round: 2 * HOURS,
293
59
			num_selected_candidates: 8,
294
59
		}
295
59
		.assimilate_storage(&mut t)
296
59
		.unwrap();
297

            
298
59
		pallet_author_mapping::GenesisConfig::<Runtime> {
299
59
			mappings: self.mappings,
300
59
		}
301
59
		.assimilate_storage(&mut t)
302
59
		.unwrap();
303

            
304
59
		let genesis_config = pallet_evm_chain_id::GenesisConfig::<Runtime> {
305
59
			chain_id: self.chain_id,
306
59
			..Default::default()
307
59
		};
308
59
		genesis_config.assimilate_storage(&mut t).unwrap();
309

            
310
59
		let genesis_config = pallet_evm::GenesisConfig::<Runtime> {
311
59
			accounts: self.evm_accounts,
312
59
			..Default::default()
313
59
		};
314
59
		genesis_config.assimilate_storage(&mut t).unwrap();
315

            
316
59
		let genesis_config = pallet_ethereum::GenesisConfig::<Runtime> {
317
59
			..Default::default()
318
59
		};
319
59
		genesis_config.assimilate_storage(&mut t).unwrap();
320

            
321
59
		let genesis_config = pallet_xcm::GenesisConfig::<Runtime> {
322
59
			safe_xcm_version: self.safe_xcm_version,
323
59
			..Default::default()
324
59
		};
325
59
		genesis_config.assimilate_storage(&mut t).unwrap();
326

            
327
59
		let genesis_config = pallet_transaction_payment::GenesisConfig::<Runtime> {
328
59
			multiplier: Multiplier::from(10u128),
329
59
			..Default::default()
330
59
		};
331
59
		genesis_config.assimilate_storage(&mut t).unwrap();
332

            
333
59
		let genesis_config = pallet_xcm_bridge::GenesisConfig::<Runtime, XcmOverPolkadotInstance> {
334
59
			opened_bridges: self.opened_bridges,
335
59
			_phantom: Default::default(),
336
59
		};
337
59
		genesis_config.assimilate_storage(&mut t).unwrap();
338

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

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

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

            
390
4
				XcmWeightTrader::add_asset(
391
4
					root_origin(),
392
4
					xcm_asset_initialization.xcm_location,
393
					MOVR,
394
				)
395
4
				.expect("register evm native foreign asset as sufficient");
396

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

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

            
417
18
pub fn origin_of(account_id: AccountId) -> <Runtime as frame_system::Config>::RuntimeOrigin {
418
18
	<Runtime as frame_system::Config>::RuntimeOrigin::signed(account_id)
419
18
}
420

            
421
11
pub fn inherent_origin() -> <Runtime as frame_system::Config>::RuntimeOrigin {
422
11
	<Runtime as frame_system::Config>::RuntimeOrigin::none()
423
11
}
424

            
425
18
pub fn root_origin() -> <Runtime as frame_system::Config>::RuntimeOrigin {
426
18
	<Runtime as frame_system::Config>::RuntimeOrigin::root()
427
18
}
428

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

            
436
11
	let author = AccountId::from(<pallet_evm::Pallet<Runtime>>::find_author());
437
11
	pallet_author_inherent::Author::<Runtime>::put(author);
438

            
439
11
	let mut relay_sproof = RelayStateSproofBuilder::default();
440
11
	relay_sproof.para_id = bp_moonriver::PARACHAIN_ID.into();
441
11
	relay_sproof.included_para_head = Some(HeadData(vec![1, 2, 3]));
442

            
443
11
	let additional_key_values = vec![];
444

            
445
11
	relay_sproof.additional_key_values = additional_key_values;
446

            
447
11
	let (relay_parent_storage_root, relay_chain_state) = relay_sproof.into_state_root_and_proof();
448

            
449
11
	let vfp = PersistedValidationData {
450
11
		relay_parent_number: 1u32,
451
11
		relay_parent_storage_root,
452
11
		..Default::default()
453
11
	};
454
11
	let data = BasicParachainInherentData {
455
11
		validation_data: vfp,
456
11
		relay_chain_state,
457
11
		collator_peer_id: Default::default(),
458
11
		relay_parent_descendants: Default::default(),
459
11
	};
460

            
461
11
	let inbound_messages_data = InboundMessagesData {
462
11
		downward_messages: Default::default(),
463
11
		horizontal_messages: Default::default(),
464
11
	};
465

            
466
11
	assert_ok!(RuntimeCall::ParachainSystem(
467
11
		cumulus_pallet_parachain_system::Call::<Runtime>::set_validation_data {
468
11
			data,
469
11
			inbound_messages_data
470
11
		}
471
11
	)
472
11
	.dispatch(inherent_origin()));
473
11
}
474

            
475
7
pub fn unchecked_eth_tx(raw_hex_tx: &str) -> UncheckedExtrinsic {
476
7
	let converter = TransactionConverter;
477
7
	converter.convert_transaction(ethereum_transaction(raw_hex_tx))
478
7
}
479

            
480
9
pub fn ethereum_transaction(raw_hex_tx: &str) -> pallet_ethereum::Transaction {
481
9
	let bytes = hex::decode(raw_hex_tx).expect("Transaction bytes.");
482
9
	let transaction = ethereum::EnvelopedDecodable::decode(&bytes[..]);
483
9
	assert!(transaction.is_ok());
484
9
	transaction.unwrap()
485
9
}
486

            
487
6600
pub(crate) fn increase_last_relay_slot_number(amount: u64) {
488
6600
	let last_relay_slot = u64::from(AsyncBacking::slot_info().unwrap_or_default().0);
489
6600
	frame_support::storage::unhashed::put(
490
6600
		&frame_support::storage::storage_prefix(b"AsyncBacking", b"SlotInfo"),
491
6600
		&((Slot::from(last_relay_slot + amount), 0)),
492
	);
493
6600
}