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 std::collections::BTreeMap;
38

            
39
use fp_rpc::ConvertTransaction;
40
use moonbeam_runtime::EvmForeignAssets;
41

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

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

            
52
// An invalid signed Alice transfer with a gas limit artifically set to 0.
53
pub const INVALID_ETH_TX: &str =
54
	"f8628085174876e800809412cb274aad8251c875c0bf6872b67d9983e53fdd01801ba011110796057\
55
	0e2d49fcc2afbc582e1abd3eeb027242b92abcebcec7cdefab63ea001732f6fac84acdd5b096554230\
56
	75003e7f07430652c3d6722e18f50b3d34e29";
57

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

            
66
/// Utility function that advances the chain to the desired block number.
67
/// If an author is provided, that author information is injected to all the blocks in the meantime.
68
8
pub fn run_to_block(n: u32, author: Option<NimbusId>) {
69
8
	// Finalize the first block
70
8
	Ethereum::on_finalize(System::block_number());
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
		Ethereum::on_initialize(System::block_number());
96
16200

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
235
	#[allow(dead_code)]
236
	pub fn with_inflation(mut self, inflation: InflationInfo<Balance>) -> Self {
237
		self.inflation = inflation;
238
		self
239
	}
240

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

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

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

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

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

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

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

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

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

            
299
64
		let mut ext = sp_io::TestExternalities::new(t);
300
64
		let xcm_assets = self.xcm_assets.clone();
301
64
		ext.execute_with(|| {
302
			// If any xcm assets specified, we register them here
303
73
			for xcm_asset_initialization in xcm_assets {
304
9
				let asset_id: AssetId = xcm_asset_initialization.asset_type.clone().into();
305
9
				if self.evm_native_foreign_assets {
306
1
					let AssetType::Xcm(location) = xcm_asset_initialization.asset_type;
307
1
					let metadata = xcm_asset_initialization.metadata.clone();
308
1
					EvmForeignAssets::register_foreign_asset(
309
1
						asset_id,
310
1
						xcm::VersionedLocation::from(location).try_into().unwrap(),
311
1
						metadata.decimals,
312
1
						metadata.symbol.try_into().unwrap(),
313
1
						metadata.name.try_into().unwrap(),
314
1
					)
315
1
					.expect("register evm native foreign asset");
316

            
317
2
					for (account, balance) in xcm_asset_initialization.balances {
318
1
						EvmForeignAssets::mint_into(asset_id.into(), account, balance.into())
319
1
							.expect("mint evm native foreign asset");
320
1
					}
321
				} else {
322
8
					AssetManager::register_foreign_asset(
323
8
						root_origin(),
324
8
						xcm_asset_initialization.asset_type,
325
8
						xcm_asset_initialization.metadata,
326
8
						1,
327
8
						xcm_asset_initialization.is_sufficient,
328
8
					)
329
8
					.unwrap();
330
16
					for (account, balance) in xcm_asset_initialization.balances {
331
8
						moonbeam_runtime::Assets::mint(
332
8
							origin_of(AssetManager::account_id()),
333
8
							asset_id.into(),
334
8
							account,
335
8
							balance,
336
8
						)
337
8
						.unwrap();
338
8
					}
339
				}
340
			}
341
64
			System::set_block_number(1);
342
64
		});
343
64
		ext
344
64
	}
345
}
346

            
347
pub const CHAIN_ID: u64 = 1281;
348
pub const ALICE: [u8; 20] = [4u8; 20];
349
pub const ALICE_NIMBUS: [u8; 32] = [4u8; 32];
350
pub const BOB: [u8; 20] = [5u8; 20];
351
pub const CHARLIE: [u8; 20] = [6u8; 20];
352
pub const DAVE: [u8; 20] = [7u8; 20];
353
pub const EVM_CONTRACT: [u8; 20] = [8u8; 20];
354

            
355
32
pub fn origin_of(account_id: AccountId) -> <Runtime as frame_system::Config>::RuntimeOrigin {
356
32
	<Runtime as frame_system::Config>::RuntimeOrigin::signed(account_id)
357
32
}
358

            
359
15
pub fn inherent_origin() -> <Runtime as frame_system::Config>::RuntimeOrigin {
360
15
	<Runtime as frame_system::Config>::RuntimeOrigin::none()
361
15
}
362

            
363
25
pub fn root_origin() -> <Runtime as frame_system::Config>::RuntimeOrigin {
364
25
	<Runtime as frame_system::Config>::RuntimeOrigin::root()
365
25
}
366

            
367
/// Mock the inherent that sets validation data in ParachainSystem, which
368
/// contains the `relay_chain_block_number`, which is used in `author-filter` as a
369
/// source of randomness to filter valid authors at each block.
370
15
pub fn set_parachain_inherent_data() {
371
	use cumulus_primitives_core::PersistedValidationData;
372
	use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder;
373

            
374
15
	let author = AccountId::from(<pallet_evm::Pallet<Runtime>>::find_author());
375
15
	pallet_author_inherent::Author::<Runtime>::put(author);
376
15

            
377
15
	let mut relay_sproof = RelayStateSproofBuilder::default();
378
15
	relay_sproof.para_id = 100u32.into();
379
15
	relay_sproof.included_para_head = Some(HeadData(vec![1, 2, 3]));
380
15

            
381
15
	let additional_key_values = vec![(
382
15
		moonbeam_core_primitives::well_known_relay_keys::TIMESTAMP_NOW.to_vec(),
383
15
		sp_timestamp::Timestamp::default().encode(),
384
15
	)];
385
15

            
386
15
	relay_sproof.additional_key_values = additional_key_values;
387
15

            
388
15
	let (relay_parent_storage_root, relay_chain_state) = relay_sproof.into_state_root_and_proof();
389
15

            
390
15
	let vfp = PersistedValidationData {
391
15
		relay_parent_number: 1u32,
392
15
		relay_parent_storage_root,
393
15
		..Default::default()
394
15
	};
395
15
	let parachain_inherent_data = ParachainInherentData {
396
15
		validation_data: vfp,
397
15
		relay_chain_state: relay_chain_state,
398
15
		downward_messages: Default::default(),
399
15
		horizontal_messages: Default::default(),
400
15
	};
401
15
	assert_ok!(RuntimeCall::ParachainSystem(
402
15
		cumulus_pallet_parachain_system::Call::<Runtime>::set_validation_data {
403
15
			data: parachain_inherent_data
404
15
		}
405
15
	)
406
15
	.dispatch(inherent_origin()));
407
15
}
408

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

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

            
421
16202
pub fn increase_last_relay_slot_number(amount: u64) {
422
16202
	let last_relay_slot = u64::from(AsyncBacking::slot_info().unwrap_or_default().0);
423
16202
	frame_support::storage::unhashed::put(
424
16202
		&frame_support::storage::storage_prefix(b"AsyncBacking", b"SlotInfo"),
425
16202
		&((Slot::from(last_relay_slot + amount), 0)),
426
16202
	);
427
16202
}