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 /// Delegate creation depth limit exceeded.
102 pub const ERR_DEPTH_EXCEEDED: i32 = -20;
103 /// Per-call delegate creation limit exceeded.
104 pub const ERR_CREATIONS_EXCEEDED: i32 = -21;
105 /// Invalid WASM module (failed to construct DelegateContainer).
106 pub const ERR_INVALID_WASM: i32 = -23;
107 /// Failed to register delegate in secret/delegate store.
108 pub const ERR_STORE_FAILED: i32 = -24;
109}
110
111// ============================================================================
112// Host function declarations (WASM only)
113// ============================================================================
114
115#[cfg(target_family = "wasm")]
116#[link(wasm_import_module = "freenet_delegate_ctx")]
117extern "C" {
118 /// Returns the current context length in bytes, or negative error code.
119 fn __frnt__delegate__ctx_len() -> i32;
120 /// Reads context into the buffer at `ptr` (max `len` bytes). Returns bytes written, or negative error code.
121 fn __frnt__delegate__ctx_read(ptr: i64, len: i32) -> i32;
122 /// Writes `len` bytes from `ptr` into the context, replacing existing content. Returns 0 on success, or negative error code.
123 fn __frnt__delegate__ctx_write(ptr: i64, len: i32) -> i32;
124}
125
126#[cfg(target_family = "wasm")]
127#[link(wasm_import_module = "freenet_delegate_secrets")]
128extern "C" {
129 /// Get a secret. Returns bytes written to `out_ptr`, or negative error code.
130 fn __frnt__delegate__get_secret(key_ptr: i64, key_len: i32, out_ptr: i64, out_len: i32) -> i32;
131 /// Get secret length without fetching value. Returns length, or negative error code.
132 fn __frnt__delegate__get_secret_len(key_ptr: i64, key_len: i32) -> i32;
133 /// Store a secret. Returns 0 on success, or negative error code.
134 fn __frnt__delegate__set_secret(key_ptr: i64, key_len: i32, val_ptr: i64, val_len: i32) -> i32;
135 /// Check if a secret exists. Returns 1 if yes, 0 if no, or negative error code.
136 fn __frnt__delegate__has_secret(key_ptr: i64, key_len: i32) -> i32;
137 /// Remove a secret. Returns 0 on success, or negative error code.
138 fn __frnt__delegate__remove_secret(key_ptr: i64, key_len: i32) -> i32;
139}
140
141#[cfg(target_family = "wasm")]
142#[link(wasm_import_module = "freenet_delegate_contracts")]
143extern "C" {
144 /// Get contract state length. Returns byte count, or negative error code (i64).
145 fn __frnt__delegate__get_contract_state_len(id_ptr: i64, id_len: i32) -> i64;
146 /// Get contract state. Returns byte count written, or negative error code (i64).
147 fn __frnt__delegate__get_contract_state(
148 id_ptr: i64,
149 id_len: i32,
150 out_ptr: i64,
151 out_len: i64,
152 ) -> i64;
153 /// Put (store) contract state. Returns 0 on success, or negative error code (i64).
154 fn __frnt__delegate__put_contract_state(
155 id_ptr: i64,
156 id_len: i32,
157 state_ptr: i64,
158 state_len: i64,
159 ) -> i64;
160 /// Update contract state (requires existing state). Returns 0 on success, or negative error code (i64).
161 fn __frnt__delegate__update_contract_state(
162 id_ptr: i64,
163 id_len: i32,
164 state_ptr: i64,
165 state_len: i64,
166 ) -> i64;
167 /// Subscribe to contract updates. Returns 0 on success, or negative error code (i64).
168 fn __frnt__delegate__subscribe_contract(id_ptr: i64, id_len: i32) -> i64;
169}
170
171#[cfg(target_family = "wasm")]
172#[link(wasm_import_module = "freenet_delegate_management")]
173extern "C" {
174 /// Create a new delegate from WASM code + parameters.
175 /// Returns 0 on success, negative error code on failure.
176 /// On success, writes 32 bytes to out_key_ptr and 32 bytes to out_hash_ptr.
177 fn __frnt__delegate__create_delegate(
178 wasm_ptr: i64,
179 wasm_len: i64,
180 params_ptr: i64,
181 params_len: i64,
182 cipher_ptr: i64,
183 nonce_ptr: i64,
184 out_key_ptr: i64,
185 out_hash_ptr: i64,
186 ) -> i32;
187}
188
189// ============================================================================
190// DelegateCtx - Unified handle to context, secrets, and contracts
191// ============================================================================
192
193/// Opaque handle to the delegate's execution environment.
194///
195/// Provides access to:
196/// - **Temporary context**: State shared within a single message batch (reset between calls)
197/// - **Persistent secrets**: Encrypted storage that survives across all invocations
198/// - **Contract state** (V2): Direct synchronous access to local contract state
199///
200/// # Context Methods
201/// - [`read`](Self::read), [`write`](Self::write), [`len`](Self::len), [`clear`](Self::clear)
202///
203/// # Secret Methods
204/// - [`get_secret`](Self::get_secret), [`set_secret`](Self::set_secret),
205/// [`has_secret`](Self::has_secret), [`remove_secret`](Self::remove_secret)
206///
207/// # Contract Methods (V2)
208/// - [`get_contract_state`](Self::get_contract_state),
209/// [`put_contract_state`](Self::put_contract_state),
210/// [`update_contract_state`](Self::update_contract_state),
211/// [`subscribe_contract`](Self::subscribe_contract)
212///
213/// # Delegate Management Methods (V2)
214/// - [`create_delegate`](Self::create_delegate)
215#[derive(Default)]
216#[repr(transparent)]
217pub struct DelegateCtx {
218 _private: (),
219}
220
221impl DelegateCtx {
222 /// Creates the context handle.
223 ///
224 /// # Safety
225 ///
226 /// This should only be called by macro-generated code when the runtime
227 /// has set up the delegate execution environment.
228 #[doc(hidden)]
229 pub unsafe fn __new() -> Self {
230 Self { _private: () }
231 }
232
233 // ========================================================================
234 // Context methods (temporary state within a batch)
235 // ========================================================================
236
237 /// Returns the current context length in bytes.
238 #[inline]
239 pub fn len(&self) -> usize {
240 #[cfg(target_family = "wasm")]
241 {
242 let len = unsafe { __frnt__delegate__ctx_len() };
243 if len < 0 {
244 0
245 } else {
246 len as usize
247 }
248 }
249 #[cfg(not(target_family = "wasm"))]
250 {
251 0
252 }
253 }
254
255 /// Returns `true` if the context is empty.
256 #[inline]
257 pub fn is_empty(&self) -> bool {
258 self.len() == 0
259 }
260
261 /// Read the current context bytes.
262 ///
263 /// Returns an empty `Vec` if no context has been written.
264 pub fn read(&self) -> Vec<u8> {
265 #[cfg(target_family = "wasm")]
266 {
267 let len = unsafe { __frnt__delegate__ctx_len() };
268 if len <= 0 {
269 return Vec::new();
270 }
271 let mut buf = vec![0u8; len as usize];
272 let read = unsafe { __frnt__delegate__ctx_read(buf.as_mut_ptr() as i64, len) };
273 buf.truncate(read.max(0) as usize);
274 buf
275 }
276 #[cfg(not(target_family = "wasm"))]
277 {
278 Vec::new()
279 }
280 }
281
282 /// Read context into a provided buffer.
283 ///
284 /// Returns the number of bytes actually read.
285 pub fn read_into(&self, buf: &mut [u8]) -> usize {
286 #[cfg(target_family = "wasm")]
287 {
288 let read =
289 unsafe { __frnt__delegate__ctx_read(buf.as_mut_ptr() as i64, buf.len() as i32) };
290 read.max(0) as usize
291 }
292 #[cfg(not(target_family = "wasm"))]
293 {
294 let _ = buf;
295 0
296 }
297 }
298
299 /// Write new context bytes, replacing any existing content.
300 ///
301 /// Returns `true` on success, `false` on error.
302 pub fn write(&mut self, data: &[u8]) -> bool {
303 #[cfg(target_family = "wasm")]
304 {
305 let result =
306 unsafe { __frnt__delegate__ctx_write(data.as_ptr() as i64, data.len() as i32) };
307 result == 0
308 }
309 #[cfg(not(target_family = "wasm"))]
310 {
311 let _ = data;
312 false
313 }
314 }
315
316 /// Clear the context.
317 #[inline]
318 pub fn clear(&mut self) {
319 self.write(&[]);
320 }
321
322 // ========================================================================
323 // Secret methods (persistent encrypted storage)
324 // ========================================================================
325
326 /// Get the length of a secret without retrieving its value.
327 ///
328 /// Returns `None` if the secret does not exist.
329 pub fn get_secret_len(&self, key: &[u8]) -> Option<usize> {
330 #[cfg(target_family = "wasm")]
331 {
332 let result =
333 unsafe { __frnt__delegate__get_secret_len(key.as_ptr() as i64, key.len() as i32) };
334 if result < 0 {
335 None
336 } else {
337 Some(result as usize)
338 }
339 }
340 #[cfg(not(target_family = "wasm"))]
341 {
342 let _ = key;
343 None
344 }
345 }
346
347 /// Get a secret by key.
348 ///
349 /// Returns `None` if the secret does not exist.
350 pub fn get_secret(&self, key: &[u8]) -> Option<Vec<u8>> {
351 #[cfg(target_family = "wasm")]
352 {
353 // First get the length to allocate the right buffer size
354 let len = self.get_secret_len(key)?;
355
356 if len == 0 {
357 return Some(Vec::new());
358 }
359
360 let mut out = vec![0u8; len];
361 let result = unsafe {
362 __frnt__delegate__get_secret(
363 key.as_ptr() as i64,
364 key.len() as i32,
365 out.as_mut_ptr() as i64,
366 out.len() as i32,
367 )
368 };
369 if result < 0 {
370 None
371 } else {
372 out.truncate(result as usize);
373 Some(out)
374 }
375 }
376 #[cfg(not(target_family = "wasm"))]
377 {
378 let _ = key;
379 None
380 }
381 }
382
383 /// Store a secret.
384 ///
385 /// Returns `true` on success, `false` on error.
386 pub fn set_secret(&mut self, key: &[u8], value: &[u8]) -> bool {
387 #[cfg(target_family = "wasm")]
388 {
389 let result = unsafe {
390 __frnt__delegate__set_secret(
391 key.as_ptr() as i64,
392 key.len() as i32,
393 value.as_ptr() as i64,
394 value.len() as i32,
395 )
396 };
397 result == 0
398 }
399 #[cfg(not(target_family = "wasm"))]
400 {
401 let _ = (key, value);
402 false
403 }
404 }
405
406 /// Check if a secret exists.
407 pub fn has_secret(&self, key: &[u8]) -> bool {
408 #[cfg(target_family = "wasm")]
409 {
410 let result =
411 unsafe { __frnt__delegate__has_secret(key.as_ptr() as i64, key.len() as i32) };
412 result == 1
413 }
414 #[cfg(not(target_family = "wasm"))]
415 {
416 let _ = key;
417 false
418 }
419 }
420
421 /// Remove a secret.
422 ///
423 /// Returns `true` if the secret was removed, `false` if it didn't exist.
424 pub fn remove_secret(&mut self, key: &[u8]) -> bool {
425 #[cfg(target_family = "wasm")]
426 {
427 let result =
428 unsafe { __frnt__delegate__remove_secret(key.as_ptr() as i64, key.len() as i32) };
429 result == 0
430 }
431 #[cfg(not(target_family = "wasm"))]
432 {
433 let _ = key;
434 false
435 }
436 }
437
438 // ========================================================================
439 // Contract methods (V2 — direct synchronous access)
440 // ========================================================================
441
442 /// Get contract state by instance ID.
443 ///
444 /// Returns `Some(state_bytes)` if the contract exists locally,
445 /// `None` if not found or on error.
446 ///
447 /// Uses a two-step protocol: first queries the state length, then reads
448 /// the state bytes into an allocated buffer.
449 pub fn get_contract_state(&self, instance_id: &[u8; 32]) -> Option<Vec<u8>> {
450 #[cfg(target_family = "wasm")]
451 {
452 // Step 1: Get the state length
453 let len = unsafe {
454 __frnt__delegate__get_contract_state_len(instance_id.as_ptr() as i64, 32)
455 };
456 if len < 0 {
457 return None;
458 }
459 let len = len as usize;
460 if len == 0 {
461 return Some(Vec::new());
462 }
463
464 // Step 2: Read the state bytes
465 let mut buf = vec![0u8; len];
466 let read = unsafe {
467 __frnt__delegate__get_contract_state(
468 instance_id.as_ptr() as i64,
469 32,
470 buf.as_mut_ptr() as i64,
471 buf.len() as i64,
472 )
473 };
474 if read < 0 {
475 None
476 } else {
477 buf.truncate(read as usize);
478 Some(buf)
479 }
480 }
481 #[cfg(not(target_family = "wasm"))]
482 {
483 let _ = instance_id;
484 None
485 }
486 }
487
488 /// Store (PUT) contract state by instance ID.
489 ///
490 /// The contract's code must already be registered in the runtime's contract
491 /// store. Returns `true` on success, `false` on error.
492 pub fn put_contract_state(&mut self, instance_id: &[u8; 32], state: &[u8]) -> bool {
493 #[cfg(target_family = "wasm")]
494 {
495 let result = unsafe {
496 __frnt__delegate__put_contract_state(
497 instance_id.as_ptr() as i64,
498 32,
499 state.as_ptr() as i64,
500 state.len() as i64,
501 )
502 };
503 result == 0
504 }
505 #[cfg(not(target_family = "wasm"))]
506 {
507 let _ = (instance_id, state);
508 false
509 }
510 }
511
512 /// Update contract state by instance ID.
513 ///
514 /// Like `put_contract_state`, but only succeeds if the contract already has
515 /// stored state. This performs a full state replacement (not a delta-based
516 /// update through the contract's `update_state` logic). Returns `true` on
517 /// success, `false` if no prior state exists or on other errors.
518 pub fn update_contract_state(&mut self, instance_id: &[u8; 32], state: &[u8]) -> bool {
519 #[cfg(target_family = "wasm")]
520 {
521 let result = unsafe {
522 __frnt__delegate__update_contract_state(
523 instance_id.as_ptr() as i64,
524 32,
525 state.as_ptr() as i64,
526 state.len() as i64,
527 )
528 };
529 result == 0
530 }
531 #[cfg(not(target_family = "wasm"))]
532 {
533 let _ = (instance_id, state);
534 false
535 }
536 }
537
538 /// Subscribe to contract updates by instance ID.
539 ///
540 /// Registers interest in receiving notifications when the contract's state
541 /// changes. Currently validates that the contract is known and returns success;
542 /// actual notification delivery is a follow-up.
543 ///
544 /// Returns `true` on success, `false` if the contract is unknown or on error.
545 pub fn subscribe_contract(&mut self, instance_id: &[u8; 32]) -> bool {
546 #[cfg(target_family = "wasm")]
547 {
548 let result =
549 unsafe { __frnt__delegate__subscribe_contract(instance_id.as_ptr() as i64, 32) };
550 result == 0
551 }
552 #[cfg(not(target_family = "wasm"))]
553 {
554 let _ = instance_id;
555 false
556 }
557 }
558
559 /// Create a new child delegate from WASM bytecode and parameters.
560 ///
561 /// This V2 host function allows a delegate to spawn new delegates at runtime.
562 /// The child delegate is registered in the node's delegate store and secret store
563 /// with the provided cipher and nonce.
564 ///
565 /// Returns `Ok((key_hash, code_hash))` where both are 32-byte arrays identifying
566 /// the newly created delegate. Returns `Err(error_code)` on failure.
567 ///
568 /// # Resource Limits
569 /// - Maximum creation depth: 4 (prevents fork bombs)
570 /// - Maximum creations per process() call: 8
571 ///
572 /// # Error Codes
573 /// - `-1`: Called outside process() context
574 /// - `-4`: Invalid parameter
575 /// - `-9`: WASM memory bounds violation
576 /// - `-20`: Depth limit exceeded
577 /// - `-21`: Per-call creation limit exceeded
578 /// - `-23`: Invalid WASM module
579 /// - `-24`: Store registration failed
580 pub fn create_delegate(
581 &mut self,
582 wasm_code: &[u8],
583 params: &[u8],
584 cipher: &[u8; 32],
585 nonce: &[u8; 24],
586 ) -> Result<([u8; 32], [u8; 32]), i32> {
587 #[cfg(target_family = "wasm")]
588 {
589 let mut key_buf = [0u8; 32];
590 let mut hash_buf = [0u8; 32];
591 let result = unsafe {
592 __frnt__delegate__create_delegate(
593 wasm_code.as_ptr() as i64,
594 wasm_code.len() as i64,
595 params.as_ptr() as i64,
596 params.len() as i64,
597 cipher.as_ptr() as i64,
598 nonce.as_ptr() as i64,
599 key_buf.as_mut_ptr() as i64,
600 hash_buf.as_mut_ptr() as i64,
601 )
602 };
603 if result == 0 {
604 Ok((key_buf, hash_buf))
605 } else {
606 Err(result)
607 }
608 }
609 #[cfg(not(target_family = "wasm"))]
610 {
611 let _ = (wasm_code, params, cipher, nonce);
612 Err(error_codes::ERR_NOT_IN_PROCESS)
613 }
614 }
615}
616
617impl std::fmt::Debug for DelegateCtx {
618 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
619 f.debug_struct("DelegateCtx")
620 .field("context_len", &self.len())
621 .finish_non_exhaustive()
622 }
623}