concordium_smart_contract_engine/
lib.rs

1//! This library provides functionality that builds on top of the [Wasm engine](https://docs.rs/concordium-wasm)
2//! and adds high-level functions for executing smart contracts on the
3//! Concordium chain.
4//!
5//! Concordium supports two versions of smart contracts, the legacy [`v0`]
6//! version and the [`v1`] version. The latter is essentially better in
7//! every way. They differ in two main ways
8//! - [`v0`] uses message passing for inter-contract communication, and has a
9//!   flat state. The state is limited to 16kB and the entire state is written
10//!   every time a contract update is done.
11//! - [`v1`] uses synchronous calls for inter-contract communication, and its
12//!   state is a trie-based structure, which supports efficient partial state
13//!   updates. The trie is implemented in the [`v1::trie`] module.
14//!
15//! Both [`v0`] and [`v1`] modules are structured similarly. The main
16//! entrypoints used by users of this library are [`v0::invoke_init`] (resp.
17//! [`v1::invoke_init`]) and [`v0::invoke_receive`] (resp.
18//! [`v1::invoke_receive`]) functions, and their variants.
19//!
20//! The respective modules provide more details on the data types involved, and
21//! any specifics of the different versions.
22//!
23//! ## Features
24//!
25//! This crate has the following features. None are enabled by default.
26//!
27//! ### `display-state`
28//! This feature exposes the function
29//! [`display_tree`](v1::trie::PersistentState::display_tree) for displaying the
30//! V1 contract state in a reasonably readable format. This is useful for deep
31//! inspection of smart contract state, and debugging.
32//!
33//! ### `async`
34//! Exposes construction of smart contract state from streams of key-value
35//! pairs, such as those received from the node's API. See
36//! - [`try_from_stream`](v1::trie::PersistentState::try_from_stream)
37//! - [`from_stream`](v1::trie::PersistentState::from_stream)
38//!
39//! ### `enable-ffi`
40//! This enables foreign function exports. This is an **internal** feature and
41//! there are no guarantees about the stability of foreign exports.
42//!
43//! ### `fuzz-coverage` and `fuzz`
44//! These features are also **internal** and exist to support fuzzing. They are
45//! used to derive Arbitrary instances and to disable inlining, the latter is
46//! necessary since the fuzzer used has bugs which prevent the coverage report
47//! being generated when functions are inlined.
48pub mod constants;
49pub mod resumption;
50pub mod utils;
51pub mod v0;
52pub mod v1;
53#[cfg(test)]
54mod validation_tests;
55use anyhow::bail;
56use derive_more::{Display, From, Into};
57
58/// Re-export the underlying Wasm execution engine used by Concordium.
59pub use concordium_wasm as wasm;
60
61/// A helper macro used to check that the declared type of a Wasm import matches
62/// the required one.
63///
64/// # Example 1
65/// ```ignore
66/// type_matches!(ty => [I32, I64])
67/// ```
68/// The declared type `ty` must have **no return value** and parameters of types
69/// `I32` and `I64`
70///
71/// # Example 2
72/// ```ignore
73/// type_matches!(ty => []; I64)
74/// ```
75/// The declared type `ty` must have return type I64 and no arguments.
76///
77/// # Example 3
78/// ```ignore
79/// type_matches!(ty => [I32, I64, I32]; I64)
80/// ```
81/// The declared type `ty` must have return type I64 and arguments of types
82/// `I32, I64, I32` in that order.
83macro_rules! type_matches {
84    ($goal:expr => $params:expr) => {
85        $goal.result.is_none() && $params == $goal.parameters.as_slice()
86    };
87    ($goal:expr => []; $result:expr) => {
88        $goal.result == Some($result) && $goal.parameters.is_empty()
89    };
90    ($goal:expr => $params:expr; $result:expr) => {
91        $goal.result == Some($result) && $params == $goal.parameters.as_slice()
92    };
93}
94pub(crate) use type_matches;
95
96/// Result of contract execution. This is just a wrapper around
97/// [`anyhow::Result`].
98pub type ExecResult<A> = anyhow::Result<A>;
99
100pub trait DebugInfo: Send + Sync + std::fmt::Debug + 'static {
101    const ENABLE_DEBUG: bool;
102
103    fn empty_trace() -> Self;
104
105    fn trace_host_call(&mut self, f: v1::ImportFunc, energy_used: InterpreterEnergy);
106
107    fn emit_debug_event(&mut self, event: v1::EmittedDebugStatement);
108}
109
110impl DebugInfo for () {
111    const ENABLE_DEBUG: bool = false;
112
113    #[inline(always)]
114    fn empty_trace() -> Self {}
115
116    #[inline(always)]
117    fn trace_host_call(&mut self, _f: v1::ImportFunc, _energy_used: InterpreterEnergy) {
118        // do nothing
119    }
120
121    #[inline(always)]
122    fn emit_debug_event(&mut self, _event: v1::EmittedDebugStatement) {
123        // do nothing
124    }
125}
126
127#[derive(
128    Default,
129    PartialEq,
130    Eq,
131    PartialOrd,
132    Ord,
133    Debug,
134    Clone,
135    Copy,
136    From,
137    Into,
138    Display,
139    derive_more::FromStr,
140)]
141#[display(fmt = "{}", energy)]
142#[repr(transparent)]
143pub struct InterpreterEnergy {
144    /// Energy left to use
145    pub energy: u64,
146}
147
148impl PartialEq<u64> for InterpreterEnergy {
149    fn eq(&self, other: &u64) -> bool { self.energy.eq(other) }
150}
151
152impl PartialOrd<u64> for InterpreterEnergy {
153    fn partial_cmp(&self, other: &u64) -> Option<std::cmp::Ordering> {
154        self.energy.partial_cmp(other)
155    }
156}
157
158impl InterpreterEnergy {
159    pub const fn new(energy: u64) -> Self {
160        Self {
161            energy,
162        }
163    }
164
165    /// Subtract the given amount from the energy, bottoming out at 0.
166    pub fn subtract(self, consumed: u64) -> Self {
167        Self {
168            energy: self.energy.saturating_sub(consumed),
169        }
170    }
171
172    /// Compute the difference of two energy amounts.
173    pub fn signed_diff(self, other: Self) -> i128 {
174        i128::from(self.energy) - i128::from(other.energy)
175    }
176
177    #[cfg(feature = "enable-ffi")]
178    /// Serialized in big-endian representation.
179    pub fn to_be_bytes(self) -> [u8; 8] { self.energy.to_be_bytes() }
180
181    /// Saturating interpreter energy subtraction.
182    ///
183    /// Computes `self - rhs` bottoming out at `0` instead of underflowing.
184    pub fn saturating_sub(self, consumed: &Self) -> Self {
185        Self {
186            energy: self.energy.saturating_sub(consumed.energy),
187        }
188    }
189
190    /// Internal helper to add amount when we know it will not overflow.
191    fn add(&mut self, other: Self) { self.energy += other.energy; }
192
193    pub fn tick_energy(&mut self, amount: u64) -> ExecResult<()> {
194        if self.energy >= amount {
195            self.energy -= amount;
196            Ok(())
197        } else {
198            self.energy = 0;
199            bail!(OutOfEnergy)
200        }
201    }
202
203    /// Charge energy for allocating the given number of pages.
204    /// Since there is a hard limit on the amount of memory this is not so
205    /// essential. The base cost of calling this host function is already
206    /// covered by the metering transformation, hence if num_pages=0 it is
207    /// OK for this function to charge nothing.
208    ///
209    /// This function will charge regardless of whether memory allocation
210    /// actually happens, i.e., even if growing the memory would go over the
211    /// maximum. This is OK since trying to allocate too much memory is likely
212    /// going to lead to program failure anyhow.
213    pub fn charge_memory_alloc(&mut self, num_pages: u32) -> ExecResult<()> {
214        let to_charge = u64::from(num_pages) * u64::from(constants::MEMORY_COST_FACTOR); // this cannot overflow because of the cast.
215        self.tick_energy(to_charge)
216    }
217}
218
219#[derive(Debug)]
220/// An error raised by the interpreter when no more interpreter energy remains
221/// for execution.
222pub struct OutOfEnergy;
223
224impl std::fmt::Display for OutOfEnergy {
225    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { "Out of energy".fmt(f) }
226}