pallet_evm_test_vector_support/
lib.rs

1// This file is part of Frontier.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18#![warn(unused_crate_dependencies)]
19
20use std::fs;
21
22use evm::{Context, ExitError, ExitReason, ExitSucceed, Transfer};
23use fp_evm::{Precompile, PrecompileFailure, PrecompileHandle};
24use sp_core::{H160, H256};
25
26#[derive(Debug, serde::Deserialize)]
27#[serde(rename_all = "PascalCase")]
28struct EthConsensusTest {
29	input: String,
30	expected: String,
31	name: String,
32	gas: Option<u64>,
33}
34
35#[derive(Debug, serde::Deserialize)]
36#[serde(rename_all = "PascalCase")]
37struct EthConsensusFailureTest {
38	input: String,
39	expected_error: String,
40	name: String,
41}
42
43pub struct MockHandle {
44	pub input: Vec<u8>,
45	pub gas_limit: Option<u64>,
46	pub context: Context,
47	pub is_static: bool,
48	pub gas_used: u64,
49}
50
51impl MockHandle {
52	pub fn new(input: Vec<u8>, gas_limit: Option<u64>, context: Context) -> Self {
53		Self {
54			input,
55			gas_limit,
56			context,
57			is_static: false,
58			gas_used: 0,
59		}
60	}
61}
62
63impl PrecompileHandle for MockHandle {
64	/// Perform subcall in provided context.
65	/// Precompile specifies in which context the subcall is executed.
66	fn call(
67		&mut self,
68		_: H160,
69		_: Option<Transfer>,
70		_: Vec<u8>,
71		_: Option<u64>,
72		_: bool,
73		_: &Context,
74	) -> (ExitReason, Vec<u8>) {
75		unimplemented!()
76	}
77
78	fn record_cost(&mut self, cost: u64) -> Result<(), ExitError> {
79		self.gas_used += cost;
80		Ok(())
81	}
82
83	fn record_external_cost(
84		&mut self,
85		_: Option<u64>,
86		_: Option<u64>,
87		_: Option<u64>,
88	) -> Result<(), ExitError> {
89		Ok(())
90	}
91
92	fn refund_external_cost(&mut self, _: Option<u64>, _: Option<u64>) {}
93
94	fn log(&mut self, _: H160, _: Vec<H256>, _: Vec<u8>) -> Result<(), ExitError> {
95		unimplemented!()
96	}
97
98	fn remaining_gas(&self) -> u64 {
99		unimplemented!()
100	}
101
102	fn code_address(&self) -> H160 {
103		unimplemented!()
104	}
105
106	fn input(&self) -> &[u8] {
107		&self.input
108	}
109
110	fn context(&self) -> &Context {
111		&self.context
112	}
113
114	fn origin(&self) -> H160 {
115		unimplemented!()
116	}
117
118	fn is_static(&self) -> bool {
119		self.is_static
120	}
121
122	fn gas_limit(&self) -> Option<u64> {
123		self.gas_limit
124	}
125
126	fn is_contract_being_constructed(&self, _address: H160) -> bool {
127		unimplemented!()
128	}
129}
130
131/// Tests a precompile against the ethereum consensus tests defined in the given file at filepath.
132/// The file is expected to be in JSON format and contain an array of test vectors, where each
133/// vector can be deserialized into an "EthConsensusTest".
134pub fn test_precompile_test_vectors<P: Precompile>(filepath: &str) -> Result<(), String> {
135	let data = fs::read_to_string(filepath).unwrap_or_else(|_| panic!("Failed to read {filepath}"));
136
137	let tests: Vec<EthConsensusTest> = serde_json::from_str(&data).expect("expected json array");
138
139	for test in tests {
140		let input: Vec<u8> = hex::decode(test.input).expect("Could not hex-decode test input data");
141
142		let cost: u64 = 10000000;
143
144		let context: Context = Context {
145			address: Default::default(),
146			caller: Default::default(),
147			apparent_value: From::from(0),
148		};
149
150		let mut handle = MockHandle::new(input, Some(cost), context);
151
152		match P::execute(&mut handle) {
153			Ok(result) => {
154				let as_hex: String = hex::encode(result.output);
155				assert_eq!(
156					result.exit_status,
157					ExitSucceed::Returned,
158					"test '{}' returned {:?} (expected 'Returned')",
159					test.name,
160					result.exit_status
161				);
162				assert_eq!(
163					as_hex, test.expected,
164					"test '{}' failed (different output)",
165					test.name
166				);
167				if let Some(expected_gas) = test.gas {
168					assert_eq!(
169						handle.gas_used, expected_gas,
170						"test '{}' failed (different gas cost)",
171						test.name
172					);
173				}
174			}
175			Err(err) => {
176				return Err(format!("Test '{}' returned error: {:?}", test.name, err));
177			}
178		}
179	}
180
181	Ok(())
182}
183
184pub fn test_precompile_failure_test_vectors<P: Precompile>(filepath: &str) -> Result<(), String> {
185	let data = fs::read_to_string(filepath).unwrap_or_else(|_| panic!("Failed to read {filepath}"));
186
187	let tests: Vec<EthConsensusFailureTest> =
188		serde_json::from_str(&data).expect("expected json array");
189
190	for test in tests {
191		let input: Vec<u8> = hex::decode(test.input).expect("Could not hex-decode test input data");
192
193		let cost: u64 = 10000000;
194
195		let context: Context = Context {
196			address: Default::default(),
197			caller: Default::default(),
198			apparent_value: From::from(0),
199		};
200
201		let mut handle = MockHandle::new(input, Some(cost), context);
202
203		match P::execute(&mut handle) {
204			Ok(..) => {
205				unreachable!("Test should be failed");
206			}
207			Err(err) => {
208				let expected_err = PrecompileFailure::Error {
209					exit_status: ExitError::Other(test.expected_error.into()),
210				};
211				assert_eq!(
212					expected_err, err,
213					"Test '{}' failed (different error)",
214					test.name
215				);
216			}
217		}
218	}
219
220	Ok(())
221}