Skip to main content

sp_virtualization/
lib.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//! This crate is intended for use by runtime code (e.g pallet-contracts) to spawn PolkaVM instances
19//! and execute calls into them. Its purpose is to add one layer of abstraction to that it works
20//! transparently from the actual runtime (via the host functions defined in this crate) but also
21//! from tests (which run natively).
22//!
23//! Additionally, this crate is also used (by the executor) to implement the host functions that are
24//! defined in this crate. This allows us to encapsulate all the logic regarding PolkaVM setup in
25//! one place.
26//!
27//! Please keep in mind that the interface is kept simple because it has to match the interface
28//! of the host function so that the abstraction works. It will never expose the whole PolkaVM
29//! interface.
30//!
31//! # ⚠️ Unstable API — Do Not Use in Production ⚠️
32//!
33//! **This crate's API is unstable and subject to breaking changes without notice.**
34//!
35//! The virtualization host functions exposed by this crate have **not been stabilized** and are
36//! **not available on Polkadot** (or any other production relay/parachain) until they are. Using
37//! them in a production runtime **will cause your runtime to break** when the API changes.
38//!
39//! This crate should **only** be used for:
40//! - Local testing and development
41//! - Experimentation on test networks
42//!
43//! **Do not** ship runtimes that depend on this crate to any chain you care about. There is no
44//! stability guarantee and no deprecation period — the interface may change at any time.
45
46#![cfg_attr(not(feature = "std"), no_std)]
47
48extern crate alloc;
49
50#[cfg(not(feature = "std"))]
51mod forwarder;
52#[cfg(not(feature = "std"))]
53pub use forwarder::Virt;
54
55#[cfg(feature = "std")]
56mod manager;
57#[cfg(feature = "std")]
58mod native;
59#[cfg(feature = "std")]
60pub use manager::VirtManager;
61#[cfg(feature = "std")]
62pub use native::Virt;
63
64mod host_functions;
65mod tests;
66
67pub use crate::tests::run as run_tests;
68
69pub use crate::host_functions::virtualization as host_fn;
70
71use codec::{Decode, Encode};
72use core::mem;
73use num_enum::{IntoPrimitive, TryFromPrimitive};
74
75/// The concrete memory type used to access the memory of [`Virt`].
76pub type Memory = <Virt as VirtT>::Memory;
77
78/// The target we use for all logging.
79pub const LOG_TARGET: &str = "virtualization";
80
81// Re-export from sp_wasm_interface so that both the executor and the runtime code
82// use the same type.
83pub use sp_wasm_interface::{ExecAction, ExecOutcome};
84
85/// Buffer shared between runtime and executor for passing syscall data across the
86/// host function boundary.
87///
88/// The runtime allocates this on its stack and passes it via pointer.
89/// The host fills it in when returning from [`VirtT::run`].
90#[derive(Debug, Default)]
91#[repr(C)]
92pub struct ExecBuffer {
93	/// Gas remaining after the execution step.
94	pub gas_left: i64,
95	/// The syscall number (only meaningful when the status is [`ExecStatus::Syscall`]).
96	pub syscall_no: u32,
97	/// Padding to maintain alignment after syscall_no.
98	pub _pad: u32,
99	/// Syscall register arguments a0-a5 (only meaningful for [`ExecStatus::Syscall`]).
100	pub a0: u64,
101	pub a1: u64,
102	pub a2: u64,
103	pub a3: u64,
104	pub a4: u64,
105	pub a5: u64,
106}
107
108/// The size of [`ExecBuffer`] in bytes.
109pub const EXEC_BUFFER_SIZE: usize = mem::size_of::<ExecBuffer>();
110
111impl AsRef<[u8]> for ExecBuffer {
112	fn as_ref(&self) -> &[u8] {
113		// SAFETY: `ExecBuffer` is `#[repr(C)]` with a well-defined layout of primitive fields.
114		unsafe { core::slice::from_raw_parts(self as *const Self as *const u8, EXEC_BUFFER_SIZE) }
115	}
116}
117
118impl AsMut<[u8]> for ExecBuffer {
119	fn as_mut(&mut self) -> &mut [u8] {
120		// SAFETY: `ExecBuffer` is `#[repr(C)]` with a well-defined layout of primitive fields.
121		unsafe { core::slice::from_raw_parts_mut(self as *mut Self as *mut u8, EXEC_BUFFER_SIZE) }
122	}
123}
124
125impl ExecBuffer {
126	/// Populate this buffer from an [`ExecOutcome`].
127	pub fn from_outcome(outcome: &ExecOutcome) -> Self {
128		match *outcome {
129			ExecOutcome::Finished { gas_left } => Self { gas_left, ..Default::default() },
130			ExecOutcome::Syscall { gas_left, syscall_no, a0, a1, a2, a3, a4, a5 } => {
131				Self { gas_left, syscall_no, _pad: 0, a0, a1, a2, a3, a4, a5 }
132			},
133		}
134	}
135
136	/// Decode a status byte and this buffer into an [`ExecOutcome`].
137	pub fn into_outcome(self, status: ExecStatus) -> ExecOutcome {
138		match status {
139			ExecStatus::Finished => ExecOutcome::Finished { gas_left: self.gas_left },
140			ExecStatus::Syscall => ExecOutcome::Syscall {
141				gas_left: self.gas_left,
142				syscall_no: self.syscall_no,
143				a0: self.a0,
144				a1: self.a1,
145				a2: self.a2,
146				a3: self.a3,
147				a4: self.a4,
148				a5: self.a5,
149			},
150		}
151	}
152}
153
154/// Status returned by the `execute` / `resume` host functions.
155#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
156#[repr(u8)]
157pub enum ExecStatus {
158	/// Execution finished normally.
159	Finished = 0,
160	/// A syscall was encountered — check the [`ExecBuffer`] for details.
161	Syscall = 1,
162}
163
164impl ExecStatus {
165	/// Derive the status from an [`ExecOutcome`].
166	pub fn from_outcome(outcome: &ExecOutcome) -> Self {
167		match outcome {
168			ExecOutcome::Finished { .. } => Self::Finished,
169			ExecOutcome::Syscall { .. } => Self::Syscall,
170		}
171	}
172}
173
174/// A virtualization instance that can be called into multiple times.
175///
176/// There are only two implementations of this trait. One which is used within runtime builds.
177/// We call this the `forwarder` since it only forwards the calls to host functions. The other
178/// one is the `native` implementation which is used to implement said host functions and is also
179/// used by the pallet's test code.
180///
181/// A trait is not strictly necessary but makes sure that both implementations do not diverge.
182///
183/// # ⚠️ Unstable — Do Not Use in Production
184///
185/// This trait and its implementations are **unstable**. The virtualization host functions are
186/// **not available on Polkadot** until the API is stabilized. Using them in a production
187/// runtime will cause breakage when the API changes. Only use for testing and experimentation.
188pub trait VirtT: Sized {
189	/// The memory implementation of this instance.
190	type Memory: MemoryT;
191
192	/// Compile and instantiate the passed `program`.
193	///
194	/// The passed program has to be a valid PolkaVM program.
195	fn instantiate(program: &[u8]) -> Result<Self, InstantiateError>;
196
197	/// Execute or resume a virtualization instance.
198	///
199	/// When `action` is [`ExecAction::Execute`], starts executing the named exported
200	/// function. The function must not take any arguments nor return any results.
201	/// When `action` is [`ExecAction::Resume`], resumes after a syscall with the given
202	/// return value (written into register `a0`).
203	///
204	/// Returns [`ExecOutcome::Finished`] when execution completes or
205	/// [`ExecOutcome::Syscall`] when a host function is called. In the latter case,
206	/// the caller should handle the syscall and call this method again with
207	/// [`ExecAction::Resume`] to continue.
208	///
209	/// * `gas_left`: How much gas the execution is allowed to consume.
210	/// * `action`: Whether to start a new execution or resume an existing one.
211	fn run(&mut self, gas_left: i64, action: ExecAction<'_>) -> Result<ExecOutcome, ExecError>;
212
213	/// Get a reference to the instances memory.
214	///
215	/// Memory access will fail with an error when this instance was destroyed.
216	fn memory(&self) -> Self::Memory;
217}
218
219/// Allows to access the memory of a [`VirtT`].
220pub trait MemoryT {
221	/// Read the instances memory at `offset` into `dest`.
222	fn read(&mut self, offset: u32, dest: &mut [u8]) -> Result<(), MemoryError>;
223
224	/// Write `src` into the instances memory at `offset`.
225	fn write(&mut self, offset: u32, src: &[u8]) -> Result<(), MemoryError>;
226}
227
228/// Errors that can be emitted when instantiating a new virtualization instance.
229#[derive(Encode, Decode, TryFromPrimitive, IntoPrimitive, Debug, PartialEq, Eq)]
230#[repr(u8)]
231pub enum InstantiateError {
232	/// The supplied code was invalid.
233	InvalidImage = 1,
234}
235
236/// Errors that can be emitted when executing a new virtualization instance.
237#[derive(Encode, Decode, TryFromPrimitive, IntoPrimitive, Debug, PartialEq, Eq)]
238#[repr(u8)]
239pub enum ExecError {
240	/// The supplied `instance_id` was invalid or the instance was destroyed.
241	///
242	/// This error will also be returned if a recursive call into the same instance
243	/// is attempted.
244	InvalidInstance = 1,
245	/// The supplied code was invalid. Most likely caused by invalid entry points.
246	InvalidImage = 2,
247	/// The execution ran out of gas before it could finish.
248	OutOfGas = 3,
249	/// The execution trapped before it could finish.
250	///
251	/// This can be caused by executing an `unimp` instruction.
252	Trap = 4,
253}
254
255/// Errors that can be emitted when accessing a virtualization instance's memory.
256#[derive(Encode, Decode, TryFromPrimitive, IntoPrimitive, Debug, PartialEq, Eq)]
257#[repr(u8)]
258pub enum MemoryError {
259	/// The supplied `instance_id` was invalid or the instance was destroyed.
260	InvalidInstance = 1,
261	/// The memory region specified is not accessible.
262	OutOfBounds = 2,
263}
264
265/// Errors that can be emitted when destroying a virtualization instance.
266#[derive(Encode, Decode, TryFromPrimitive, IntoPrimitive, Debug, PartialEq, Eq)]
267#[repr(u8)]
268pub enum DestroyError {
269	/// The supplied `instance_id` was invalid or the instance was destroyed.
270	InvalidInstance = 1,
271}