casper_executor_wasm_interface/
lib.rs

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