precompile_utils/testing/
execution.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
19use crate::{
20	solidity::codec::Codec,
21	testing::{decode_revert_message, MockHandle, PrettyLog, SubcallHandle, SubcallTrait},
22};
23use alloc::boxed::Box;
24use fp_evm::{
25	Context, ExitError, ExitSucceed, Log, PrecompileFailure, PrecompileOutput, PrecompileResult,
26	PrecompileSet,
27};
28use sp_core::{H160, U256};
29
30#[must_use]
31pub struct PrecompilesTester<'p, P> {
32	precompiles: &'p P,
33	handle: MockHandle,
34
35	target_gas: Option<u64>,
36	subcall_handle: Option<SubcallHandle>,
37
38	expected_cost: Option<u64>,
39	expected_logs: Option<Vec<PrettyLog>>,
40	static_call: bool,
41}
42
43impl<'p, P: PrecompileSet> PrecompilesTester<'p, P> {
44	pub fn new(
45		precompiles: &'p P,
46		from: impl Into<H160>,
47		to: impl Into<H160>,
48		data: Vec<u8>,
49	) -> Self {
50		let to = to.into();
51		let mut handle = MockHandle::new(
52			to,
53			Context {
54				address: to,
55				caller: from.into(),
56				apparent_value: U256::zero(),
57			},
58		);
59
60		handle.input = data;
61
62		Self {
63			precompiles,
64			handle,
65
66			target_gas: None,
67			subcall_handle: None,
68
69			expected_cost: None,
70			expected_logs: None,
71			static_call: false,
72		}
73	}
74
75	pub fn with_value(mut self, value: impl Into<U256>) -> Self {
76		self.handle.context.apparent_value = value.into();
77		self
78	}
79
80	pub fn with_subcall_handle(mut self, subcall_handle: impl SubcallTrait) -> Self {
81		self.subcall_handle = Some(Box::new(subcall_handle));
82		self
83	}
84
85	pub fn with_target_gas(mut self, target_gas: Option<u64>) -> Self {
86		self.target_gas = target_gas;
87		self
88	}
89
90	pub fn with_static_call(mut self, static_call: bool) -> Self {
91		self.static_call = static_call;
92		self
93	}
94
95	pub fn expect_cost(mut self, cost: u64) -> Self {
96		self.expected_cost = Some(cost);
97		self
98	}
99
100	pub fn expect_no_logs(mut self) -> Self {
101		self.expected_logs = Some(vec![]);
102		self
103	}
104
105	pub fn expect_log(mut self, log: Log) -> Self {
106		self.expected_logs = Some({
107			let mut logs = self.expected_logs.unwrap_or_default();
108			logs.push(PrettyLog(log));
109			logs
110		});
111		self
112	}
113
114	fn assert_optionals(&self) {
115		if let Some(cost) = &self.expected_cost {
116			assert_eq!(&self.handle.gas_used, cost);
117		}
118
119		if let Some(logs) = &self.expected_logs {
120			similar_asserts::assert_eq!(&self.handle.logs, logs);
121		}
122	}
123
124	fn execute(&mut self) -> Option<PrecompileResult> {
125		let handle = &mut self.handle;
126		handle.subcall_handle = self.subcall_handle.take();
127		handle.is_static = self.static_call;
128
129		if let Some(gas_limit) = self.target_gas {
130			handle.gas_limit = gas_limit;
131		}
132
133		let res = self.precompiles.execute(handle);
134
135		self.subcall_handle = handle.subcall_handle.take();
136
137		res
138	}
139
140	/// Execute the precompile set and expect some precompile to have been executed, regardless of the
141	/// result.
142	pub fn execute_some(mut self) {
143		let res = self.execute();
144		assert!(res.is_some());
145		self.assert_optionals();
146	}
147
148	/// Execute the precompile set and expect no precompile to have been executed.
149	pub fn execute_none(mut self) {
150		let res = self.execute();
151		assert!(res.is_none());
152		self.assert_optionals();
153	}
154
155	/// Execute the precompile set and check it returns provided output.
156	pub fn execute_returns_raw(mut self, output: Vec<u8>) {
157		let res = self.execute();
158
159		match res {
160			Some(Err(PrecompileFailure::Revert { output, .. })) => {
161				let decoded = decode_revert_message(&output);
162				eprintln!(
163					"Revert message (bytes): {:?}",
164					sp_core::hexdisplay::HexDisplay::from(&decoded)
165				);
166				eprintln!(
167					"Revert message (string): {:?}",
168					core::str::from_utf8(decoded).ok()
169				);
170				panic!("Shouldn't have reverted");
171			}
172			Some(Ok(PrecompileOutput {
173				exit_status: ExitSucceed::Returned,
174				output: execution_output,
175			})) => {
176				if execution_output != output {
177					eprintln!(
178						"Output (bytes): {:?}",
179						sp_core::hexdisplay::HexDisplay::from(&execution_output)
180					);
181					eprintln!(
182						"Output (string): {:?}",
183						core::str::from_utf8(&execution_output).ok()
184					);
185					panic!("Output doesn't match");
186				}
187			}
188			other => panic!("Unexpected result: {other:?}"),
189		}
190
191		self.assert_optionals();
192	}
193
194	/// Execute the precompile set and check it returns provided Solidity encoded output.
195	pub fn execute_returns(self, output: impl Codec) {
196		self.execute_returns_raw(crate::solidity::encode_return_value(output))
197	}
198
199	/// Execute the precompile set and check if it reverts.
200	/// Take a closure allowing to perform custom matching on the output.
201	pub fn execute_reverts(mut self, check: impl Fn(&[u8]) -> bool) {
202		let res = self.execute();
203
204		match res {
205			Some(Err(PrecompileFailure::Revert { output, .. })) => {
206				let decoded = decode_revert_message(&output);
207				if !check(decoded) {
208					eprintln!(
209						"Revert message (bytes): {:?}",
210						sp_core::hexdisplay::HexDisplay::from(&decoded)
211					);
212					eprintln!(
213						"Revert message (string): {:?}",
214						core::str::from_utf8(decoded).ok()
215					);
216					panic!("Revert reason doesn't match !");
217				}
218			}
219			other => panic!("Didn't revert, instead returned {other:?}"),
220		}
221
222		self.assert_optionals();
223	}
224
225	/// Execute the precompile set and check it returns provided output.
226	pub fn execute_error(mut self, error: ExitError) {
227		let res = self.execute();
228		assert_eq!(
229			res,
230			Some(Err(PrecompileFailure::Error { exit_status: error }))
231		);
232		self.assert_optionals();
233	}
234}
235
236pub trait PrecompileTesterExt: PrecompileSet + Sized {
237	fn prepare_test(
238		&self,
239		from: impl Into<H160>,
240		to: impl Into<H160>,
241		data: impl Into<Vec<u8>>,
242	) -> PrecompilesTester<Self>;
243}
244
245impl<T: PrecompileSet> PrecompileTesterExt for T {
246	fn prepare_test(
247		&self,
248		from: impl Into<H160>,
249		to: impl Into<H160>,
250		data: impl Into<Vec<u8>>,
251	) -> PrecompilesTester<Self> {
252		PrecompilesTester::new(self, from, to, data.into())
253	}
254}