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
	asset_config::AssetRegistrarMetadata, currency::GLMR, xcm_config::AssetType, AccountId,
27
	AssetId, AssetManager, AsyncBacking, AuthorInherent, Balance, Ethereum, InflationInfo,
28
	ParachainStaking, Range, Runtime, RuntimeCall, RuntimeEvent, System, TransactionConverter,
29
	UncheckedExtrinsic, HOURS,
30
};
31
use nimbus_primitives::{NimbusId, NIMBUS_ENGINE_ID};
32
use polkadot_parachain::primitives::HeadData;
33
use sp_consensus_slots::Slot;
34
use sp_core::{Encode, H160};
35
use sp_runtime::{traits::Dispatchable, BuildStorage, Digest, DigestItem, Perbill, Percent};
36

            
37
use cumulus_pallet_parachain_system::MessagingStateSnapshot;
38
use cumulus_primitives_core::AbridgedHrmpChannel;
39
use fp_rpc::ConvertTransaction;
40
use moonbeam_runtime::bridge_config::XcmOverKusamaInstance;
41
use moonbeam_runtime::{EvmForeignAssets, XcmWeightTrader};
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
	"02f869820501808085e8d4a51000825208943cd0a705a2dc65e5b1e1205896baa2be8a07c6e00180c\
52
	001a061087911e877a5802142a89a40d231d50913db399eb50839bb2d04e612b22ec8a01aa313efdf2\
53
	793bea76da6813bda611444af16a6207a8cfef2d9c8aa8f8012f7";
54

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

            
91
16200
		increase_last_relay_slot_number(1);
92
16200

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

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

            
102
3
pub fn last_event() -> RuntimeEvent {
103
3
	System::events().pop().expect("Event expected").event
104
3
}
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_type: AssetType,
121
	pub metadata: AssetRegistrarMetadata,
122
	pub balances: Vec<(AccountId, Balance)>,
123
	pub is_sufficient: bool,
124
}
125

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

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

            
187
impl ExtBuilder {
188
2
	pub fn with_evm_accounts(mut self, accounts: BTreeMap<H160, GenesisAccount>) -> Self {
189
2
		self.evm_accounts = accounts;
190
2
		self
191
2
	}
192

            
193
46
	pub fn with_balances(mut self, balances: Vec<(AccountId, Balance)>) -> Self {
194
46
		self.balances = balances;
195
46
		self
196
46
	}
197

            
198
17
	pub fn with_collators(mut self, collators: Vec<(AccountId, Balance)>) -> Self {
199
17
		self.collators = collators;
200
17
		self
201
17
	}
202

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

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

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

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

            
226
5
	pub fn with_evm_native_foreign_assets(mut self) -> Self {
227
5
		self.evm_native_foreign_assets = true;
228
5
		self
229
5
	}
230

            
231
9
	pub fn with_safe_xcm_version(mut self, safe_xcm_version: u32) -> Self {
232
9
		self.safe_xcm_version = Some(safe_xcm_version);
233
9
		self
234
9
	}
235

            
236
2
	pub fn with_open_bridges(
237
2
		mut self,
238
2
		opened_bridges: Vec<(Location, InteriorLocation, Option<bp_moonbeam::LaneId>)>,
239
2
	) -> Self {
240
2
		self.opened_bridges = opened_bridges;
241
2
		self
242
2
	}
243

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

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

            
255
66
		parachain_info::GenesisConfig::<Runtime> {
256
66
			parachain_id: <bp_moonbeam::Moonbeam as bp_runtime::Parachain>::PARACHAIN_ID.into(),
257
66
			_config: Default::default(),
258
66
		}
259
66
		.assimilate_storage(&mut t)
260
66
		.unwrap();
261
66

            
262
66
		pallet_balances::GenesisConfig::<Runtime> {
263
66
			balances: self.balances,
264
66
		}
265
66
		.assimilate_storage(&mut t)
266
66
		.unwrap();
267
66

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

            
280
66
		pallet_crowdloan_rewards::GenesisConfig::<Runtime> {
281
66
			funded_amount: self.crowdloan_fund,
282
66
		}
283
66
		.assimilate_storage(&mut t)
284
66
		.unwrap();
285
66

            
286
66
		pallet_author_mapping::GenesisConfig::<Runtime> {
287
66
			mappings: self.mappings,
288
66
		}
289
66
		.assimilate_storage(&mut t)
290
66
		.unwrap();
291
66

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

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

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

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

            
315
66
		let genesis_config = pallet_xcm_bridge::GenesisConfig::<Runtime, XcmOverKusamaInstance> {
316
66
			opened_bridges: self.opened_bridges,
317
66
			_phantom: Default::default(),
318
66
		};
319
66
		genesis_config.assimilate_storage(&mut t).unwrap();
320
66

            
321
66
		let mut ext = sp_io::TestExternalities::new(t);
322
66
		let xcm_assets = self.xcm_assets.clone();
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_000.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
75
			for xcm_asset_initialization in xcm_assets {
346
9
				let asset_id: AssetId = xcm_asset_initialization.asset_type.clone().into();
347
9
				if self.evm_native_foreign_assets {
348
5
					let AssetType::Xcm(location) = xcm_asset_initialization.asset_type;
349
5
					let metadata = xcm_asset_initialization.metadata.clone();
350
5
					EvmForeignAssets::register_foreign_asset(
351
5
						asset_id,
352
5
						xcm::VersionedLocation::from(location).try_into().unwrap(),
353
5
						metadata.decimals,
354
5
						metadata.symbol.try_into().unwrap(),
355
5
						metadata.name.try_into().unwrap(),
356
5
					)
357
5
					.expect("register evm native foreign asset");
358
5

            
359
5
					if xcm_asset_initialization.is_sufficient {
360
5
						XcmWeightTrader::add_asset(
361
5
							root_origin(),
362
5
							xcm::VersionedLocation::from(location).try_into().unwrap(),
363
5
							GLMR,
364
5
						)
365
5
						.expect("register evm native foreign asset as sufficient");
366
5
					}
367

            
368
10
					for (account, balance) in xcm_asset_initialization.balances {
369
5
						EvmForeignAssets::mint_into(asset_id.into(), account, balance.into())
370
5
							.expect("mint evm native foreign asset");
371
5
					}
372
				} else {
373
4
					AssetManager::register_foreign_asset(
374
4
						root_origin(),
375
4
						xcm_asset_initialization.asset_type,
376
4
						xcm_asset_initialization.metadata,
377
4
						1,
378
4
						xcm_asset_initialization.is_sufficient,
379
4
					)
380
4
					.unwrap();
381
8
					for (account, balance) in xcm_asset_initialization.balances {
382
4
						moonbeam_runtime::Assets::mint(
383
4
							origin_of(AssetManager::account_id()),
384
4
							asset_id.into(),
385
4
							account,
386
4
							balance,
387
4
						)
388
4
						.unwrap();
389
4
					}
390
				}
391
			}
392
66
			System::set_block_number(1);
393
66
		});
394
66
		ext
395
66
	}
396
}
397

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

            
406
29
pub fn origin_of(account_id: AccountId) -> <Runtime as frame_system::Config>::RuntimeOrigin {
407
29
	<Runtime as frame_system::Config>::RuntimeOrigin::signed(account_id)
408
29
}
409

            
410
15
pub fn inherent_origin() -> <Runtime as frame_system::Config>::RuntimeOrigin {
411
15
	<Runtime as frame_system::Config>::RuntimeOrigin::none()
412
15
}
413

            
414
28
pub fn root_origin() -> <Runtime as frame_system::Config>::RuntimeOrigin {
415
28
	<Runtime as frame_system::Config>::RuntimeOrigin::root()
416
28
}
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
15
pub fn set_parachain_inherent_data() {
422
	use cumulus_primitives_core::PersistedValidationData;
423
	use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder;
424

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

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

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

            
437
15
	relay_sproof.additional_key_values = additional_key_values;
438
15

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

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

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

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

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