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}