referrerpolicy=no-referrer-when-downgrade

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
18//! Shared test driver for the virtualization forwarder API.
19//!
20//! The cases exercised here only use the public [`Module`] / [`Instance`] / [`Execution`]
21//! surface, so they can be invoked from two distinct contexts:
22//!
23//! - **Runtime-side**, compiled into `sc-runtime-test` and dispatched through the wasm executor —
24//!   this exercises the full host-function FFI round-trip.
25//! - **Host-side**, invoked as a regular `#[test]` from `sc-virtualization` — this exercises the
26//!   native dispatch path with no wasm involved.
27//!
28//! Entry point: [`run`]. Tests that need pre-populated externalities (e.g. the storage
29//! fallback path of `Module::from_storage_key`) live as standalone host-only `#[test]`s
30//! in `sc-virtualization` rather than here.
31
32use crate::{ExecError, ExecResult, Execution, Instance, Module, ModuleError};
33
34/// Default gas budget used by every test driver.
35pub const GAS_MAX: i64 = i64::MAX;
36
37/// Run every test that uses only the public forwarder API.
38///
39/// Exported as a regular function so it can be invoked from both:
40/// - the runtime (compiled into `sc-runtime-test`) — exercises the wasm/forwarder path end-to-end,
41///   including the host-function FFI;
42/// - native host tests (in `sc-virtualization`) — exercises the host-side dispatch directly.
43///
44/// Tests that need pre-populated externalities (storage fallback for `compile_from_storage_key`)
45/// can't run from a runtime context and live as standalone `#[test]`s in `sc-virtualization`.
46///
47/// The `program` needs to be set to `sp_virtualization_test_fixture::binary()`. It can't be
48/// hard coded because when this crate is compiled into a runtime the binary is not available.
49/// Instead, we pass it as an argument to the runtime exported function.
50pub fn run(program: &[u8]) {
51	counter_start_at_0(program);
52	counter_start_at_7(program);
53	counter_multiple_calls(program);
54	panic_works(program);
55	exit_works(program);
56	run_out_of_gas_works(program);
57	gas_consumption_works(program);
58	memory_reset_on_instantiate(program);
59	memory_persistent(program);
60	counter_in_subcall(program);
61	from_storage_key_not_found(program);
62}
63
64/// The result of running a program to completion.
65pub enum RunResult {
66	/// Execution finished normally. The idle instance is returned for reuse.
67	Ok(Instance),
68	/// A syscall handler signalled exit.
69	Exit,
70	/// Execution returned an error.
71	Err(ExecError),
72}
73
74/// Drives the prepare/run loop calling `handler` for each syscall.
75///
76/// The closure receives `(execution, syscall_symbol, a0, a1, a2, a3, a4, a5)` and returns
77/// `Ok(return_value)` to resume or `Err(())` to signal exit (trap).
78pub fn run_loop(
79	mut execution: Execution,
80	gas_left: &mut i64,
81	mut handler: impl FnMut(&mut Execution, &[u8], u64, u64, u64, u64, u64, u64) -> Result<u64, ()>,
82) -> RunResult {
83	let mut a0 = 0u64;
84	loop {
85		match execution.run(*gas_left, a0) {
86			ExecResult::Finished { instance, gas_left: g } => {
87				*gas_left = g;
88				return RunResult::Ok(instance);
89			},
90			ExecResult::Syscall {
91				execution: e,
92				gas_left: g,
93				syscall_symbol,
94				a0: sa0,
95				a1,
96				a2,
97				a3,
98				a4,
99				a5,
100			} => {
101				execution = e;
102				*gas_left = g;
103				match handler(&mut execution, syscall_symbol.as_ref(), sa0, a1, a2, a3, a4, a5) {
104					Ok(result) => a0 = result,
105					Err(()) => return RunResult::Exit,
106				}
107			},
108			ExecResult::Error { instance: _, error: ExecError::OutOfGas } => {
109				*gas_left = 0;
110				return RunResult::Err(ExecError::OutOfGas);
111			},
112			ExecResult::Error { instance: _, error } => return RunResult::Err(error),
113		}
114	}
115}
116
117/// The standard syscall handler for the test fixture.
118///
119/// Captures `counter` from the caller; memory access goes through the `&mut Execution` passed
120/// on each invocation.
121pub fn make_handler<'a>(
122	counter: &'a mut u64,
123) -> impl FnMut(&mut Execution, &[u8], u64, u64, u64, u64, u64, u64) -> Result<u64, ()> + 'a {
124	move |execution, syscall_symbol, a0, _a1, _a2, _a3, _a4, _a5| match syscall_symbol {
125		b"read_counter" => {
126			let buf = counter.to_le_bytes();
127			execution.write_memory(a0 as u32, buf.as_ref()).unwrap();
128			Ok(1)
129		},
130		b"increment_counter" => {
131			let mut buf = [0u8; 8];
132			execution.read_memory(a0 as u32, buf.as_mut()).unwrap();
133			*counter += u64::from_le_bytes(buf);
134			Ok(2u64 << 56)
135		},
136		b"exit" => Err(()),
137		_ => panic!("unknown syscall: {:?}", syscall_symbol),
138	}
139}
140
141/// Checks memory access and user state functionality.
142fn counter_start_at_0(program: &[u8]) {
143	let instance = Module::from_bytes(program, None).unwrap().0.instantiate().unwrap();
144	let execution = instance.prepare(b"counter").unwrap();
145	let mut gas_left = GAS_MAX;
146	let mut counter: u64 = 0;
147	let result = run_loop(execution, &mut gas_left, make_handler(&mut counter));
148	assert!(matches!(result, RunResult::Ok(_)));
149	assert_eq!(counter, 8);
150}
151
152/// Checks memory access and user state functionality.
153fn counter_start_at_7(program: &[u8]) {
154	let instance = Module::from_bytes(program, None).unwrap().0.instantiate().unwrap();
155	let execution = instance.prepare(b"counter").unwrap();
156	let mut gas_left = GAS_MAX;
157	let mut counter: u64 = 7;
158	let result = run_loop(execution, &mut gas_left, make_handler(&mut counter));
159	assert!(matches!(result, RunResult::Ok(_)));
160	assert_eq!(counter, 15);
161}
162
163/// Makes sure user state is persistent between calls into the same instance.
164fn counter_multiple_calls(program: &[u8]) {
165	let instance = Module::from_bytes(program, None).unwrap().0.instantiate().unwrap();
166	let execution = instance.prepare(b"counter").unwrap();
167	let mut gas_left = GAS_MAX;
168	let mut counter: u64 = 7;
169
170	let instance = match run_loop(execution, &mut gas_left, make_handler(&mut counter)) {
171		RunResult::Ok(instance) => instance,
172		_ => panic!("expected Ok"),
173	};
174	assert_eq!(counter, 15);
175
176	let execution = instance.prepare(b"counter").unwrap();
177	let result = run_loop(execution, &mut gas_left, make_handler(&mut counter));
178	assert!(matches!(result, RunResult::Ok(_)));
179	assert_eq!(counter, 23);
180}
181
182/// Check the correct status is returned when hitting an `unimp` instruction.
183fn panic_works(program: &[u8]) {
184	let instance = Module::from_bytes(program, None).unwrap().0.instantiate().unwrap();
185	let execution = instance.prepare(b"do_panic").unwrap();
186	let mut gas_left = GAS_MAX;
187	let mut counter: u64 = 0;
188	let result = run_loop(execution, &mut gas_left, make_handler(&mut counter));
189	assert!(matches!(result, RunResult::Err(ExecError::Trap)));
190	assert_eq!(counter, 0);
191}
192
193/// Check that setting exit in a host function aborts the execution.
194fn exit_works(program: &[u8]) {
195	let instance = Module::from_bytes(program, None).unwrap().0.instantiate().unwrap();
196	let execution = instance.prepare(b"do_exit").unwrap();
197	let mut gas_left = GAS_MAX;
198	let mut counter: u64 = 0;
199	let result = run_loop(execution, &mut gas_left, make_handler(&mut counter));
200	assert!(matches!(result, RunResult::Exit));
201	assert_eq!(counter, 0);
202}
203
204/// Increment the counter in an endless loop until we run out of gas.
205fn run_out_of_gas_works(program: &[u8]) {
206	let instance = Module::from_bytes(program, None).unwrap().0.instantiate().unwrap();
207	let execution = instance.prepare(b"increment_forever").unwrap();
208	let mut gas_left: i64 = 100_000;
209	let mut counter: u64 = 0;
210	let result = run_loop(execution, &mut gas_left, make_handler(&mut counter));
211	assert!(matches!(result, RunResult::Err(ExecError::OutOfGas)));
212	assert_eq!(counter, 793);
213	assert_eq!(gas_left, 0);
214}
215
216/// Call same function with different gas limits and make sure they consume the same amount of gas.
217fn gas_consumption_works(program: &[u8]) {
218	let gas_limit_0 = GAS_MAX;
219	let gas_limit_1 = gas_limit_0 / 2;
220
221	let instance = Module::from_bytes(program, None).unwrap().0.instantiate().unwrap();
222	let execution = instance.prepare(b"counter").unwrap();
223	let mut gas_left = gas_limit_0;
224	let mut counter: u64 = 0;
225	let result = run_loop(execution, &mut gas_left, make_handler(&mut counter));
226	assert!(matches!(result, RunResult::Ok(_)));
227	let gas_consumed = gas_limit_0 - gas_left;
228
229	let instance = Module::from_bytes(program, None).unwrap().0.instantiate().unwrap();
230	let execution = instance.prepare(b"counter").unwrap();
231	let mut gas_left = gas_limit_1;
232	let mut counter: u64 = 0;
233	let result = run_loop(execution, &mut gas_left, make_handler(&mut counter));
234	assert!(matches!(result, RunResult::Ok(_)));
235	assert_eq!(gas_consumed, gas_limit_1 - gas_left);
236}
237
238/// Make sure that globals are reset for a new instance.
239fn memory_reset_on_instantiate(program: &[u8]) {
240	let instance = Module::from_bytes(program, None).unwrap().0.instantiate().unwrap();
241	let execution = instance.prepare(b"offset").unwrap();
242	let mut gas_left = GAS_MAX;
243	let mut counter: u64 = 0;
244	let result = run_loop(execution, &mut gas_left, make_handler(&mut counter));
245	assert!(matches!(result, RunResult::Ok(_)));
246	assert_eq!(counter, 3);
247
248	let instance = Module::from_bytes(program, None).unwrap().0.instantiate().unwrap();
249	let execution = instance.prepare(b"offset").unwrap();
250	let result = run_loop(execution, &mut gas_left, make_handler(&mut counter));
251	assert!(matches!(result, RunResult::Ok(_)));
252	assert_eq!(counter, 6);
253}
254
255/// Make sure globals are not reset between multiple calls into the same instance.
256fn memory_persistent(program: &[u8]) {
257	let instance = Module::from_bytes(program, None).unwrap().0.instantiate().unwrap();
258	let execution = instance.prepare(b"offset").unwrap();
259	let mut gas_left = GAS_MAX;
260	let mut counter: u64 = 0;
261
262	let instance = match run_loop(execution, &mut gas_left, make_handler(&mut counter)) {
263		RunResult::Ok(instance) => instance,
264		_ => panic!("expected Ok"),
265	};
266	assert_eq!(counter, 3);
267
268	let execution = instance.prepare(b"offset").unwrap();
269	let result = run_loop(execution, &mut gas_left, make_handler(&mut counter));
270	assert!(matches!(result, RunResult::Ok(_)));
271	assert_eq!(counter, 7);
272}
273
274/// Calls a function that spawns another instance where it calls the `counter` entry point.
275fn counter_in_subcall(program: &[u8]) {
276	let instance = Module::from_bytes(program, None).unwrap().0.instantiate().unwrap();
277	let execution = instance.prepare(b"do_subcall").unwrap();
278	let mut gas_left = GAS_MAX;
279	let mut counter: u64 = 0;
280	let program = program.to_vec();
281	let result =
282		run_loop(execution, &mut gas_left, |execution, syscall_symbol, a0, a1, a2, a3, a4, a5| {
283			match syscall_symbol {
284				b"read_counter" | b"increment_counter" | b"exit" => {
285					make_handler(&mut counter)(execution, syscall_symbol, a0, a1, a2, a3, a4, a5)
286				},
287				// subcall: spawn a new instance and run counter in it
288				b"subcall" => {
289					let sub_instance = Module::from_bytes(program.as_ref(), None)
290						.unwrap()
291						.0
292						.instantiate()
293						.unwrap();
294					let sub_execution = sub_instance.prepare(b"counter").unwrap();
295					let mut sub_gas = GAS_MAX;
296					let mut sub_counter: u64 = 0;
297					let result =
298						run_loop(sub_execution, &mut sub_gas, make_handler(&mut sub_counter));
299					assert!(matches!(result, RunResult::Ok(_)));
300					assert_eq!(sub_counter, 8);
301					Ok(0)
302				},
303				_ => panic!("unknown syscall: {:?}", syscall_symbol),
304			}
305		});
306	assert!(matches!(result, RunResult::Ok(_)));
307	// sub call should not affect parent state
308	assert_eq!(counter, 0);
309}
310
311/// Storage key not in cache and no code in storage returns NotFound.
312fn from_storage_key_not_found(_program: &[u8]) {
313	let storage_key = b"::missing::";
314	assert!(matches!(Module::from_storage_key(storage_key, b""), Err(ModuleError::NotFound)));
315}