use super::*;
const CALL_FLAG_WRITE_STATES: u8 = 0b0010;
impl ExecutionContext {
pub(crate) fn handle_storage_syscall(&mut self, name: &str) -> Result<bool, RuntimeError> {
match name {
"System.Storage.GetContext" | "System.Storage.GetReadOnlyContext" => {
self.push_stack(StackItem::byte_array(Vec::new()))?;
Ok(true)
}
"System.Storage.AsReadOnly" => {
let context = self.pop_stack()?;
self.push_stack(context)?;
Ok(true)
}
"System.Storage.Get" => {
let _context = self.pop_stack()?; let slot_item = self.pop_stack()?;
let key = Self::stack_item_to_bytes(slot_item);
let value = if let Some(entry) = self.storage_overlay.get(&key) {
entry.value.clone().unwrap_or_default()
} else {
let fetched = self.fetch_storage_value(&key)?;
let bytes = fetched.clone().unwrap_or_default();
self.storage_overlay.insert(
key.clone(),
OverlayEntry {
value: fetched,
dirty: false,
},
);
bytes
};
self.push_stack(StackItem::byte_array(value))?;
Ok(true)
}
"System.Storage.Put" => {
if self.active_call_flags & CALL_FLAG_WRITE_STATES == 0 {
return Err(RuntimeError::ExecutionError {
message: "Storage.Put requires CallFlag.WriteStates".to_string(),
});
}
let _context = self.pop_stack()?; let slot_item = self.pop_stack()?; let value_item = self.pop_stack()?;
let key = Self::stack_item_to_bytes(slot_item);
let value = Self::stack_item_to_bytes(value_item);
const MAX_STORAGE_KEY_SIZE: usize = 64;
const MAX_STORAGE_VALUE_SIZE: usize = 65535;
if key.len() > MAX_STORAGE_KEY_SIZE {
return Err(RuntimeError::ExecutionError {
message: format!(
"Storage.Put: key length {} exceeds Neo N3 MaxStorageKeySize ({MAX_STORAGE_KEY_SIZE})",
key.len()
),
});
}
if value.len() > MAX_STORAGE_VALUE_SIZE {
return Err(RuntimeError::ExecutionError {
message: format!(
"Storage.Put: value length {} exceeds Neo N3 MaxStorageValueSize ({MAX_STORAGE_VALUE_SIZE})",
value.len()
),
});
}
self.enforce_storage_limit(&key, &value)?;
let entry =
self.storage_overlay
.entry(key.clone())
.or_insert_with(|| OverlayEntry {
value: None,
dirty: false,
});
entry.value = if value.is_empty() { None } else { Some(value) };
entry.dirty = true;
Ok(true)
}
"System.Storage.Delete" => {
if self.active_call_flags & CALL_FLAG_WRITE_STATES == 0 {
return Err(RuntimeError::ExecutionError {
message: "Storage.Delete requires CallFlag.WriteStates".to_string(),
});
}
let _context = self.pop_stack()?; let slot_item = self.pop_stack()?; let key = Self::stack_item_to_bytes(slot_item);
self.storage_overlay.insert(
key,
OverlayEntry {
value: None,
dirty: true,
},
);
Ok(true)
}
"System.Storage.Find" => {
let _context = self.pop_stack()?;
let prefix = Self::stack_item_to_bytes(self.pop_stack()?);
let options = Self::stack_item_to_int(self.pop_stack()?);
let entries = self.build_storage_entries(prefix, options)?;
let token = self.allocate_iterator(entries);
self.push_stack(token)?;
Ok(true)
}
_ => Ok(false),
}
}
fn enforce_storage_limit(&self, key: &[u8], value: &[u8]) -> Result<(), RuntimeError> {
let mut current: usize = 0;
for (k, entry) in self.storage_overlay.iter() {
if let Some(v) = &entry.value {
current = current.saturating_add(k.len()).saturating_add(v.len());
}
}
let replaced = self
.storage_overlay
.get(key)
.and_then(|e| e.value.as_ref().map(|v| key.len() + v.len()))
.unwrap_or(0);
let after_replace = current.saturating_sub(replaced);
let new_entry = if value.is_empty() {
0 } else {
key.len().saturating_add(value.len())
};
let projected = after_replace.saturating_add(new_entry);
if projected > self.storage_limit {
return Err(RuntimeError::ExecutionError {
message: format!(
"Storage.Put: would exceed storage_limit ({} + {} > {})",
after_replace, new_entry, self.storage_limit
),
});
}
Ok(())
}
}