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::AbridgedHrmpChannel;
38
use fp_rpc::ConvertTransaction;
39
use moonbase_runtime::XcmWeightTrader;
40
use pallet_transaction_payment::Multiplier;
41
use std::collections::BTreeMap;
42
use xcm::prelude::{InteriorLocation, Location};
43

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

            
48
// A valid signed Alice transfer.
49
pub const VALID_ETH_TX: &str =
50
	"02f86d8205018085174876e80085e8d4a5100082520894f24ff3a9cf04c71dbc94d0b566f7a27b9456\
51
	6cac8080c001a0e1094e1a52520a75c0255db96132076dd0f1263089f838bea548cbdbfc64a4d19f031c\
52
	92a8cb04e2d68d20a6158d542a07ac440cc8d07b6e36af02db046d92df";
53

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

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

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

            
90
6600
		increase_last_relay_slot_number(1);
91
6600

            
92
6600
		// Initialize the new block
93
6600
		AuthorInherent::on_initialize(System::block_number());
94
6600
		ParachainStaking::on_initialize(System::block_number());
95
6600
		Ethereum::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
8
pub fn last_event() -> RuntimeEvent {
103
8
	System::events().pop().expect("Event expected").event
104
8
}
105

            
106
// Test struct with the purpose of initializing xcm assets
107
#[derive(Clone)]
108
pub struct XcmAssetInitialization {
109
	pub asset_id: u128,
110
	pub xcm_location: xcm::v5::Location,
111
	pub decimals: u8,
112
	pub name: &'static str,
113
	pub symbol: &'static str,
114
	pub balances: Vec<(AccountId, Balance)>,
115
}
116

            
117
pub struct ExtBuilder {
118
	// endowed accounts with balances
119
	balances: Vec<(AccountId, Balance)>,
120
	// [collator, amount]
121
	collators: Vec<(AccountId, Balance)>,
122
	// [delegator, collator, nomination_amount]
123
	delegations: Vec<(AccountId, AccountId, Balance, Percent)>,
124
	// per-round inflation config
125
	inflation: InflationInfo<Balance>,
126
	// AuthorId -> AccoutId mappings
127
	mappings: Vec<(NimbusId, AccountId)>,
128
	// Crowdloan fund
129
	crowdloan_fund: Balance,
130
	// Chain id
131
	chain_id: u64,
132
	// EVM genesis accounts
133
	evm_accounts: BTreeMap<H160, GenesisAccount>,
134
	// [assettype, metadata, Vec<Account, Balance>]
135
	xcm_assets: Vec<XcmAssetInitialization>,
136
	safe_xcm_version: Option<u32>,
137
	opened_bridges: Vec<(Location, InteriorLocation, Option<bp_moonbase::LaneId>)>,
138
}
139

            
140
impl Default for ExtBuilder {
141
66
	fn default() -> ExtBuilder {
142
66
		ExtBuilder {
143
66
			balances: vec![],
144
66
			delegations: vec![],
145
66
			collators: vec![],
146
66
			inflation: InflationInfo {
147
66
				expect: Range {
148
66
					min: 100_000 * UNIT,
149
66
					ideal: 200_000 * UNIT,
150
66
					max: 500_000 * UNIT,
151
66
				},
152
66
				// not used
153
66
				annual: Range {
154
66
					min: Perbill::from_percent(50),
155
66
					ideal: Perbill::from_percent(50),
156
66
					max: Perbill::from_percent(50),
157
66
				},
158
66
				// unrealistically high parameterization, only for testing
159
66
				round: Range {
160
66
					min: Perbill::from_percent(5),
161
66
					ideal: Perbill::from_percent(5),
162
66
					max: Perbill::from_percent(5),
163
66
				},
164
66
			},
165
66
			mappings: vec![],
166
66
			crowdloan_fund: 0,
167
66
			chain_id: CHAIN_ID,
168
66
			evm_accounts: BTreeMap::new(),
169
66
			xcm_assets: vec![],
170
66
			safe_xcm_version: None,
171
66
			opened_bridges: vec![],
172
66
		}
173
66
	}
174
}
175

            
176
impl ExtBuilder {
177
2
	pub fn with_evm_accounts(mut self, accounts: BTreeMap<H160, GenesisAccount>) -> Self {
178
2
		self.evm_accounts = accounts;
179
2
		self
180
2
	}
181

            
182
44
	pub fn with_balances(mut self, balances: Vec<(AccountId, Balance)>) -> Self {
183
44
		self.balances = balances;
184
44
		self
185
44
	}
186

            
187
	pub fn with_open_bridges(
188
		mut self,
189
		opened_bridges: Vec<(Location, InteriorLocation, Option<bp_moonbase::LaneId>)>,
190
	) -> Self {
191
		self.opened_bridges = opened_bridges;
192
		self
193
	}
194

            
195
14
	pub fn with_collators(mut self, collators: Vec<(AccountId, Balance)>) -> Self {
196
14
		self.collators = collators;
197
14
		self
198
14
	}
199

            
200
7
	pub fn with_delegations(mut self, delegations: Vec<(AccountId, AccountId, Balance)>) -> Self {
201
7
		self.delegations = delegations
202
7
			.into_iter()
203
8
			.map(|d| (d.0, d.1, d.2, Percent::zero()))
204
7
			.collect();
205
7
		self
206
7
	}
207

            
208
4
	pub fn with_xcm_assets(mut self, xcm_assets: Vec<XcmAssetInitialization>) -> Self {
209
4
		self.xcm_assets = xcm_assets;
210
4
		self
211
4
	}
212

            
213
6
	pub fn with_crowdloan_fund(mut self, crowdloan_fund: Balance) -> Self {
214
6
		self.crowdloan_fund = crowdloan_fund;
215
6
		self
216
6
	}
217

            
218
13
	pub fn with_mappings(mut self, mappings: Vec<(NimbusId, AccountId)>) -> Self {
219
13
		self.mappings = mappings;
220
13
		self
221
13
	}
222

            
223
6
	pub fn with_safe_xcm_version(mut self, safe_xcm_version: u32) -> Self {
224
6
		self.safe_xcm_version = Some(safe_xcm_version);
225
6
		self
226
6
	}
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
66
	pub fn build(self) -> sp_io::TestExternalities {
235
66
		let mut t = frame_system::GenesisConfig::<Runtime>::default()
236
66
			.build_storage()
237
66
			.unwrap();
238
66

            
239
66
		#[cfg(any(feature = "bridge-stagenet", feature = "bridge-betanet"))]
240
66
		parachain_info::GenesisConfig::<Runtime> {
241
66
			parachain_id:
242
66
				<moonbase_runtime::bridge_config::ThisChain as bp_runtime::Parachain>::PARACHAIN_ID
243
66
					.into(),
244
66
			_config: Default::default(),
245
66
		}
246
66
		.assimilate_storage(&mut t)
247
66
		.unwrap();
248
66

            
249
66
		pallet_balances::GenesisConfig::<Runtime> {
250
66
			balances: self.balances,
251
66
		}
252
66
		.assimilate_storage(&mut t)
253
66
		.unwrap();
254
66

            
255
66
		pallet_parachain_staking::GenesisConfig::<Runtime> {
256
66
			candidates: self.collators,
257
66
			delegations: self.delegations,
258
66
			inflation_config: self.inflation,
259
66
			collator_commission: Perbill::from_percent(20),
260
66
			parachain_bond_reserve_percent: Percent::from_percent(30),
261
66
			blocks_per_round: 2 * HOURS,
262
66
			num_selected_candidates: 8,
263
66
		}
264
66
		.assimilate_storage(&mut t)
265
66
		.unwrap();
266
66

            
267
66
		pallet_crowdloan_rewards::GenesisConfig::<Runtime> {
268
66
			funded_amount: self.crowdloan_fund,
269
66
		}
270
66
		.assimilate_storage(&mut t)
271
66
		.unwrap();
272
66

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

            
279
66
		#[cfg(any(feature = "bridge-stagenet", feature = "bridge-betanet"))]
280
66
		pallet_xcm_bridge::GenesisConfig::<
281
66
			Runtime,
282
66
			moonbase_runtime::bridge_config::XcmBridgeInstance,
283
66
		> {
284
66
			opened_bridges: self.opened_bridges,
285
66
			_phantom: Default::default(),
286
66
		}
287
66
		.assimilate_storage(&mut t)
288
66
		.unwrap();
289
66

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

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

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

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

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

            
319
66
		let mut ext = sp_io::TestExternalities::new(t);
320
66

            
321
66
		let xcm_assets = self.xcm_assets.clone();
322
66

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

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

            
367
4
				XcmWeightTrader::add_asset(
368
4
					root_origin(),
369
4
					xcm_asset_initialization.xcm_location,
370
4
					UNIT,
371
4
				)
372
4
				.expect("register evm native foreign asset as sufficient");
373

            
374
8
				for (account, balance) in xcm_asset_initialization.balances {
375
4
					if EvmForeignAssets::mint_into(asset_id, account, balance.into()).is_err() {
376
						panic!("fail to mint foreign asset");
377
4
					}
378
				}
379
			}
380
66
			System::set_block_number(1);
381
66
		});
382
66
		ext
383
66
	}
384
}
385

            
386
pub const CHAIN_ID: u64 = 1281;
387
pub const ALICE: [u8; 20] = [4u8; 20];
388
pub const ALICE_NIMBUS: [u8; 32] = [4u8; 32];
389
pub const BOB: [u8; 20] = [5u8; 20];
390
pub const CHARLIE: [u8; 20] = [6u8; 20];
391
pub const DAVE: [u8; 20] = [7u8; 20];
392
pub const EVM_CONTRACT: [u8; 20] = [8u8; 20];
393

            
394
23
pub fn origin_of(account_id: AccountId) -> <Runtime as frame_system::Config>::RuntimeOrigin {
395
23
	<Runtime as frame_system::Config>::RuntimeOrigin::signed(account_id)
396
23
}
397

            
398
7
pub fn inherent_origin() -> <Runtime as frame_system::Config>::RuntimeOrigin {
399
7
	<Runtime as frame_system::Config>::RuntimeOrigin::none()
400
7
}
401

            
402
32
pub fn root_origin() -> <Runtime as frame_system::Config>::RuntimeOrigin {
403
32
	<Runtime as frame_system::Config>::RuntimeOrigin::root()
404
32
}
405

            
406
7
pub fn unchecked_eth_tx(raw_hex_tx: &str) -> UncheckedExtrinsic {
407
7
	let converter = TransactionConverter;
408
7
	converter.convert_transaction(ethereum_transaction(raw_hex_tx))
409
7
}
410

            
411
9
pub fn ethereum_transaction(raw_hex_tx: &str) -> pallet_ethereum::Transaction {
412
9
	let bytes = hex::decode(raw_hex_tx).expect("Transaction bytes.");
413
9
	let transaction = ethereum::EnvelopedDecodable::decode(&bytes[..]);
414
9
	assert!(transaction.is_ok());
415
9
	transaction.unwrap()
416
9
}
417

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

            
425
7
	let author = AccountId::from(<pallet_evm::Pallet<Runtime>>::find_author());
426
7
	pallet_author_inherent::Author::<Runtime>::put(author);
427
7

            
428
7
	let mut relay_sproof = RelayStateSproofBuilder::default();
429
7
	relay_sproof.para_id = 100u32.into();
430
7
	relay_sproof.included_para_head = Some(HeadData(vec![1, 2, 3]));
431
7

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

            
437
7
	relay_sproof.additional_key_values = additional_key_values;
438
7

            
439
7
	let (relay_parent_storage_root, relay_chain_state) = relay_sproof.into_state_root_and_proof();
440
7

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

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