Skip to content

Commit 5dde706

Browse files
feat: implemented delete_unaccessed function (#2099)
Signed-off-by: Dori Medini <dori@starkware.co>
1 parent cd6164e commit 5dde706

File tree

4 files changed

+134
-13
lines changed

4 files changed

+134
-13
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
#### Upcoming Changes
44

5-
* fix: also mark PC as accessed in run_instruction [#2106](https://github.com/lambdaclass/cairo-vm/pull/2106)
5+
* feat: implemented delete_unaccessed function [#2099](https://github.com/lambdaclass/cairo-vm/pull/2099)
6+
7+
* fix: also mark PC as accessed in run_instruction [#2106](https://github.com/lambdaclass/cairo-vm/pull/2106)
68

79
#### [2.1.0] - 2025-05-21
810

vm/src/vm/errors/memory_errors.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ use crate::types::{
1515

1616
#[derive(Debug, PartialEq, Error)]
1717
pub enum MemoryError {
18+
#[error("Cell {0} has already been accessed; it cannot be removed.")]
19+
UnsetAccessedCell(Relocatable),
20+
#[error("Cell {0} is not allocated; it cannot be removed.")]
21+
UnsetUnallocatedCell(Relocatable),
1822
#[error(transparent)]
1923
Math(#[from] MathError),
2024
#[error(transparent)]

vm/src/vm/vm_core.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,17 @@ impl VirtualMachine {
947947
self.segments.memory.insert_value(key, val)
948948
}
949949

950+
/// Removes (unsets) a value from a memory cell that was not accessed by the VM.
951+
///
952+
/// This function can be used to implement lazy opening of merkelized contracts. The full
953+
/// program is initially loaded into memory via a hint. After execution, any entry points to
954+
/// contract segments that were not accessed are replaced with an invalid opcode.
955+
///
956+
/// [Use case](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/core/os/contract_class/compiled_class.cairo#L244-L253)
957+
pub fn delete_unaccessed(&mut self, addr: Relocatable) -> Result<(), MemoryError> {
958+
self.segments.memory.delete_unaccessed(addr)
959+
}
960+
950961
///Writes data into the memory from address ptr and returns the first address after the data.
951962
pub fn load_data(
952963
&mut self,
@@ -4733,6 +4744,84 @@ mod tests {
47334744
assert_eq!(vm.segments.compute_effective_sizes(), &vec![4]);
47344745
}
47354746

4747+
#[test]
4748+
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
4749+
fn test_delete_unaccessed() {
4750+
let mut vm = vm!();
4751+
4752+
let segment0 = vm.segments.add();
4753+
let segment1 = vm.segments.add();
4754+
let segment2 = vm.segments.add();
4755+
let segment3 = vm.segments.add();
4756+
let tmp_segment = vm.add_temporary_segment();
4757+
assert_eq!(segment0.segment_index, 0);
4758+
assert_eq!(segment1.segment_index, 1);
4759+
assert_eq!(segment2.segment_index, 2);
4760+
assert_eq!(segment3.segment_index, 3);
4761+
assert_eq!(tmp_segment.segment_index, -1);
4762+
vm.segments.memory = memory![
4763+
((0, 1), 1),
4764+
((1, 0), 3),
4765+
((1, 1), 4),
4766+
((2, 0), 7),
4767+
((3, 0), 7),
4768+
((-1, 0), 5),
4769+
((-1, 1), 5),
4770+
((-1, 2), 5)
4771+
];
4772+
vm.run_finished = true;
4773+
4774+
vm.mark_address_range_as_accessed((2, 0).into(), 1).unwrap();
4775+
4776+
let cell0 = Relocatable::from((0, 0));
4777+
let cell1 = Relocatable::from((1, 1));
4778+
let cell2 = Relocatable::from((2, 0));
4779+
let cell3 = Relocatable::from((3, 7));
4780+
let cell7 = Relocatable::from((7, 17));
4781+
let cell_tmp = Relocatable::from((-1, 1));
4782+
vm.delete_unaccessed(cell0).unwrap();
4783+
vm.delete_unaccessed(cell1).unwrap();
4784+
vm.delete_unaccessed(cell_tmp).unwrap();
4785+
4786+
// Check that the cells were set to NONE.
4787+
assert!(vm
4788+
.segments
4789+
.memory
4790+
.get_cell_for_testing(cell0)
4791+
.unwrap()
4792+
.is_none());
4793+
assert!(vm
4794+
.segments
4795+
.memory
4796+
.get_cell_for_testing(cell1)
4797+
.unwrap()
4798+
.is_none());
4799+
assert!(vm
4800+
.segments
4801+
.memory
4802+
.get_cell_for_testing(cell_tmp)
4803+
.unwrap()
4804+
.is_none());
4805+
// Segment 3 cell was out of offset range, so it should not be modified or allocated.
4806+
assert!(vm.segments.memory.get_cell_for_testing(cell3).is_none());
4807+
// Segment 2 cell was accessed, so attempting to unset the memory should result in error.
4808+
assert_matches!(
4809+
vm.delete_unaccessed(cell2).unwrap_err(),
4810+
MemoryError::UnsetAccessedCell(relocatable) if relocatable == cell2
4811+
);
4812+
// Segment 3 is unallocated, so attempting to unset the memory should result in error.
4813+
assert_matches!(
4814+
vm.delete_unaccessed(cell3).unwrap_err(),
4815+
MemoryError::UnsetUnallocatedCell(relocatable) if relocatable == cell3
4816+
);
4817+
// Segment 7 was not allocated, so attempting to unset the memory should result in error.
4818+
assert_matches!(
4819+
vm.delete_unaccessed(cell7).unwrap_err(),
4820+
MemoryError::UnallocatedSegment(boxed)
4821+
if *boxed == (cell7.segment_index.try_into().unwrap(), vm.segments.memory.data.len())
4822+
);
4823+
}
4824+
47364825
#[test]
47374826
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
47384827
fn mark_as_accessed() {

vm/src/vm/vm_memory/memory.rs

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,18 @@ impl Memory {
185185
}
186186
}
187187

188+
fn get_segment(&mut self, key: Relocatable) -> Result<&mut Vec<MemoryCell>, MemoryError> {
189+
let (value_index, _) = from_relocatable_to_indexes(key);
190+
let data = if key.segment_index.is_negative() {
191+
&mut self.temp_data
192+
} else {
193+
&mut self.data
194+
};
195+
let data_len = data.len();
196+
data.get_mut(value_index)
197+
.ok_or_else(|| MemoryError::UnallocatedSegment(Box::new((value_index, data_len))))
198+
}
199+
188200
/// Inserts a value into a memory address
189201
/// Will return an Error if the segment index given by the address corresponds to a non-allocated segment,
190202
/// or if the inserted value is inconsistent with the current value at the memory cell
@@ -194,18 +206,8 @@ impl Memory {
194206
MaybeRelocatable: From<V>,
195207
{
196208
let val = MaybeRelocatable::from(val);
197-
let (value_index, value_offset) = from_relocatable_to_indexes(key);
198-
199-
let data = if key.segment_index.is_negative() {
200-
&mut self.temp_data
201-
} else {
202-
&mut self.data
203-
};
204-
205-
let data_len = data.len();
206-
let segment = data
207-
.get_mut(value_index)
208-
.ok_or_else(|| MemoryError::UnallocatedSegment(Box::new((value_index, data_len))))?;
209+
let segment = self.get_segment(key)?;
210+
let (_, value_offset) = from_relocatable_to_indexes(key);
209211

210212
//Check if the element is inserted next to the last one on the segment
211213
//Forgoing this check would allow data to be inserted in a different index
@@ -237,6 +239,25 @@ impl Memory {
237239
self.validate_memory_cell(key)
238240
}
239241

242+
pub(crate) fn delete_unaccessed(&mut self, addr: Relocatable) -> Result<(), MemoryError> {
243+
let (_, offset) = from_relocatable_to_indexes(addr);
244+
let segment = self.get_segment(addr)?;
245+
246+
// Make sure the offset exists.
247+
if offset >= segment.len() {
248+
return Err(MemoryError::UnsetUnallocatedCell(addr));
249+
}
250+
251+
// Ensure the cell has not been accessed.
252+
if segment[offset].is_accessed() {
253+
return Err(MemoryError::UnsetAccessedCell(addr));
254+
}
255+
256+
// Unset the cell.
257+
segment[offset] = MemoryCell::NONE;
258+
Ok(())
259+
}
260+
240261
/// Retrieve a value from memory (either normal or temporary) and apply relocation rules
241262
pub(crate) fn get<'a, 'b: 'a, K: 'a>(&'b self, key: &'a K) -> Option<Cow<'b, MaybeRelocatable>>
242263
where
@@ -671,6 +692,11 @@ impl Memory {
671692
data.get(i)?.get(j)
672693
}
673694

695+
#[cfg(test)]
696+
pub(crate) fn get_cell_for_testing(&self, addr: Relocatable) -> Option<&MemoryCell> {
697+
self.get_cell(addr)
698+
}
699+
674700
pub fn is_accessed(&self, addr: &Relocatable) -> Result<bool, MemoryError> {
675701
Ok(self
676702
.get_cell(*addr)

0 commit comments

Comments
 (0)