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 moonbeam_runtime::{
26
	currency::GLMR, 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 moonbeam_runtime::bridge_config::XcmOverKusamaInstance;
40
use moonbeam_runtime::{EvmForeignAssets, XcmWeightTrader};
41
use std::collections::BTreeMap;
42
use xcm::latest::{InteriorLocation, Location};
43

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

            
48
// A valid signed Alice transfer.
49
pub const VALID_ETH_TX: &str =
50
	"02f869820501808085e8d4a51000825208943cd0a705a2dc65e5b1e1205896baa2be8a07c6e00180c\
51
	001a061087911e877a5802142a89a40d231d50913db399eb50839bb2d04e612b22ec8a01aa313efdf2\
52
	793bea76da6813bda611444af16a6207a8cfef2d9c8aa8f8012f7";
53

            
54
// An invalid signed Alice transfer with a gas limit artifically set to 0.
55
pub const INVALID_ETH_TX: &str =
56
	"f8628085174876e800809412cb274aad8251c875c0bf6872b67d9983e53fdd01801ba011110796057\
57
	0e2d49fcc2afbc582e1abd3eeb027242b92abcebcec7cdefab63ea001732f6fac84acdd5b096554230\
58
	75003e7f07430652c3d6722e18f50b3d34e29";
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
8
pub fn run_to_block(n: u32, author: Option<NimbusId>) {
71
16208
	while System::block_number() < n {
72
		// Set the new block number and author
73
16200
		match author {
74
16200
			Some(ref author) => {
75
16200
				let pre_digest = Digest {
76
16200
					logs: vec![DigestItem::PreRuntime(NIMBUS_ENGINE_ID, author.encode())],
77
16200
				};
78
16200
				System::reset_events();
79
16200
				System::initialize(
80
16200
					&(System::block_number() + 1),
81
16200
					&System::parent_hash(),
82
16200
					&pre_digest,
83
16200
				);
84
16200
			}
85
			None => {
86
				System::set_block_number(System::block_number() + 1);
87
			}
88
		}
89

            
90
16200
		increase_last_relay_slot_number(1);
91
16200

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

            
96
16200
		// Finalize the block
97
16200
		ParachainStaking::on_finalize(System::block_number());
98
	}
99
8
}
100

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

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

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

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

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

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

            
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
44
	pub fn with_balances(mut self, balances: Vec<(AccountId, Balance)>) -> Self {
202
44
		self.balances = balances;
203
44
		self
204
44
	}
205

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

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

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

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

            
229
6
	pub fn with_xcm_assets(mut self, xcm_assets: Vec<XcmAssetInitialization>) -> Self {
230
6
		self.xcm_assets = xcm_assets;
231
6
		self
232
6
	}
233

            
234
	pub fn with_evm_native_foreign_assets(mut self) -> Self {
235
		self.evm_native_foreign_assets = true;
236
		self
237
	}
238

            
239
9
	pub fn with_safe_xcm_version(mut self, safe_xcm_version: u32) -> Self {
240
9
		self.safe_xcm_version = Some(safe_xcm_version);
241
9
		self
242
9
	}
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
	#[allow(dead_code)]
253
	pub fn with_inflation(mut self, inflation: InflationInfo<Balance>) -> Self {
254
		self.inflation = inflation;
255
		self
256
	}
257

            
258
64
	pub fn build(self) -> sp_io::TestExternalities {
259
64
		let mut t = frame_system::GenesisConfig::<Runtime>::default()
260
64
			.build_storage()
261
64
			.unwrap();
262
64

            
263
64
		parachain_info::GenesisConfig::<Runtime> {
264
64
			parachain_id: <bp_moonbeam::Moonbeam as bp_runtime::Parachain>::PARACHAIN_ID.into(),
265
64
			_config: Default::default(),
266
64
		}
267
64
		.assimilate_storage(&mut t)
268
64
		.unwrap();
269
64

            
270
64
		pallet_balances::GenesisConfig::<Runtime> {
271
64
			balances: self.balances,
272
64
			dev_accounts: None,
273
64
		}
274
64
		.assimilate_storage(&mut t)
275
64
		.unwrap();
276
64

            
277
64
		pallet_parachain_staking::GenesisConfig::<Runtime> {
278
64
			candidates: self.collators,
279
64
			delegations: self.delegations,
280
64
			inflation_config: self.inflation,
281
64
			collator_commission: Perbill::from_percent(20),
282
64
			parachain_bond_reserve_percent: Percent::from_percent(30),
283
64
			blocks_per_round: 6 * HOURS,
284
64
			num_selected_candidates: 8,
285
64
		}
286
64
		.assimilate_storage(&mut t)
287
64
		.unwrap();
288
64

            
289
64
		pallet_crowdloan_rewards::GenesisConfig::<Runtime> {
290
64
			funded_amount: self.crowdloan_fund,
291
64
		}
292
64
		.assimilate_storage(&mut t)
293
64
		.unwrap();
294
64

            
295
64
		pallet_author_mapping::GenesisConfig::<Runtime> {
296
64
			mappings: self.mappings,
297
64
		}
298
64
		.assimilate_storage(&mut t)
299
64
		.unwrap();
300
64

            
301
64
		let genesis_config = pallet_evm_chain_id::GenesisConfig::<Runtime> {
302
64
			chain_id: self.chain_id,
303
64
			..Default::default()
304
64
		};
305
64
		genesis_config.assimilate_storage(&mut t).unwrap();
306
64

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

            
313
64
		let genesis_config = pallet_ethereum::GenesisConfig::<Runtime> {
314
64
			..Default::default()
315
64
		};
316
64
		genesis_config.assimilate_storage(&mut t).unwrap();
317
64

            
318
64
		let genesis_config = pallet_xcm::GenesisConfig::<Runtime> {
319
64
			safe_xcm_version: self.safe_xcm_version,
320
64
			..Default::default()
321
64
		};
322
64
		genesis_config.assimilate_storage(&mut t).unwrap();
323
64

            
324
64
		let genesis_config = pallet_xcm_bridge::GenesisConfig::<Runtime, XcmOverKusamaInstance> {
325
64
			opened_bridges: self.opened_bridges,
326
64
			_phantom: Default::default(),
327
64
		};
328
64
		genesis_config.assimilate_storage(&mut t).unwrap();
329
64

            
330
64
		let mut ext = sp_io::TestExternalities::new(t);
331
64
		let xcm_assets = self.xcm_assets.clone();
332
64
		ext.execute_with(|| {
333
64
			if self.asset_hub_migration_started {
334
1
				// Indicate that the asset-hub migration has already started
335
1
				moonbeam_runtime::xcm_config::AssetHubMigrationStartsAtRelayBlock::set(&0);
336
1

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

            
339
1
				validation_data.relay_parent_number =
340
1
					moonbeam_runtime::xcm_config::AssetHubMigrationStartsAtRelayBlock::get();
341
1
				ValidationData::<Runtime>::set(Some(validation_data));
342
63
			}
343

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

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

            
387
6
				XcmWeightTrader::add_asset(
388
6
					root_origin(),
389
6
					xcm_asset_initialization.xcm_location,
390
6
					GLMR,
391
6
				)
392
6
				.expect("failed to register asset in weight trader");
393

            
394
12
				for (account, balance) in xcm_asset_initialization.balances {
395
6
					if EvmForeignAssets::mint_into(asset_id, account, balance.into()).is_err() {
396
						panic!("failed to mint foreign asset");
397
6
					}
398
				}
399
			}
400
64
			System::set_block_number(1);
401
64
		});
402
64
		ext
403
64
	}
404
}
405

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

            
414
27
pub fn origin_of(account_id: AccountId) -> <Runtime as frame_system::Config>::RuntimeOrigin {
415
27
	<Runtime as frame_system::Config>::RuntimeOrigin::signed(account_id)
416
27
}
417

            
418
18
pub fn inherent_origin() -> <Runtime as frame_system::Config>::RuntimeOrigin {
419
18
	<Runtime as frame_system::Config>::RuntimeOrigin::none()
420
18
}
421

            
422
31
pub fn root_origin() -> <Runtime as frame_system::Config>::RuntimeOrigin {
423
31
	<Runtime as frame_system::Config>::RuntimeOrigin::root()
424
31
}
425

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

            
433
18
	let author = AccountId::from(<pallet_evm::Pallet<Runtime>>::find_author());
434
18
	pallet_author_inherent::Author::<Runtime>::put(author);
435
18

            
436
18
	let mut relay_sproof = RelayStateSproofBuilder::default();
437
18
	relay_sproof.para_id = bp_moonbeam::PARACHAIN_ID.into();
438
18
	relay_sproof.included_para_head = Some(HeadData(vec![1, 2, 3]));
439
18

            
440
18
	let additional_key_values = vec![];
441
18

            
442
18
	relay_sproof.additional_key_values = additional_key_values;
443
18

            
444
18
	let (relay_parent_storage_root, relay_chain_state) = relay_sproof.into_state_root_and_proof();
445
18

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

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

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

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