calimero_runtime/logic/
imports.rs1use core::cell::RefCell;
2use core::sync::atomic::{AtomicBool, Ordering};
3use std::sync::Once;
4
5use wasmer::{Imports, Store};
6
7use super::{HostError, Location, PanicContext, VMLogic};
8
9thread_local! {
10 static HOOKER: Once = const { Once::new() };
12 static PAYLOAD: RefCell<Option<(String, Location)>> = const { RefCell::new(None) };
13 static HOST_CTX: AtomicBool = const { AtomicBool::new(false) };
14}
15
16impl VMLogic<'_> {
17 pub fn imports(&mut self, store: &mut Store) -> Imports {
18 imports! {
19 store;
20 logic: self;
21
22 fn panic(location_ptr: u64);
23 fn panic_utf8(msg_ptr: u64, file_ptr: u64);
24
25 fn register_len(register_id: u64) -> u64;
27 fn read_register(register_id: u64, register_ptr: u64) -> u32;
28
29 fn context_id(register_id: u64);
30 fn executor_id(register_id: u64);
31
32 fn input(register_id: u64);
33 fn value_return(value_ptr: u64);
34 fn log_utf8(log_ptr: u64);
35 fn emit(event_ptr: u64);
36 fn emit_with_handler(event_ptr: u64, handler_ptr: u64);
37
38 fn commit(root_hash_ptr: u64, artifact_ptr: u64);
39
40 fn storage_write(
41 key_ptr: u64,
42 value_ptr: u64,
43 register_id: u64,
44 ) -> u32;
45 fn storage_read(key_ptr: u64, register_id: u64) -> u32;
46 fn storage_remove(key_ptr: u64, register_id: u64) -> u32;
47
48 fn fetch(
49 url_ptr: u64,
50 method_ptr: u64,
51 headers_ptr: u64,
52 body_ptr: u64,
53 register_id: u64,
54 ) -> u32;
55
56 fn random_bytes(ptr: u64);
57 fn time_now(ptr: u64);
58
59 fn send_proposal(actions_ptr: u64, id_ptr: u64);
60 fn approve_proposal(approval_ptr: u64);
61
62 fn blob_create() -> u64;
63 fn blob_write(fd: u64, data_ptr: u64) -> u64;
64 fn blob_close(fd: u64, blob_id_ptr: u64) -> u32;
65 fn blob_open(blob_id_ptr: u64) -> u64;
66 fn blob_read(fd: u64, data_ptr: u64) -> u64;
67 fn blob_announce_to_context(blob_id_ptr: u64, context_id_ptr: u64) -> u32;
68 }
69 }
70}
71
72macro_rules! _imports {
73 ($store:ident; logic: $logic:ident; $(fn $func:ident($($arg:ident: $arg_ty:ty),*$(,)?) $(-> $returns:ty)?;)*) => {
74 {
75 $(
76 #[expect(clippy::allow_attributes, reason = "Needed for the macro")]
77 #[allow(unused_parens, reason = "Needed for the macro")]
78 fn $func(
79 mut env: wasmer::FunctionEnvMut<'_, fragile::Fragile<*mut ()>>,
80 $($arg: $arg_ty),*
81 ) -> Result<($( $returns )?), wasmer::RuntimeError> {
82 #[cfg(feature = "host-traces")]
83 use owo_colors::OwoColorize;
84
85 #[cfg(feature = "host-traces")]
86 {
87 let params: &[String] = &[$(
88 format!(
89 "{}: {} = {}",
90 stringify!($arg).fg_rgb::<253, 151, 31>(),
91 stringify!($arg_ty).fg_rgb::<102, 217, 239>(),
92 $arg.fg_rgb::<190, 132, 255>()
93 )
94 ),*][..];
95
96 let decorator = format!(
97 "{} {}({})",
98 "fn".fg_rgb::<102, 217, 239>(),
99 stringify!($func).fg_rgb::<166, 226, 46>(),
100 params.join(", ")
101 );
102
103 println!("{}", decorator);
104 };
105
106 HOST_CTX.with(|ctx| ctx.store(true, Ordering::Relaxed));
107 let res = std::panic::catch_unwind(core::panic::AssertUnwindSafe(|| {
108 let (data, store) = env.data_and_store_mut();
109 let data = unsafe { &mut *(*data.get_mut()).cast::<VMLogic<'_>>() };
110
111 data.host_functions(store).$func($($arg),*)
112 })).unwrap_or_else(|_| {
113 let (message, location) = PAYLOAD.with(|payload| {
114 payload.borrow_mut().take().unwrap_or_else(|| ("<no message>".to_owned(), Location::Unknown))
115 });
116
117 Err(HostError::Panic {
118 context: PanicContext::Host,
119 message,
120 location,
121 }.into())
122 });
123 HOST_CTX.with(|ctx| ctx.store(false, Ordering::Relaxed));
124
125 #[cfg(feature = "host-traces")]
126 {
127 #[allow(unused_mut, unused_assignments)]
128 let mut return_ty = "()";
129 $( return_ty = stringify!($returns); )?
130 println!(
131 " ⇲ {}(..) -> {} = {res:?}",
132 stringify!($func).fg_rgb::<166, 226, 46>(),
133 return_ty.fg_rgb::<102, 217, 239>()
134 );
135 }
136
137 res.map_err(|err| wasmer::RuntimeError::user(Box::new(err)))
138 }
139 )*
140
141 let mut store = $store;
142 let logic = $logic;
143
144 HOOKER.with(|hooker| {
145 hooker.call_once(|| {
146 let prev_hook = std::panic::take_hook();
147 std::panic::set_hook(Box::new(move |info| {
148 if !HOST_CTX.with(|ctx| ctx.load(Ordering::Relaxed)) {
149 return prev_hook(info);
150 }
151 PAYLOAD.with(|payload| {
152 let message = match info.payload().downcast_ref::<&'static str>() {
153 Some(message) => *message,
154 None => match info.payload().downcast_ref::<String>() {
155 Some(message) => &**message,
156 None => "<no message>",
157 },
158 };
159
160 *payload.borrow_mut() = Some(match info.location() {
161 Some(location) => (message.to_owned(), Location::from(location)),
162 None => (message.to_owned(), Location::Unknown),
163 });
164 });
165
166 prev_hook(info);
167 }));
168 });
169 });
170
171 let env = wasmer::FunctionEnv::new(&mut store, fragile::Fragile::new(core::ptr::from_mut(logic).cast::<()>()));
172
173 wasmer::imports! {
174 "env" => {
175 $(
176 stringify!($func) => wasmer::Function::new_typed_with_env(&mut store, &env, $func),
177 )*
178 }
179 }
180 }
181 };
182}
183
184use _imports as imports;