precompile_utils_tests_external/
lib.rs

1// This file is part of Frontier.
2
3// Copyright (c) Moonsong Labs.
4// Copyright (C) Parity Technologies (UK) Ltd.
5// SPDX-License-Identifier: Apache-2.0
6
7// Licensed under the Apache License, Version 2.0 (the "License");
8// you may not use this file except in compliance with the License.
9// You may obtain a copy of the License at
10//
11// 	http://www.apache.org/licenses/LICENSE-2.0
12//
13// Unless required by applicable law or agreed to in writing, software
14// distributed under the License is distributed on an "AS IS" BASIS,
15// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16// See the License for the specific language governing permissions and
17// limitations under the License.
18
19#![cfg(test)]
20
21// #[precompile_utils::precompile] need this
22extern crate alloc;
23
24use std::{cell::RefCell, rc::Rc};
25
26// Substrate
27use frame_support::{
28	construct_runtime, derive_impl, parameter_types, traits::Everything, weights::Weight,
29};
30use sp_core::{H160, H256, U256};
31use sp_runtime::{
32	traits::{BlakeTwo256, IdentityLookup},
33	BuildStorage, Perbill,
34};
35// Frontier
36use fp_evm::{ExitReason, ExitRevert, PrecompileFailure, PrecompileHandle};
37use pallet_evm::{CodeMetadata, EnsureAddressNever, EnsureAddressRoot};
38use precompile_utils::{
39	precompile_set::*,
40	solidity::{codec::Writer, revert::revert},
41	testing::*,
42	EvmResult,
43};
44
45pub type AccountId = MockAccount;
46pub type Balance = u128;
47
48construct_runtime!(
49	pub enum Runtime {
50		System: frame_system::{Pallet, Call, Config<T>, Storage, Event<T>},
51		Balances: pallet_balances::{Pallet, Call, Storage, Event<T>},
52		Evm: pallet_evm::{Pallet, Call, Storage, Event<T>},
53		Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent},
54	}
55);
56
57parameter_types! {
58	pub const BlockHashCount: u32 = 250;
59	pub const MaximumBlockWeight: Weight = Weight::from_parts(1024, 1);
60	pub const MaximumBlockLength: u32 = 2 * 1024;
61	pub const AvailableBlockRatio: Perbill = Perbill::one();
62	pub const SS58Prefix: u8 = 42;
63}
64
65#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
66impl frame_system::Config for Runtime {
67	type RuntimeEvent = RuntimeEvent;
68	type BaseCallFilter = Everything;
69	type BlockWeights = ();
70	type BlockLength = ();
71	type RuntimeOrigin = RuntimeOrigin;
72	type RuntimeCall = RuntimeCall;
73	type RuntimeTask = RuntimeTask;
74	type Nonce = u64;
75	type Hash = H256;
76	type Hashing = BlakeTwo256;
77	type AccountId = AccountId;
78	type Lookup = IdentityLookup<Self::AccountId>;
79	type Block = frame_system::mocking::MockBlock<Self>;
80	type BlockHashCount = BlockHashCount;
81	type DbWeight = ();
82	type Version = ();
83	type PalletInfo = PalletInfo;
84	type AccountData = pallet_balances::AccountData<Balance>;
85	type OnNewAccount = ();
86	type OnKilledAccount = ();
87	type SystemWeightInfo = ();
88	type SS58Prefix = SS58Prefix;
89	type OnSetCode = ();
90	type MaxConsumers = frame_support::traits::ConstU32<16>;
91}
92
93parameter_types! {
94	pub const ExistentialDeposit: u128 = 0;
95}
96impl pallet_balances::Config for Runtime {
97	type RuntimeEvent = RuntimeEvent;
98	type RuntimeHoldReason = RuntimeHoldReason;
99	type RuntimeFreezeReason = RuntimeFreezeReason;
100	type WeightInfo = ();
101	type Balance = Balance;
102	type DustRemoval = ();
103	type ExistentialDeposit = ExistentialDeposit;
104	type AccountStore = System;
105	type ReserveIdentifier = [u8; 8];
106	type FreezeIdentifier = RuntimeFreezeReason;
107	type MaxLocks = ();
108	type MaxReserves = ();
109	type MaxFreezes = ();
110	type DoneSlashHandler = ();
111}
112
113#[derive(Debug, Clone)]
114pub struct MockPrecompile;
115
116#[precompile_utils::precompile]
117impl MockPrecompile {
118	// a3cab0dd
119	#[precompile::public("subcall()")]
120	fn subcall(handle: &mut impl PrecompileHandle) -> EvmResult {
121		match handle.call(
122			handle.code_address(),
123			None,
124			// calls subcallLayer2()
125			Writer::new_with_selector(0x0b93381bu32).build(),
126			None,
127			false,
128			&evm::Context {
129				caller: handle.code_address(),
130				address: handle.code_address(),
131				apparent_value: 0.into(),
132			},
133		) {
134			(ExitReason::Succeed(_), _) => Ok(()),
135			(ExitReason::Revert(_), v) => Err(PrecompileFailure::Revert {
136				exit_status: ExitRevert::Reverted,
137				output: v,
138			}),
139			_ => Err(revert("unexpected error")),
140		}
141	}
142
143	// 0b93381b
144	#[precompile::public("success()")]
145	fn success(_: &mut impl PrecompileHandle) -> EvmResult {
146		Ok(())
147	}
148}
149
150#[derive(Default)]
151struct MockPrecompileHandle {
152	contracts_being_constructed: Vec<H160>,
153}
154impl MockPrecompileHandle {
155	fn with_contracts_being_constructed(mut self, contracts_being_constructed: Vec<H160>) -> Self {
156		self.contracts_being_constructed = contracts_being_constructed;
157		self
158	}
159}
160impl PrecompileHandle for MockPrecompileHandle {
161	fn call(
162		&mut self,
163		_: H160,
164		_: Option<evm::Transfer>,
165		_: Vec<u8>,
166		_: Option<u64>,
167		_: bool,
168		_: &evm::Context,
169	) -> (ExitReason, Vec<u8>) {
170		unimplemented!()
171	}
172
173	fn record_cost(&mut self, _: u64) -> Result<(), evm::ExitError> {
174		Ok(())
175	}
176
177	fn record_external_cost(
178		&mut self,
179		_ref_time: Option<u64>,
180		_proof_size: Option<u64>,
181		_storage_growth: Option<u64>,
182	) -> Result<(), fp_evm::ExitError> {
183		Ok(())
184	}
185
186	fn refund_external_cost(&mut self, _ref_time: Option<u64>, _proof_size: Option<u64>) {}
187
188	fn remaining_gas(&self) -> u64 {
189		0
190	}
191
192	fn log(&mut self, _: H160, _: Vec<H256>, _: Vec<u8>) -> Result<(), evm::ExitError> {
193		unimplemented!()
194	}
195
196	fn code_address(&self) -> H160 {
197		unimplemented!()
198	}
199
200	fn input(&self) -> &[u8] {
201		unimplemented!()
202	}
203
204	fn context(&self) -> &evm::Context {
205		unimplemented!()
206	}
207
208	fn origin(&self) -> H160 {
209		Alice.into()
210	}
211
212	fn is_static(&self) -> bool {
213		true
214	}
215
216	fn gas_limit(&self) -> Option<u64> {
217		unimplemented!()
218	}
219
220	fn is_contract_being_constructed(&self, address: H160) -> bool {
221		self.contracts_being_constructed.contains(&address)
222	}
223}
224
225pub type Precompiles<R> = PrecompileSetBuilder<
226	R,
227	(
228		PrecompileAt<AddressU64<1>, MockPrecompile>,
229		PrecompileAt<AddressU64<2>, MockPrecompile, CallableByContract>,
230		PrecompileAt<AddressU64<3>, MockPrecompile, CallableByPrecompile>,
231		PrecompileAt<AddressU64<4>, MockPrecompile, SubcallWithMaxNesting<1>>,
232	),
233>;
234
235pub type PCall = MockPrecompileCall;
236
237const MAX_POV_SIZE: u64 = 5 * 1024 * 1024;
238
239parameter_types! {
240	pub BlockGasLimit: U256 = U256::from(u64::MAX);
241	pub PrecompilesValue: Precompiles<Runtime> = Precompiles::new();
242	pub const WeightPerGas: Weight = Weight::from_parts(1, 0);
243	pub GasLimitPovSizeRatio: u64 = {
244		let block_gas_limit = BlockGasLimit::get().min(u64::MAX.into()).low_u64();
245		block_gas_limit.saturating_div(MAX_POV_SIZE)
246	};
247}
248
249impl pallet_evm::Config for Runtime {
250	type AccountProvider = pallet_evm::FrameSystemAccountProvider<Self>;
251	type FeeCalculator = ();
252	type GasWeightMapping = pallet_evm::FixedGasWeightMapping<Self>;
253	type WeightPerGas = WeightPerGas;
254	type BlockHashMapping = pallet_evm::SubstrateBlockHashMapping<Self>;
255	type CallOrigin = EnsureAddressRoot<AccountId>;
256	type WithdrawOrigin = EnsureAddressNever<AccountId>;
257	type AddressMapping = AccountId;
258	type Currency = Balances;
259	type PrecompilesType = Precompiles<Runtime>;
260	type PrecompilesValue = PrecompilesValue;
261	type ChainId = ();
262	type BlockGasLimit = BlockGasLimit;
263	type Runner = pallet_evm::runner::stack::Runner<Self>;
264	type OnChargeTransaction = ();
265	type OnCreate = ();
266	type FindAuthor = ();
267	type GasLimitPovSizeRatio = GasLimitPovSizeRatio;
268	type GasLimitStorageGrowthRatio = ();
269	type Timestamp = Timestamp;
270	type CreateInnerOriginFilter = ();
271	type CreateOriginFilter = ();
272	type WeightInfo = pallet_evm::weights::SubstrateWeight<Runtime>;
273}
274
275parameter_types! {
276	pub const MinimumPeriod: u64 = 5;
277}
278impl pallet_timestamp::Config for Runtime {
279	type Moment = u64;
280	type OnTimestampSet = ();
281	type MinimumPeriod = MinimumPeriod;
282	type WeightInfo = ();
283}
284
285#[derive(Default)]
286struct ExtBuilder {}
287
288impl ExtBuilder {
289	fn build(self) -> sp_io::TestExternalities {
290		let t = frame_system::GenesisConfig::<Runtime>::default()
291			.build_storage()
292			.expect("Frame system builds valid default genesis config");
293
294		let mut ext = sp_io::TestExternalities::new(t);
295		ext.execute_with(|| {
296			System::set_block_number(1);
297		});
298		ext
299	}
300}
301
302fn precompiles() -> Precompiles<Runtime> {
303	PrecompilesValue::get()
304}
305
306#[test]
307fn default_checks_succeed_when_called_by_eoa() {
308	ExtBuilder::default().build().execute_with(|| {
309		precompiles()
310			.prepare_test(Alice, H160::from_low_u64_be(1), PCall::success {})
311			.with_subcall_handle(|Subcall { .. }| panic!("there should be no subcall"))
312			.execute_returns(())
313	})
314}
315
316#[test]
317fn default_checks_revert_when_called_by_precompile() {
318	ExtBuilder::default().build().execute_with(|| {
319		precompiles()
320			.prepare_test(
321				H160::from_low_u64_be(1),
322				H160::from_low_u64_be(1),
323				PCall::success {},
324			)
325			.with_subcall_handle(|Subcall { .. }| panic!("there should be no subcall"))
326			.execute_reverts(|r| r == b"Function not callable by precompiles")
327	})
328}
329
330#[test]
331fn default_checks_revert_when_called_by_contract() {
332	ExtBuilder::default().build().execute_with(|| {
333		let _ = pallet_evm::Pallet::<Runtime>::create_account(
334			Alice.into(),
335			hex_literal::hex!("1460006000fd").to_vec(),
336			None,
337		);
338
339		precompiles()
340			.prepare_test(Alice, H160::from_low_u64_be(1), PCall::success {})
341			.with_subcall_handle(|Subcall { .. }| panic!("there should be no subcall"))
342			.execute_reverts(|r| r == b"Function not callable by smart contracts")
343	})
344}
345
346#[test]
347fn default_checks_revert_when_doing_subcall() {
348	ExtBuilder::default().build().execute_with(|| {
349		precompiles()
350			.prepare_test(Alice, H160::from_low_u64_be(1), PCall::subcall {})
351			.with_subcall_handle(|Subcall { .. }| panic!("there should be no subcall"))
352			.execute_reverts(|r| r == b"subcalls disabled for this precompile")
353	})
354}
355
356#[test]
357fn callable_by_contract_works() {
358	ExtBuilder::default().build().execute_with(|| {
359		let _ = pallet_evm::Pallet::<Runtime>::create_account(
360			Alice.into(),
361			hex_literal::hex!("1460006000fd").to_vec(),
362			None,
363		);
364
365		precompiles()
366			.prepare_test(Alice, H160::from_low_u64_be(2), PCall::success {})
367			.with_subcall_handle(|Subcall { .. }| panic!("there should be no subcall"))
368			.execute_returns(())
369	})
370}
371
372#[test]
373fn callable_by_precompile_works() {
374	ExtBuilder::default().build().execute_with(|| {
375		precompiles()
376			.prepare_test(
377				H160::from_low_u64_be(3),
378				H160::from_low_u64_be(3),
379				PCall::success {},
380			)
381			.with_subcall_handle(|Subcall { .. }| panic!("there should be no subcall"))
382			.execute_returns(())
383	})
384}
385
386#[test]
387fn subcalls_works_when_allowed() {
388	ExtBuilder::default().build().execute_with(|| {
389		let subcall_occured = Rc::new(RefCell::new(false));
390		{
391			let subcall_occured = Rc::clone(&subcall_occured);
392			precompiles()
393				.prepare_test(Alice, H160::from_low_u64_be(4), PCall::subcall {})
394				.with_subcall_handle(move |Subcall { .. }| {
395					*subcall_occured.borrow_mut() = true;
396					SubcallOutput::succeed()
397				})
398				.execute_returns(());
399		}
400		assert!(*subcall_occured.borrow());
401	})
402}
403
404#[test]
405fn get_address_type_works_for_eoa() {
406	ExtBuilder::default().build().execute_with(|| {
407		let externally_owned_account: H160 = Alice.into();
408		let mut handle = MockPrecompileHandle::default();
409
410		assert_eq!(
411			AddressType::EOA,
412			get_address_type::<Runtime>(&mut handle, externally_owned_account).expect("OOG")
413		);
414	})
415}
416
417#[test]
418fn get_address_type_works_for_precompile() {
419	ExtBuilder::default().build().execute_with(|| {
420		let precompiles: Vec<H160> = Precompiles::<Runtime>::used_addresses_h160().collect();
421		// We expect 4 precompiles
422		assert_eq!(precompiles.len(), 4);
423
424		let mut handle = MockPrecompileHandle::default();
425		precompiles.iter().cloned().for_each(|precompile| {
426			assert_eq!(
427				AddressType::Precompile,
428				get_address_type::<Runtime>(&mut handle, precompile).expect("OOG")
429			);
430		});
431	})
432}
433
434#[test]
435fn get_address_type_works_for_smart_contract() {
436	ExtBuilder::default().build().execute_with(|| {
437		let address = H160::repeat_byte(0x1d);
438		pallet_evm::AccountCodesMetadata::<Runtime>::insert(
439			address,
440			CodeMetadata {
441				hash: Default::default(),
442				size: 1,
443			},
444		);
445
446		let mut handle = MockPrecompileHandle::default();
447		assert_eq!(
448			AddressType::Contract,
449			get_address_type::<Runtime>(&mut handle, address).expect("OOG")
450		);
451	})
452}
453
454#[test]
455fn get_address_type_works_for_smart_contract_being_constructed() {
456	ExtBuilder::default().build().execute_with(|| {
457		let contract_being_constucted = H160::repeat_byte(0x1d);
458		let mut handle = MockPrecompileHandle::default()
459			.with_contracts_being_constructed(vec![contract_being_constucted]);
460
461		assert_eq!(
462			AddressType::Contract,
463			get_address_type::<Runtime>(&mut handle, contract_being_constucted).expect("OOG")
464		);
465	})
466}