Skip to main content

sp_virtualization/
tests.rs

1// This file is part of Substrate.
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
18use crate::{ExecAction, ExecError, ExecOutcome, MemoryT, Virt, VirtT};
19
20const GAS_MAX: i64 = i64::MAX;
21
22/// Run all tests.
23///
24/// This is exported even without a test build in order to make it callable from the
25/// `sc-runtime-test`. This is necessary in order to compile these tests into a runtime so that
26/// the forwarder implementation is used. Otherwise only the native implementation is tested through
27/// cargos test framework.
28///
29/// The `program` needs to be set to `sp_virtualization_test_fixture::binary()`. It can't be
30/// hard coded because when this crate is compiled into a runtime the binary is not available.
31/// Instead, we pass it as an argument to the runtime exported function.
32pub fn run(program: &[u8]) {
33	counter_start_at_0(program);
34	counter_start_at_7(program);
35	counter_multiple_calls(program);
36	panic_works(program);
37	exit_works(program);
38	run_out_of_gas_works(program);
39	gas_consumption_works(program);
40	memory_reset_on_instantiate(program);
41	memory_persistent(program);
42	counter_in_subcall(program);
43}
44
45/// The result of running a program to completion.
46enum RunResult {
47	/// Execution finished normally.
48	Ok,
49	/// A syscall handler signalled exit.
50	Exit,
51	/// Execution returned an error.
52	Err(ExecError),
53}
54
55/// Drives the execute/resume loop calling `handler` for each syscall.
56///
57/// The closure receives `(syscall_no, a0, a1, a2, a3, a4, a5)` and returns
58/// `Ok(return_value)` to resume or `Err(())` to signal exit (trap).
59fn run_loop(
60	virt: &mut Virt,
61	function: &str,
62	gas_left: &mut i64,
63	mut handler: impl FnMut(u32, u64, u64, u64, u64, u64, u64) -> Result<u64, ()>,
64) -> RunResult {
65	let mut action = ExecAction::Execute(function);
66	loop {
67		let outcome = match virt.run(*gas_left, action) {
68			Ok(outcome) => outcome,
69			Err(ExecError::OutOfGas) => {
70				*gas_left = 0;
71				return RunResult::Err(ExecError::OutOfGas);
72			},
73			Err(err) => return RunResult::Err(err),
74		};
75		match outcome {
76			ExecOutcome::Finished { gas_left: g } => {
77				*gas_left = g;
78				return RunResult::Ok;
79			},
80			ExecOutcome::Syscall { gas_left: g, syscall_no, a0, a1, a2, a3, a4, a5 } => {
81				*gas_left = g;
82				match handler(syscall_no, a0, a1, a2, a3, a4, a5) {
83					Ok(result) => action = ExecAction::Resume(result),
84					Err(()) => return RunResult::Exit,
85				}
86			},
87		}
88	}
89}
90
91/// The standard syscall handler for the test fixture.
92///
93/// Captures `counter` and `memory` from the caller.
94fn make_handler<'a>(
95	counter: &'a mut u64,
96	memory: &'a mut <Virt as VirtT>::Memory,
97) -> impl FnMut(u32, u64, u64, u64, u64, u64, u64) -> Result<u64, ()> + 'a {
98	move |syscall_no, a0, _a1, _a2, _a3, _a4, _a5| match syscall_no {
99		// read_counter
100		1 => {
101			let buf = counter.to_le_bytes();
102			memory.write(a0 as u32, buf.as_ref()).unwrap();
103			Ok(syscall_no.into())
104		},
105		// increment counter
106		2 => {
107			let mut buf = [0u8; 8];
108			memory.read(a0 as u32, buf.as_mut()).unwrap();
109			*counter += u64::from_le_bytes(buf);
110			Ok(u64::from(syscall_no) << 56)
111		},
112		// exit
113		3 => Err(()),
114		_ => panic!("unknown syscall: {:?}", syscall_no),
115	}
116}
117
118/// Checks memory access and user state functionality.
119fn counter_start_at_0(program: &[u8]) {
120	let mut instance = Virt::instantiate(program).unwrap();
121	let mut gas_left = GAS_MAX;
122	let mut counter: u64 = 0;
123	let mut memory = instance.memory();
124	let result =
125		run_loop(&mut instance, "counter", &mut gas_left, make_handler(&mut counter, &mut memory));
126	assert!(matches!(result, RunResult::Ok));
127	assert_eq!(counter, 8);
128}
129
130/// Checks memory access and user state functionality.
131fn counter_start_at_7(program: &[u8]) {
132	let mut instance = Virt::instantiate(program).unwrap();
133	let mut gas_left = GAS_MAX;
134	let mut counter: u64 = 7;
135	let mut memory = instance.memory();
136	let result =
137		run_loop(&mut instance, "counter", &mut gas_left, make_handler(&mut counter, &mut memory));
138	assert!(matches!(result, RunResult::Ok));
139	assert_eq!(counter, 15);
140}
141
142/// Makes sure user state is persistent between calls into the same instance.
143fn counter_multiple_calls(program: &[u8]) {
144	let mut instance = Virt::instantiate(program).unwrap();
145	let mut gas_left = GAS_MAX;
146	let mut counter: u64 = 7;
147	let mut memory = instance.memory();
148
149	let result =
150		run_loop(&mut instance, "counter", &mut gas_left, make_handler(&mut counter, &mut memory));
151	assert!(matches!(result, RunResult::Ok));
152	assert_eq!(counter, 15);
153
154	let result =
155		run_loop(&mut instance, "counter", &mut gas_left, make_handler(&mut counter, &mut memory));
156	assert!(matches!(result, RunResult::Ok));
157	assert_eq!(counter, 23);
158}
159
160/// Check the correct status is returned when hitting an `unimp` instruction.
161fn panic_works(program: &[u8]) {
162	let mut instance = Virt::instantiate(program).unwrap();
163	let mut gas_left = GAS_MAX;
164	let mut counter: u64 = 0;
165	let mut memory = instance.memory();
166	let result =
167		run_loop(&mut instance, "do_panic", &mut gas_left, make_handler(&mut counter, &mut memory));
168	assert!(matches!(result, RunResult::Err(ExecError::Trap)));
169	assert_eq!(counter, 0);
170}
171
172/// Check that setting exit in a host function aborts the execution.
173fn exit_works(program: &[u8]) {
174	let mut instance = Virt::instantiate(program).unwrap();
175	let mut gas_left = GAS_MAX;
176	let mut counter: u64 = 0;
177	let mut memory = instance.memory();
178	let result =
179		run_loop(&mut instance, "do_exit", &mut gas_left, make_handler(&mut counter, &mut memory));
180	assert!(matches!(result, RunResult::Exit));
181	assert_eq!(counter, 0);
182}
183
184/// Increment the counter in an endless loop until we run out of gas.
185fn run_out_of_gas_works(program: &[u8]) {
186	let mut instance = Virt::instantiate(program).unwrap();
187	let mut gas_left: i64 = 100_000;
188	let mut counter: u64 = 0;
189	let mut memory = instance.memory();
190	let result = run_loop(
191		&mut instance,
192		"increment_forever",
193		&mut gas_left,
194		make_handler(&mut counter, &mut memory),
195	);
196	assert!(matches!(result, RunResult::Err(ExecError::OutOfGas)));
197	assert_eq!(counter, 14_285);
198	assert_eq!(gas_left, 0);
199}
200
201/// Call same function with different gas limits and make sure they consume the same amount of gas.
202fn gas_consumption_works(program: &[u8]) {
203	let gas_limit_0 = GAS_MAX;
204	let gas_limit_1 = gas_limit_0 / 2;
205
206	let mut instance = Virt::instantiate(program).unwrap();
207	let mut gas_left = gas_limit_0;
208	let mut counter: u64 = 0;
209	let mut memory = instance.memory();
210	let result =
211		run_loop(&mut instance, "counter", &mut gas_left, make_handler(&mut counter, &mut memory));
212	assert!(matches!(result, RunResult::Ok));
213	let gas_consumed = gas_limit_0 - gas_left;
214
215	let mut instance = Virt::instantiate(program).unwrap();
216	let mut gas_left = gas_limit_1;
217	let mut counter: u64 = 0;
218	let mut memory = instance.memory();
219	let result =
220		run_loop(&mut instance, "counter", &mut gas_left, make_handler(&mut counter, &mut memory));
221	assert!(matches!(result, RunResult::Ok));
222	assert_eq!(gas_consumed, gas_limit_1 - gas_left);
223}
224
225/// Make sure that globals are reset for a new instance.
226fn memory_reset_on_instantiate(program: &[u8]) {
227	let mut instance = Virt::instantiate(program).unwrap();
228	let mut gas_left = GAS_MAX;
229	let mut counter: u64 = 0;
230	let mut memory = instance.memory();
231	let result =
232		run_loop(&mut instance, "offset", &mut gas_left, make_handler(&mut counter, &mut memory));
233	assert!(matches!(result, RunResult::Ok));
234	assert_eq!(counter, 3);
235
236	let mut instance = Virt::instantiate(program).unwrap();
237	let mut memory = instance.memory();
238	let result =
239		run_loop(&mut instance, "offset", &mut gas_left, make_handler(&mut counter, &mut memory));
240	assert!(matches!(result, RunResult::Ok));
241	assert_eq!(counter, 6);
242}
243
244/// Make sure globals are not reset between multiple calls into the same instance.
245fn memory_persistent(program: &[u8]) {
246	let mut instance = Virt::instantiate(program).unwrap();
247	let mut gas_left = GAS_MAX;
248	let mut counter: u64 = 0;
249	let mut memory = instance.memory();
250
251	let result =
252		run_loop(&mut instance, "offset", &mut gas_left, make_handler(&mut counter, &mut memory));
253	assert!(matches!(result, RunResult::Ok));
254	assert_eq!(counter, 3);
255
256	let result =
257		run_loop(&mut instance, "offset", &mut gas_left, make_handler(&mut counter, &mut memory));
258	assert!(matches!(result, RunResult::Ok));
259	assert_eq!(counter, 7);
260}
261
262/// Calls a function that spawns another instance where it calls the `counter` entry point.
263fn counter_in_subcall(program: &[u8]) {
264	let mut instance = Virt::instantiate(program).unwrap();
265	let mut gas_left = GAS_MAX;
266	let mut counter: u64 = 0;
267	let mut memory = instance.memory();
268	let program = program.to_vec();
269	let result = run_loop(
270		&mut instance,
271		"do_subcall",
272		&mut gas_left,
273		|syscall_no, a0, a1, a2, a3, a4, a5| {
274			match syscall_no {
275				1..=3 => {
276					make_handler(&mut counter, &mut memory)(syscall_no, a0, a1, a2, a3, a4, a5)
277				},
278				// subcall: spawn a new instance and run counter in it
279				4 => {
280					let mut sub_instance = Virt::instantiate(program.as_ref()).unwrap();
281					let mut sub_gas = GAS_MAX;
282					let mut sub_counter: u64 = 0;
283					let mut sub_memory = sub_instance.memory();
284					let result = run_loop(
285						&mut sub_instance,
286						"counter",
287						&mut sub_gas,
288						make_handler(&mut sub_counter, &mut sub_memory),
289					);
290					assert!(matches!(result, RunResult::Ok));
291					assert_eq!(sub_counter, 8);
292					Ok(0)
293				},
294				_ => panic!("unknown syscall: {:?}", syscall_no),
295			}
296		},
297	);
298	assert!(matches!(result, RunResult::Ok));
299	// sub call should not affect parent state
300	assert_eq!(counter, 0);
301}
302
303#[cfg(test)]
304#[test]
305fn tests() {
306	sp_tracing::try_init_simple();
307	run(sp_virtualization_test_fixture::binary());
308}