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 moonriver_runtime::{
26
	currency::MOVR, 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, ValidationData};
37
use cumulus_primitives_core::AbridgedHrmpChannel;
38
use fp_rpc::ConvertTransaction;
39
use moonriver_runtime::bridge_config::XcmOverPolkadotInstance;
40
use moonriver_runtime::{EvmForeignAssets, XcmWeightTrader};
41
use pallet_transaction_payment::Multiplier;
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
// A valid signed Alice transfer.
50
pub const VALID_ETH_TX: &str =
51
	"02f86d8205018085174876e80085e8d4a5100082520894f24ff3a9cf04c71dbc94d0b566f7a27b9456\
52
	6cac8080c001a0e1094e1a52520a75c0255db96132076dd0f1263089f838bea548cbdbfc64a4d19f031c\
53
	92a8cb04e2d68d20a6158d542a07ac440cc8d07b6e36af02db046d92df";
54

            
55
// An invalid signed Alice transfer with a gas limit artifically set to 0.
56
pub const INVALID_ETH_TX: &str =
57
	"f86180843b9aca00809412cb274aad8251c875c0bf6872b67d9983e53fdd01801ca00e28ba2dd3c5a\
58
	3fd467d4afd7aefb4a34b373314fff470bb9db743a84d674a0aa06e5994f2d07eafe1c37b4ce5471ca\
59
	ecec29011f6f5bf0b1a552c55ea348df35f";
60

            
61
3
pub fn rpc_run_to_block(n: u32) {
62
6
	while System::block_number() < n {
63
3
		Ethereum::on_finalize(System::block_number());
64
3
		System::set_block_number(System::block_number() + 1);
65
3
		Ethereum::on_initialize(System::block_number());
66
3
	}
67
3
}
68

            
69
/// Utility function that advances the chain to the desired block number.
70
/// If an author is provided, that author information is injected to all the blocks in the meantime.
71
9
pub fn run_to_block(n: u32, author: Option<NimbusId>) {
72
6609
	while System::block_number() < n {
73
		// Set the new block number and author
74
6600
		match author {
75
6600
			Some(ref author) => {
76
6600
				let pre_digest = Digest {
77
6600
					logs: vec![DigestItem::PreRuntime(NIMBUS_ENGINE_ID, author.encode())],
78
6600
				};
79
6600
				System::reset_events();
80
6600
				System::initialize(
81
6600
					&(System::block_number() + 1),
82
6600
					&System::parent_hash(),
83
6600
					&pre_digest,
84
6600
				);
85
6600
			}
86
			None => {
87
				System::set_block_number(System::block_number() + 1);
88
			}
89
		}
90

            
91
6600
		increase_last_relay_slot_number(1);
92
6600

            
93
6600
		// Initialize the new block
94
6600
		AuthorInherent::on_initialize(System::block_number());
95
6600
		ParachainStaking::on_initialize(System::block_number());
96
6600

            
97
6600
		// Finalize the block
98
6600
		ParachainStaking::on_finalize(System::block_number());
99
	}
100
9
}
101

            
102
1
pub fn last_event() -> RuntimeEvent {
103
1
	System::events().pop().expect("Event expected").event
104
1
}
105

            
106
// Helper function to give a simple evm context suitable for tests.
107
// We can remove this once https://github.com/rust-blockchain/evm/pull/35
108
// is in our dependency graph.
109
pub fn evm_test_context() -> fp_evm::Context {
110
	fp_evm::Context {
111
		address: Default::default(),
112
		caller: Default::default(),
113
		apparent_value: From::from(0),
114
	}
115
}
116

            
117
// Test struct with the purpose of initializing xcm assets
118
#[derive(Clone)]
119
pub struct XcmAssetInitialization {
120
	pub asset_id: u128,
121
	pub xcm_location: xcm::v5::Location,
122
	pub decimals: u8,
123
	pub name: &'static str,
124
	pub symbol: &'static str,
125
	pub balances: Vec<(AccountId, Balance)>,
126
}
127

            
128
pub struct ExtBuilder {
129
	asset_hub_migration_started: bool,
130
	// endowed accounts with balances
131
	balances: Vec<(AccountId, Balance)>,
132
	// [collator, amount]
133
	collators: Vec<(AccountId, Balance)>,
134
	// [delegator, collator, nomination_amount]
135
	delegations: Vec<(AccountId, AccountId, Balance, Percent)>,
136
	// per-round inflation config
137
	inflation: InflationInfo<Balance>,
138
	// AuthorId -> AccoutId mappings
139
	mappings: Vec<(NimbusId, AccountId)>,
140
	// Crowdloan fund
141
	crowdloan_fund: Balance,
142
	// Chain id
143
	chain_id: u64,
144
	// EVM genesis accounts
145
	evm_accounts: BTreeMap<H160, GenesisAccount>,
146
	// [assettype, metadata, Vec<Account, Balance,>, is_sufficient]
147
	xcm_assets: Vec<XcmAssetInitialization>,
148
	safe_xcm_version: Option<u32>,
149
	opened_bridges: Vec<(Location, InteriorLocation, Option<bp_moonbeam::LaneId>)>,
150
}
151

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

            
189
impl ExtBuilder {
190
1
	pub fn asset_hub_migration_has_started(mut self) -> Self {
191
1
		self.asset_hub_migration_started = true;
192
1
		self
193
1
	}
194

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

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

            
205
9
	pub fn with_collators(mut self, collators: Vec<(AccountId, Balance)>) -> Self {
206
9
		self.collators = collators;
207
9
		self
208
9
	}
209

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

            
218
	pub fn with_crowdloan_fund(mut self, crowdloan_fund: Balance) -> Self {
219
		self.crowdloan_fund = crowdloan_fund;
220
		self
221
	}
222

            
223
8
	pub fn with_mappings(mut self, mappings: Vec<(NimbusId, AccountId)>) -> Self {
224
8
		self.mappings = mappings;
225
8
		self
226
8
	}
227

            
228
	#[allow(dead_code)]
229
	pub fn with_inflation(mut self, inflation: InflationInfo<Balance>) -> Self {
230
		self.inflation = inflation;
231
		self
232
	}
233

            
234
6
	pub fn with_xcm_assets(mut self, xcm_assets: Vec<XcmAssetInitialization>) -> Self {
235
6
		self.xcm_assets = xcm_assets;
236
6
		self
237
6
	}
238

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

            
244
2
	pub fn with_open_bridges(
245
2
		mut self,
246
2
		opened_bridges: Vec<(Location, InteriorLocation, Option<bp_moonbeam::LaneId>)>,
247
2
	) -> Self {
248
2
		self.opened_bridges = opened_bridges;
249
2
		self
250
2
	}
251

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

            
257
58
		parachain_info::GenesisConfig::<Runtime> {
258
58
			parachain_id: <bp_moonriver::Moonriver as bp_runtime::Parachain>::PARACHAIN_ID.into(),
259
58
			_config: Default::default(),
260
58
		}
261
58
		.assimilate_storage(&mut t)
262
58
		.unwrap();
263
58

            
264
58
		pallet_balances::GenesisConfig::<Runtime> {
265
58
			balances: self.balances,
266
58
			dev_accounts: None,
267
58
		}
268
58
		.assimilate_storage(&mut t)
269
58
		.unwrap();
270
58

            
271
58
		pallet_parachain_staking::GenesisConfig::<Runtime> {
272
58
			candidates: self.collators,
273
58
			delegations: self.delegations,
274
58
			inflation_config: self.inflation,
275
58
			collator_commission: Perbill::from_percent(20),
276
58
			parachain_bond_reserve_percent: Percent::from_percent(30),
277
58
			blocks_per_round: 2 * HOURS,
278
58
			num_selected_candidates: 8,
279
58
		}
280
58
		.assimilate_storage(&mut t)
281
58
		.unwrap();
282
58

            
283
58
		pallet_author_mapping::GenesisConfig::<Runtime> {
284
58
			mappings: self.mappings,
285
58
		}
286
58
		.assimilate_storage(&mut t)
287
58
		.unwrap();
288
58

            
289
58
		let genesis_config = pallet_evm_chain_id::GenesisConfig::<Runtime> {
290
58
			chain_id: self.chain_id,
291
58
			..Default::default()
292
58
		};
293
58
		genesis_config.assimilate_storage(&mut t).unwrap();
294
58

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

            
301
58
		let genesis_config = pallet_ethereum::GenesisConfig::<Runtime> {
302
58
			..Default::default()
303
58
		};
304
58
		genesis_config.assimilate_storage(&mut t).unwrap();
305
58

            
306
58
		let genesis_config = pallet_xcm::GenesisConfig::<Runtime> {
307
58
			safe_xcm_version: self.safe_xcm_version,
308
58
			..Default::default()
309
58
		};
310
58
		genesis_config.assimilate_storage(&mut t).unwrap();
311
58

            
312
58
		let genesis_config = pallet_transaction_payment::GenesisConfig::<Runtime> {
313
58
			multiplier: Multiplier::from(10u128),
314
58
			..Default::default()
315
58
		};
316
58
		genesis_config.assimilate_storage(&mut t).unwrap();
317
58

            
318
58
		let genesis_config = pallet_xcm_bridge::GenesisConfig::<Runtime, XcmOverPolkadotInstance> {
319
58
			opened_bridges: self.opened_bridges,
320
58
			_phantom: Default::default(),
321
58
		};
322
58
		genesis_config.assimilate_storage(&mut t).unwrap();
323
58

            
324
58
		let mut ext = sp_io::TestExternalities::new(t);
325
58
		let xcm_assets = self.xcm_assets.clone();
326
58
		ext.execute_with(|| {
327
58
			if self.asset_hub_migration_started {
328
1
				// Indicate that the asset-hub migration has already started
329
1
				moonriver_runtime::xcm_config::AssetHubMigrationStartsAtRelayBlock::set(&0);
330
1

            
331
1
				let mut validation_data = ValidationData::<Runtime>::get().unwrap_or_default();
332
1

            
333
1
				validation_data.relay_parent_number =
334
1
					moonriver_runtime::xcm_config::AssetHubMigrationStartsAtRelayBlock::get();
335
1
				ValidationData::<Runtime>::set(Some(validation_data));
336
57
			}
337

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

            
358
			// If any xcm assets specified, we register them here
359
64
			for xcm_asset_initialization in xcm_assets {
360
6
				let asset_id = xcm_asset_initialization.asset_id;
361
6
				EvmForeignAssets::create_foreign_asset(
362
6
					root_origin(),
363
6
					asset_id,
364
6
					xcm_asset_initialization.xcm_location.clone(),
365
6
					xcm_asset_initialization.decimals,
366
6
					xcm_asset_initialization
367
6
						.symbol
368
6
						.as_bytes()
369
6
						.to_vec()
370
6
						.try_into()
371
6
						.expect("too long"),
372
6
					xcm_asset_initialization
373
6
						.name
374
6
						.as_bytes()
375
6
						.to_vec()
376
6
						.try_into()
377
6
						.expect("too long"),
378
6
				)
379
6
				.expect("fail to create foreign asset");
380
6

            
381
6
				XcmWeightTrader::add_asset(
382
6
					root_origin(),
383
6
					xcm_asset_initialization.xcm_location,
384
6
					MOVR,
385
6
				)
386
6
				.expect("register evm native foreign asset as sufficient");
387

            
388
12
				for (account, balance) in xcm_asset_initialization.balances {
389
6
					if EvmForeignAssets::mint_into(asset_id, account, balance.into()).is_err() {
390
						panic!("fail to mint foreign asset");
391
6
					}
392
				}
393
			}
394
58
			System::set_block_number(1);
395
58
		});
396
58
		ext
397
58
	}
398
}
399

            
400
pub const CHAIN_ID: u64 = 1281;
401
pub const ALICE: [u8; 20] = [4u8; 20];
402
pub const ALICE_NIMBUS: [u8; 32] = [4u8; 32];
403
pub const BOB: [u8; 20] = [5u8; 20];
404
pub const CHARLIE: [u8; 20] = [6u8; 20];
405
pub const DAVE: [u8; 20] = [7u8; 20];
406
pub const EVM_CONTRACT: [u8; 20] = [8u8; 20];
407

            
408
20
pub fn origin_of(account_id: AccountId) -> <Runtime as frame_system::Config>::RuntimeOrigin {
409
20
	<Runtime as frame_system::Config>::RuntimeOrigin::signed(account_id)
410
20
}
411

            
412
8
pub fn inherent_origin() -> <Runtime as frame_system::Config>::RuntimeOrigin {
413
8
	<Runtime as frame_system::Config>::RuntimeOrigin::none()
414
8
}
415

            
416
23
pub fn root_origin() -> <Runtime as frame_system::Config>::RuntimeOrigin {
417
23
	<Runtime as frame_system::Config>::RuntimeOrigin::root()
418
23
}
419

            
420
/// Mock the inherent that sets validation data in ParachainSystem, which
421
/// contains the `relay_chain_block_number`, which is used in `author-filter` as a
422
/// source of randomness to filter valid authors at each block.
423
8
pub fn set_parachain_inherent_data() {
424
	use cumulus_primitives_core::PersistedValidationData;
425
	use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder;
426

            
427
8
	let author = AccountId::from(<pallet_evm::Pallet<Runtime>>::find_author());
428
8
	pallet_author_inherent::Author::<Runtime>::put(author);
429
8

            
430
8
	let mut relay_sproof = RelayStateSproofBuilder::default();
431
8
	relay_sproof.para_id = bp_moonriver::PARACHAIN_ID.into();
432
8
	relay_sproof.included_para_head = Some(HeadData(vec![1, 2, 3]));
433
8

            
434
8
	let additional_key_values = vec![(
435
8
		moonbeam_core_primitives::well_known_relay_keys::TIMESTAMP_NOW.to_vec(),
436
8
		sp_timestamp::Timestamp::default().encode(),
437
8
	)];
438
8

            
439
8
	relay_sproof.additional_key_values = additional_key_values;
440
8

            
441
8
	let (relay_parent_storage_root, relay_chain_state) = relay_sproof.into_state_root_and_proof();
442
8

            
443
8
	let vfp = PersistedValidationData {
444
8
		relay_parent_number: 1u32,
445
8
		relay_parent_storage_root,
446
8
		..Default::default()
447
8
	};
448
8
	let parachain_inherent_data = ParachainInherentData {
449
8
		validation_data: vfp,
450
8
		relay_chain_state: relay_chain_state,
451
8
		downward_messages: Default::default(),
452
8
		horizontal_messages: Default::default(),
453
8
	};
454
8
	assert_ok!(RuntimeCall::ParachainSystem(
455
8
		cumulus_pallet_parachain_system::Call::<Runtime>::set_validation_data {
456
8
			data: parachain_inherent_data
457
8
		}
458
8
	)
459
8
	.dispatch(inherent_origin()));
460
8
}
461

            
462
7
pub fn unchecked_eth_tx(raw_hex_tx: &str) -> UncheckedExtrinsic {
463
7
	let converter = TransactionConverter;
464
7
	converter.convert_transaction(ethereum_transaction(raw_hex_tx))
465
7
}
466

            
467
9
pub fn ethereum_transaction(raw_hex_tx: &str) -> pallet_ethereum::Transaction {
468
9
	let bytes = hex::decode(raw_hex_tx).expect("Transaction bytes.");
469
9
	let transaction = ethereum::EnvelopedDecodable::decode(&bytes[..]);
470
9
	assert!(transaction.is_ok());
471
9
	transaction.unwrap()
472
9
}
473

            
474
6602
pub(crate) fn increase_last_relay_slot_number(amount: u64) {
475
6602
	let last_relay_slot = u64::from(AsyncBacking::slot_info().unwrap_or_default().0);
476
6602
	frame_support::storage::unhashed::put(
477
6602
		&frame_support::storage::storage_prefix(b"AsyncBacking", b"SlotInfo"),
478
6602
		&((Slot::from(last_relay_slot + amount), 0)),
479
6602
	);
480
6602
}