Skip to content

Commit 95d021e

Browse files
starknet_os_flow_tests: split fuzz test manager from context (#13041)
1 parent e2888a0 commit 95d021e

1 file changed

Lines changed: 74 additions & 50 deletions

File tree

crates/starknet_os_flow_tests/src/fuzz_tests.rs

Lines changed: 74 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ impl RevertInfo {
297297
}
298298

299299
/// Represents the call tree of a fuzz test.
300-
struct FuzzTestManager {
300+
struct FuzzTestContext {
301301
/// The call tree of the fuzz test.
302302
/// The first frame is the frame called by the orchestrator (it's parent frame is the
303303
/// orchestrator).
@@ -325,12 +325,39 @@ struct FuzzTestManager {
325325
/// Next salt to use for a deploy operation.
326326
pub next_salt: ContractAddressSalt,
327327

328-
pub test_manager: TestBuilder<DictStateReader>,
329-
pub first_called_address: ContractAddress,
330328
pub orchestrator_contract_address: ContractAddress,
331329
pub rng: ChaCha8Rng,
332330
}
333331

332+
impl FuzzTestContext {
333+
pub fn init(
334+
seed: u64,
335+
orchestrator_contract_address: ContractAddress,
336+
first_call: FuzzCallInfo,
337+
deployed_fuzz_contracts: BTreeMap<ContractAddress, ClassHash>,
338+
) -> Self {
339+
Self {
340+
calls: vec![first_call],
341+
current_call: vec![0],
342+
final_state: FinalizedState::Ongoing,
343+
operations: vec![],
344+
deployed_contracts: deployed_fuzz_contracts,
345+
class_replaced: false,
346+
next_storage_write_value: StarknetStorageValue(Felt::from(1u16 << 12)),
347+
next_salt: ContractAddressSalt(Felt::from(1u32 << 16)),
348+
orchestrator_contract_address,
349+
rng: ChaCha8Rng::seed_from_u64(seed),
350+
}
351+
}
352+
}
353+
354+
/// Manages the fuzz flow test, with the underlying flow test state.
355+
struct FuzzTestManager {
356+
pub context: FuzzTestContext,
357+
pub test_manager: TestBuilder<DictStateReader>,
358+
pub first_called_address: ContractAddress,
359+
}
360+
334361
impl FuzzTestManager {
335362
pub async fn init(seed: u64) -> Self {
336363
// Initialize the state with:
@@ -381,40 +408,36 @@ impl FuzzTestManager {
381408
ParentFailureBehavior::Cairo1Catching,
382409
);
383410
Self {
384-
calls: vec![first_call],
385-
current_call: vec![0],
386-
final_state: FinalizedState::Ongoing,
387-
operations: vec![],
388-
deployed_contracts: deployed_fuzz_contracts,
389-
class_replaced: false,
390-
next_storage_write_value: StarknetStorageValue(Felt::from(1u16 << 12)),
391-
next_salt: ContractAddressSalt(Felt::from(1u32 << 16)),
411+
context: FuzzTestContext::init(
412+
seed,
413+
orchestrator_contract_address,
414+
first_call,
415+
deployed_fuzz_contracts,
416+
),
392417
test_manager,
393418
first_called_address,
394-
orchestrator_contract_address,
395-
rng: ChaCha8Rng::seed_from_u64(seed),
396419
}
397420
}
398421

399422
pub fn finalized(&self) -> bool {
400-
self.final_state.finalized()
423+
self.context.final_state.finalized()
401424
}
402425

403426
pub fn current_fuzz_call_info(&self) -> &FuzzCallInfo {
404-
let mut call = &self.calls[self.current_call[0]];
405-
for index in self.current_call.iter().skip(1) {
427+
let mut call = &self.context.calls[self.context.current_call[0]];
428+
for index in self.context.current_call.iter().skip(1) {
406429
call = &call.inner_calls[*index];
407430
}
408431
call
409432
}
410433

411434
pub fn current_fuzz_call_info_mut(&mut self) -> &mut FuzzCallInfo {
412-
let current_call = self.current_call.clone();
435+
let current_call = self.context.current_call.clone();
413436
self.fuzz_call_info_mut(&current_call)
414437
}
415438

416439
pub fn fuzz_call_info_mut(&mut self, call_path: &[usize]) -> &mut FuzzCallInfo {
417-
let mut call = &mut self.calls[call_path[0]];
440+
let mut call = &mut self.context.calls[call_path[0]];
418441
for index in call_path.iter().skip(1) {
419442
call = &mut call.inner_calls[*index];
420443
}
@@ -453,7 +476,8 @@ impl FuzzTestManager {
453476
// There are two Cairo0 contracts and two Cairo1 contracts that can be called.
454477
// When calling from a Cairo1 context, the caller can unwrap the call result or not.
455478
let current_context_is_cairo1 = self.is_current_context_cairo1();
456-
self.deployed_contracts
479+
self.context
480+
.deployed_contracts
457481
.keys()
458482
.flat_map(|address| {
459483
if current_context_is_cairo1 {
@@ -507,13 +531,13 @@ impl FuzzTestManager {
507531
.map(|storage_key| {
508532
FuzzOperationData::Write(
509533
StorageKey::try_from(*storage_key).unwrap(),
510-
self.next_storage_write_value,
534+
self.context.next_storage_write_value,
511535
)
512536
})
513537
.collect(),
514538
FuzzOperation::ReplaceClass => {
515539
// If class was already replaced, no more replacements are allowed.
516-
if self.class_replaced {
540+
if self.context.class_replaced {
517541
// TODO(Dori): In this case, replace back to original class.
518542
return vec![];
519543
}
@@ -525,7 +549,7 @@ impl FuzzTestManager {
525549
.keys()
526550
.map(|class_hash| FuzzOperationData::Deploy {
527551
class_hash: *class_hash,
528-
salt: self.next_salt,
552+
salt: self.context.next_salt,
529553
})
530554
.collect()
531555
}
@@ -558,7 +582,7 @@ impl FuzzTestManager {
558582
parent_failure_behavior,
559583
));
560584
}
561-
self.current_call.push(next_call_index);
585+
self.context.current_call.push(next_call_index);
562586
}
563587

564588
/// Enter a new call context.
@@ -578,10 +602,10 @@ impl FuzzTestManager {
578602

579603
/// Exit the current call context.
580604
pub fn exit_call(&mut self) {
581-
self.current_call.pop();
605+
self.context.current_call.pop();
582606
// If we returned to orchestrator context, no more operations can be applied.
583-
if self.current_call.is_empty() {
584-
self.final_state = FinalizedState::Succeeded;
607+
if self.context.current_call.is_empty() {
608+
self.context.final_state = FinalizedState::Succeeded;
585609
}
586610
}
587611

@@ -595,7 +619,7 @@ impl FuzzTestManager {
595619
calculate_contract_address(
596620
salt,
597621
class_hash,
598-
&calldata![**self.orchestrator_contract_address],
622+
&calldata![**self.context.orchestrator_contract_address],
599623
ContractAddress::default(),
600624
)
601625
.unwrap()
@@ -630,38 +654,38 @@ impl FuzzTestManager {
630654
// Revert class replacement. Do this before "undeploying" deployed contracts so we don't
631655
// "redeploy" anything when we only intend to revert the class hash change.
632656
if let Some((address, class_hash)) = revert_info.class_replaced_and_original_class_hash {
633-
self.deployed_contracts.insert(address, class_hash);
657+
self.context.deployed_contracts.insert(address, class_hash);
634658
}
635659
// "Undeploy" all deployed contracts.
636660
for address in revert_info.deployed_addresses.iter() {
637661
// Remove without asserting that the address was actually deployed - the
638662
// constructor may have reverted before being finalized.
639-
self.deployed_contracts.remove(address);
663+
self.context.deployed_contracts.remove(address);
640664
}
641665
}
642666

643667
/// Remove the entire call tree. Used when an uncatchable error occurs, or we cleanly return
644668
/// to the orchestrator context.
645669
/// State should always be finalized after this.
646670
pub fn pop_entire_call_tree(&mut self, succeeded: bool) {
647-
self.calls.clear();
648-
self.current_call.clear();
649-
self.final_state =
671+
self.context.calls.clear();
672+
self.context.current_call.clear();
673+
self.context.final_state =
650674
if succeeded { FinalizedState::Succeeded } else { FinalizedState::Reverted };
651675
}
652676

653677
/// Applies the operation and updates the context.
654678
pub fn apply(&mut self, operation: FuzzOperationData) {
655679
assert!(!self.finalized());
656-
self.operations.push(operation);
680+
self.context.operations.push(operation);
657681
match operation {
658682
FuzzOperationData::Return => {
659683
// Go up the call tree.
660684
self.exit_call()
661685
}
662686
FuzzOperationData::Call(call_operation_data) => {
663687
let address = *call_operation_data.address();
664-
let class_hash = *self.deployed_contracts.get(&address).unwrap();
688+
let class_hash = *self.context.deployed_contracts.get(&address).unwrap();
665689
self.enter_call(address, class_hash, call_operation_data.parent_failure_behavior());
666690
}
667691
FuzzOperationData::LibraryCall(library_call_operation_data) => {
@@ -673,15 +697,15 @@ impl FuzzTestManager {
673697
);
674698
}
675699
FuzzOperationData::Write(_, _) => {
676-
self.next_storage_write_value.0 += Felt::ONE;
700+
self.context.next_storage_write_value.0 += Felt::ONE;
677701
}
678702
FuzzOperationData::ReplaceClass(class_hash) => {
679-
assert!(!self.class_replaced);
703+
assert!(!self.context.class_replaced);
680704
assert_eq!(class_hash, *CAIRO1_REPLACEMENT_CLASS_HASH);
681-
self.class_replaced = true;
705+
self.context.class_replaced = true;
682706
// Update the mapping from address to class hash, so subsequent calls to this
683707
// address will correctly use the new class hash.
684-
self.deployed_contracts.insert(self.current_address(), class_hash);
708+
self.context.deployed_contracts.insert(self.current_address(), class_hash);
685709
// Update the current call to mark that it was replaced at this point, to make it
686710
// easy to track if the change must be reverted mid-test.
687711
self.current_fuzz_call_info_mut().class_replaced_here = true;
@@ -692,9 +716,9 @@ impl FuzzTestManager {
692716
FuzzOperationData::Deploy { class_hash, salt } => {
693717
let deployed_address = self.address_of_deploy(class_hash, salt);
694718
// Increment the salt for the next deploy operation.
695-
self.next_salt.0 += Felt::ONE;
719+
self.context.next_salt.0 += Felt::ONE;
696720
// Update the mapping from address to class hash.
697-
self.deployed_contracts.insert(deployed_address, class_hash);
721+
self.context.deployed_contracts.insert(deployed_address, class_hash);
698722
// Enter constructor context.
699723
self.enter_deploy(deployed_address, class_hash);
700724
}
@@ -714,7 +738,7 @@ impl FuzzTestManager {
714738
== ParentFailureBehavior::Cairo1Propagating
715739
{
716740
// No need to finalize deploys here - we are reverting.
717-
self.current_call.pop();
741+
self.context.current_call.pop();
718742
}
719743
match self.current_fuzz_call_info().parent_failure_behavior {
720744
// The simple case is when the parent is "uncatchable"; the entire tx will be
@@ -739,7 +763,7 @@ impl FuzzTestManager {
739763
// The first panic is caught and will revert the replace class. Unless the
740764
// inner call is popped, the second panic will attempt to revert the replace
741765
// class again.
742-
if self.current_call.is_empty() {
766+
if self.context.current_call.is_empty() {
743767
// We are back at the orchestrator context. Pop the entire call tree.
744768
// Tx should be successful.
745769
self.pop_entire_call_tree(true);
@@ -761,7 +785,7 @@ impl FuzzTestManager {
761785
if valid_operations.is_empty() {
762786
return Err(());
763787
}
764-
let operation = *valid_operations.iter().choose(&mut self.rng).unwrap();
788+
let operation = *valid_operations.iter().choose(&mut self.context.rng).unwrap();
765789
self.apply(operation);
766790
Ok(())
767791
}
@@ -790,7 +814,7 @@ impl FuzzTestManager {
790814
#[allow(unused)]
791815
pub fn prettify_operations(&self) -> String {
792816
let mut output = vec![];
793-
for operation in self.operations.iter() {
817+
for operation in self.context.operations.iter() {
794818
let operation_felt_hexes = operation
795819
.felt_vector()
796820
.iter()
@@ -801,7 +825,7 @@ impl FuzzTestManager {
801825
FuzzOperationData::Call(call_operation_data) => {
802826
// It's possible that the address is no longer deployed (post-revert).
803827
let class_info_string =
804-
match self.deployed_contracts.get(call_operation_data.address()) {
828+
match self.context.deployed_contracts.get(call_operation_data.address()) {
805829
Some(class_hash) => format!(
806830
"Cairo{} address, class hash: {}",
807831
if self.is_cairo1_class(class_hash) { "1" } else { "0" },
@@ -888,13 +912,13 @@ impl FuzzTestManager {
888912
/// the context - if the finalized state is Ongoing it will be converted to Succeeded).
889913
pub async fn run_test(mut self) {
890914
if !self.finalized() {
891-
self.final_state = FinalizedState::Succeeded;
915+
self.context.final_state = FinalizedState::Succeeded;
892916
}
893917

894918
// Initialize the orchestrator contract with the scenario data.
895-
let scenario_data = Self::operations_to_scenario_data(&self.operations);
919+
let scenario_data = Self::operations_to_scenario_data(&self.context.operations);
896920
let orchestrator_calldata = create_calldata(
897-
self.orchestrator_contract_address,
921+
self.context.orchestrator_contract_address,
898922
"initialize",
899923
&[vec![Felt::from(scenario_data.len())], scenario_data].concat(),
900924
);
@@ -903,13 +927,13 @@ impl FuzzTestManager {
903927

904928
// Invoke the test.
905929
let start_test_calldata = create_calldata(
906-
self.orchestrator_contract_address,
930+
self.context.orchestrator_contract_address,
907931
"start_test",
908932
&[**self.first_called_address],
909933
);
910934

911935
// Whether or not a revert is expected depends on context.
912-
let tx_revert_error = match self.final_state {
936+
let tx_revert_error = match self.context.final_state {
913937
FinalizedState::Succeeded => None,
914938
FinalizedState::Reverted => Some("".to_string()),
915939
FinalizedState::Ongoing => unreachable!(),

0 commit comments

Comments
 (0)