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
//! Test utilities
18

            
19
use ethereum::{TransactionAction, TransactionSignature};
20
use frame_support::{
21
	parameter_types,
22
	traits::{ConstU32, FindAuthor, InstanceFilter},
23
	weights::Weight,
24
	ConsensusEngineId, PalletId,
25
};
26
use frame_system::{pallet_prelude::BlockNumberFor, EnsureRoot};
27
use pallet_evm::{
28
	AddressMapping, EnsureAddressTruncated, FeeCalculator, FrameSystemAccountProvider,
29
};
30
use rlp::RlpStream;
31
use sp_core::{hashing::keccak_256, H160, H256, U256};
32
use sp_runtime::{
33
	traits::{BlakeTwo256, IdentityLookup},
34
	AccountId32, BuildStorage,
35
};
36

            
37
use super::*;
38
use pallet_ethereum::{IntermediateStateRoot, PostLogContent};
39
use sp_runtime::{
40
	traits::DispatchInfoOf,
41
	transaction_validity::{TransactionValidity, TransactionValidityError},
42
};
43

            
44
pub type BlockNumber = BlockNumberFor<Test>;
45

            
46
type Block = frame_system::mocking::MockBlock<Test>;
47

            
48
2474
frame_support::construct_runtime! {
49
	pub enum Test
50
	{
51
		System: frame_system,
52
		Balances: pallet_balances,
53
		Timestamp: pallet_timestamp,
54
		EVM: pallet_evm,
55
		Ethereum: pallet_ethereum,
56
		EthereumXcm: crate,
57
		Proxy: pallet_proxy,
58
	}
59
4846
}
60

            
61
parameter_types! {
62
	pub const BlockHashCount: u32 = 250;
63
	pub BlockWeights: frame_system::limits::BlockWeights =
64
		frame_system::limits::BlockWeights::simple_max(Weight::from_parts(1024, 1));
65
}
66

            
67
impl frame_system::Config for Test {
68
	type BaseCallFilter = frame_support::traits::Everything;
69
	type BlockWeights = ();
70
	type BlockLength = ();
71
	type DbWeight = ();
72
	type RuntimeOrigin = RuntimeOrigin;
73
	type RuntimeTask = RuntimeTask;
74
	type Nonce = u64;
75
	type Block = Block;
76
	type Hash = H256;
77
	type RuntimeCall = RuntimeCall;
78
	type Hashing = BlakeTwo256;
79
	type AccountId = AccountId32;
80
	type Lookup = IdentityLookup<Self::AccountId>;
81
	type RuntimeEvent = RuntimeEvent;
82
	type BlockHashCount = BlockHashCount;
83
	type Version = ();
84
	type PalletInfo = PalletInfo;
85
	type AccountData = pallet_balances::AccountData<u64>;
86
	type OnNewAccount = ();
87
	type OnKilledAccount = ();
88
	type SystemWeightInfo = ();
89
	type SS58Prefix = ();
90
	type OnSetCode = ();
91
	type MaxConsumers = ConstU32<16>;
92
	type SingleBlockMigrations = ();
93
	type MultiBlockMigrator = ();
94
	type PreInherents = ();
95
	type PostInherents = ();
96
	type PostTransactions = ();
97
}
98

            
99
parameter_types! {
100
	// For weight estimation, we assume that the most locks on an individual account will be 50.
101
	// This number may need to be adjusted in the future if this assumption no longer holds true.
102
	pub const MaxLocks: u32 = 50;
103
	pub const ExistentialDeposit: u64 = 500;
104
}
105

            
106
impl pallet_balances::Config for Test {
107
	type MaxLocks = MaxLocks;
108
	type Balance = u64;
109
	type RuntimeEvent = RuntimeEvent;
110
	type DustRemoval = ();
111
	type ExistentialDeposit = ExistentialDeposit;
112
	type AccountStore = System;
113
	type WeightInfo = ();
114
	type MaxReserves = ();
115
	type ReserveIdentifier = ();
116
	type RuntimeHoldReason = ();
117
	type FreezeIdentifier = ();
118
	type MaxFreezes = ();
119
	type RuntimeFreezeReason = ();
120
}
121

            
122
parameter_types! {
123
	pub const MinimumPeriod: u64 = 6000 / 2;
124
}
125

            
126
impl pallet_timestamp::Config for Test {
127
	type Moment = u64;
128
	type OnTimestampSet = ();
129
	type MinimumPeriod = MinimumPeriod;
130
	type WeightInfo = ();
131
}
132

            
133
pub struct FixedGasPrice;
134
impl FeeCalculator for FixedGasPrice {
135
38
	fn min_gas_price() -> (U256, Weight) {
136
38
		(1.into(), Weight::zero())
137
38
	}
138
}
139

            
140
pub struct FindAuthorTruncated;
141
impl FindAuthor<H160> for FindAuthorTruncated {
142
43
	fn find_author<'a, I>(_digests: I) -> Option<H160>
143
43
	where
144
43
		I: 'a + IntoIterator<Item = (ConsensusEngineId, &'a [u8])>,
145
43
	{
146
43
		Some(address_build(0).address)
147
43
	}
148
}
149

            
150
const MAX_POV_SIZE: u64 = 5 * 1024 * 1024;
151
/// Block storage limit in bytes. Set to 40 KB.
152
const BLOCK_STORAGE_LIMIT: u64 = 40 * 1024;
153

            
154
parameter_types! {
155
	pub const TransactionByteFee: u64 = 1;
156
	pub const ChainId: u64 = 42;
157
	pub const EVMModuleId: PalletId = PalletId(*b"py/evmpa");
158
	pub const BlockGasLimit: U256 = U256::MAX;
159
	pub WeightPerGas: Weight = Weight::from_parts(1, 0);
160
	pub GasLimitPovSizeRatio: u64 = {
161
		let block_gas_limit = BlockGasLimit::get().min(u64::MAX.into()).low_u64();
162
		block_gas_limit.saturating_div(MAX_POV_SIZE)
163
	};
164
	pub GasLimitStorageGrowthRatio: u64 = {
165
		let block_gas_limit = BlockGasLimit::get().min(u64::MAX.into()).low_u64();
166
		block_gas_limit.saturating_div(BLOCK_STORAGE_LIMIT)
167
	};
168
}
169

            
170
pub struct HashedAddressMapping;
171

            
172
impl AddressMapping<AccountId32> for HashedAddressMapping {
173
188
	fn into_account_id(address: H160) -> AccountId32 {
174
188
		let mut data = [0u8; 32];
175
188
		data[0..20].copy_from_slice(&address[..]);
176
188
		AccountId32::from(Into::<[u8; 32]>::into(data))
177
188
	}
178
}
179

            
180
impl pallet_evm::Config for Test {
181
	type FeeCalculator = FixedGasPrice;
182
	type GasWeightMapping = pallet_evm::FixedGasWeightMapping<Self>;
183
	type WeightPerGas = WeightPerGas;
184
	type CallOrigin = EnsureAddressTruncated;
185
	type WithdrawOrigin = EnsureAddressTruncated;
186
	type AddressMapping = HashedAddressMapping;
187
	type Currency = Balances;
188
	type RuntimeEvent = RuntimeEvent;
189
	type PrecompilesType = ();
190
	type PrecompilesValue = ();
191
	type Runner = pallet_evm::runner::stack::Runner<Self>;
192
	type ChainId = ChainId;
193
	type BlockGasLimit = BlockGasLimit;
194
	type OnChargeTransaction = ();
195
	type FindAuthor = FindAuthorTruncated;
196
	type BlockHashMapping = pallet_ethereum::EthereumBlockHashMapping<Self>;
197
	type OnCreate = ();
198
	type GasLimitPovSizeRatio = GasLimitPovSizeRatio;
199
	type SuicideQuickClearLimit = ConstU32<0>;
200
	type GasLimitStorageGrowthRatio = GasLimitStorageGrowthRatio;
201
	type Timestamp = Timestamp;
202
	type WeightInfo = pallet_evm::weights::SubstrateWeight<Test>;
203
	type AccountProvider = FrameSystemAccountProvider<Test>;
204
}
205

            
206
parameter_types! {
207
	pub const PostBlockAndTxnHashes: PostLogContent = PostLogContent::BlockAndTxnHashes;
208
}
209

            
210
impl pallet_ethereum::Config for Test {
211
	type RuntimeEvent = RuntimeEvent;
212
	type StateRoot = IntermediateStateRoot<<Test as frame_system::Config>::Version>;
213
	type PostLogContent = PostBlockAndTxnHashes;
214
	type ExtraDataLength = ConstU32<30>;
215
}
216

            
217
parameter_types! {
218
	pub ReservedXcmpWeight: Weight = Weight::from_parts(u64::max_value(), 1);
219
}
220

            
221
#[derive(
222
	Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, MaxEncodedLen, TypeInfo,
223
)]
224
pub enum ProxyType {
225
4
	NotAllowed = 0,
226
9
	Any = 1,
227
}
228

            
229
impl pallet_evm_precompile_proxy::EvmProxyCallFilter for ProxyType {}
230

            
231
impl InstanceFilter<RuntimeCall> for ProxyType {
232
	fn filter(&self, _c: &RuntimeCall) -> bool {
233
		match self {
234
			ProxyType::NotAllowed => false,
235
			ProxyType::Any => true,
236
		}
237
	}
238
	fn is_superset(&self, _o: &Self) -> bool {
239
		false
240
	}
241
}
242

            
243
impl Default for ProxyType {
244
	fn default() -> Self {
245
		Self::NotAllowed
246
	}
247
}
248

            
249
parameter_types! {
250
	pub const ProxyCost: u64 = 1;
251
}
252

            
253
impl pallet_proxy::Config for Test {
254
	type RuntimeEvent = RuntimeEvent;
255
	type RuntimeCall = RuntimeCall;
256
	type Currency = Balances;
257
	type ProxyType = ProxyType;
258
	type ProxyDepositBase = ProxyCost;
259
	type ProxyDepositFactor = ProxyCost;
260
	type MaxProxies = ConstU32<32>;
261
	type WeightInfo = pallet_proxy::weights::SubstrateWeight<Test>;
262
	type MaxPending = ConstU32<32>;
263
	type CallHasher = BlakeTwo256;
264
	type AnnouncementDepositBase = ProxyCost;
265
	type AnnouncementDepositFactor = ProxyCost;
266
}
267

            
268
pub struct EthereumXcmEnsureProxy;
269
impl xcm_primitives::EnsureProxy<AccountId32> for EthereumXcmEnsureProxy {
270
13
	fn ensure_ok(delegator: AccountId32, delegatee: AccountId32) -> Result<(), &'static str> {
271
13
		let f = |x: &pallet_proxy::ProxyDefinition<AccountId32, ProxyType, BlockNumber>| -> bool {
272
9
			x.delegate == delegatee && (x.proxy_type == ProxyType::Any)
273
9
		};
274
13
		Proxy::proxies(delegator)
275
13
			.0
276
13
			.into_iter()
277
13
			.find(f)
278
13
			.map(|_| ())
279
13
			.ok_or("proxy error: expected `ProxyType::Any`")
280
13
	}
281
}
282

            
283
impl crate::Config for Test {
284
	type RuntimeEvent = RuntimeEvent;
285
	type InvalidEvmTransactionError = pallet_ethereum::InvalidTransactionWrapper;
286
	type ValidatedTransaction = pallet_ethereum::ValidatedTransaction<Self>;
287
	type XcmEthereumOrigin = crate::EnsureXcmEthereumTransaction;
288
	type ReservedXcmpWeight = ReservedXcmpWeight;
289
	type EnsureProxy = EthereumXcmEnsureProxy;
290
	type ControllerOrigin = EnsureRoot<AccountId32>;
291
	type ForceOrigin = EnsureRoot<AccountId32>;
292
}
293

            
294
impl fp_self_contained::SelfContainedCall for RuntimeCall {
295
	type SignedInfo = H160;
296

            
297
	fn is_self_contained(&self) -> bool {
298
		match self {
299
			RuntimeCall::Ethereum(call) => call.is_self_contained(),
300
			_ => false,
301
		}
302
	}
303

            
304
	fn check_self_contained(&self) -> Option<Result<Self::SignedInfo, TransactionValidityError>> {
305
		match self {
306
			RuntimeCall::Ethereum(call) => call.check_self_contained(),
307
			_ => None,
308
		}
309
	}
310

            
311
	fn validate_self_contained(
312
		&self,
313
		info: &Self::SignedInfo,
314
		dispatch_info: &DispatchInfoOf<RuntimeCall>,
315
		len: usize,
316
	) -> Option<TransactionValidity> {
317
		match self {
318
			RuntimeCall::Ethereum(call) => call.validate_self_contained(info, dispatch_info, len),
319
			_ => None,
320
		}
321
	}
322

            
323
	fn pre_dispatch_self_contained(
324
		&self,
325
		info: &Self::SignedInfo,
326
		dispatch_info: &DispatchInfoOf<RuntimeCall>,
327
		len: usize,
328
	) -> Option<Result<(), TransactionValidityError>> {
329
		match self {
330
			RuntimeCall::Ethereum(call) => {
331
				call.pre_dispatch_self_contained(info, dispatch_info, len)
332
			}
333
			_ => None,
334
		}
335
	}
336

            
337
	fn apply_self_contained(
338
		self,
339
		info: Self::SignedInfo,
340
	) -> Option<sp_runtime::DispatchResultWithInfo<sp_runtime::traits::PostDispatchInfoOf<Self>>> {
341
		use sp_runtime::traits::Dispatchable as _;
342
		match self {
343
			call @ RuntimeCall::Ethereum(pallet_ethereum::Call::transact { .. }) => {
344
				Some(call.dispatch(RuntimeOrigin::from(
345
					pallet_ethereum::RawOrigin::EthereumTransaction(info),
346
				)))
347
			}
348
			_ => None,
349
		}
350
	}
351
}
352

            
353
pub struct AccountInfo {
354
	pub address: H160,
355
	pub account_id: AccountId32,
356
	pub private_key: H256,
357
}
358

            
359
138
fn address_build(seed: u8) -> AccountInfo {
360
138
	let private_key = H256::from_slice(&[(seed + 1) as u8; 32]);
361
138
	let secret_key = libsecp256k1::SecretKey::parse_slice(&private_key[..]).unwrap();
362
138
	let public_key = &libsecp256k1::PublicKey::from_secret_key(&secret_key).serialize()[1..65];
363
138
	let address = H160::from(H256::from(keccak_256(public_key)));
364
138

            
365
138
	let mut data = [0u8; 32];
366
138
	data[0..20].copy_from_slice(&address[..]);
367
138

            
368
138
	AccountInfo {
369
138
		private_key,
370
138
		account_id: AccountId32::from(Into::<[u8; 32]>::into(data)),
371
138
		address,
372
138
	}
373
138
}
374

            
375
// This function basically just builds a genesis storage key/value store according to
376
// our desired mockup.
377
43
pub fn new_test_ext(accounts_len: usize) -> (Vec<AccountInfo>, sp_io::TestExternalities) {
378
43
	// sc_cli::init_logger("");
379
43
	let mut ext = frame_system::GenesisConfig::<Test>::default()
380
43
		.build_storage()
381
43
		.unwrap();
382
43

            
383
43
	let pairs = (0..accounts_len)
384
95
		.map(|i| address_build(i as u8))
385
43
		.collect::<Vec<_>>();
386
43

            
387
43
	let balances: Vec<_> = (0..accounts_len)
388
95
		.map(|i| (pairs[i].account_id.clone(), 10_000_000))
389
43
		.collect();
390
43

            
391
43
	pallet_balances::GenesisConfig::<Test> { balances }
392
43
		.assimilate_storage(&mut ext)
393
43
		.unwrap();
394
43

            
395
43
	(pairs, ext.into())
396
43
}
397

            
398
pub struct LegacyUnsignedTransaction {
399
	pub nonce: U256,
400
	pub gas_price: U256,
401
	pub gas_limit: U256,
402
	pub action: TransactionAction,
403
	pub value: U256,
404
	pub input: Vec<u8>,
405
}
406

            
407
impl LegacyUnsignedTransaction {
408
1
	fn signing_rlp_append(&self, s: &mut RlpStream) {
409
1
		s.begin_list(9);
410
1
		s.append(&self.nonce);
411
1
		s.append(&self.gas_price);
412
1
		s.append(&self.gas_limit);
413
1
		s.append(&self.action);
414
1
		s.append(&self.value);
415
1
		s.append(&self.input);
416
1
		s.append(&ChainId::get());
417
1
		s.append(&0u8);
418
1
		s.append(&0u8);
419
1
	}
420

            
421
1
	fn signing_hash(&self) -> H256 {
422
1
		let mut stream = RlpStream::new();
423
1
		self.signing_rlp_append(&mut stream);
424
1
		H256::from(keccak_256(&stream.out()))
425
1
	}
426

            
427
1
	pub fn sign(&self, key: &H256) -> Transaction {
428
1
		self.sign_with_chain_id(key, ChainId::get())
429
1
	}
430

            
431
1
	pub fn sign_with_chain_id(&self, key: &H256, chain_id: u64) -> Transaction {
432
1
		let hash = self.signing_hash();
433
1
		let msg = libsecp256k1::Message::parse(hash.as_fixed_bytes());
434
1
		let s = libsecp256k1::sign(
435
1
			&msg,
436
1
			&libsecp256k1::SecretKey::parse_slice(&key[..]).unwrap(),
437
1
		);
438
1
		let sig = s.0.serialize();
439
1

            
440
1
		let sig = TransactionSignature::new(
441
1
			s.1.serialize() as u64 % 2 + chain_id * 2 + 35,
442
1
			H256::from_slice(&sig[0..32]),
443
1
			H256::from_slice(&sig[32..64]),
444
1
		)
445
1
		.unwrap();
446
1

            
447
1
		Transaction::Legacy(ethereum::LegacyTransaction {
448
1
			nonce: self.nonce,
449
1
			gas_price: self.gas_price,
450
1
			gas_limit: self.gas_limit,
451
1
			action: self.action,
452
1
			value: self.value,
453
1
			input: self.input.clone(),
454
1
			signature: sig,
455
1
		})
456
1
	}
457
}
458

            
459
pub struct EIP2930UnsignedTransaction {
460
	pub nonce: U256,
461
	pub gas_price: U256,
462
	pub gas_limit: U256,
463
	pub action: TransactionAction,
464
	pub value: U256,
465
	pub input: Vec<u8>,
466
}
467

            
468
impl EIP2930UnsignedTransaction {
469
1
	pub fn sign(&self, secret: &H256, chain_id: Option<u64>) -> Transaction {
470
1
		let secret = {
471
1
			let mut sk: [u8; 32] = [0u8; 32];
472
1
			sk.copy_from_slice(&secret[0..]);
473
1
			libsecp256k1::SecretKey::parse(&sk).unwrap()
474
1
		};
475
1
		let chain_id = chain_id.unwrap_or(ChainId::get());
476
1
		let msg = ethereum::EIP2930TransactionMessage {
477
1
			chain_id,
478
1
			nonce: self.nonce,
479
1
			gas_price: self.gas_price,
480
1
			gas_limit: self.gas_limit,
481
1
			action: self.action,
482
1
			value: self.value,
483
1
			input: self.input.clone(),
484
1
			access_list: vec![],
485
1
		};
486
1
		let signing_message = libsecp256k1::Message::parse_slice(&msg.hash()[..]).unwrap();
487
1

            
488
1
		let (signature, recid) = libsecp256k1::sign(&signing_message, &secret);
489
1
		let rs = signature.serialize();
490
1
		let r = H256::from_slice(&rs[0..32]);
491
1
		let s = H256::from_slice(&rs[32..64]);
492
1
		Transaction::EIP2930(ethereum::EIP2930Transaction {
493
1
			chain_id: msg.chain_id,
494
1
			nonce: msg.nonce,
495
1
			gas_price: msg.gas_price,
496
1
			gas_limit: msg.gas_limit,
497
1
			action: msg.action,
498
1
			value: msg.value,
499
1
			input: msg.input.clone(),
500
1
			access_list: msg.access_list,
501
1
			odd_y_parity: recid.serialize() != 0,
502
1
			r,
503
1
			s,
504
1
		})
505
1
	}
506
}
507

            
508
pub struct EIP1559UnsignedTransaction {
509
	pub nonce: U256,
510
	pub max_priority_fee_per_gas: U256,
511
	pub max_fee_per_gas: U256,
512
	pub gas_limit: U256,
513
	pub action: TransactionAction,
514
	pub value: U256,
515
	pub input: Vec<u8>,
516
}
517

            
518
impl EIP1559UnsignedTransaction {
519
2
	pub fn sign(&self, secret: &H256, chain_id: Option<u64>) -> Transaction {
520
2
		let secret = {
521
2
			let mut sk: [u8; 32] = [0u8; 32];
522
2
			sk.copy_from_slice(&secret[0..]);
523
2
			libsecp256k1::SecretKey::parse(&sk).unwrap()
524
2
		};
525
2
		let chain_id = chain_id.unwrap_or(ChainId::get());
526
2
		let msg = ethereum::EIP1559TransactionMessage {
527
2
			chain_id,
528
2
			nonce: self.nonce,
529
2
			max_priority_fee_per_gas: self.max_priority_fee_per_gas,
530
2
			max_fee_per_gas: self.max_fee_per_gas,
531
2
			gas_limit: self.gas_limit,
532
2
			action: self.action,
533
2
			value: self.value,
534
2
			input: self.input.clone(),
535
2
			access_list: vec![],
536
2
		};
537
2
		let signing_message = libsecp256k1::Message::parse_slice(&msg.hash()[..]).unwrap();
538
2

            
539
2
		let (signature, recid) = libsecp256k1::sign(&signing_message, &secret);
540
2
		let rs = signature.serialize();
541
2
		let r = H256::from_slice(&rs[0..32]);
542
2
		let s = H256::from_slice(&rs[32..64]);
543
2
		Transaction::EIP1559(ethereum::EIP1559Transaction {
544
2
			chain_id: msg.chain_id,
545
2
			nonce: msg.nonce,
546
2
			max_priority_fee_per_gas: msg.max_priority_fee_per_gas,
547
2
			max_fee_per_gas: msg.max_fee_per_gas,
548
2
			gas_limit: msg.gas_limit,
549
2
			action: msg.action,
550
2
			value: msg.value,
551
2
			input: msg.input.clone(),
552
2
			access_list: msg.access_list,
553
2
			odd_y_parity: recid.serialize() != 0,
554
2
			r,
555
2
			s,
556
2
		})
557
2
	}
558
}