calimero_runtime/logic/
imports.rs

1use 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    // https://open.spotify.com/track/7DPUuTaTZCtQ6o4Xx00qzT
11    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            // todo! custom memory injection
26            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;