Skip to content

Commit ded2ac0

Browse files
starknet_os: migrate test_aggregator
1 parent fd03221 commit ded2ac0

File tree

5 files changed

+236
-8
lines changed

5 files changed

+236
-8
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/starknet_os/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ itertools.workspace = true
7373
rstest.workspace = true
7474
starknet_committer.workspace = true
7575
starknet_patricia = { workspace = true, features = ["testing"] }
76+
tempfile.workspace = true
7677

7778
[lints]
7879
workspace = true

crates/starknet_os/src/hints/hint_implementation/aggregator/test.rs

Lines changed: 232 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,50 @@
1-
#![allow(dead_code)]
2-
1+
use std::collections::HashMap;
32
use std::sync::LazyLock;
43

4+
use apollo_starknet_os_program::AGGREGATOR_PROGRAM;
55
use ark_bls12_381::Fr;
6+
use cairo_vm::types::builtin_name::BuiltinName;
7+
use cairo_vm::types::layout_name::LayoutName;
8+
use cairo_vm::vm::runners::cairo_pie::{
9+
BuiltinAdditionalData,
10+
OutputBuiltinAdditionalData,
11+
PublicMemoryPage,
12+
};
13+
use itertools::Itertools;
614
use num_bigint::BigUint;
715
use num_integer::Integer;
816
use num_traits::ToPrimitive;
17+
use rstest::rstest;
918
use starknet_api::core::{ChainId, ClassHash, ContractAddress, Nonce};
1019
use starknet_api::state::StorageKey;
1120
use starknet_types_core::felt::Felt;
1221
use starknet_types_core::hash::{Poseidon, StarkHash};
22+
use tempfile::NamedTempFile;
1323

24+
use crate::hint_processor::aggregator_hint_processor::{
25+
AggregatorHintProcessor,
26+
AggregatorInput,
27+
DataAvailability,
28+
};
1429
use crate::hints::hint_implementation::kzg::utils::{
1530
polynomial_coefficients_to_kzg_commitment,
1631
BLS_PRIME,
1732
};
33+
use crate::hints::hint_implementation::output::{MAX_PAGE_SIZE, OUTPUT_ATTRIBUTE_FACT_TOPOLOGY};
1834
use crate::hints::hint_implementation::stateless_compression::utils::compress;
1935
use crate::io::os_input::OsChainInfo;
2036
use crate::io::os_output_types::{
2137
FullContractChanges,
2238
FullContractStorageUpdate,
2339
N_UPDATES_SMALL_PACKING_BOUND,
2440
};
41+
use crate::runner::{run_program, RunnerReturnObject};
42+
use crate::test_utils::validations::validate_builtins;
2543

2644
// Dummy values for the test.
2745
static OS_CONFIG_HASH: LazyLock<Felt> = LazyLock::new(|| {
2846
OsChainInfo {
29-
chain_id: ChainId::Other("0".to_string()),
47+
chain_id: ChainId::Other("\0".to_string()),
3048
strk_fee_token_address: ContractAddress::default(),
3149
}
3250
.compute_os_config_hash(None)
@@ -156,6 +174,107 @@ impl FailureModifier {
156174
}
157175
}
158176

177+
#[derive(Debug, PartialEq)]
178+
pub(crate) struct FactTopology {
179+
pub(crate) tree_structure: Vec<usize>,
180+
pub(crate) page_sizes: Vec<usize>,
181+
}
182+
183+
impl FactTopology {
184+
pub(crate) fn from_output_additional_data(
185+
output_size: usize,
186+
data: &OutputBuiltinAdditionalData,
187+
) -> Self {
188+
let tree_structure = match data.attributes.get(OUTPUT_ATTRIBUTE_FACT_TOPOLOGY).cloned() {
189+
Some(tree_structure) => {
190+
let bound = 1usize << 30;
191+
assert_eq!(tree_structure.len() % 2, 0, "Tree structure should be of even length.");
192+
assert!(!tree_structure.is_empty());
193+
assert!(tree_structure.len() <= 10);
194+
assert!(tree_structure.iter().all(|x| *x <= bound));
195+
tree_structure
196+
}
197+
None => {
198+
assert!(
199+
data.pages.is_empty(),
200+
"Additional pages cannot be used since the '{OUTPUT_ATTRIBUTE_FACT_TOPOLOGY}' \
201+
attribute is not specified."
202+
);
203+
vec![1, 0]
204+
}
205+
};
206+
Self {
207+
tree_structure,
208+
page_sizes: Self::get_page_sizes_from_page_dict(output_size, &data.pages),
209+
}
210+
}
211+
212+
pub(crate) fn trivial(page0_size: usize) -> Self {
213+
assert!(
214+
page0_size <= MAX_PAGE_SIZE,
215+
"Page size {page0_size} exceeded the maximum {MAX_PAGE_SIZE}."
216+
);
217+
Self { tree_structure: vec![1, 0], page_sizes: vec![page0_size] }
218+
}
219+
220+
/// Returns the sizes of the program output pages, given the pages dictionary that appears in
221+
/// the additional attributes of the output builtin.
222+
fn get_page_sizes_from_page_dict(
223+
output_size: usize,
224+
pages: &HashMap<usize, PublicMemoryPage>,
225+
) -> Vec<usize> {
226+
// Make sure the pages are adjacent to each other.
227+
228+
// The first page id is expected to be 1.
229+
let mut expected_page_id = 1;
230+
// We don't expect anything on its start value.
231+
let mut expected_page_start = None;
232+
// The size of page 0 is output_size if there are no other pages, or the start of page 1
233+
// otherwise.
234+
let mut page0_size = output_size;
235+
236+
for (page_id, page_start, page_size) in
237+
pages.iter().map(|(page_id, page)| (*page_id, page.start, page.size)).sorted()
238+
{
239+
assert_eq!(
240+
page_id, expected_page_id,
241+
"Expected page id {expected_page_id}, found {page_id}."
242+
);
243+
if page_id == 1 {
244+
assert!(page_start > 0, "Page start must be greater than 0.");
245+
assert!(
246+
page_start <= output_size,
247+
"Page start must be less than or equal to output size. Found {page_start}, \
248+
output size is {output_size}."
249+
);
250+
page0_size = page_start;
251+
} else {
252+
assert_eq!(page_start, expected_page_start.unwrap());
253+
}
254+
255+
assert!(page_size > 0, "Page size must be greater than 0.");
256+
assert!(
257+
page_size <= output_size,
258+
"Page size must be less than or equal to output size. Found {page_size}, output \
259+
size is {output_size}."
260+
);
261+
expected_page_start = Some(page_start + page_size);
262+
expected_page_id += 1;
263+
}
264+
265+
if !pages.is_empty() {
266+
assert_eq!(
267+
expected_page_start.unwrap(),
268+
output_size,
269+
"Pages must cover the entire program output. Expected size of \
270+
{expected_page_start:?}, found {output_size}."
271+
);
272+
}
273+
274+
[vec![page0_size], pages.values().map(|page| page.size).collect::<Vec<usize>>()].concat()
275+
}
276+
}
277+
159278
fn multi_block0_output(full_output: bool) -> Vec<Felt> {
160279
let partial_res = [
161280
vec![
@@ -287,7 +406,7 @@ fn multi_block1_output(full_output: bool, modifier: FailureModifier) -> Vec<Felt
287406
addr: ContractAddress(CONTRACT_ADDR0.try_into().unwrap()),
288407
prev_nonce: Nonce(Felt::ONE),
289408
new_nonce: Nonce(Felt::TWO),
290-
prev_class_hash: ClassHash(CLASS_HASH0_0),
409+
prev_class_hash: ClassHash(CLASS_HASH0_1),
291410
new_class_hash: ClassHash(CLASS_HASH0_1),
292411
storage_changes: vec![
293412
FullContractStorageUpdate {
@@ -353,8 +472,6 @@ fn multi_block1_output(full_output: bool, modifier: FailureModifier) -> Vec<Felt
353472
} else {
354473
vec![]
355474
},
356-
vec![COMPILED_CLASS_HASH0_1, CLASS_HASH1_0],
357-
if full_output { vec![COMPILED_CLASS_HASH1_0] } else { vec![] },
358475
vec![COMPILED_CLASS_HASH0_2],
359476
]
360477
.concat();
@@ -510,3 +627,112 @@ fn bootloader_output(full_output: bool, modifier: FailureModifier) -> Vec<Felt>
510627
]
511628
.concat()
512629
}
630+
631+
#[rstest]
632+
#[case(false, false, FailureModifier::None, None)]
633+
#[case(true, false, FailureModifier::None, None)]
634+
#[case(false, true, FailureModifier::None, None)]
635+
#[case(true, true, FailureModifier::None, None)]
636+
#[case(
637+
true,
638+
false,
639+
FailureModifier::BlockHash,
640+
Some(format!("{MULTI_BLOCK0_HASH} != {}", MULTI_BLOCK0_HASH + 10))
641+
)]
642+
#[case(
643+
true,
644+
false,
645+
FailureModifier::BlockNumber,
646+
Some(format!("{NUMBER_OF_BLOCKS_IN_MULTI_BLOCK} != {}", NUMBER_OF_BLOCKS_IN_MULTI_BLOCK + 10))
647+
)]
648+
#[case(true, false, FailureModifier::ProgramHash, Some("0 != 10".to_string()))]
649+
#[case(
650+
true,
651+
false,
652+
FailureModifier::OsConfigHash,
653+
Some(format!("{} != {}", *OS_CONFIG_HASH, *OS_CONFIG_HASH + 10))
654+
)]
655+
#[case(
656+
true,
657+
false,
658+
FailureModifier::StorageValue,
659+
Some(format!("{STORAGE_VALUE0_1} != {}", STORAGE_VALUE0_1 + 10))
660+
)]
661+
#[case(
662+
true,
663+
false,
664+
FailureModifier::CompiledClassHash,
665+
Some(format!("{COMPILED_CLASS_HASH0_1} != {}", COMPILED_CLASS_HASH0_1 + 10))
666+
)]
667+
fn test_aggregator(
668+
#[case] full_output: bool,
669+
#[case] use_kzg_da: bool,
670+
#[case] modifier: FailureModifier,
671+
#[case] error_message: Option<String>,
672+
) {
673+
let temp_file = NamedTempFile::new().unwrap();
674+
let temp_file_path = temp_file.path();
675+
676+
let bootloader_output_data = bootloader_output(true, modifier);
677+
let aggregator_input = AggregatorInput {
678+
bootloader_output: Some(bootloader_output_data.clone()),
679+
full_output,
680+
da: if use_kzg_da {
681+
DataAvailability::Blob(temp_file_path.to_path_buf())
682+
} else {
683+
DataAvailability::CallData
684+
},
685+
debug_mode: false,
686+
fee_token_address: Felt::ZERO,
687+
chain_id: Felt::ZERO,
688+
public_keys: None,
689+
};
690+
691+
// Create the aggregator hint processor.
692+
let mut aggregator_hint_processor =
693+
AggregatorHintProcessor::new(&AGGREGATOR_PROGRAM, aggregator_input);
694+
695+
let result =
696+
run_program(LayoutName::all_cairo, &AGGREGATOR_PROGRAM, &mut aggregator_hint_processor);
697+
698+
let RunnerReturnObject { raw_output, cairo_pie, mut cairo_runner } = match result {
699+
Err(error) => {
700+
assert!(error.to_string().contains(error_message.unwrap().as_str()));
701+
return;
702+
}
703+
Ok(runner_output) => {
704+
assert!(error_message.is_none());
705+
runner_output
706+
}
707+
};
708+
709+
validate_builtins(&mut cairo_runner);
710+
711+
let combined_output = combined_output(full_output, use_kzg_da);
712+
assert_eq!(
713+
raw_output.iter().collect::<Vec<&Felt>>(),
714+
bootloader_output_data.iter().chain(combined_output.iter()).collect::<Vec<_>>()
715+
);
716+
717+
let BuiltinAdditionalData::Output(output_builtin_data) =
718+
cairo_pie.additional_data.0.get(&BuiltinName::output).unwrap()
719+
else {
720+
panic!("Output builtin data should be present in the CairoPie.");
721+
};
722+
let fact_topology =
723+
FactTopology::from_output_additional_data(raw_output.len(), output_builtin_data);
724+
725+
if use_kzg_da {
726+
assert_eq!(fact_topology, FactTopology::trivial(raw_output.len()));
727+
} else {
728+
let da_len = combined_output_da(full_output).len();
729+
let len_without_da = raw_output.len() - da_len;
730+
assert_eq!(
731+
fact_topology,
732+
FactTopology {
733+
tree_structure: vec![2, 1, 0, 2],
734+
page_sizes: vec![len_without_da, da_len]
735+
}
736+
);
737+
}
738+
}

crates/starknet_os/src/hints/hint_implementation/output.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use crate::hints::error::{OsHintError, OsHintResult};
2121
use crate::hints::types::HintArgs;
2222
use crate::hints::vars::{Const, Ids, Scope};
2323

24-
const MAX_PAGE_SIZE: usize = 3800;
24+
pub(crate) const MAX_PAGE_SIZE: usize = 3800;
2525
pub(crate) const OUTPUT_ATTRIBUTE_FACT_TOPOLOGY: &str = "gps_fact_topology";
2626

2727
fn felt_to_bool(felt: Felt, id: Ids) -> Result<bool, OsHintError> {

crates/starknet_os/src/runner.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ pub struct RunnerReturnObject {
4747

4848
// TODO(Aner): replace the return type with Result<StarknetRunnerOutput,...>
4949
// TODO(Aner): Make generic (CommonHintProcessor trait) depend on testing flag.
50-
fn run_program<'a, HP: HintProcessor + CommonHintProcessor<'a>>(
50+
pub(crate) fn run_program<'a, HP: HintProcessor + CommonHintProcessor<'a>>(
5151
layout: LayoutName,
5252
program: &Program,
5353
hint_processor: &mut HP,

0 commit comments

Comments
 (0)