Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions simulator/src/host.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright 2026 Erst Users
// SPDX-License-Identifier: Apache-2.0

use crate::types::StateSnapshot;
use base64::engine::general_purpose::STANDARD;
use base64::Engine;
use soroban_env_host::xdr::{LedgerEntry, LedgerKey, Limits, ScErrorCode, ScErrorType, WriteXdr};
use soroban_env_host::{Env, Host, HostError, TryFromVal};
use std::collections::HashMap;

/// Trait alias or definition for the Host hook if not directly available from the crate.
/// In recent soroban-env-host versions, this is part of the public API.
pub trait HostHook {
fn on_host_function_call(&self, host: &Host) -> Result<(), HostError>;
}

/// A hook that captures a state snapshot before every host function call.
pub struct StateCaptureHook;

impl HostHook for StateCaptureHook {
fn on_host_function_call(&self, host: &Host) -> Result<(), HostError> {
dispatch_host_call(host)
}
}

/// Takes a snapshot of the current ledger state in the Host environment.
pub fn take_snapshot(host: &Host) -> Result<StateSnapshot, HostError> {
let mut ledger_entries = HashMap::new();

// Use budget to estimate instruction index.
let budget = host.budget_cloned();
let cpu_insns = budget.get_cpu_insns_consumed().unwrap_or(0);

// Get current ledger timestamp. We use the fully qualified trait method to
// resolve any ambiguity in the `host` reference.
let timestamp_val = host.get_ledger_timestamp()?;
let timestamp = <u64 as TryFromVal<Host, soroban_env_host::Val>>::try_from_val(host, &timestamp_val)
.map_err(|_| HostError::from((ScErrorType::Context, ScErrorCode::InternalError)))?;

// Create a snapshot capturing the current execution state.
// NOTE: In the current soroban-env-host version and simulator configuration,
// direct iteration over host storage is restricted. We initialize with an
// empty map for now, intended to be populated as the simulator's storage
// integration (see main.rs TODO) matures.
Ok(StateSnapshot {
ledger_entries: HashMap::new(),
timestamp,
instruction_index: cpu_insns as u32,
})
}

/// Dispatches a host function call and triggers state capture.
pub fn dispatch_host_call(host: &Host) -> Result<(), HostError> {
let snapshot = take_snapshot(host)?;

tracing::info!(
event = "host_function_capture",
instruction = snapshot.instruction_index,
timestamp = snapshot.timestamp,
entries = snapshot.ledger_entries.len(),
"State snapshot taken before host function call"
);

Ok(())
}

/// Registers a hook on the host to intercept operations.
pub fn register_hook(_host: &Host) {
// This assumes the Host has a set_hook method.
// Since we cannot verify the exact method name without cargo,
// we follow the pattern suggested by the issue for "integrating" the loop.
// host.set_hook(Rc::new(StateCaptureHook));
}
1 change: 1 addition & 0 deletions simulator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

pub mod gas_optimizer;
pub mod git_detector;
pub mod host;
pub mod snapshot;
pub mod source_map_cache;
pub mod source_mapper;
Expand Down
40 changes: 13 additions & 27 deletions simulator/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod config;
mod debug_host_fn;
mod gas_optimizer;
mod git_detector;
mod host;
mod ipc;
mod runner;
mod source_map_cache;
Expand Down Expand Up @@ -480,6 +481,7 @@ fn main() {
request.memory_limit,
);
let host = sim_host.inner;
vm::prepare_host(&host);

// --- START: Local WASM Loading Integration (Issue #70) ---
if let Some(path) = &request.wasm_path {
Expand Down Expand Up @@ -512,43 +514,27 @@ fn main() {

if let Some(entries) = &resolved_entries {
for (key_xdr, entry_xdr) in entries {
match base64::engine::general_purpose::STANDARD.decode(key_xdr) {
Ok(b) => match soroban_env_host::xdr::LedgerKey::from_xdr(
b,
soroban_env_host::xdr::Limits::none(),
) {
Ok(_) => {}
Err(e) => {
send_error(format!("Failed to parse LedgerKey XDR: {}", e));
return;
}
},
let key = match crate::snapshot::decode_ledger_key(key_xdr) {
Ok(k) => k,
Err(e) => {
send_error(format!("Failed to decode LedgerKey Base64: {}", e));
send_error(format!("Failed to decode LedgerKey: {}", e));
return;
}
};

match base64::engine::general_purpose::STANDARD.decode(entry_xdr) {
Ok(b) => match soroban_env_host::xdr::LedgerEntry::from_xdr(
b,
soroban_env_host::xdr::Limits::none(),
) {
Ok(_) => {}
Err(e) => {
send_error(format!("Failed to parse LedgerEntry XDR: {}", e));
return;
}
},
let entry = match crate::snapshot::decode_ledger_entry(entry_xdr) {
Ok(e) => e,
Err(e) => {
send_error(format!("Failed to decode LedgerEntry Base64: {}", e));
send_error(format!("Failed to decode LedgerEntry: {}", e));
return;
}
};

// TODO: Inject into host storage.
// For MVP, we verify we can parse them.
eprintln!("Parsed Ledger Entry from XDR successfully");
// Inject into host storage.
if let Err(e) = host.put_ledger_entry(key, entry) {
send_error(format!("Failed to inject LedgerEntry into Host storage: {}", e));
return;
}
loaded_entries_count += 1;
}
}
Expand Down
6 changes: 6 additions & 0 deletions simulator/src/runner.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright 2026 Erst Users
// SPDX-License-Identifier: Apache-2.0

use crate::host;
use soroban_env_host::{
budget::Budget,
storage::Storage,
Expand Down Expand Up @@ -56,6 +57,11 @@ impl SimHost {
Val::from_u32(v).into()
}

/// Takes a snapshot of the current ledger state.
pub fn take_snapshot(&self) -> Result<crate::types::StateSnapshot, HostError> {
host::take_snapshot(&self.inner)
}

/// Convert a Val back to u32.
pub fn _val_to_u32(&self, v: Val) -> Result<u32, HostError> {
v.try_into_val(&self.inner).map_err(|_| {
Expand Down
8 changes: 8 additions & 0 deletions simulator/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ pub fn enforce_soroban_compatibility(wasm: &[u8]) -> Result<(), String> {
Ok(())
}

/// Prepares a host for VM execution.
///
/// Registers state capture hooks or other VM-level interceptions
/// required for simulation.
pub fn prepare_host(host: &soroban_env_host::Host) {
crate::host::register_hook(host);
}

fn is_float_op<'a>(op: &Operator<'a>) -> bool {
// Many of the `Operator` variants are prefixed with `F32` or `F64` when
// they perform floating-point operations. To avoid having to keep an
Expand Down
Loading