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

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

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

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

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

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

            
89
6600
		increase_last_relay_slot_number(1);
90
6600

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

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

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

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

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

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

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

            
179
40
	pub fn with_balances(mut self, balances: Vec<(AccountId, Balance)>) -> Self {
180
40
		self.balances = balances;
181
40
		self
182
40
	}
183

            
184
	pub fn with_trace_logs(self) -> Self {
185
		frame_support::__private::sp_tracing::init_for_tests();
186
		self
187
	}
188

            
189
8
	pub fn with_collators(mut self, collators: Vec<(AccountId, Balance)>) -> Self {
190
8
		self.collators = collators;
191
8
		self
192
8
	}
193

            
194
7
	pub fn with_delegations(mut self, delegations: Vec<(AccountId, AccountId, Balance)>) -> Self {
195
7
		self.delegations = delegations
196
7
			.into_iter()
197
8
			.map(|d| (d.0, d.1, d.2, Percent::zero()))
198
7
			.collect();
199
7
		self
200
7
	}
201

            
202
4
	pub fn with_xcm_assets(mut self, xcm_assets: Vec<XcmAssetInitialization>) -> Self {
203
4
		self.xcm_assets = xcm_assets;
204
4
		self
205
4
	}
206

            
207
	pub fn with_crowdloan_fund(mut self, crowdloan_fund: Balance) -> Self {
208
		self.crowdloan_fund = crowdloan_fund;
209
		self
210
	}
211

            
212
7
	pub fn with_mappings(mut self, mappings: Vec<(NimbusId, AccountId)>) -> Self {
213
7
		self.mappings = mappings;
214
7
		self
215
7
	}
216

            
217
6
	pub fn with_safe_xcm_version(mut self, safe_xcm_version: u32) -> Self {
218
6
		self.safe_xcm_version = Some(safe_xcm_version);
219
6
		self
220
6
	}
221

            
222
	#[allow(dead_code)]
223
	pub fn with_inflation(mut self, inflation: InflationInfo<Balance>) -> Self {
224
		self.inflation = inflation;
225
		self
226
	}
227

            
228
63
	pub fn build(self) -> sp_io::TestExternalities {
229
63
		let mut t = frame_system::GenesisConfig::<Runtime>::default()
230
63
			.build_storage()
231
63
			.unwrap();
232
63

            
233
63
		pallet_balances::GenesisConfig::<Runtime> {
234
63
			balances: self.balances,
235
63
			dev_accounts: None,
236
63
		}
237
63
		.assimilate_storage(&mut t)
238
63
		.unwrap();
239
63

            
240
63
		pallet_parachain_staking::GenesisConfig::<Runtime> {
241
63
			candidates: self.collators,
242
63
			delegations: self.delegations,
243
63
			inflation_config: self.inflation,
244
63
			collator_commission: Perbill::from_percent(20),
245
63
			parachain_bond_reserve_percent: Percent::from_percent(30),
246
63
			blocks_per_round: 2 * HOURS,
247
63
			num_selected_candidates: 8,
248
63
		}
249
63
		.assimilate_storage(&mut t)
250
63
		.unwrap();
251
63

            
252
63
		pallet_author_mapping::GenesisConfig::<Runtime> {
253
63
			mappings: self.mappings,
254
63
		}
255
63
		.assimilate_storage(&mut t)
256
63
		.unwrap();
257
63

            
258
63
		let genesis_config = pallet_evm_chain_id::GenesisConfig::<Runtime> {
259
63
			chain_id: self.chain_id,
260
63
			..Default::default()
261
63
		};
262
63
		genesis_config.assimilate_storage(&mut t).unwrap();
263
63

            
264
63
		let genesis_config = pallet_evm::GenesisConfig::<Runtime> {
265
63
			accounts: self.evm_accounts,
266
63
			..Default::default()
267
63
		};
268
63
		genesis_config.assimilate_storage(&mut t).unwrap();
269
63

            
270
63
		let genesis_config = pallet_ethereum::GenesisConfig::<Runtime> {
271
63
			..Default::default()
272
63
		};
273
63
		genesis_config.assimilate_storage(&mut t).unwrap();
274
63

            
275
63
		let genesis_config = pallet_xcm::GenesisConfig::<Runtime> {
276
63
			safe_xcm_version: self.safe_xcm_version,
277
63
			..Default::default()
278
63
		};
279
63
		genesis_config.assimilate_storage(&mut t).unwrap();
280
63

            
281
63
		let genesis_config = pallet_transaction_payment::GenesisConfig::<Runtime> {
282
63
			multiplier: Multiplier::from(8u128),
283
63
			..Default::default()
284
63
		};
285
63
		genesis_config.assimilate_storage(&mut t).unwrap();
286
63

            
287
63
		let mut ext = sp_io::TestExternalities::new(t);
288
63

            
289
63
		let xcm_assets = self.xcm_assets.clone();
290
63

            
291
63
		ext.execute_with(|| {
292
63
			// Mock hrmp egress_channels
293
63
			cumulus_pallet_parachain_system::RelevantMessagingState::<Runtime>::put(
294
63
				MessagingStateSnapshot {
295
63
					dmq_mqc_head: Default::default(),
296
63
					relay_dispatch_queue_remaining_capacity: Default::default(),
297
63
					ingress_channels: vec![],
298
63
					egress_channels: vec![(
299
63
						1_001.into(),
300
63
						AbridgedHrmpChannel {
301
63
							max_capacity: u32::MAX,
302
63
							max_total_size: u32::MAX,
303
63
							max_message_size: u32::MAX,
304
63
							msg_count: 0,
305
63
							total_size: 0,
306
63
							mqc_head: None,
307
63
						},
308
63
					)],
309
63
				},
310
63
			);
311

            
312
			// If any xcm assets specified, we register them here
313
67
			for xcm_asset_initialization in xcm_assets {
314
4
				let asset_id = xcm_asset_initialization.asset_id;
315
4
				EvmForeignAssets::create_foreign_asset(
316
4
					root_origin(),
317
4
					asset_id,
318
4
					xcm_asset_initialization.xcm_location.clone(),
319
4
					xcm_asset_initialization.decimals,
320
4
					xcm_asset_initialization
321
4
						.symbol
322
4
						.as_bytes()
323
4
						.to_vec()
324
4
						.try_into()
325
4
						.expect("too long"),
326
4
					xcm_asset_initialization
327
4
						.name
328
4
						.as_bytes()
329
4
						.to_vec()
330
4
						.try_into()
331
4
						.expect("too long"),
332
4
				)
333
4
				.expect("fail to create foreign asset");
334
4

            
335
4
				XcmWeightTrader::add_asset(
336
4
					root_origin(),
337
4
					xcm_asset_initialization.xcm_location,
338
4
					UNIT,
339
4
				)
340
4
				.expect("register evm native foreign asset as sufficient");
341

            
342
8
				for (account, balance) in xcm_asset_initialization.balances {
343
4
					if EvmForeignAssets::mint_into(asset_id, account, balance.into()).is_err() {
344
						panic!("fail to mint foreign asset");
345
4
					}
346
				}
347
			}
348
63
			System::set_block_number(1);
349
63
		});
350
63
		ext
351
63
	}
352
}
353

            
354
pub const CHAIN_ID: u64 = 1281;
355
pub const ALICE: [u8; 20] = [4u8; 20];
356
pub const ALICE_NIMBUS: [u8; 32] = [4u8; 32];
357
pub const BOB: [u8; 20] = [5u8; 20];
358
pub const CHARLIE: [u8; 20] = [6u8; 20];
359
pub const DAVE: [u8; 20] = [7u8; 20];
360
pub const EVM_CONTRACT: [u8; 20] = [8u8; 20];
361

            
362
18
pub fn origin_of(account_id: AccountId) -> <Runtime as frame_system::Config>::RuntimeOrigin {
363
18
	<Runtime as frame_system::Config>::RuntimeOrigin::signed(account_id)
364
18
}
365

            
366
8
pub fn inherent_origin() -> <Runtime as frame_system::Config>::RuntimeOrigin {
367
8
	<Runtime as frame_system::Config>::RuntimeOrigin::none()
368
8
}
369

            
370
25
pub fn root_origin() -> <Runtime as frame_system::Config>::RuntimeOrigin {
371
25
	<Runtime as frame_system::Config>::RuntimeOrigin::root()
372
25
}
373

            
374
7
pub fn unchecked_eth_tx(raw_hex_tx: &str) -> UncheckedExtrinsic {
375
7
	let converter = TransactionConverter;
376
7
	converter.convert_transaction(ethereum_transaction(raw_hex_tx))
377
7
}
378

            
379
9
pub fn ethereum_transaction(raw_hex_tx: &str) -> pallet_ethereum::Transaction {
380
9
	let bytes = hex::decode(raw_hex_tx).expect("Transaction bytes.");
381
9
	let transaction = ethereum::EnvelopedDecodable::decode(&bytes[..]);
382
9
	assert!(transaction.is_ok());
383
9
	transaction.unwrap()
384
9
}
385

            
386
/// Mock the inherent that sets validation data in ParachainSystem, which
387
/// contains the `relay_chain_block_number`, which is used in `author-filter` as a
388
/// source of randomness to filter valid authors at each block.
389
8
pub fn set_parachain_inherent_data() {
390
	use cumulus_primitives_core::PersistedValidationData;
391
	use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder;
392

            
393
8
	let author = AccountId::from(<pallet_evm::Pallet<Runtime>>::find_author());
394
8
	pallet_author_inherent::Author::<Runtime>::put(author);
395
8

            
396
8
	let mut relay_sproof = RelayStateSproofBuilder::default();
397
8
	relay_sproof.para_id = 100u32.into();
398
8
	relay_sproof.included_para_head = Some(HeadData(vec![1, 2, 3]));
399
8

            
400
8
	let additional_key_values = vec![];
401
8

            
402
8
	relay_sproof.additional_key_values = additional_key_values;
403
8

            
404
8
	let (relay_parent_storage_root, relay_chain_state) = relay_sproof.into_state_root_and_proof();
405
8

            
406
8
	let vfp = PersistedValidationData {
407
8
		relay_parent_number: 1u32,
408
8
		relay_parent_storage_root,
409
8
		..Default::default()
410
8
	};
411
8
	let parachain_inherent_data = ParachainInherentData {
412
8
		validation_data: vfp,
413
8
		relay_chain_state: relay_chain_state,
414
8
		downward_messages: Default::default(),
415
8
		horizontal_messages: Default::default(),
416
8
	};
417
8
	assert_ok!(RuntimeCall::ParachainSystem(
418
8
		cumulus_pallet_parachain_system::Call::<Runtime>::set_validation_data {
419
8
			data: parachain_inherent_data
420
8
		}
421
8
	)
422
8
	.dispatch(inherent_origin()));
423
8
}
424

            
425
6602
pub(crate) fn increase_last_relay_slot_number(amount: u64) {
426
6602
	let last_relay_slot = u64::from(AsyncBacking::slot_info().unwrap_or_default().0);
427
6602
	frame_support::storage::unhashed::put(
428
6602
		&frame_support::storage::storage_prefix(b"AsyncBacking", b"SlotInfo"),
429
6602
		&((Slot::from(last_relay_slot + amount), 0)),
430
6602
	);
431
6602
}