Skip to content

Commit 6aec9d9

Browse files
starknet_os: allocate and replace aliases test
1 parent 295c5e9 commit 6aec9d9

File tree

2 files changed

+275
-22
lines changed

2 files changed

+275
-22
lines changed

crates/starknet_os/src/test_utils/utils.rs

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ use std::sync::LazyLock;
44

55
use cairo_vm::hint_processor::builtin_hint_processor::dict_hint_utils::DICT_ACCESS_SIZE;
66
use cairo_vm::types::layout_name::LayoutName;
7+
use cairo_vm::types::relocatable::{MaybeRelocatable, Relocatable};
8+
use cairo_vm::vm::vm_core::VirtualMachine;
79
use ethnum::U256;
810
use num_bigint::{BigInt, Sign};
911
use rand::rngs::StdRng;
@@ -17,8 +19,6 @@ use crate::test_utils::cairo_runner::{
1719
EndpointArg,
1820
EntryPointRunnerConfig,
1921
ImplicitArg,
20-
PointerArg,
21-
ValueArg,
2222
};
2323

2424
#[allow(clippy::too_many_arguments)]
@@ -49,22 +49,33 @@ pub fn run_cairo_function_and_check_result(
4949
Ok(())
5050
}
5151

52-
pub fn create_squashed_cairo_dict(
53-
prev_values: &HashMap<Felt, EndpointArg>,
54-
new_values: &HashMap<Felt, EndpointArg>,
55-
) -> PointerArg {
56-
let mut squashed_dict: Vec<EndpointArg> = vec![];
52+
/// Creates a squashed dict from previous and new values, and stores it in a new memory segment.
53+
pub fn allocate_squashed_cairo_dict(
54+
prev_values: &HashMap<Felt, MaybeRelocatable>,
55+
new_values: &HashMap<Felt, MaybeRelocatable>,
56+
vm: &mut VirtualMachine,
57+
) -> (Relocatable, Relocatable) {
58+
let squashed_dict = flatten_cairo_dict(prev_values, new_values);
59+
let dict_segment_start = vm.add_memory_segment();
60+
let dict_segment_end = vm.load_data(dict_segment_start, &squashed_dict).unwrap();
61+
(dict_segment_start, dict_segment_end)
62+
}
63+
64+
pub fn flatten_cairo_dict(
65+
prev_values: &HashMap<Felt, MaybeRelocatable>,
66+
new_values: &HashMap<Felt, MaybeRelocatable>,
67+
) -> Vec<MaybeRelocatable> {
68+
let mut squashed_dict = vec![];
5769
let mut sorted_new_values: Vec<_> = new_values.iter().collect();
5870
sorted_new_values.sort_by_key(|(key, _)| *key);
5971

6072
for (key, value) in sorted_new_values {
61-
let prev_value: &EndpointArg =
62-
prev_values.get(key).unwrap_or(&EndpointArg::Value(ValueArg::Single(Felt::ZERO)));
73+
let prev_value = prev_values.get(key).unwrap_or(&MaybeRelocatable::Int(Felt::ZERO));
6374
squashed_dict.push((*key).into());
6475
squashed_dict.push(prev_value.clone());
6576
squashed_dict.push(value.clone());
6677
}
67-
PointerArg::Composed(squashed_dict)
78+
squashed_dict
6879
}
6980

7081
pub fn parse_squashed_cairo_dict(squashed_dict: &[Felt]) -> HashMap<Felt, Felt> {

crates/starknet_os/src/tests/aliases.rs

Lines changed: 254 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
use std::collections::{HashMap, HashSet};
1+
use core::panic;
2+
use std::collections::{BTreeSet, HashMap, HashSet};
23

34
use apollo_starknet_os_program::test_programs::ALIASES_TEST_BYTES;
45
use blockifier::state::stateful_compression::{ALIAS_COUNTER_STORAGE_KEY, INITIAL_AVAILABLE_ALIAS};
@@ -15,20 +16,25 @@ use starknet_types_core::felt::Felt;
1516

1617
use crate::test_utils::cairo_runner::{
1718
initialize_and_run_cairo_0_entry_point,
19+
initialize_cairo_runner,
20+
run_cairo_0_entrypoint,
1821
EndpointArg,
1922
EntryPointRunnerConfig,
2023
ImplicitArg,
2124
PointerArg,
2225
ValueArg,
2326
};
2427
use crate::test_utils::utils::{
28+
allocate_squashed_cairo_dict,
29+
flatten_cairo_dict,
2530
get_entrypoint_runner_config,
2631
parse_squashed_cairo_dict,
2732
test_cairo_function,
2833
};
2934

3035
// TODO(Nimrod): Move this next to the stateful compression hints implementation.
31-
// TODO(Amos): This test is incomplete. Add the rest of the test cases and remove this todo.
36+
37+
const DEFAULT_CLASS_HASH: u128 = 7777;
3238

3339
#[test]
3440
fn test_constants() {
@@ -290,22 +296,258 @@ fn allocate_aliases_for_keys_and_replace(
290296
Some(state_reader),
291297
)
292298
.unwrap();
293-
if let [
299+
let [
294300
EndpointArg::Value(ValueArg::Single(n_aliases)),
295301
EndpointArg::Pointer(PointerArg::Array(aliases_storage_updates)),
296302
EndpointArg::Pointer(PointerArg::Array(alias_per_key)),
297303
] = explicit_return_values.as_slice()
298-
{
299-
let n_aliases = felt_to_usize(n_aliases).unwrap();
300-
assert_eq!(n_aliases, aliases_storage_updates.len() / DICT_ACCESS_SIZE);
301-
let aliases_storage_updates_as_felts: Vec<Felt> =
302-
aliases_storage_updates.iter().map(|f| f.get_int().unwrap()).collect();
303-
let actual_alias_storage = parse_squashed_cairo_dict(&aliases_storage_updates_as_felts);
304-
let alias_per_key: Vec<Felt> = alias_per_key.iter().map(|f| f.get_int().unwrap()).collect();
305-
(actual_alias_storage, alias_per_key)
306-
} else {
304+
else {
307305
panic!(
308306
"The return value doesn't match the given format.\n Got: {explicit_return_values:?}"
309307
);
308+
};
309+
let n_aliases = felt_to_usize(n_aliases).unwrap();
310+
assert_eq!(n_aliases, aliases_storage_updates.len() / DICT_ACCESS_SIZE);
311+
let aliases_storage_updates_as_felts: Vec<Felt> =
312+
aliases_storage_updates.iter().map(|f| f.get_int().unwrap()).collect();
313+
let actual_alias_storage = parse_squashed_cairo_dict(&aliases_storage_updates_as_felts);
314+
let alias_per_key: Vec<Felt> = alias_per_key.iter().map(|f| f.get_int().unwrap()).collect();
315+
(actual_alias_storage, alias_per_key)
316+
}
317+
318+
#[rstest]
319+
#[case::non_allocation_of_address_lt_16_from_empty_storage(
320+
HashMap::from([
321+
(
322+
15,
323+
HashMap::from([(5534, 1), (98435, 1), (99999, 1)])
324+
),
325+
(
326+
16,
327+
HashMap::from([(11, 1), (127, 1), (128, 1), (129, 1), (225, 1), (7659, 1)])
328+
),
329+
(
330+
7659,
331+
HashMap::from([(12, 0), (200, 1), (300, 1), (1111, 1)])
332+
),
333+
(
334+
99999,
335+
HashMap::from([(225, 1)])
336+
)
337+
]),
338+
HashMap::new(),
339+
HashMap::new(),
340+
HashMap::new(),
341+
HashMap::from([(0, 136), (128, 128), (129, 129), (200, 132), (225, 130), (300, 133), (1111, 134), (7659, 131), (99999, 135)])
342+
)]
343+
#[case::non_allocation_of_address_lt_16_from_non_empty_storage(
344+
HashMap::from([
345+
(
346+
9,
347+
HashMap::from([(5534, 1), (98435, 1), (99999, 1)])
348+
),
349+
(
350+
44,
351+
HashMap::from([(11, 1), (129, 1), (225, 1), (7659, 1)])
352+
),
353+
(
354+
400,
355+
HashMap::from([(225, 1), (400, 1), (700, 1), (701, 1), (1111, 1)])
356+
),
357+
]),
358+
HashMap::new(),
359+
HashMap::new(),
360+
HashMap::from([(0, 135), (129, 128), (225, 129), (7659, 130), (200, 131), (300, 132), (1111, 133), (99999, 134)]),
361+
HashMap::from([(0, 138), (129, 128), (225, 129), (400, 135), (700, 136), (701, 137), (1111, 133), (7659, 130)]
362+
)
363+
)]
364+
#[case::non_allocation_with_only_trivial_updates(
365+
HashMap::from([
366+
(
367+
11,
368+
HashMap::from([(5534, 1), (98435, 1), (99999, 1)])
369+
),
370+
(
371+
44,
372+
HashMap::from([(11, 0), (129, 1), (225, 0), (400, 1), (7659, 1)])
373+
),
374+
(
375+
400,
376+
HashMap::from([(225, 0), (406, 0), (700, 1), (701, 1), (1111, 1)])
377+
),
378+
(
379+
598,
380+
HashMap::from([(2255, 0), (7008, 0)]) // Trivial update.
381+
)
382+
]),
383+
HashMap::new(),
384+
HashMap::new(),
385+
HashMap::new(),
386+
HashMap::from([(0, 134), (129, 128), (400, 129), (700, 131), (701, 132), (1111, 133), (7659, 130)]
387+
)
388+
)]
389+
#[case::allocation_with_only_nonce_change(
390+
HashMap::new(),
391+
HashMap::from([
392+
(13, 1),
393+
(58, 1),
394+
(11111, DEFAULT_CLASS_HASH), // Gets a new nonce.
395+
(222222, 1),
396+
(3333333, 1),
397+
(3333336, DEFAULT_CLASS_HASH), // Nothing changed.
398+
]),
399+
HashMap::from([(11111, 1)]),
400+
HashMap::new(),
401+
HashMap::from([(0, 131), (11111, 128), (222222, 129), (3333333, 130)]
402+
)
403+
)]
404+
#[case::non_allocation_with_trivial_class_hash_update(
405+
HashMap::new(),
406+
HashMap::from([(24, 1), (5000, 1), (6666, 1), (9999, 1), (11111, DEFAULT_CLASS_HASH),
407+
]),
408+
HashMap::new(),
409+
HashMap::from([(0, 133), (5000, 128), (11111, 129), (222222, 130), (3333333, 131), (87777, 132)]),
410+
HashMap::from([(0, 135), (5000, 128), (6666, 133), (9999, 134)]
411+
)
412+
)]
413+
#[case::allocation_with_partially_trivial_updates(
414+
HashMap::from([
415+
(
416+
1,
417+
HashMap::from([(777, 1), (8888, 1), (9999, 1)]) // No aliases.
418+
),
419+
(
420+
100,
421+
HashMap::from([(200, 1), (777, 1), (888, 0)]) // Aliases for non-trivial diffs.
422+
),
423+
(
424+
600,
425+
HashMap::from([(2000, 1), (3000, 1)])
426+
),
427+
(
428+
800,
429+
HashMap::from([(700, 1), (701, 1)])
430+
),
431+
(
432+
3000,
433+
HashMap::from([(600, 1), (2000, 1)])
434+
),
435+
(
436+
10000,
437+
HashMap::from([(34567, 0), (435, 0)]) // No aliases (all diffs trivial).
438+
)
439+
]),
440+
HashMap::from([(200, 1), (500, 1), (700, 1), (800, DEFAULT_CLASS_HASH)]),
441+
HashMap::from([(700, 1), (10000, 0)]),
442+
HashMap::new(),
443+
HashMap::from([(0, 137), (200, 128), (500, 130), (600, 133), (700, 134), (701, 135), (777, 129), (800, 136), (2000, 131), (3000, 132)])
444+
)]
445+
fn test_allocate_addresses_for_state_diff_and_replace(
446+
#[case] storage_updates: HashMap<u128, HashMap<u128, u128>>,
447+
#[case] address_to_class_hash: HashMap<u128, u128>,
448+
#[case] address_to_nonce: HashMap<u128, u128>,
449+
#[case] initial_alias_storage: HashMap<u128, u128>,
450+
#[case] expected_alias_storage: HashMap<u128, u128>,
451+
) {
452+
let runner_config = get_entrypoint_runner_config();
453+
let entrypoint = "__main__.allocate_aliases_and_replace";
454+
let implicit_args = [ImplicitArg::Builtin(BuiltinName::range_check)];
455+
let modified_contracts: BTreeSet<_> = storage_updates
456+
.keys()
457+
.chain(address_to_class_hash.keys().chain(address_to_nonce.keys()))
458+
.collect();
459+
460+
// Initialize the runner to be able to allocate segments.
461+
let (mut cairo_runner, program, entrypoint) = initialize_cairo_runner(
462+
&runner_config,
463+
ALIASES_TEST_BYTES,
464+
entrypoint,
465+
&implicit_args,
466+
HashMap::new(),
467+
)
468+
.unwrap();
469+
470+
// Construct the contract state changes.
471+
let mut prev_state_entries = HashMap::new();
472+
let mut new_state_entries = HashMap::new();
473+
let n_contracts = modified_contracts.len();
474+
for address in modified_contracts {
475+
let inner_updates = storage_updates
476+
.get(address)
477+
.unwrap_or(&HashMap::new())
478+
.iter()
479+
.map(|(k, v)| ((*k).into(), Felt::from(*v).into()))
480+
.collect();
481+
let (new_nonce, prev_nonce) = (address_to_nonce.get(address).copied().unwrap_or(0), 0);
482+
let (new_class_hash, prev_class_hash) = (
483+
address_to_class_hash.get(address).copied().unwrap_or(DEFAULT_CLASS_HASH),
484+
DEFAULT_CLASS_HASH,
485+
);
486+
let (prev_storage_ptr, new_storage_ptr) =
487+
allocate_squashed_cairo_dict(&HashMap::new(), &inner_updates, &mut cairo_runner.vm);
488+
let new_state_entry: Vec<MaybeRelocatable> = vec![
489+
Felt::from(new_class_hash).into(),
490+
new_storage_ptr.into(),
491+
Felt::from(new_nonce).into(),
492+
];
493+
let prev_state_entry: Vec<MaybeRelocatable> = vec![
494+
Felt::from(prev_class_hash).into(),
495+
prev_storage_ptr.into(),
496+
Felt::from(prev_nonce).into(),
497+
];
498+
new_state_entries
499+
.insert((*address).into(), cairo_runner.vm.gen_arg(&new_state_entry).unwrap());
500+
prev_state_entries
501+
.insert((*address).into(), cairo_runner.vm.gen_arg(&prev_state_entry).unwrap());
310502
}
503+
let flat_contract_state_changes = flatten_cairo_dict(&prev_state_entries, &new_state_entries);
504+
let explicit_args = vec![
505+
EndpointArg::Value(ValueArg::Single(n_contracts.into())),
506+
EndpointArg::Pointer(PointerArg::Array(flat_contract_state_changes)),
507+
];
508+
let storage_view = initial_alias_storage
509+
.into_iter()
510+
.map(|(key, value)| ((*ALIAS_CONTRACT_ADDRESS, key.into()), value.into()))
511+
.collect();
512+
let state_reader = DictStateReader { storage_view, ..Default::default() };
513+
let expected_aliases_storage_flat_length = expected_alias_storage.len() * DICT_ACCESS_SIZE;
514+
let expected_explicit_return_values = vec![
515+
EndpointArg::Pointer(PointerArg::Array(vec![
516+
MaybeRelocatable::Int(Felt::ZERO);
517+
expected_aliases_storage_flat_length
518+
])),
519+
EndpointArg::Pointer(PointerArg::Array(vec![])),
520+
EndpointArg::Pointer(PointerArg::Array(vec![])),
521+
];
522+
523+
let (_, explicit_return_values) = run_cairo_0_entrypoint(
524+
entrypoint,
525+
&explicit_args,
526+
&implicit_args,
527+
Some(state_reader),
528+
&mut cairo_runner,
529+
&program,
530+
&runner_config,
531+
&expected_explicit_return_values,
532+
)
533+
.unwrap();
534+
535+
// TODO(Nimrod): Complete this test to also compare the other return values.
536+
let [
537+
EndpointArg::Pointer(PointerArg::Array(aliases_storage_updates)),
538+
EndpointArg::Pointer(PointerArg::Array(_)),
539+
EndpointArg::Pointer(PointerArg::Array(_)),
540+
] = explicit_return_values.as_slice()
541+
else {
542+
panic!(
543+
"The return value doesn't match the given format.\n Got: {explicit_return_values:?}"
544+
);
545+
};
546+
547+
let aliases_storage_updates_as_felts: Vec<Felt> =
548+
aliases_storage_updates.iter().map(|f| f.get_int().unwrap()).collect();
549+
let actual_alias_storage = parse_squashed_cairo_dict(&aliases_storage_updates_as_felts);
550+
let expected_alias_storage: HashMap<Felt, Felt> =
551+
expected_alias_storage.into_iter().map(|(key, value)| (key.into(), value.into())).collect();
552+
assert_eq!(actual_alias_storage, expected_alias_storage);
311553
}

0 commit comments

Comments
 (0)