casper_executor_wasm_interface/
lib.rs

1pub mod executor;
2
3use bytes::Bytes;
4use thiserror::Error;
5
6use casper_executor_wasm_common::{
7    error::{CallError, TrapCode, CALLEE_SUCCEEDED},
8    flags::ReturnFlags,
9};
10
11/// Interface version for the Wasm host functions.
12///
13/// This defines behavior of the Wasm execution environment i.e. the host behavior, serialiation,
14/// etc.
15///
16/// Only the highest `interface_version_X` is taken from the imports table which means Wasm has to
17/// support X-1, X-2 versions as well.
18#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
19pub struct InterfaceVersion(u32);
20
21impl From<u32> for InterfaceVersion {
22    fn from(value: u32) -> Self {
23        InterfaceVersion(value)
24    }
25}
26
27pub type HostResult = Result<(), CallError>;
28
29/// Converts a host result into a u32.
30#[must_use]
31pub fn u32_from_host_result(result: HostResult) -> u32 {
32    match result {
33        Ok(()) => CALLEE_SUCCEEDED,
34        Err(host_error) => host_error.into_u32(),
35    }
36}
37
38/// Errors that can occur when resolving imports.
39#[derive(Debug, Error)]
40pub enum Resolver {
41    #[error("export {name} not found.")]
42    Export { name: String },
43    /// Trying to call a function pointer by index.
44    #[error("function pointer {index} not found.")]
45    Table { index: u32 },
46}
47
48#[derive(Error, Debug)]
49pub enum ExportError {
50    /// An error than occurs when the exported type and the expected type
51    /// are incompatible.
52    #[error("incompatible type")]
53    IncompatibleType,
54    /// This error arises when an export is missing
55    #[error("missing export {0}")]
56    Missing(String),
57}
58
59#[derive(Error, Debug)]
60#[non_exhaustive]
61pub enum MemoryError {
62    /// Memory access is outside heap bounds.
63    #[error("memory access out of bounds")]
64    HeapOutOfBounds,
65    /// Address calculation overflow.
66    #[error("address calculation overflow")]
67    Overflow,
68    /// String is not valid UTF-8.
69    #[error("string is not valid utf-8")]
70    NonUtf8String,
71}
72
73/// The outcome of a call.
74/// We can fold all errors into this type and return it from the host functions and remove Outcome
75/// type.
76#[derive(Debug, Error)]
77pub enum VMError {
78    #[error("Return 0x{flags:?} {data:?}")]
79    Return {
80        flags: ReturnFlags,
81        data: Option<Bytes>,
82    },
83    #[error("export: {0}")]
84    Export(ExportError),
85    #[error("Out of gas")]
86    OutOfGas,
87    /// Error while executing Wasm: traps, memory access errors, etc.
88    ///
89    /// NOTE: for supporting multiple different backends we may want to abstract this a bit and
90    /// extract memory access errors, trap codes, and unify error reporting.
91    #[error("Trap: {0}")]
92    Trap(TrapCode),
93}
94
95impl VMError {
96    /// Returns the output data if the error is a `Return` error.
97    pub fn into_output_data(self) -> Option<Bytes> {
98        match self {
99            VMError::Return { data, .. } => data,
100            _ => None,
101        }
102    }
103}
104
105/// Result of a VM operation.
106pub type VMResult<T> = Result<T, VMError>;
107
108/// Configuration for the Wasm engine.
109#[derive(Clone, Debug)]
110pub struct Config {
111    gas_limit: u64,
112    memory_limit: u32,
113}
114
115impl Config {
116    #[must_use]
117    pub fn gas_limit(&self) -> u64 {
118        self.gas_limit
119    }
120
121    #[must_use]
122    pub fn memory_limit(&self) -> u32 {
123        self.memory_limit
124    }
125}
126
127/// Configuration for the Wasm engine.
128#[derive(Clone, Debug, Default)]
129pub struct ConfigBuilder {
130    gas_limit: Option<u64>,
131    /// Memory limit in pages.
132    memory_limit: Option<u32>,
133}
134
135impl ConfigBuilder {
136    /// Create a new configuration builder.
137    #[must_use]
138    pub fn new() -> Self {
139        Self::default()
140    }
141
142    /// Gas limit in units.
143    #[must_use]
144    pub fn with_gas_limit(mut self, gas_limit: u64) -> Self {
145        self.gas_limit = Some(gas_limit);
146        self
147    }
148
149    /// Memory limit denominated in pages.
150    #[must_use]
151    pub fn with_memory_limit(mut self, memory_limit: u32) -> Self {
152        self.memory_limit = Some(memory_limit);
153        self
154    }
155
156    /// Build the configuration.
157    #[must_use]
158    pub fn build(self) -> Config {
159        let gas_limit = self.gas_limit.expect("Required field missing: gas_limit");
160        let memory_limit = self
161            .memory_limit
162            .expect("Required field missing: memory_limit");
163        Config {
164            gas_limit,
165            memory_limit,
166        }
167    }
168}
169
170#[derive(Debug, Copy, Clone, PartialEq, Eq)]
171pub enum MeteringPoints {
172    Remaining(u64),
173    Exhausted,
174}
175
176impl MeteringPoints {
177    pub fn try_into_remaining(self) -> Result<u64, Self> {
178        if let Self::Remaining(v) = self {
179            Ok(v)
180        } else {
181            Err(self)
182        }
183    }
184}
185
186/// An abstraction over the 'caller' object of a host function that works for any Wasm VM.
187///
188/// This allows access for important instances such as the context object that was passed to the
189/// instance, wasm linear memory access, etc.
190pub trait Caller {
191    type Context;
192
193    fn context(&self) -> &Self::Context;
194    fn context_mut(&mut self) -> &mut Self::Context;
195    /// Returns currently running *unmodified* bytecode.
196    fn bytecode(&self) -> Bytes;
197
198    /// Check if an export is present in the module.
199    fn has_export(&self, name: &str) -> bool;
200
201    fn memory_read(&self, offset: u32, size: usize) -> VMResult<Vec<u8>> {
202        let mut vec = vec![0; size];
203        self.memory_read_into(offset, &mut vec)?;
204        Ok(vec)
205    }
206    fn memory_read_into(&self, offset: u32, output: &mut [u8]) -> VMResult<()>;
207    fn memory_write(&self, offset: u32, data: &[u8]) -> VMResult<()>;
208    /// Allocates memory inside the Wasm VM by calling an export.
209    ///
210    /// Error is a type-erased error coming from the VM itself.
211    fn alloc(&mut self, idx: u32, size: usize, ctx: u32) -> VMResult<u32>;
212    /// Returns the amount of gas used.
213    fn gas_consumed(&mut self) -> MeteringPoints;
214    /// Set the amount of gas used.
215    fn consume_gas(&mut self, value: u64) -> VMResult<()>;
216}
217
218#[derive(Debug, Error)]
219pub enum WasmPreparationError {
220    #[error("Missing export {0}")]
221    MissingExport(String),
222    #[error("Compile error: {0}")]
223    Compile(String),
224    #[error("Memory instantiation error: {0}")]
225    Memory(String),
226    #[error("Instantiation error: {0}")]
227    Instantiation(String),
228}
229
230#[derive(Debug)]
231pub struct GasUsage {
232    /// The amount of gas used by the execution.
233    gas_limit: u64,
234    /// The amount of gas remaining after the execution.
235    remaining_points: u64,
236}
237
238impl GasUsage {
239    #[must_use]
240    pub fn new(gas_limit: u64, remaining_points: u64) -> Self {
241        GasUsage {
242            gas_limit,
243            remaining_points,
244        }
245    }
246
247    #[must_use]
248    pub fn gas_spent(&self) -> u64 {
249        debug_assert!(self.remaining_points <= self.gas_limit);
250        self.gas_limit - self.remaining_points
251    }
252
253    #[must_use]
254    pub fn gas_limit(&self) -> u64 {
255        self.gas_limit
256    }
257
258    #[must_use]
259    pub fn remaining_points(&self) -> u64 {
260        self.remaining_points
261    }
262}
263
264/// A trait that represents a Wasm instance.
265pub trait WasmInstance {
266    type Context;
267
268    fn call_export(&mut self, name: &str) -> (Result<(), VMError>, GasUsage);
269    fn teardown(self) -> Self::Context;
270}