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 frame_support::{
20
	parameter_types,
21
	traits::{ConstU32, FindAuthor, InstanceFilter},
22
	weights::Weight,
23
	ConsensusEngineId, PalletId,
24
};
25
use frame_system::{pallet_prelude::BlockNumberFor, EnsureRoot};
26
use pallet_evm::{
27
	AddressMapping, EnsureAddressTruncated, FeeCalculator, FrameSystemAccountProvider,
28
};
29
use sp_core::{hashing::keccak_256, H160, H256, U256};
30
use sp_runtime::{
31
	traits::{BlakeTwo256, IdentityLookup},
32
	AccountId32, BuildStorage,
33
};
34

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

            
42
pub type BlockNumber = BlockNumberFor<Test>;
43

            
44
type Block = frame_system::mocking::MockBlock<Test>;
45

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

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

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

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

            
105
impl pallet_balances::Config for Test {
106
	type MaxLocks = MaxLocks;
107
	type Balance = u64;
108
	type RuntimeEvent = RuntimeEvent;
109
	type DustRemoval = ();
110
	type ExistentialDeposit = ExistentialDeposit;
111
	type AccountStore = System;
112
	type WeightInfo = ();
113
	type MaxReserves = ();
114
	type ReserveIdentifier = ();
115
	type RuntimeHoldReason = ();
116
	type FreezeIdentifier = ();
117
	type MaxFreezes = ();
118
	type RuntimeFreezeReason = ();
119
	type DoneSlashHandler = ();
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
54
	fn min_gas_price() -> (U256, Weight) {
136
54
		(1.into(), Weight::zero())
137
54
	}
138
}
139

            
140
pub struct FindAuthorTruncated;
141
impl FindAuthor<H160> for FindAuthorTruncated {
142
51
	fn find_author<'a, I>(_digests: I) -> Option<H160>
143
51
	where
144
51
		I: 'a + IntoIterator<Item = (ConsensusEngineId, &'a [u8])>,
145
51
	{
146
51
		Some(address_build(0).address)
147
51
	}
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
229
	fn into_account_id(address: H160) -> AccountId32 {
174
229
		let mut data = [0u8; 32];
175
229
		data[0..20].copy_from_slice(&address[..]);
176
229
		AccountId32::from(Into::<[u8; 32]>::into(data))
177
229
	}
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 GasLimitStorageGrowthRatio = GasLimitStorageGrowthRatio;
200
	type Timestamp = Timestamp;
201
	type WeightInfo = pallet_evm::weights::SubstrateWeight<Test>;
202
	type AccountProvider = FrameSystemAccountProvider<Test>;
203
	type CreateOriginFilter = ();
204
	type CreateInnerOriginFilter = ();
205
}
206

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

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

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

            
222
#[derive(
223
	Copy,
224
	Clone,
225
	Eq,
226
	PartialEq,
227
	Ord,
228
	PartialOrd,
229
	Encode,
230
	Decode,
231
	Debug,
232
	MaxEncodedLen,
233
	TypeInfo,
234
	DecodeWithMemTracking,
235
)]
236
pub enum ProxyType {
237
5
	NotAllowed = 0,
238
12
	Any = 1,
239
}
240

            
241
impl pallet_evm_precompile_proxy::EvmProxyCallFilter for ProxyType {}
242

            
243
impl InstanceFilter<RuntimeCall> for ProxyType {
244
	fn filter(&self, _c: &RuntimeCall) -> bool {
245
		match self {
246
			ProxyType::NotAllowed => false,
247
			ProxyType::Any => true,
248
		}
249
	}
250
	fn is_superset(&self, _o: &Self) -> bool {
251
		false
252
	}
253
}
254

            
255
impl Default for ProxyType {
256
	fn default() -> Self {
257
		Self::NotAllowed
258
	}
259
}
260

            
261
parameter_types! {
262
	pub const ProxyCost: u64 = 1;
263
}
264

            
265
impl pallet_proxy::Config for Test {
266
	type RuntimeEvent = RuntimeEvent;
267
	type RuntimeCall = RuntimeCall;
268
	type Currency = Balances;
269
	type ProxyType = ProxyType;
270
	type ProxyDepositBase = ProxyCost;
271
	type ProxyDepositFactor = ProxyCost;
272
	type MaxProxies = ConstU32<32>;
273
	type WeightInfo = pallet_proxy::weights::SubstrateWeight<Test>;
274
	type MaxPending = ConstU32<32>;
275
	type CallHasher = BlakeTwo256;
276
	type AnnouncementDepositBase = ProxyCost;
277
	type AnnouncementDepositFactor = ProxyCost;
278
	type BlockNumberProvider = System;
279
}
280

            
281
pub struct EthereumXcmEnsureProxy;
282
impl xcm_primitives::EnsureProxy<AccountId32> for EthereumXcmEnsureProxy {
283
17
	fn ensure_ok(delegator: AccountId32, delegatee: AccountId32) -> Result<(), &'static str> {
284
17
		let f = |x: &pallet_proxy::ProxyDefinition<AccountId32, ProxyType, BlockNumber>| -> bool {
285
12
			x.delegate == delegatee && (x.proxy_type == ProxyType::Any)
286
12
		};
287
17
		Proxy::proxies(delegator)
288
17
			.0
289
17
			.into_iter()
290
17
			.find(f)
291
17
			.map(|_| ())
292
17
			.ok_or("proxy error: expected `ProxyType::Any`")
293
17
	}
294
}
295

            
296
impl crate::Config for Test {
297
	type RuntimeEvent = RuntimeEvent;
298
	type InvalidEvmTransactionError = pallet_ethereum::InvalidTransactionWrapper;
299
	type ValidatedTransaction = pallet_ethereum::ValidatedTransaction<Self>;
300
	type XcmEthereumOrigin = crate::EnsureXcmEthereumTransaction;
301
	type ReservedXcmpWeight = ReservedXcmpWeight;
302
	type EnsureProxy = EthereumXcmEnsureProxy;
303
	type ControllerOrigin = EnsureRoot<AccountId32>;
304
	type ForceOrigin = EnsureRoot<AccountId32>;
305
}
306

            
307
impl fp_self_contained::SelfContainedCall for RuntimeCall {
308
	type SignedInfo = H160;
309

            
310
	fn is_self_contained(&self) -> bool {
311
		match self {
312
			RuntimeCall::Ethereum(call) => call.is_self_contained(),
313
			_ => false,
314
		}
315
	}
316

            
317
	fn check_self_contained(&self) -> Option<Result<Self::SignedInfo, TransactionValidityError>> {
318
		match self {
319
			RuntimeCall::Ethereum(call) => call.check_self_contained(),
320
			_ => None,
321
		}
322
	}
323

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

            
336
	fn pre_dispatch_self_contained(
337
		&self,
338
		info: &Self::SignedInfo,
339
		dispatch_info: &DispatchInfoOf<RuntimeCall>,
340
		len: usize,
341
	) -> Option<Result<(), TransactionValidityError>> {
342
		match self {
343
			RuntimeCall::Ethereum(call) => {
344
				call.pre_dispatch_self_contained(info, dispatch_info, len)
345
			}
346
			_ => None,
347
		}
348
	}
349

            
350
	fn apply_self_contained(
351
		self,
352
		info: Self::SignedInfo,
353
	) -> Option<sp_runtime::DispatchResultWithInfo<sp_runtime::traits::PostDispatchInfoOf<Self>>> {
354
		use sp_runtime::traits::Dispatchable as _;
355
		match self {
356
			call @ RuntimeCall::Ethereum(pallet_ethereum::Call::transact { .. }) => {
357
				Some(call.dispatch(RuntimeOrigin::from(
358
					pallet_ethereum::RawOrigin::EthereumTransaction(info),
359
				)))
360
			}
361
			_ => None,
362
		}
363
	}
364
}
365

            
366
pub struct AccountInfo {
367
	pub address: H160,
368
	pub account_id: AccountId32,
369
}
370

            
371
175
fn address_build(seed: u8) -> AccountInfo {
372
175
	let private_key = H256::from_slice(&[(seed + 1) as u8; 32]);
373
175
	let secret_key = libsecp256k1::SecretKey::parse_slice(&private_key[..]).unwrap();
374
175
	let public_key = &libsecp256k1::PublicKey::from_secret_key(&secret_key).serialize()[1..65];
375
175
	let address = H160::from(H256::from(keccak_256(public_key)));
376
175

            
377
175
	let mut data = [0u8; 32];
378
175
	data[0..20].copy_from_slice(&address[..]);
379
175

            
380
175
	AccountInfo {
381
175
		account_id: AccountId32::from(Into::<[u8; 32]>::into(data)),
382
175
		address,
383
175
	}
384
175
}
385

            
386
// This function basically just builds a genesis storage key/value store according to
387
// our desired mockup.
388
56
pub fn new_test_ext(accounts_len: usize) -> (Vec<AccountInfo>, sp_io::TestExternalities) {
389
56
	// sc_cli::init_logger("");
390
56
	let mut ext = frame_system::GenesisConfig::<Test>::default()
391
56
		.build_storage()
392
56
		.unwrap();
393
56

            
394
56
	let pairs = (0..accounts_len)
395
124
		.map(|i| address_build(i as u8))
396
56
		.collect::<Vec<_>>();
397
56

            
398
56
	let balances: Vec<_> = (0..accounts_len)
399
124
		.map(|i| (pairs[i].account_id.clone(), 10_000_000))
400
56
		.collect();
401
56

            
402
56
	pallet_balances::GenesisConfig::<Test> {
403
56
		balances,
404
56
		dev_accounts: Default::default(),
405
56
	}
406
56
	.assimilate_storage(&mut ext)
407
56
	.unwrap();
408
56

            
409
56
	(pairs, ext.into())
410
56
}