Skip to main content

freenet_stdlib/
delegate_host.rs

1//! Host function API for delegates.
2//!
3//! This module provides synchronous access to delegate context, secrets, and
4//! contract state via host functions, eliminating the need for message round-trips.
5//!
6//! # Example
7//!
8//! ```ignore
9//! use freenet_stdlib::prelude::*;
10//!
11//! #[delegate]
12//! impl DelegateInterface for MyDelegate {
13//!     fn process(
14//!         ctx: &mut DelegateCtx,
15//!         _params: Parameters<'static>,
16//!         _attested: Option<&'static [u8]>,
17//!         message: InboundDelegateMsg,
18//!     ) -> Result<Vec<OutboundDelegateMsg>, DelegateError> {
19//!         // Read/write temporary context
20//!         let data = ctx.read();
21//!         ctx.write(b"new state");
22//!
23//!         // Access persistent secrets
24//!         if let Some(key) = ctx.get_secret(b"private_key") {
25//!             // use key...
26//!         }
27//!         ctx.set_secret(b"new_secret", b"value");
28//!
29//!         // V2: Direct contract access (no round-trips!)
30//!         let contract_id = [0u8; 32]; // your contract instance ID
31//!         if let Some(state) = ctx.get_contract_state(&contract_id) {
32//!             // process state...
33//!         }
34//!         ctx.put_contract_state(&contract_id, b"new state");
35//!
36//!         Ok(vec![])
37//!     }
38//! }
39//! ```
40//!
41//! # Context vs Secrets vs Contracts
42//!
43//! - **Context** (`read`/`write`): Temporary state within a single message batch.
44//!   Reset between separate runtime calls. Use for intermediate processing state.
45//!
46//! - **Secrets** (`get_secret`/`set_secret`): Persistent encrypted storage.
47//!   Survives across all delegate invocations. Use for private keys, tokens, etc.
48//!
49//! - **Contracts** (`get_contract_state`/`put_contract_state`/`update_contract_state`/
50//!   `subscribe_contract`): V2 host functions for direct contract state access.
51//!   Synchronous local reads/writes — no request/response round-trips.
52//!
53//! # Error Codes
54//!
55//! Host functions return negative values to indicate errors:
56//!
57//! | Code | Meaning |
58//! |------|---------|
59//! | 0    | Success |
60//! | -1   | Called outside process() context |
61//! | -2   | Secret not found |
62//! | -3   | Storage operation failed |
63//! | -4   | Invalid parameter (e.g., negative length) |
64//! | -5   | Context too large (exceeds i32::MAX) |
65//! | -6   | Buffer too small |
66//! | -7   | Contract not found in local store |
67//! | -8   | Internal state store error |
68//! | -9   | WASM memory bounds violation |
69//! | -10  | Contract code not registered |
70//!
71//! The wrapper methods in [`DelegateCtx`] handle these error codes and present
72//! a more ergonomic API.
73
74/// Error codes returned by host functions.
75///
76/// Negative values indicate errors, non-negative values indicate success
77/// (usually the number of bytes read/written).
78pub mod error_codes {
79    /// Operation succeeded.
80    pub const SUCCESS: i32 = 0;
81    /// Called outside of a process() context.
82    pub const ERR_NOT_IN_PROCESS: i32 = -1;
83    /// Secret not found.
84    pub const ERR_SECRET_NOT_FOUND: i32 = -2;
85    /// Storage operation failed.
86    pub const ERR_STORAGE_FAILED: i32 = -3;
87    /// Invalid parameter (e.g., negative length).
88    pub const ERR_INVALID_PARAM: i32 = -4;
89    /// Context too large (exceeds i32::MAX).
90    pub const ERR_CONTEXT_TOO_LARGE: i32 = -5;
91    /// Buffer too small to hold the data.
92    pub const ERR_BUFFER_TOO_SMALL: i32 = -6;
93    /// Contract not found in local store.
94    pub const ERR_CONTRACT_NOT_FOUND: i32 = -7;
95    /// Internal state store error.
96    pub const ERR_STORE_ERROR: i32 = -8;
97    /// WASM memory bounds violation (pointer/length out of range).
98    pub const ERR_MEMORY_BOUNDS: i32 = -9;
99    /// Contract code not registered in the index.
100    pub const ERR_CONTRACT_CODE_NOT_REGISTERED: i32 = -10;
101}
102
103// ============================================================================
104// Host function declarations (WASM only)
105// ============================================================================
106
107#[cfg(target_family = "wasm")]
108#[link(wasm_import_module = "freenet_delegate_ctx")]
109extern "C" {
110    /// Returns the current context length in bytes, or negative error code.
111    fn __frnt__delegate__ctx_len() -> i32;
112    /// Reads context into the buffer at `ptr` (max `len` bytes). Returns bytes written, or negative error code.
113    fn __frnt__delegate__ctx_read(ptr: i64, len: i32) -> i32;
114    /// Writes `len` bytes from `ptr` into the context, replacing existing content. Returns 0 on success, or negative error code.
115    fn __frnt__delegate__ctx_write(ptr: i64, len: i32) -> i32;
116}
117
118#[cfg(target_family = "wasm")]
119#[link(wasm_import_module = "freenet_delegate_secrets")]
120extern "C" {
121    /// Get a secret. Returns bytes written to `out_ptr`, or negative error code.
122    fn __frnt__delegate__get_secret(key_ptr: i64, key_len: i32, out_ptr: i64, out_len: i32) -> i32;
123    /// Get secret length without fetching value. Returns length, or negative error code.
124    fn __frnt__delegate__get_secret_len(key_ptr: i64, key_len: i32) -> i32;
125    /// Store a secret. Returns 0 on success, or negative error code.
126    fn __frnt__delegate__set_secret(key_ptr: i64, key_len: i32, val_ptr: i64, val_len: i32) -> i32;
127    /// Check if a secret exists. Returns 1 if yes, 0 if no, or negative error code.
128    fn __frnt__delegate__has_secret(key_ptr: i64, key_len: i32) -> i32;
129    /// Remove a secret. Returns 0 on success, or negative error code.
130    fn __frnt__delegate__remove_secret(key_ptr: i64, key_len: i32) -> i32;
131}
132
133#[cfg(target_family = "wasm")]
134#[link(wasm_import_module = "freenet_delegate_contracts")]
135extern "C" {
136    /// Get contract state length. Returns byte count, or negative error code (i64).
137    fn __frnt__delegate__get_contract_state_len(id_ptr: i64, id_len: i32) -> i64;
138    /// Get contract state. Returns byte count written, or negative error code (i64).
139    fn __frnt__delegate__get_contract_state(
140        id_ptr: i64,
141        id_len: i32,
142        out_ptr: i64,
143        out_len: i64,
144    ) -> i64;
145    /// Put (store) contract state. Returns 0 on success, or negative error code (i64).
146    fn __frnt__delegate__put_contract_state(
147        id_ptr: i64,
148        id_len: i32,
149        state_ptr: i64,
150        state_len: i64,
151    ) -> i64;
152    /// Update contract state (requires existing state). Returns 0 on success, or negative error code (i64).
153    fn __frnt__delegate__update_contract_state(
154        id_ptr: i64,
155        id_len: i32,
156        state_ptr: i64,
157        state_len: i64,
158    ) -> i64;
159    /// Subscribe to contract updates. Returns 0 on success, or negative error code (i64).
160    fn __frnt__delegate__subscribe_contract(id_ptr: i64, id_len: i32) -> i64;
161}
162
163// ============================================================================
164// DelegateCtx - Unified handle to context, secrets, and contracts
165// ============================================================================
166
167/// Opaque handle to the delegate's execution environment.
168///
169/// Provides access to:
170/// - **Temporary context**: State shared within a single message batch (reset between calls)
171/// - **Persistent secrets**: Encrypted storage that survives across all invocations
172/// - **Contract state** (V2): Direct synchronous access to local contract state
173///
174/// # Context Methods
175/// - [`read`](Self::read), [`write`](Self::write), [`len`](Self::len), [`clear`](Self::clear)
176///
177/// # Secret Methods
178/// - [`get_secret`](Self::get_secret), [`set_secret`](Self::set_secret),
179///   [`has_secret`](Self::has_secret), [`remove_secret`](Self::remove_secret)
180///
181/// # Contract Methods (V2)
182/// - [`get_contract_state`](Self::get_contract_state),
183///   [`put_contract_state`](Self::put_contract_state),
184///   [`update_contract_state`](Self::update_contract_state),
185///   [`subscribe_contract`](Self::subscribe_contract)
186#[derive(Default)]
187#[repr(transparent)]
188pub struct DelegateCtx {
189    _private: (),
190}
191
192impl DelegateCtx {
193    /// Creates the context handle.
194    ///
195    /// # Safety
196    ///
197    /// This should only be called by macro-generated code when the runtime
198    /// has set up the delegate execution environment.
199    #[doc(hidden)]
200    pub unsafe fn __new() -> Self {
201        Self { _private: () }
202    }
203
204    // ========================================================================
205    // Context methods (temporary state within a batch)
206    // ========================================================================
207
208    /// Returns the current context length in bytes.
209    #[inline]
210    pub fn len(&self) -> usize {
211        #[cfg(target_family = "wasm")]
212        {
213            let len = unsafe { __frnt__delegate__ctx_len() };
214            if len < 0 {
215                0
216            } else {
217                len as usize
218            }
219        }
220        #[cfg(not(target_family = "wasm"))]
221        {
222            0
223        }
224    }
225
226    /// Returns `true` if the context is empty.
227    #[inline]
228    pub fn is_empty(&self) -> bool {
229        self.len() == 0
230    }
231
232    /// Read the current context bytes.
233    ///
234    /// Returns an empty `Vec` if no context has been written.
235    pub fn read(&self) -> Vec<u8> {
236        #[cfg(target_family = "wasm")]
237        {
238            let len = unsafe { __frnt__delegate__ctx_len() };
239            if len <= 0 {
240                return Vec::new();
241            }
242            let mut buf = vec![0u8; len as usize];
243            let read = unsafe { __frnt__delegate__ctx_read(buf.as_mut_ptr() as i64, len) };
244            buf.truncate(read.max(0) as usize);
245            buf
246        }
247        #[cfg(not(target_family = "wasm"))]
248        {
249            Vec::new()
250        }
251    }
252
253    /// Read context into a provided buffer.
254    ///
255    /// Returns the number of bytes actually read.
256    pub fn read_into(&self, buf: &mut [u8]) -> usize {
257        #[cfg(target_family = "wasm")]
258        {
259            let read =
260                unsafe { __frnt__delegate__ctx_read(buf.as_mut_ptr() as i64, buf.len() as i32) };
261            read.max(0) as usize
262        }
263        #[cfg(not(target_family = "wasm"))]
264        {
265            let _ = buf;
266            0
267        }
268    }
269
270    /// Write new context bytes, replacing any existing content.
271    ///
272    /// Returns `true` on success, `false` on error.
273    pub fn write(&mut self, data: &[u8]) -> bool {
274        #[cfg(target_family = "wasm")]
275        {
276            let result =
277                unsafe { __frnt__delegate__ctx_write(data.as_ptr() as i64, data.len() as i32) };
278            result == 0
279        }
280        #[cfg(not(target_family = "wasm"))]
281        {
282            let _ = data;
283            false
284        }
285    }
286
287    /// Clear the context.
288    #[inline]
289    pub fn clear(&mut self) {
290        self.write(&[]);
291    }
292
293    // ========================================================================
294    // Secret methods (persistent encrypted storage)
295    // ========================================================================
296
297    /// Get the length of a secret without retrieving its value.
298    ///
299    /// Returns `None` if the secret does not exist.
300    pub fn get_secret_len(&self, key: &[u8]) -> Option<usize> {
301        #[cfg(target_family = "wasm")]
302        {
303            let result =
304                unsafe { __frnt__delegate__get_secret_len(key.as_ptr() as i64, key.len() as i32) };
305            if result < 0 {
306                None
307            } else {
308                Some(result as usize)
309            }
310        }
311        #[cfg(not(target_family = "wasm"))]
312        {
313            let _ = key;
314            None
315        }
316    }
317
318    /// Get a secret by key.
319    ///
320    /// Returns `None` if the secret does not exist.
321    pub fn get_secret(&self, key: &[u8]) -> Option<Vec<u8>> {
322        #[cfg(target_family = "wasm")]
323        {
324            // First get the length to allocate the right buffer size
325            let len = self.get_secret_len(key)?;
326
327            if len == 0 {
328                return Some(Vec::new());
329            }
330
331            let mut out = vec![0u8; len];
332            let result = unsafe {
333                __frnt__delegate__get_secret(
334                    key.as_ptr() as i64,
335                    key.len() as i32,
336                    out.as_mut_ptr() as i64,
337                    out.len() as i32,
338                )
339            };
340            if result < 0 {
341                None
342            } else {
343                out.truncate(result as usize);
344                Some(out)
345            }
346        }
347        #[cfg(not(target_family = "wasm"))]
348        {
349            let _ = key;
350            None
351        }
352    }
353
354    /// Store a secret.
355    ///
356    /// Returns `true` on success, `false` on error.
357    pub fn set_secret(&mut self, key: &[u8], value: &[u8]) -> bool {
358        #[cfg(target_family = "wasm")]
359        {
360            let result = unsafe {
361                __frnt__delegate__set_secret(
362                    key.as_ptr() as i64,
363                    key.len() as i32,
364                    value.as_ptr() as i64,
365                    value.len() as i32,
366                )
367            };
368            result == 0
369        }
370        #[cfg(not(target_family = "wasm"))]
371        {
372            let _ = (key, value);
373            false
374        }
375    }
376
377    /// Check if a secret exists.
378    pub fn has_secret(&self, key: &[u8]) -> bool {
379        #[cfg(target_family = "wasm")]
380        {
381            let result =
382                unsafe { __frnt__delegate__has_secret(key.as_ptr() as i64, key.len() as i32) };
383            result == 1
384        }
385        #[cfg(not(target_family = "wasm"))]
386        {
387            let _ = key;
388            false
389        }
390    }
391
392    /// Remove a secret.
393    ///
394    /// Returns `true` if the secret was removed, `false` if it didn't exist.
395    pub fn remove_secret(&mut self, key: &[u8]) -> bool {
396        #[cfg(target_family = "wasm")]
397        {
398            let result =
399                unsafe { __frnt__delegate__remove_secret(key.as_ptr() as i64, key.len() as i32) };
400            result == 0
401        }
402        #[cfg(not(target_family = "wasm"))]
403        {
404            let _ = key;
405            false
406        }
407    }
408
409    // ========================================================================
410    // Contract methods (V2 — direct synchronous access)
411    // ========================================================================
412
413    /// Get contract state by instance ID.
414    ///
415    /// Returns `Some(state_bytes)` if the contract exists locally,
416    /// `None` if not found or on error.
417    ///
418    /// Uses a two-step protocol: first queries the state length, then reads
419    /// the state bytes into an allocated buffer.
420    pub fn get_contract_state(&self, instance_id: &[u8; 32]) -> Option<Vec<u8>> {
421        #[cfg(target_family = "wasm")]
422        {
423            // Step 1: Get the state length
424            let len = unsafe {
425                __frnt__delegate__get_contract_state_len(instance_id.as_ptr() as i64, 32)
426            };
427            if len < 0 {
428                return None;
429            }
430            let len = len as usize;
431            if len == 0 {
432                return Some(Vec::new());
433            }
434
435            // Step 2: Read the state bytes
436            let mut buf = vec![0u8; len];
437            let read = unsafe {
438                __frnt__delegate__get_contract_state(
439                    instance_id.as_ptr() as i64,
440                    32,
441                    buf.as_mut_ptr() as i64,
442                    buf.len() as i64,
443                )
444            };
445            if read < 0 {
446                None
447            } else {
448                buf.truncate(read as usize);
449                Some(buf)
450            }
451        }
452        #[cfg(not(target_family = "wasm"))]
453        {
454            let _ = instance_id;
455            None
456        }
457    }
458
459    /// Store (PUT) contract state by instance ID.
460    ///
461    /// The contract's code must already be registered in the runtime's contract
462    /// store. Returns `true` on success, `false` on error.
463    pub fn put_contract_state(&mut self, instance_id: &[u8; 32], state: &[u8]) -> bool {
464        #[cfg(target_family = "wasm")]
465        {
466            let result = unsafe {
467                __frnt__delegate__put_contract_state(
468                    instance_id.as_ptr() as i64,
469                    32,
470                    state.as_ptr() as i64,
471                    state.len() as i64,
472                )
473            };
474            result == 0
475        }
476        #[cfg(not(target_family = "wasm"))]
477        {
478            let _ = (instance_id, state);
479            false
480        }
481    }
482
483    /// Update contract state by instance ID.
484    ///
485    /// Like `put_contract_state`, but only succeeds if the contract already has
486    /// stored state. This performs a full state replacement (not a delta-based
487    /// update through the contract's `update_state` logic). Returns `true` on
488    /// success, `false` if no prior state exists or on other errors.
489    pub fn update_contract_state(&mut self, instance_id: &[u8; 32], state: &[u8]) -> bool {
490        #[cfg(target_family = "wasm")]
491        {
492            let result = unsafe {
493                __frnt__delegate__update_contract_state(
494                    instance_id.as_ptr() as i64,
495                    32,
496                    state.as_ptr() as i64,
497                    state.len() as i64,
498                )
499            };
500            result == 0
501        }
502        #[cfg(not(target_family = "wasm"))]
503        {
504            let _ = (instance_id, state);
505            false
506        }
507    }
508
509    /// Subscribe to contract updates by instance ID.
510    ///
511    /// Registers interest in receiving notifications when the contract's state
512    /// changes. Currently validates that the contract is known and returns success;
513    /// actual notification delivery is a follow-up.
514    ///
515    /// Returns `true` on success, `false` if the contract is unknown or on error.
516    pub fn subscribe_contract(&mut self, instance_id: &[u8; 32]) -> bool {
517        #[cfg(target_family = "wasm")]
518        {
519            let result =
520                unsafe { __frnt__delegate__subscribe_contract(instance_id.as_ptr() as i64, 32) };
521            result == 0
522        }
523        #[cfg(not(target_family = "wasm"))]
524        {
525            let _ = instance_id;
526            false
527        }
528    }
529}
530
531impl std::fmt::Debug for DelegateCtx {
532    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
533        f.debug_struct("DelegateCtx")
534            .field("context_len", &self.len())
535            .finish_non_exhaustive()
536    }
537}