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
	asset_config::AssetRegistrarMetadata, currency::MOVR, 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 std::collections::BTreeMap;
38

            
39
use fp_rpc::ConvertTransaction;
40
use moonriver_runtime::{Assets, EvmForeignAssets};
41
use pallet_transaction_payment::Multiplier;
42
use sp_runtime::traits::MaybeEquivalence;
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
	"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
9
	// Finalize the first block
72
9
	Ethereum::on_finalize(System::block_number());
73
6609
	while System::block_number() < n {
74
		// Set the new block number and author
75
6600
		match author {
76
6600
			Some(ref author) => {
77
6600
				let pre_digest = Digest {
78
6600
					logs: vec![DigestItem::PreRuntime(NIMBUS_ENGINE_ID, author.encode())],
79
6600
				};
80
6600
				System::reset_events();
81
6600
				System::initialize(
82
6600
					&(System::block_number() + 1),
83
6600
					&System::parent_hash(),
84
6600
					&pre_digest,
85
6600
				);
86
6600
			}
87
			None => {
88
				System::set_block_number(System::block_number() + 1);
89
			}
90
		}
91

            
92
6600
		increase_last_relay_slot_number(1);
93
6600

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

            
99
6600
		// Finalize the block
100
6600
		Ethereum::on_finalize(System::block_number());
101
6600
		ParachainStaking::on_finalize(System::block_number());
102
	}
103
9
}
104

            
105
3
pub fn last_event() -> RuntimeEvent {
106
3
	System::events().pop().expect("Event expected").event
107
3
}
108

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

            
120
// Test struct with the purpose of initializing xcm assets
121
#[derive(Clone)]
122
pub struct XcmAssetInitialization {
123
	pub asset_type: AssetType,
124
	pub metadata: AssetRegistrarMetadata,
125
	pub balances: Vec<(AccountId, Balance)>,
126
	pub is_sufficient: bool,
127
}
128

            
129
pub struct ExtBuilder {
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
	evm_native_foreign_assets: bool,
149
	safe_xcm_version: Option<u32>,
150
}
151

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

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

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

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

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

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

            
217
14
	pub fn with_mappings(mut self, mappings: Vec<(NimbusId, AccountId)>) -> Self {
218
14
		self.mappings = mappings;
219
14
		self
220
14
	}
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
9
	pub fn with_xcm_assets(mut self, xcm_assets: Vec<XcmAssetInitialization>) -> Self {
229
9
		self.xcm_assets = xcm_assets;
230
9
		self
231
9
	}
232

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

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

            
243
61
	pub fn build(self) -> sp_io::TestExternalities {
244
61
		let mut t = frame_system::GenesisConfig::<Runtime>::default()
245
61
			.build_storage()
246
61
			.unwrap();
247
61

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

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

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

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

            
278
61
		let genesis_config = pallet_evm_chain_id::GenesisConfig::<Runtime> {
279
61
			chain_id: self.chain_id,
280
61
			..Default::default()
281
61
		};
282
61
		genesis_config.assimilate_storage(&mut t).unwrap();
283
61

            
284
61
		let genesis_config = pallet_evm::GenesisConfig::<Runtime> {
285
61
			accounts: self.evm_accounts,
286
61
			..Default::default()
287
61
		};
288
61
		genesis_config.assimilate_storage(&mut t).unwrap();
289
61

            
290
61
		let genesis_config = pallet_ethereum::GenesisConfig::<Runtime> {
291
61
			..Default::default()
292
61
		};
293
61
		genesis_config.assimilate_storage(&mut t).unwrap();
294
61

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

            
301
61
		let genesis_config = pallet_transaction_payment::GenesisConfig::<Runtime> {
302
61
			multiplier: Multiplier::from(10u128),
303
61
			..Default::default()
304
61
		};
305
61
		genesis_config.assimilate_storage(&mut t).unwrap();
306
61

            
307
61
		let mut ext = sp_io::TestExternalities::new(t);
308
61
		let xcm_assets = self.xcm_assets.clone();
309
61
		ext.execute_with(|| {
310
			// If any xcm assets specified, we register them here
311
70
			for xcm_asset_initialization in xcm_assets {
312
9
				let asset_id: AssetId = xcm_asset_initialization.asset_type.clone().into();
313
9
				if self.evm_native_foreign_assets {
314
1
					let AssetType::Xcm(location) = xcm_asset_initialization.asset_type;
315
1
					let metadata = xcm_asset_initialization.metadata.clone();
316
1
					EvmForeignAssets::register_foreign_asset(
317
1
						asset_id,
318
1
						xcm_builder::WithLatestLocationConverter::convert_back(&location).unwrap(),
319
1
						metadata.decimals,
320
1
						metadata.symbol.try_into().unwrap(),
321
1
						metadata.name.try_into().unwrap(),
322
1
					)
323
1
					.expect("register evm native foreign asset");
324

            
325
2
					for (account, balance) in xcm_asset_initialization.balances {
326
1
						EvmForeignAssets::mint_into(asset_id.into(), account, balance.into())
327
1
							.expect("mint evm native foreign asset");
328
1
					}
329
				} else {
330
8
					AssetManager::register_foreign_asset(
331
8
						root_origin(),
332
8
						xcm_asset_initialization.asset_type,
333
8
						xcm_asset_initialization.metadata,
334
8
						1,
335
8
						xcm_asset_initialization.is_sufficient,
336
8
					)
337
8
					.unwrap();
338
16
					for (account, balance) in xcm_asset_initialization.balances {
339
8
						Assets::mint(
340
8
							origin_of(AssetManager::account_id()),
341
8
							asset_id.into(),
342
8
							account,
343
8
							balance,
344
8
						)
345
8
						.unwrap();
346
8
					}
347
				}
348
			}
349
61
			System::set_block_number(1);
350
61
		});
351
61
		ext
352
61
	}
353
}
354

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

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

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

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

            
375
/// Mock the inherent that sets validation data in ParachainSystem, which
376
/// contains the `relay_chain_block_number`, which is used in `author-filter` as a
377
/// source of randomness to filter valid authors at each block.
378
14
pub fn set_parachain_inherent_data() {
379
14
	use cumulus_primitives_core::PersistedValidationData;
380
14
	use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder;
381
14

            
382
14
	let author = AccountId::from(<pallet_evm::Pallet<Runtime>>::find_author());
383
14
	pallet_author_inherent::Author::<Runtime>::put(author);
384
14

            
385
14
	let mut relay_sproof = RelayStateSproofBuilder::default();
386
14
	relay_sproof.para_id = 100u32.into();
387
14
	relay_sproof.included_para_head = Some(HeadData(vec![1, 2, 3]));
388
14

            
389
14
	let additional_key_values = vec![(
390
14
		moonbeam_core_primitives::well_known_relay_keys::TIMESTAMP_NOW.to_vec(),
391
14
		sp_timestamp::Timestamp::default().encode(),
392
14
	)];
393
14

            
394
14
	relay_sproof.additional_key_values = additional_key_values;
395
14

            
396
14
	let (relay_parent_storage_root, relay_chain_state) = relay_sproof.into_state_root_and_proof();
397
14

            
398
14
	let vfp = PersistedValidationData {
399
14
		relay_parent_number: 1u32,
400
14
		relay_parent_storage_root,
401
14
		..Default::default()
402
14
	};
403
14
	let parachain_inherent_data = ParachainInherentData {
404
14
		validation_data: vfp,
405
14
		relay_chain_state: relay_chain_state,
406
14
		downward_messages: Default::default(),
407
14
		horizontal_messages: Default::default(),
408
14
	};
409
14
	assert_ok!(RuntimeCall::ParachainSystem(
410
14
		cumulus_pallet_parachain_system::Call::<Runtime>::set_validation_data {
411
14
			data: parachain_inherent_data
412
14
		}
413
14
	)
414
14
	.dispatch(inherent_origin()));
415
14
}
416

            
417
7
pub fn unchecked_eth_tx(raw_hex_tx: &str) -> UncheckedExtrinsic {
418
7
	let converter = TransactionConverter;
419
7
	converter.convert_transaction(ethereum_transaction(raw_hex_tx))
420
7
}
421

            
422
9
pub fn ethereum_transaction(raw_hex_tx: &str) -> pallet_ethereum::Transaction {
423
9
	let bytes = hex::decode(raw_hex_tx).expect("Transaction bytes.");
424
9
	let transaction = ethereum::EnvelopedDecodable::decode(&bytes[..]);
425
9
	assert!(transaction.is_ok());
426
9
	transaction.unwrap()
427
9
}
428

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