precompile_utils/testing/
execution.rs1use 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 pub fn execute_some(mut self) {
143 let res = self.execute();
144 assert!(res.is_some());
145 self.assert_optionals();
146 }
147
148 pub fn execute_none(mut self) {
150 let res = self.execute();
151 assert!(res.is_none());
152 self.assert_optionals();
153 }
154
155 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 pub fn execute_returns(self, output: impl Codec) {
196 self.execute_returns_raw(crate::solidity::encode_return_value(output))
197 }
198
199 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 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}