precompile_utils/testing/
handle.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::testing::PrettyLog;
20use alloc::boxed::Box;
21use evm::{ExitRevert, ExitSucceed};
22use fp_evm::{Context, ExitError, ExitReason, Log, PrecompileHandle, Transfer};
23use sp_core::{H160, H256};
24
25use super::Alice;
26
27#[derive(Debug, Clone)]
28pub struct Subcall {
29	pub address: H160,
30	pub transfer: Option<Transfer>,
31	pub input: Vec<u8>,
32	pub target_gas: Option<u64>,
33	pub is_static: bool,
34	pub context: Context,
35}
36
37#[derive(Debug, Clone)]
38pub struct SubcallOutput {
39	pub reason: ExitReason,
40	pub output: Vec<u8>,
41	pub cost: u64,
42	pub logs: Vec<Log>,
43}
44
45impl SubcallOutput {
46	pub fn revert() -> Self {
47		Self {
48			reason: ExitReason::Revert(ExitRevert::Reverted),
49			output: Vec::new(),
50			cost: 0,
51			logs: Vec::new(),
52		}
53	}
54
55	pub fn succeed() -> Self {
56		Self {
57			reason: ExitReason::Succeed(ExitSucceed::Returned),
58			output: Vec::new(),
59			cost: 0,
60			logs: Vec::new(),
61		}
62	}
63
64	pub fn out_of_gas() -> Self {
65		Self {
66			reason: ExitReason::Error(ExitError::OutOfGas),
67			output: Vec::new(),
68			cost: 0,
69			logs: Vec::new(),
70		}
71	}
72}
73
74pub trait SubcallTrait: FnMut(Subcall) -> SubcallOutput + 'static {}
75
76impl<T: FnMut(Subcall) -> SubcallOutput + 'static> SubcallTrait for T {}
77
78pub type SubcallHandle = Box<dyn SubcallTrait>;
79
80/// Mock handle to write tests for precompiles.
81pub struct MockHandle {
82	pub gas_limit: u64,
83	pub gas_used: u64,
84	pub logs: Vec<PrettyLog>,
85	pub subcall_handle: Option<SubcallHandle>,
86	pub code_address: H160,
87	pub input: Vec<u8>,
88	pub context: Context,
89	pub is_static: bool,
90}
91
92impl MockHandle {
93	pub fn new(code_address: H160, context: Context) -> Self {
94		Self {
95			gas_limit: u64::MAX,
96			gas_used: 0,
97			logs: vec![],
98			subcall_handle: None,
99			code_address,
100			input: Vec::new(),
101			context,
102			is_static: false,
103		}
104	}
105}
106
107impl PrecompileHandle for MockHandle {
108	/// Perform subcall in provided context.
109	/// Precompile specifies in which context the subcall is executed.
110	fn call(
111		&mut self,
112		address: H160,
113		transfer: Option<Transfer>,
114		input: Vec<u8>,
115		target_gas: Option<u64>,
116		is_static: bool,
117		context: &Context,
118	) -> (ExitReason, Vec<u8>) {
119		if self
120			.record_cost(crate::evm::costs::call_cost(
121				context.apparent_value,
122				&evm::Config::pectra(),
123			))
124			.is_err()
125		{
126			return (ExitReason::Error(ExitError::OutOfGas), vec![]);
127		}
128
129		match &mut self.subcall_handle {
130			Some(handle) => {
131				let SubcallOutput {
132					reason,
133					output,
134					cost,
135					logs,
136				} = handle(Subcall {
137					address,
138					transfer,
139					input,
140					target_gas,
141					is_static,
142					context: context.clone(),
143				});
144
145				if self.record_cost(cost).is_err() {
146					return (ExitReason::Error(ExitError::OutOfGas), vec![]);
147				}
148
149				for log in logs {
150					self.log(log.address, log.topics, log.data)
151						.expect("cannot fail");
152				}
153
154				(reason, output)
155			}
156			None => panic!("no subcall handle registered"),
157		}
158	}
159
160	fn record_cost(&mut self, cost: u64) -> Result<(), ExitError> {
161		self.gas_used += cost;
162
163		if self.gas_used > self.gas_limit {
164			Err(ExitError::OutOfGas)
165		} else {
166			Ok(())
167		}
168	}
169
170	fn remaining_gas(&self) -> u64 {
171		self.gas_limit - self.gas_used
172	}
173
174	fn log(&mut self, address: H160, topics: Vec<H256>, data: Vec<u8>) -> Result<(), ExitError> {
175		self.logs.push(PrettyLog(Log {
176			address,
177			topics,
178			data,
179		}));
180		Ok(())
181	}
182
183	/// Retrieve the code address (what is the address of the precompile being called).
184	fn code_address(&self) -> H160 {
185		self.code_address
186	}
187
188	/// Retrieve the input data the precompile is called with.
189	fn input(&self) -> &[u8] {
190		&self.input
191	}
192
193	/// Retrieve the context in which the precompile is executed.
194	fn context(&self) -> &Context {
195		&self.context
196	}
197
198	/// Is the precompile call is done statically.
199	fn is_static(&self) -> bool {
200		self.is_static
201	}
202
203	/// Retrieve the gas limit of this call.
204	fn gas_limit(&self) -> Option<u64> {
205		Some(self.gas_limit)
206	}
207
208	fn record_external_cost(
209		&mut self,
210		_ref_time: Option<u64>,
211		_proof_size: Option<u64>,
212		_storage_growth: Option<u64>,
213	) -> Result<(), ExitError> {
214		Ok(())
215	}
216
217	fn refund_external_cost(&mut self, _ref_time: Option<u64>, _proof_size: Option<u64>) {}
218
219	fn origin(&self) -> H160 {
220		Alice.into()
221	}
222
223	fn is_contract_being_constructed(&self, _address: H160) -> bool {
224		false
225	}
226}