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#[derive(Error, Debug)]
74/// Represents a catastrophic internal host error.
75pub enum InternalHostError {
76    #[error("type conversion failure")]
77    TypeConversion,
78    #[error("contract already exists")]
79    ContractAlreadyExists,
80    #[error("tracking copy error")]
81    TrackingCopy,
82    #[error("failed building execution request")]
83    ExecuteRequestBuildFailure,
84    #[error("unexpected entity kind")]
85    UnexpectedEntityKind,
86    #[error("failed reading total balance")]
87    TotalBalanceReadFailure,
88    #[error("total balance exceeded u64::MAX")]
89    TotalBalanceOverflow,
90    #[error("remaining gas exceeded the gas limit")]
91    RemainingGasExceedsGasLimit,
92    #[error("account not found under key")]
93    AccountRecordNotFound,
94    #[error("message did not have a checksum")]
95    MessageChecksumMissing,
96}
97
98/// The outcome of a call.
99/// We can fold all errors into this type and return it from the host functions and remove Outcome
100/// type.
101#[derive(Debug, Error)]
102pub enum VMError {
103    #[error("Return 0x{flags:?} {data:?}")]
104    Return {
105        flags: ReturnFlags,
106        data: Option<Bytes>,
107    },
108    #[error("export: {0}")]
109    Export(ExportError),
110    #[error("Out of gas")]
111    OutOfGas,
112    #[error("Internal host error")]
113    Internal(InternalHostError),
114    /// Error while executing Wasm: traps, memory access errors, etc.
115    ///
116    /// NOTE: for supporting multiple different backends we may want to abstract this a bit and
117    /// extract memory access errors, trap codes, and unify error reporting.
118    #[error("Trap: {0}")]
119    Trap(TrapCode),
120}
121
122impl VMError {
123    /// Returns the output data if the error is a `Return` error.
124    pub fn into_output_data(self) -> Option<Bytes> {
125        match self {
126            VMError::Return { data, .. } => data,
127            _ => None,
128        }
129    }
130}
131
132/// Result of a VM operation.
133pub type VMResult<T> = Result<T, VMError>;
134
135impl From<InternalHostError> for VMError {
136    fn from(value: InternalHostError) -> Self {
137        Self::Internal(value)
138    }
139}
140
141/// Configuration for the Wasm engine.
142#[derive(Clone, Debug)]
143pub struct Config {
144    gas_limit: u64,
145    memory_limit: u32,
146}
147
148impl Config {
149    #[must_use]
150    pub fn gas_limit(&self) -> u64 {
151        self.gas_limit
152    }
153
154    #[must_use]
155    pub fn memory_limit(&self) -> u32 {
156        self.memory_limit
157    }
158}
159
160/// Configuration for the Wasm engine.
161#[derive(Clone, Debug, Default)]
162pub struct ConfigBuilder {
163    gas_limit: Option<u64>,
164    /// Memory limit in pages.
165    memory_limit: Option<u32>,
166}
167
168impl ConfigBuilder {
169    /// Create a new configuration builder.
170    #[must_use]
171    pub fn new() -> Self {
172        Self::default()
173    }
174
175    /// Gas limit in units.
176    #[must_use]
177    pub fn with_gas_limit(mut self, gas_limit: u64) -> Self {
178        self.gas_limit = Some(gas_limit);
179        self
180    }
181
182    /// Memory limit denominated in pages.
183    #[must_use]
184    pub fn with_memory_limit(mut self, memory_limit: u32) -> Self {
185        self.memory_limit = Some(memory_limit);
186        self
187    }
188
189    /// Build the configuration.
190    #[must_use]
191    pub fn build(self) -> Config {
192        let gas_limit = self.gas_limit.expect("Required field missing: gas_limit");
193        let memory_limit = self
194            .memory_limit
195            .expect("Required field missing: memory_limit");
196        Config {
197            gas_limit,
198            memory_limit,
199        }
200    }
201}
202
203#[derive(Debug, Copy, Clone, PartialEq, Eq)]
204pub enum MeteringPoints {
205    Remaining(u64),
206    Exhausted,
207}
208
209impl MeteringPoints {
210    pub fn try_into_remaining(self) -> Result<u64, Self> {
211        if let Self::Remaining(v) = self {
212            Ok(v)
213        } else {
214            Err(self)
215        }
216    }
217}
218
219/// An abstraction over the 'caller' object of a host function that works for any Wasm VM.
220///
221/// This allows access for important instances such as the context object that was passed to the
222/// instance, wasm linear memory access, etc.
223pub trait Caller {
224    type Context;
225
226    fn context(&self) -> &Self::Context;
227    fn context_mut(&mut self) -> &mut Self::Context;
228    /// Returns currently running *unmodified* bytecode.
229    fn bytecode(&self) -> Bytes;
230
231    /// Check if an export is present in the module.
232    fn has_export(&self, name: &str) -> bool;
233
234    fn memory_read(&self, offset: u32, size: usize) -> VMResult<Vec<u8>> {
235        let mut vec = vec![0; size];
236        self.memory_read_into(offset, &mut vec)?;
237        Ok(vec)
238    }
239    fn memory_read_into(&self, offset: u32, output: &mut [u8]) -> VMResult<()>;
240    fn memory_write(&self, offset: u32, data: &[u8]) -> VMResult<()>;
241    /// Allocates memory inside the Wasm VM by calling an export.
242    ///
243    /// Error is a type-erased error coming from the VM itself.
244    fn alloc(&mut self, idx: u32, size: usize, ctx: u32) -> VMResult<u32>;
245    /// Returns the amount of gas used.
246    fn gas_consumed(&mut self) -> MeteringPoints;
247    /// Set the amount of gas used.
248    fn consume_gas(&mut self, value: u64) -> VMResult<()>;
249}
250
251#[derive(Debug, Error)]
252pub enum WasmPreparationError {
253    #[error("Missing export {0}")]
254    MissingExport(String),
255    #[error("Compile error: {0}")]
256    Compile(String),
257    #[error("Memory instantiation error: {0}")]
258    Memory(String),
259    #[error("Instantiation error: {0}")]
260    Instantiation(String),
261}
262
263#[derive(Debug)]
264pub struct GasUsage {
265    /// The amount of gas used by the execution.
266    gas_limit: u64,
267    /// The amount of gas remaining after the execution.
268    remaining_points: u64,
269}
270
271impl GasUsage {
272    #[must_use]
273    pub fn new(gas_limit: u64, remaining_points: u64) -> Self {
274        GasUsage {
275            gas_limit,
276            remaining_points,
277        }
278    }
279
280    #[must_use]
281    pub fn gas_spent(&self) -> u64 {
282        debug_assert!(self.remaining_points <= self.gas_limit);
283        self.gas_limit - self.remaining_points
284    }
285
286    #[must_use]
287    pub fn gas_limit(&self) -> u64 {
288        self.gas_limit
289    }
290
291    #[must_use]
292    pub fn remaining_points(&self) -> u64 {
293        self.remaining_points
294    }
295}
296
297/// A trait that represents a Wasm instance.
298pub trait WasmInstance {
299    type Context;
300
301    fn call_export(&mut self, name: &str) -> (Result<(), VMError>, GasUsage);
302    fn teardown(self) -> Self::Context;
303}