use std::cell::RefCell;
use std::rc::Rc;
use calimero_primitives::context::ContextId;
use calimero_primitives::identity::PublicKey;
use calimero_storage::env::RuntimeEnv;
use calimero_storage::store::Key;
use calimero_store::{key, types, Store};
use tracing::warn;
pub fn create_runtime_env(
store: &Store,
context_id: ContextId,
executor_id: PublicKey,
) -> RuntimeEnv {
let callbacks = create_storage_callbacks(store, context_id);
RuntimeEnv::new(
callbacks.read,
callbacks.write,
callbacks.remove,
*context_id.as_ref(),
*executor_id.as_ref(),
)
}
#[expect(
clippy::type_complexity,
reason = "Matches RuntimeEnv callback signatures"
)]
struct StorageCallbacks {
read: Rc<dyn Fn(&Key) -> Option<Vec<u8>>>,
write: Rc<dyn Fn(Key, &[u8]) -> bool>,
remove: Rc<dyn Fn(&Key) -> bool>,
}
#[expect(
clippy::type_complexity,
reason = "Matches RuntimeEnv callback signatures"
)]
fn create_storage_callbacks(store: &Store, context_id: ContextId) -> StorageCallbacks {
let read: Rc<dyn Fn(&Key) -> Option<Vec<u8>>> = {
let handle = store.handle();
let ctx_id = context_id;
Rc::new(move |key: &Key| {
let storage_key = key.to_bytes();
let state_key = key::ContextState::new(ctx_id, storage_key);
match handle.get(&state_key) {
Ok(Some(state)) => Some(state.value.into_boxed().into_vec()),
Ok(None) => None,
Err(e) => {
warn!(
%ctx_id,
storage_key = %hex::encode(storage_key),
error = ?e,
"Storage read failed"
);
None
}
}
})
};
let write: Rc<dyn Fn(Key, &[u8]) -> bool> = {
let handle_cell: Rc<RefCell<_>> = Rc::new(RefCell::new(store.handle()));
let ctx_id = context_id;
Rc::new(move |key: Key, value: &[u8]| {
let storage_key = key.to_bytes();
let state_key = key::ContextState::new(ctx_id, storage_key);
let slice: calimero_store::slice::Slice<'_> = value.to_vec().into();
let state_value = types::ContextState::from(slice);
handle_cell
.borrow_mut()
.put(&state_key, &state_value)
.is_ok()
})
};
let remove: Rc<dyn Fn(&Key) -> bool> = {
let handle_cell: Rc<RefCell<_>> = Rc::new(RefCell::new(store.handle()));
let ctx_id = context_id;
Rc::new(move |key: &Key| {
let storage_key = key.to_bytes();
let state_key = key::ContextState::new(ctx_id, storage_key);
handle_cell.borrow_mut().delete(&state_key).is_ok()
})
};
StorageCallbacks {
read,
write,
remove,
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use super::*;
use calimero_storage::env::with_runtime_env;
use calimero_storage::index::Index;
use calimero_storage::store::MainStorage;
use calimero_store::db::InMemoryDB;
#[test]
fn test_create_runtime_env_with_inmemory() {
let db = InMemoryDB::owned();
let store = Store::new(Arc::new(db));
let context_id = ContextId::from([1u8; 32]);
let identity = PublicKey::from([2u8; 32]);
let env = create_runtime_env(&store, context_id, identity);
let result = with_runtime_env(env, || {
Index::<MainStorage>::get_index(calimero_storage::address::Id::root())
});
assert!(result.is_ok());
}
#[test]
fn test_write_and_read_entity_via_bridge() {
use calimero_storage::address::Id;
use calimero_storage::entities::Metadata;
use calimero_storage::interface::{Action, ApplyContext, Interface};
let db = InMemoryDB::owned();
let store = Store::new(Arc::new(db));
let context_id = ContextId::from([1u8; 32]);
let identity = PublicKey::from([2u8; 32]);
let env = create_runtime_env(&store, context_id, identity);
let root_id = Id::new(*context_id.as_ref());
let write_result = with_runtime_env(env.clone(), || {
Interface::<MainStorage>::apply_action(
Action::Update {
id: root_id,
data: vec![],
ancestors: vec![],
metadata: Metadata::default(),
},
&ApplyContext::empty(),
)
});
assert!(write_result.is_ok(), "apply_action should succeed");
let read_result =
with_runtime_env(env.clone(), || Index::<MainStorage>::get_index(root_id));
assert!(read_result.is_ok(), "get_index should not error");
assert!(
read_result.unwrap().is_some(),
"root entity should exist after apply_action"
);
let hash_result = with_runtime_env(env.clone(), || {
Index::<MainStorage>::get_hashes_for(root_id)
});
assert!(hash_result.is_ok());
let hashes = hash_result.unwrap();
assert!(hashes.is_some(), "root should have hashes");
let db2 = InMemoryDB::owned();
let store2 = Store::new(Arc::new(db2));
{
let src_handle = store.handle();
let mut dst_handle = store2.handle();
let mut copied = 0;
let mut iter = src_handle
.iter::<calimero_store::key::ContextState>()
.unwrap();
for (key_result, value_result) in iter.entries() {
let key = key_result.unwrap();
let value = value_result.unwrap();
if key.context_id() == context_id {
let state_key = key.state_key();
let dst_key = calimero_store::key::ContextState::new(context_id, state_key);
let slice: calimero_store::slice::Slice<'_> = value.value.to_vec().into();
let dst_value = calimero_store::types::ContextState::from(slice);
dst_handle.put(&dst_key, &dst_value).unwrap();
copied += 1;
}
}
eprintln!("Copied {} ContextState records", copied);
assert!(copied > 0, "should have copied records");
}
let env2 = create_runtime_env(&store2, context_id, identity);
let read_result2 = with_runtime_env(env2, || Index::<MainStorage>::get_index(root_id));
eprintln!("Read from store2: {:?}", read_result2);
assert!(
read_result2.is_ok(),
"get_index from snapshot-restored store should not error: {:?}",
read_result2.err()
);
assert!(
read_result2.unwrap().is_some(),
"root entity should exist in snapshot-restored store"
);
}
}