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}