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}