use dashmap::DashMap;
use freenet_stdlib::prelude::{ContractInstanceId, ContractKey, DelegateKey, SecretsId};
use std::collections::HashSet;
use std::sync::LazyLock;
use std::sync::atomic::{AtomicUsize, Ordering};
use super::contract_store::ContractStore;
use super::delegate_store::DelegateStore;
use super::runtime::InstanceInfo;
use super::secrets_store::SecretsStore;
use crate::contract::storages::Storage;
pub(super) static MEM_ADDR: LazyLock<DashMap<InstanceId, InstanceInfo>> =
LazyLock::new(DashMap::default);
pub(super) static DELEGATE_ENV: LazyLock<DashMap<InstanceId, DelegateCallEnv>> =
LazyLock::new(DashMap::default);
pub(crate) static DELEGATE_SUBSCRIPTIONS: LazyLock<
DashMap<ContractInstanceId, HashSet<DelegateKey>>,
> = LazyLock::new(DashMap::default);
pub(crate) static DELEGATE_INHERITED_ORIGINS: LazyLock<
DashMap<DelegateKey, Vec<ContractInstanceId>>,
> = LazyLock::new(DashMap::default);
pub(crate) static CREATED_DELEGATES_COUNT: AtomicUsize = AtomicUsize::new(0);
thread_local! {
pub(super) static CURRENT_DELEGATE_INSTANCE: std::cell::Cell<InstanceId> =
const { std::cell::Cell::new(-1) };
}
pub(super) type InstanceId = i64;
pub(super) struct PendingContractData {
pub data: Vec<u8>,
pub cursor: usize,
}
pub(super) static CONTRACT_IO: LazyLock<DashMap<(InstanceId, i64), PendingContractData>> =
LazyLock::new(DashMap::default);
pub(super) fn fill_buffer_impl(instance_id: InstanceId, buf_ptr_offset: i64) -> u32 {
use freenet_stdlib::memory::WasmLinearMem;
use freenet_stdlib::memory::buf::{BufferBuilder, compute_ptr};
let Some(mut pending) = CONTRACT_IO.get_mut(&(instance_id, buf_ptr_offset)) else {
return 0; };
if pending.cursor >= pending.data.len() {
return 0; }
let Some(info) = MEM_ADDR.get(&instance_id) else {
panic!(
"fill_buffer_impl: MEM_ADDR missing for instance {instance_id} — \
this indicates a bug in the WASM runtime (memory info not refreshed)"
);
};
let linear_mem =
unsafe { WasmLinearMem::new(info.start_ptr as *const u8, info.mem_size as u64) };
let builder_ptr = compute_ptr(buf_ptr_offset as *mut BufferBuilder, &linear_mem);
let builder = unsafe { &mut *builder_ptr };
let capacity = builder.capacity();
unsafe {
let read_ptr = compute_ptr(builder.last_read_ptr(), &linear_mem);
let write_ptr = compute_ptr(builder.last_write_ptr(), &linear_mem);
*read_ptr = 0;
*write_ptr = 0;
let remaining = &pending.data[pending.cursor..];
let chunk_size = remaining.len().min(capacity);
let buf_data_ptr = compute_ptr(builder.start(), &linear_mem);
std::ptr::copy_nonoverlapping(remaining.as_ptr(), buf_data_ptr, chunk_size);
let write_ptr = compute_ptr(builder.last_write_ptr(), &linear_mem);
*write_ptr = chunk_size as u32;
pending.cursor += chunk_size;
chunk_size as u32
}
}
pub mod error_codes {
pub const SUCCESS: i32 = 0;
pub const ERR_NOT_IN_PROCESS: i32 = -1;
pub const ERR_SECRET_NOT_FOUND: i32 = -2;
pub const ERR_STORAGE_FAILED: i32 = -3;
pub const ERR_INVALID_PARAM: i32 = -4;
pub const ERR_CONTEXT_TOO_LARGE: i32 = -5;
pub const ERR_BUFFER_TOO_SMALL: i32 = -6;
pub const ERR_MEMORY_BOUNDS: i32 = -7;
}
pub(super) struct DelegateCallEnv {
pub context: Vec<u8>,
secret_store: std::cell::UnsafeCell<*mut SecretsStore>,
pub delegate_key: DelegateKey,
contract_store: *const ContractStore,
state_store_db: Option<Storage>,
delegate_store: std::cell::UnsafeCell<*mut DelegateStore>,
pub(super) creation_depth: u32,
pub(super) creations_this_call: std::cell::Cell<u32>,
origin_contracts: Vec<ContractInstanceId>,
}
unsafe impl Send for DelegateCallEnv {}
unsafe impl Sync for DelegateCallEnv {}
#[derive(Debug)]
#[allow(dead_code)] pub(super) enum DelegateEnvError {
StoreNotConfigured,
ContractCodeNotRegistered,
NoExistingState,
StorageError(String),
}
#[derive(Debug)]
pub(super) enum DelegateCreateError {
DepthExceeded,
CreationsExceeded,
NodeLimitExceeded,
InvalidWasm(String),
StoreFailed(String),
}
impl DelegateCallEnv {
#[allow(clippy::too_many_arguments)]
pub unsafe fn new(
context: Vec<u8>,
secret_store: &mut SecretsStore,
contract_store: &ContractStore,
state_store_db: Option<Storage>,
delegate_key: DelegateKey,
delegate_store: &mut DelegateStore,
creation_depth: u32,
origin_contracts: Vec<ContractInstanceId>,
) -> Self {
Self {
context,
secret_store: std::cell::UnsafeCell::new(secret_store as *mut SecretsStore),
delegate_key,
contract_store: contract_store as *const ContractStore,
state_store_db,
delegate_store: std::cell::UnsafeCell::new(delegate_store as *mut DelegateStore),
creation_depth,
creations_this_call: std::cell::Cell::new(0),
origin_contracts,
}
}
fn secret_store(&self) -> &SecretsStore {
unsafe { &**self.secret_store.get() }
}
#[allow(clippy::mut_from_ref)]
fn secret_store_mut(&self) -> &mut SecretsStore {
unsafe { &mut **self.secret_store.get() }
}
fn contract_store(&self) -> &ContractStore {
unsafe { &*self.contract_store }
}
#[allow(clippy::mut_from_ref)]
fn delegate_store_mut(&self) -> &mut DelegateStore {
unsafe { &mut **self.delegate_store.get() }
}
pub(crate) const MAX_WASM_CODE_SIZE: usize = 10 * 1024 * 1024;
pub(super) fn create_delegate_sync(
&self,
wasm_bytes: &[u8],
params: &[u8],
cipher_bytes: [u8; 32],
nonce_bytes: [u8; 24],
) -> Result<DelegateKey, DelegateCreateError> {
use crate::contract::{
MAX_CREATED_DELEGATES_PER_NODE, MAX_DELEGATE_CREATION_DEPTH,
MAX_DELEGATE_CREATIONS_PER_CALL,
};
use chacha20poly1305::{KeyInit, XChaCha20Poly1305, XNonce};
use freenet_stdlib::prelude::{
Delegate, DelegateCode, DelegateContainer, DelegateWasmAPIVersion, Parameters,
};
let current = self.creations_this_call.get();
if current >= MAX_DELEGATE_CREATIONS_PER_CALL {
return Err(DelegateCreateError::CreationsExceeded);
}
if self.creation_depth >= MAX_DELEGATE_CREATION_DEPTH {
return Err(DelegateCreateError::DepthExceeded);
}
if CREATED_DELEGATES_COUNT.load(Ordering::Relaxed) >= MAX_CREATED_DELEGATES_PER_NODE {
return Err(DelegateCreateError::NodeLimitExceeded);
}
if wasm_bytes.len() > Self::MAX_WASM_CODE_SIZE {
return Err(DelegateCreateError::InvalidWasm(format!(
"WASM code size {} exceeds maximum {} bytes",
wasm_bytes.len(),
Self::MAX_WASM_CODE_SIZE
)));
}
let params_owned = Parameters::from(params.to_vec());
let delegate_code = DelegateCode::from(wasm_bytes.to_vec());
let delegate = Delegate::from((&delegate_code, ¶ms_owned));
let container = DelegateContainer::Wasm(DelegateWasmAPIVersion::V1(delegate));
let child_key = container.key().clone();
let cipher = XChaCha20Poly1305::new((&cipher_bytes).into());
let nonce = XNonce::from(nonce_bytes);
self.secret_store_mut()
.register_delegate(child_key.clone(), cipher, nonce)
.map_err(|e| DelegateCreateError::StoreFailed(e.to_string()))?;
if let Err(e) = self.delegate_store_mut().store_delegate(container) {
self.secret_store_mut().remove_delegate_cipher(&child_key);
return Err(DelegateCreateError::StoreFailed(e.to_string()));
}
self.creations_this_call.set(current + 1);
CREATED_DELEGATES_COUNT.fetch_add(1, Ordering::Relaxed);
if !self.origin_contracts.is_empty() {
DELEGATE_INHERITED_ORIGINS.insert(child_key.clone(), self.origin_contracts.clone());
}
tracing::info!(
parent = %self.delegate_key,
child = %child_key,
depth = self.creation_depth,
creations = current + 1,
"Delegate created child delegate"
);
Ok(child_key)
}
fn resolve_contract_key(
&self,
instance_id: &ContractInstanceId,
) -> Result<ContractKey, DelegateEnvError> {
let code_hash = self
.contract_store()
.code_hash_from_id(instance_id)
.ok_or(DelegateEnvError::ContractCodeNotRegistered)?;
Ok(ContractKey::from_id_and_code(*instance_id, code_hash))
}
pub(super) fn get_contract_state_sync(
&self,
instance_id: &ContractInstanceId,
) -> Result<Option<Vec<u8>>, DelegateEnvError> {
let Some(ref db) = self.state_store_db else {
return Err(DelegateEnvError::StoreNotConfigured);
};
let code_hash = match self.contract_store().code_hash_from_id(instance_id) {
Some(ch) => ch,
None => return Ok(None),
};
let contract_key = ContractKey::from_id_and_code(*instance_id, code_hash);
match db.get_state_sync(&contract_key) {
Ok(Some(wrapped_state)) => Ok(Some(wrapped_state.as_ref().to_vec())),
Ok(None) => Ok(None),
Err(e) => Err(DelegateEnvError::StorageError(e.to_string())),
}
}
pub(super) fn put_contract_state_sync(
&self,
instance_id: &ContractInstanceId,
state: Vec<u8>,
) -> Result<(), DelegateEnvError> {
let Some(ref db) = self.state_store_db else {
return Err(DelegateEnvError::StoreNotConfigured);
};
let contract_key = self.resolve_contract_key(instance_id)?;
db.store_state_sync(
&contract_key,
freenet_stdlib::prelude::WrappedState::new(state),
)
.map_err(|e| DelegateEnvError::StorageError(e.to_string()))
}
pub(super) fn update_contract_state_sync(
&self,
instance_id: &ContractInstanceId,
state: Vec<u8>,
) -> Result<(), DelegateEnvError> {
let Some(ref db) = self.state_store_db else {
return Err(DelegateEnvError::StoreNotConfigured);
};
let contract_key = self.resolve_contract_key(instance_id)?;
match db.update_state_sync(
&contract_key,
freenet_stdlib::prelude::WrappedState::new(state),
) {
Ok(true) => Ok(()),
Ok(false) => Err(DelegateEnvError::NoExistingState),
Err(e) => Err(DelegateEnvError::StorageError(e.to_string())),
}
}
pub(super) fn subscribe_contract_sync(
&self,
instance_id: &ContractInstanceId,
) -> Result<(), DelegateEnvError> {
let _contract_key = self.resolve_contract_key(instance_id)?;
DELEGATE_SUBSCRIPTIONS
.entry(*instance_id)
.or_default()
.insert(self.delegate_key.clone());
Ok(())
}
}
fn current_instance_id() -> InstanceId {
CURRENT_DELEGATE_INSTANCE.with(|c| c.get())
}
#[inline(always)]
fn validate_and_compute_ptr<T>(
ptr: i64,
start_ptr: i64,
size: usize,
mem_size: usize,
) -> Option<*mut T> {
if ptr < 0 {
tracing::warn!("Memory bounds violation: negative offset {ptr}");
return None;
}
let ptr_usize = ptr as usize;
let end_offset = ptr_usize.checked_add(size)?;
if end_offset > mem_size {
tracing::warn!(
"Memory bounds violation: access range [{ptr_usize}, {end_offset}) exceeds current memory size {mem_size}"
);
return None;
}
let host_ptr = start_ptr.checked_add(ptr)?;
Some(host_ptr as *mut T)
}
pub(super) mod log {
use super::*;
pub(crate) fn info(id: i64, ptr: i64, len: i32) {
if id == -1 {
tracing::error!("freenet_log::info called with unset module id");
return;
}
let info = MEM_ADDR.get(&id).expect("instance mem space not recorded");
let Some(ptr) =
validate_and_compute_ptr::<u8>(ptr, info.start_ptr, len as usize, info.mem_size)
else {
tracing::error!("Memory bounds violation in freenet_log::info");
return;
};
let bytes = unsafe { std::slice::from_raw_parts(ptr, len as usize) };
let msg = String::from_utf8_lossy(bytes);
tracing::info!(target: "contract", contract = %info.value().key(), "{msg}");
}
}
pub(super) mod rand {
use ::rand::{RngCore, rng};
use super::*;
pub(crate) fn rand_bytes(id: i64, ptr: i64, len: u32) {
if id == -1 {
tracing::error!(
"freenet_rand::rand_bytes called with unset module id; \
output buffer NOT written — caller will read uninitialized memory"
);
return;
}
let info = MEM_ADDR.get(&id).expect("instance mem space not recorded");
let Some(ptr) =
validate_and_compute_ptr::<u8>(ptr, info.start_ptr, len as usize, info.mem_size)
else {
tracing::error!("Memory bounds violation in freenet_rand::rand_bytes");
return;
};
let slice = unsafe { &mut *std::ptr::slice_from_raw_parts_mut(ptr, len as usize) };
let mut rng = rng();
rng.fill_bytes(slice);
}
}
pub(super) mod time {
use super::*;
use chrono::{DateTime, Utc as UtcOriginal};
pub(crate) fn utc_now(id: i64, ptr: i64) {
if id == -1 {
tracing::error!(
"freenet_time::utc_now called with unset module id; \
output buffer NOT written — caller will read uninitialized memory"
);
return;
}
let info = MEM_ADDR.get(&id).expect("instance mem space not recorded");
let now = UtcOriginal::now();
let Some(ptr) = validate_and_compute_ptr::<DateTime<UtcOriginal>>(
ptr,
info.start_ptr,
std::mem::size_of::<DateTime<UtcOriginal>>(),
info.mem_size,
) else {
tracing::error!("Memory bounds violation in freenet_time::utc_now");
return;
};
unsafe {
ptr.write(now);
};
}
}
pub(super) mod delegate_context {
use super::*;
pub(crate) fn context_len() -> i32 {
let id = current_instance_id();
if id == -1 {
tracing::warn!("delegate context_len called outside process()");
return error_codes::ERR_NOT_IN_PROCESS;
}
let Some(env) = DELEGATE_ENV.get(&id) else {
tracing::warn!("delegate call env not set for instance {id}");
return error_codes::ERR_NOT_IN_PROCESS;
};
let len = env.context.len();
if len > i32::MAX as usize {
return error_codes::ERR_CONTEXT_TOO_LARGE;
}
len as i32
}
pub(crate) fn context_read(ptr: i64, len: i32) -> i32 {
let id = current_instance_id();
if id == -1 {
tracing::warn!("delegate context_read called outside process()");
return error_codes::ERR_NOT_IN_PROCESS;
}
if len < 0 {
tracing::warn!("delegate context_read called with negative length: {len}");
return error_codes::ERR_INVALID_PARAM;
}
let Some(info) = MEM_ADDR.get(&id) else {
tracing::warn!("instance mem space not recorded for {id}");
return error_codes::ERR_NOT_IN_PROCESS;
};
let Some(env) = DELEGATE_ENV.get(&id) else {
tracing::warn!("delegate call env not set for instance {id}");
return error_codes::ERR_NOT_IN_PROCESS;
};
let to_copy = env.context.len().min(len as usize);
if to_copy == 0 {
return 0;
}
let Some(dst) = validate_and_compute_ptr::<u8>(ptr, info.start_ptr, to_copy, info.mem_size)
else {
tracing::error!("Memory bounds violation in delegate context_read");
return error_codes::ERR_MEMORY_BOUNDS;
};
unsafe {
std::ptr::copy_nonoverlapping(env.context.as_ptr(), dst, to_copy);
}
to_copy as i32
}
pub(crate) fn context_write(ptr: i64, len: i32) -> i32 {
let id = current_instance_id();
if id == -1 {
tracing::warn!("delegate context_write called outside process()");
return error_codes::ERR_NOT_IN_PROCESS;
}
if len < 0 {
tracing::warn!("delegate context_write called with negative length: {len}");
return error_codes::ERR_INVALID_PARAM;
}
let Some(info) = MEM_ADDR.get(&id) else {
tracing::warn!("instance mem space not recorded for {id}");
return error_codes::ERR_NOT_IN_PROCESS;
};
let Some(mut env) = DELEGATE_ENV.get_mut(&id) else {
tracing::warn!("delegate call env not set for instance {id}");
return error_codes::ERR_NOT_IN_PROCESS;
};
if len == 0 {
env.context.clear();
return error_codes::SUCCESS;
}
let Some(src) =
validate_and_compute_ptr::<u8>(ptr, info.start_ptr, len as usize, info.mem_size)
else {
tracing::error!("Memory bounds violation in delegate context_write");
return error_codes::ERR_MEMORY_BOUNDS;
};
let bytes = unsafe { std::slice::from_raw_parts(src, len as usize) };
env.context = bytes.to_vec();
error_codes::SUCCESS
}
}
pub(super) mod delegate_secrets {
use super::*;
pub(crate) fn get_secret_len(key_ptr: i64, key_len: i32) -> i32 {
let id = current_instance_id();
if id == -1 {
tracing::warn!("delegate get_secret_len called outside process()");
return error_codes::ERR_NOT_IN_PROCESS;
}
if key_len < 0 {
tracing::warn!("delegate get_secret_len called with negative key_len: {key_len}");
return error_codes::ERR_INVALID_PARAM;
}
let Some(info) = MEM_ADDR.get(&id) else {
tracing::warn!("instance mem space not recorded for {id}");
return error_codes::ERR_NOT_IN_PROCESS;
};
let Some(env) = DELEGATE_ENV.get(&id) else {
tracing::warn!("delegate call env not set for instance {id}");
return error_codes::ERR_NOT_IN_PROCESS;
};
let Some(key_src) = validate_and_compute_ptr::<u8>(
key_ptr,
info.start_ptr,
key_len as usize,
info.mem_size,
) else {
tracing::error!("Memory bounds violation in delegate get_secret_len");
return error_codes::ERR_MEMORY_BOUNDS;
};
let key_bytes = unsafe { std::slice::from_raw_parts(key_src, key_len as usize) };
let secret_id = SecretsId::new(key_bytes.to_vec());
match env.secret_store().get_secret(&env.delegate_key, &secret_id) {
Ok(plaintext) => {
let len = plaintext.len();
if len > i32::MAX as usize {
i32::MAX
} else {
len as i32
}
}
Err(e) => {
tracing::debug!(
delegate = %env.delegate_key,
secret_id = ?secret_id,
error = %e,
"get_secret_len: secret not found or storage error"
);
error_codes::ERR_SECRET_NOT_FOUND
}
}
}
pub(crate) fn get_secret(key_ptr: i64, key_len: i32, out_ptr: i64, out_len: i32) -> i32 {
let id = current_instance_id();
if id == -1 {
tracing::warn!("delegate get_secret called outside process()");
return error_codes::ERR_NOT_IN_PROCESS;
}
if key_len < 0 || out_len < 0 {
tracing::warn!(
"delegate get_secret called with negative lengths: key_len={key_len}, out_len={out_len}"
);
return error_codes::ERR_INVALID_PARAM;
}
let Some(info) = MEM_ADDR.get(&id) else {
tracing::warn!("instance mem space not recorded for {id}");
return error_codes::ERR_NOT_IN_PROCESS;
};
let Some(env) = DELEGATE_ENV.get(&id) else {
tracing::warn!("delegate call env not set for instance {id}");
return error_codes::ERR_NOT_IN_PROCESS;
};
let Some(key_src) = validate_and_compute_ptr::<u8>(
key_ptr,
info.start_ptr,
key_len as usize,
info.mem_size,
) else {
tracing::error!("Memory bounds violation in delegate get_secret (key)");
return error_codes::ERR_MEMORY_BOUNDS;
};
let key_bytes = unsafe { std::slice::from_raw_parts(key_src, key_len as usize) };
let secret_id = SecretsId::new(key_bytes.to_vec());
match env.secret_store().get_secret(&env.delegate_key, &secret_id) {
Ok(plaintext) => {
let secret_len = plaintext.len();
let out_len_usize = out_len as usize;
if secret_len > out_len_usize {
tracing::debug!(
"delegate get_secret buffer too small: need {secret_len}, have {out_len_usize}"
);
return error_codes::ERR_BUFFER_TOO_SMALL;
}
if secret_len == 0 {
return 0;
}
let Some(dst) = validate_and_compute_ptr::<u8>(
out_ptr,
info.start_ptr,
secret_len,
info.mem_size,
) else {
tracing::error!("Memory bounds violation in delegate get_secret (output)");
return error_codes::ERR_MEMORY_BOUNDS;
};
unsafe {
std::ptr::copy_nonoverlapping(plaintext.as_ptr(), dst, secret_len);
}
secret_len as i32
}
Err(e) => {
tracing::debug!(
delegate = %env.delegate_key,
secret_id = ?secret_id,
error = %e,
"get_secret: secret not found or storage error"
);
error_codes::ERR_SECRET_NOT_FOUND
}
}
}
pub(crate) fn set_secret(key_ptr: i64, key_len: i32, val_ptr: i64, val_len: i32) -> i32 {
let id = current_instance_id();
if id == -1 {
tracing::warn!("delegate set_secret called outside process()");
return error_codes::ERR_NOT_IN_PROCESS;
}
if key_len < 0 || val_len < 0 {
tracing::warn!(
"delegate set_secret called with negative lengths: key_len={key_len}, val_len={val_len}"
);
return error_codes::ERR_INVALID_PARAM;
}
let Some(info) = MEM_ADDR.get(&id) else {
tracing::warn!("instance mem space not recorded for {id}");
return error_codes::ERR_NOT_IN_PROCESS;
};
let Some(env) = DELEGATE_ENV.get(&id) else {
tracing::warn!("delegate call env not set for instance {id}");
return error_codes::ERR_NOT_IN_PROCESS;
};
let Some(key_src) = validate_and_compute_ptr::<u8>(
key_ptr,
info.start_ptr,
key_len as usize,
info.mem_size,
) else {
tracing::error!("Memory bounds violation in delegate set_secret (key)");
return error_codes::ERR_MEMORY_BOUNDS;
};
let key_bytes = unsafe { std::slice::from_raw_parts(key_src, key_len as usize) };
let secret_id = SecretsId::new(key_bytes.to_vec());
let Some(val_src) = validate_and_compute_ptr::<u8>(
val_ptr,
info.start_ptr,
val_len as usize,
info.mem_size,
) else {
tracing::error!("Memory bounds violation in delegate set_secret (value)");
return error_codes::ERR_MEMORY_BOUNDS;
};
let value = unsafe { std::slice::from_raw_parts(val_src, val_len as usize) }.to_vec();
match env
.secret_store_mut()
.store_secret(&env.delegate_key, &secret_id, value)
{
Ok(()) => error_codes::SUCCESS,
Err(e) => {
tracing::error!("delegate set_secret failed: {e}");
error_codes::ERR_STORAGE_FAILED
}
}
}
pub(crate) fn has_secret(key_ptr: i64, key_len: i32) -> i32 {
let id = current_instance_id();
if id == -1 {
tracing::warn!("delegate has_secret called outside process()");
return error_codes::ERR_NOT_IN_PROCESS;
}
if key_len < 0 {
tracing::warn!("delegate has_secret called with negative key_len: {key_len}");
return error_codes::ERR_INVALID_PARAM;
}
let Some(info) = MEM_ADDR.get(&id) else {
tracing::warn!("instance mem space not recorded for {id}");
return error_codes::ERR_NOT_IN_PROCESS;
};
let Some(env) = DELEGATE_ENV.get(&id) else {
tracing::warn!("delegate call env not set for instance {id}");
return error_codes::ERR_NOT_IN_PROCESS;
};
let Some(key_src) = validate_and_compute_ptr::<u8>(
key_ptr,
info.start_ptr,
key_len as usize,
info.mem_size,
) else {
tracing::error!("Memory bounds violation in delegate has_secret");
return error_codes::ERR_MEMORY_BOUNDS;
};
let key_bytes = unsafe { std::slice::from_raw_parts(key_src, key_len as usize) };
let secret_id = SecretsId::new(key_bytes.to_vec());
match env.secret_store().get_secret(&env.delegate_key, &secret_id) {
Ok(_) => 1,
Err(e) => {
tracing::debug!(
delegate = %env.delegate_key,
secret_id = ?secret_id,
error = %e,
"has_secret: secret not found or storage error"
);
0
}
}
}
pub(crate) fn remove_secret(key_ptr: i64, key_len: i32) -> i32 {
let id = current_instance_id();
if id == -1 {
tracing::warn!("delegate remove_secret called outside process()");
return error_codes::ERR_NOT_IN_PROCESS;
}
if key_len < 0 {
tracing::warn!("delegate remove_secret called with negative key_len: {key_len}");
return error_codes::ERR_INVALID_PARAM;
}
let Some(info) = MEM_ADDR.get(&id) else {
tracing::warn!("instance mem space not recorded for {id}");
return error_codes::ERR_NOT_IN_PROCESS;
};
let Some(env) = DELEGATE_ENV.get(&id) else {
tracing::warn!("delegate call env not set for instance {id}");
return error_codes::ERR_NOT_IN_PROCESS;
};
let Some(key_src) = validate_and_compute_ptr::<u8>(
key_ptr,
info.start_ptr,
key_len as usize,
info.mem_size,
) else {
tracing::error!("Memory bounds violation in delegate remove_secret");
return error_codes::ERR_MEMORY_BOUNDS;
};
let key_bytes = unsafe { std::slice::from_raw_parts(key_src, key_len as usize) };
let secret_id = SecretsId::new(key_bytes.to_vec());
match env
.secret_store_mut()
.remove_secret(&env.delegate_key, &secret_id)
{
Ok(()) => error_codes::SUCCESS,
Err(e) => {
tracing::debug!(
delegate = %env.delegate_key,
secret_id = ?secret_id,
error = %e,
"remove_secret: secret not found or storage error"
);
error_codes::ERR_SECRET_NOT_FOUND
}
}
}
}
pub(super) mod delegate_contracts {
use super::*;
use crate::wasm_runtime::delegate_api::contract_error_codes;
pub(crate) fn get_contract_state_len_impl(id_ptr: i64, id_len: i32) -> i64 {
let id = current_instance_id();
if id == -1 {
tracing::warn!("delegate get_contract_state_len called outside process()");
return contract_error_codes::ERR_NOT_IN_PROCESS as i64;
}
if id_len != 32 {
tracing::warn!(
"delegate get_contract_state_len: expected 32-byte instance ID, got {id_len}"
);
return contract_error_codes::ERR_INVALID_PARAM as i64;
}
let Some(info) = MEM_ADDR.get(&id) else {
tracing::warn!("instance mem space not recorded for {id}");
return contract_error_codes::ERR_NOT_IN_PROCESS as i64;
};
let Some(env) = DELEGATE_ENV.get(&id) else {
tracing::warn!("delegate call env not set for instance {id}");
return contract_error_codes::ERR_NOT_IN_PROCESS as i64;
};
let Some(id_src) =
validate_and_compute_ptr::<u8>(id_ptr, info.start_ptr, 32, info.mem_size)
else {
tracing::error!("Memory bounds violation in delegate get_contract_state_len");
return contract_error_codes::ERR_MEMORY_BOUNDS as i64;
};
let id_bytes: [u8; 32] = unsafe { std::slice::from_raw_parts(id_src, 32) }
.try_into()
.unwrap();
let contract_id = ContractInstanceId::new(id_bytes);
match env.get_contract_state_sync(&contract_id) {
Ok(Some(state_bytes)) => {
tracing::debug!(
contract = %contract_id,
size = state_bytes.len(),
"V2 delegate: get_contract_state_len succeeded"
);
state_bytes.len() as i64
}
Ok(None) => {
tracing::debug!(
contract = %contract_id,
"V2 delegate: contract not found in local store"
);
contract_error_codes::ERR_CONTRACT_NOT_FOUND as i64
}
Err(e) => {
tracing::error!(
contract = %contract_id,
error = ?e,
"V2 delegate: state store error"
);
delegate_env_error_to_code(&e)
}
}
}
fn delegate_env_error_to_code(err: &DelegateEnvError) -> i64 {
match err {
DelegateEnvError::StoreNotConfigured => contract_error_codes::ERR_STORE_ERROR as i64,
DelegateEnvError::ContractCodeNotRegistered => {
contract_error_codes::ERR_CONTRACT_CODE_NOT_REGISTERED as i64
}
DelegateEnvError::NoExistingState => {
contract_error_codes::ERR_CONTRACT_NOT_FOUND as i64
}
DelegateEnvError::StorageError(_) => contract_error_codes::ERR_STORE_ERROR as i64,
}
}
fn read_instance_id(id_ptr: i64, id_len: i32) -> Result<ContractInstanceId, i64> {
let id = current_instance_id();
if id == -1 {
return Err(contract_error_codes::ERR_NOT_IN_PROCESS as i64);
}
if id_len != 32 {
tracing::warn!("delegate contract host fn: expected 32-byte instance ID, got {id_len}");
return Err(contract_error_codes::ERR_INVALID_PARAM as i64);
}
let Some(info) = MEM_ADDR.get(&id) else {
return Err(contract_error_codes::ERR_NOT_IN_PROCESS as i64);
};
let start_ptr = info.start_ptr;
let mem_size = info.mem_size;
drop(info);
let Some(id_src) = validate_and_compute_ptr::<u8>(id_ptr, start_ptr, 32, mem_size) else {
return Err(contract_error_codes::ERR_MEMORY_BOUNDS as i64);
};
let id_bytes: [u8; 32] = unsafe { std::slice::from_raw_parts(id_src, 32) }
.try_into()
.unwrap();
Ok(ContractInstanceId::new(id_bytes))
}
pub(crate) fn get_contract_state_impl(
id_ptr: i64,
id_len: i32,
out_ptr: i64,
out_len: i64,
) -> i64 {
let id = current_instance_id();
if id == -1 {
tracing::warn!("delegate get_contract_state called outside process()");
return contract_error_codes::ERR_NOT_IN_PROCESS as i64;
}
if id_len != 32 {
tracing::warn!(
"delegate get_contract_state: expected 32-byte instance ID, got {id_len}"
);
return contract_error_codes::ERR_INVALID_PARAM as i64;
}
if out_len < 0 {
tracing::warn!("delegate get_contract_state: negative out_len={out_len}");
return contract_error_codes::ERR_INVALID_PARAM as i64;
}
let Some(info) = MEM_ADDR.get(&id) else {
tracing::warn!("instance mem space not recorded for {id}");
return contract_error_codes::ERR_NOT_IN_PROCESS as i64;
};
let Some(env) = DELEGATE_ENV.get(&id) else {
tracing::warn!("delegate call env not set for instance {id}");
return contract_error_codes::ERR_NOT_IN_PROCESS as i64;
};
let Some(id_src) =
validate_and_compute_ptr::<u8>(id_ptr, info.start_ptr, 32, info.mem_size)
else {
tracing::error!("Memory bounds violation in delegate get_contract_state (id)");
return contract_error_codes::ERR_MEMORY_BOUNDS as i64;
};
let id_bytes: [u8; 32] = unsafe { std::slice::from_raw_parts(id_src, 32) }
.try_into()
.unwrap();
let contract_id = ContractInstanceId::new(id_bytes);
match env.get_contract_state_sync(&contract_id) {
Ok(Some(state_bytes)) => {
let state_len = state_bytes.len();
let out_len_usize = out_len as usize;
if state_len > out_len_usize {
tracing::debug!(
"delegate get_contract_state buffer too small: need {state_len}, have {out_len_usize}"
);
return contract_error_codes::ERR_BUFFER_TOO_SMALL as i64;
}
if state_len == 0 {
return 0;
}
let Some(dst) = validate_and_compute_ptr::<u8>(
out_ptr,
info.start_ptr,
state_len,
info.mem_size,
) else {
tracing::error!(
"Memory bounds violation in delegate get_contract_state (output)"
);
return contract_error_codes::ERR_MEMORY_BOUNDS as i64;
};
unsafe {
std::ptr::copy_nonoverlapping(state_bytes.as_ptr(), dst, state_len);
}
tracing::debug!(
contract = %contract_id,
bytes_written = state_len,
"V2 delegate: get_contract_state succeeded"
);
state_len as i64
}
Ok(None) => {
tracing::debug!(
contract = %contract_id,
"V2 delegate: contract not found"
);
contract_error_codes::ERR_CONTRACT_NOT_FOUND as i64
}
Err(e) => {
tracing::error!(
contract = %contract_id,
error = ?e,
"V2 delegate: state store error"
);
delegate_env_error_to_code(&e)
}
}
}
pub(crate) fn put_contract_state_impl(
id_ptr: i64,
id_len: i32,
state_ptr: i64,
state_len: i64,
) -> i64 {
let contract_id = match read_instance_id(id_ptr, id_len) {
Ok(id) => id,
Err(code) => return code,
};
if state_len < 0 {
tracing::warn!("delegate put_contract_state: negative state_len={state_len}");
return contract_error_codes::ERR_INVALID_PARAM as i64;
}
let id = current_instance_id();
let Some(info) = MEM_ADDR.get(&id) else {
return contract_error_codes::ERR_NOT_IN_PROCESS as i64;
};
let Some(env) = DELEGATE_ENV.get(&id) else {
return contract_error_codes::ERR_NOT_IN_PROCESS as i64;
};
let state_bytes = if state_len == 0 {
vec![]
} else {
let Some(src) = validate_and_compute_ptr::<u8>(
state_ptr,
info.start_ptr,
state_len as usize,
info.mem_size,
) else {
tracing::error!("Memory bounds violation in delegate put_contract_state (state)");
return contract_error_codes::ERR_MEMORY_BOUNDS as i64;
};
unsafe { std::slice::from_raw_parts(src, state_len as usize) }.to_vec()
};
match env.put_contract_state_sync(&contract_id, state_bytes) {
Ok(()) => {
tracing::debug!(
contract = %contract_id,
"V2 delegate: put_contract_state succeeded"
);
contract_error_codes::SUCCESS as i64
}
Err(ref e) => {
tracing::debug!(
contract = %contract_id,
error = ?e,
"V2 delegate: put_contract_state failed"
);
delegate_env_error_to_code(e)
}
}
}
pub(crate) fn update_contract_state_impl(
id_ptr: i64,
id_len: i32,
state_ptr: i64,
state_len: i64,
) -> i64 {
let contract_id = match read_instance_id(id_ptr, id_len) {
Ok(id) => id,
Err(code) => return code,
};
if state_len < 0 {
tracing::warn!("delegate update_contract_state: negative state_len={state_len}");
return contract_error_codes::ERR_INVALID_PARAM as i64;
}
let id = current_instance_id();
let Some(info) = MEM_ADDR.get(&id) else {
return contract_error_codes::ERR_NOT_IN_PROCESS as i64;
};
let Some(env) = DELEGATE_ENV.get(&id) else {
return contract_error_codes::ERR_NOT_IN_PROCESS as i64;
};
let state_bytes = if state_len == 0 {
vec![]
} else {
let Some(src) = validate_and_compute_ptr::<u8>(
state_ptr,
info.start_ptr,
state_len as usize,
info.mem_size,
) else {
tracing::error!(
"Memory bounds violation in delegate update_contract_state (state)"
);
return contract_error_codes::ERR_MEMORY_BOUNDS as i64;
};
unsafe { std::slice::from_raw_parts(src, state_len as usize) }.to_vec()
};
match env.update_contract_state_sync(&contract_id, state_bytes) {
Ok(()) => {
tracing::debug!(
contract = %contract_id,
"V2 delegate: update_contract_state succeeded"
);
contract_error_codes::SUCCESS as i64
}
Err(ref e) => {
tracing::debug!(
contract = %contract_id,
error = ?e,
"V2 delegate: update_contract_state failed"
);
delegate_env_error_to_code(e)
}
}
}
pub(crate) fn subscribe_contract_impl(id_ptr: i64, id_len: i32) -> i64 {
let contract_id = match read_instance_id(id_ptr, id_len) {
Ok(id) => id,
Err(code) => return code,
};
let id = current_instance_id();
let Some(env) = DELEGATE_ENV.get(&id) else {
return contract_error_codes::ERR_NOT_IN_PROCESS as i64;
};
match env.subscribe_contract_sync(&contract_id) {
Ok(()) => {
tracing::debug!(
contract = %contract_id,
"V2 delegate: subscribe_contract succeeded"
);
contract_error_codes::SUCCESS as i64
}
Err(ref e) => {
tracing::debug!(
contract = %contract_id,
error = ?e,
"V2 delegate: subscribe_contract failed"
);
delegate_env_error_to_code(e)
}
}
}
}
pub(super) mod delegate_management {
use super::*;
use crate::wasm_runtime::delegate_api::delegate_mgmt_error_codes;
#[allow(clippy::too_many_arguments)]
pub(crate) fn create_delegate_impl(
wasm_ptr: i64,
wasm_len: i64,
params_ptr: i64,
params_len: i64,
cipher_ptr: i64,
nonce_ptr: i64,
out_key_ptr: i64,
out_hash_ptr: i64,
) -> i32 {
let id = current_instance_id();
if id == -1 {
tracing::warn!("delegate create_delegate called outside process()");
return delegate_mgmt_error_codes::ERR_NOT_IN_PROCESS;
}
if wasm_len < 0 || params_len < 0 {
tracing::warn!("delegate create_delegate: negative length");
return delegate_mgmt_error_codes::ERR_INVALID_PARAM;
}
let wasm_len_usize = wasm_len as usize;
let params_len_usize = params_len as usize;
let Some(info) = MEM_ADDR.get(&id) else {
tracing::warn!("instance mem space not recorded for {id}");
return delegate_mgmt_error_codes::ERR_NOT_IN_PROCESS;
};
let start_ptr = info.start_ptr;
let mem_size = info.mem_size;
drop(info);
let Some(wasm_src) =
validate_and_compute_ptr::<u8>(wasm_ptr, start_ptr, wasm_len_usize, mem_size)
else {
tracing::error!("Memory bounds violation reading WASM bytes in create_delegate");
return delegate_mgmt_error_codes::ERR_MEMORY_BOUNDS;
};
let wasm_bytes = unsafe { std::slice::from_raw_parts(wasm_src, wasm_len_usize) };
let params_bytes = if params_len_usize > 0 {
let Some(params_src) =
validate_and_compute_ptr::<u8>(params_ptr, start_ptr, params_len_usize, mem_size)
else {
tracing::error!("Memory bounds violation reading params in create_delegate");
return delegate_mgmt_error_codes::ERR_MEMORY_BOUNDS;
};
unsafe { std::slice::from_raw_parts(params_src, params_len_usize) }
} else {
&[]
};
let Some(cipher_src) = validate_and_compute_ptr::<u8>(cipher_ptr, start_ptr, 32, mem_size)
else {
tracing::error!("Memory bounds violation reading cipher in create_delegate");
return delegate_mgmt_error_codes::ERR_MEMORY_BOUNDS;
};
let mut cipher_bytes = [0u8; 32];
unsafe { std::ptr::copy_nonoverlapping(cipher_src, cipher_bytes.as_mut_ptr(), 32) };
let Some(nonce_src) = validate_and_compute_ptr::<u8>(nonce_ptr, start_ptr, 24, mem_size)
else {
tracing::error!("Memory bounds violation reading nonce in create_delegate");
return delegate_mgmt_error_codes::ERR_MEMORY_BOUNDS;
};
let mut nonce_bytes = [0u8; 24];
unsafe { std::ptr::copy_nonoverlapping(nonce_src, nonce_bytes.as_mut_ptr(), 24) };
let Some(out_key_dst) =
validate_and_compute_ptr::<u8>(out_key_ptr, start_ptr, 32, mem_size)
else {
tracing::error!("Memory bounds violation for out_key_ptr in create_delegate");
return delegate_mgmt_error_codes::ERR_MEMORY_BOUNDS;
};
let Some(out_hash_dst) =
validate_and_compute_ptr::<u8>(out_hash_ptr, start_ptr, 32, mem_size)
else {
tracing::error!("Memory bounds violation for out_hash_ptr in create_delegate");
return delegate_mgmt_error_codes::ERR_MEMORY_BOUNDS;
};
let Some(env) = DELEGATE_ENV.get(&id) else {
tracing::warn!("delegate call env not set for instance {id}");
return delegate_mgmt_error_codes::ERR_NOT_IN_PROCESS;
};
match env.create_delegate_sync(wasm_bytes, params_bytes, cipher_bytes, nonce_bytes) {
Ok(child_key) => {
let key_bytes = child_key.bytes();
unsafe {
std::ptr::copy_nonoverlapping(key_bytes.as_ptr(), out_key_dst, 32);
}
let code_hash_bytes = child_key.code_hash().as_ref();
unsafe {
std::ptr::copy_nonoverlapping(code_hash_bytes.as_ptr(), out_hash_dst, 32);
}
delegate_mgmt_error_codes::SUCCESS
}
Err(DelegateCreateError::DepthExceeded) => {
tracing::warn!("delegate create_delegate: depth limit exceeded");
delegate_mgmt_error_codes::ERR_DEPTH_EXCEEDED
}
Err(DelegateCreateError::CreationsExceeded) => {
tracing::warn!("delegate create_delegate: per-call creation limit exceeded");
delegate_mgmt_error_codes::ERR_CREATIONS_EXCEEDED
}
Err(DelegateCreateError::NodeLimitExceeded) => {
tracing::warn!("delegate create_delegate: per-node creation limit exceeded");
delegate_mgmt_error_codes::ERR_NODE_LIMIT_EXCEEDED
}
Err(DelegateCreateError::InvalidWasm(msg)) => {
tracing::error!("delegate create_delegate: invalid WASM: {msg}");
delegate_mgmt_error_codes::ERR_INVALID_WASM
}
Err(DelegateCreateError::StoreFailed(msg)) => {
tracing::error!("delegate create_delegate: store failed: {msg}");
delegate_mgmt_error_codes::ERR_STORE_FAILED
}
}
}
}