Skip to content

Commit b83b978

Browse files
starknet_os_flow_tests: add control for expected reverting txs
1 parent 13fbf11 commit b83b978

File tree

2 files changed

+115
-37
lines changed

2 files changed

+115
-37
lines changed

crates/starknet_os_flow_tests/src/test_manager.rs

Lines changed: 85 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use blockifier::context::{BlockContext, ChainInfo, FeeTokenAddresses};
99
use blockifier::state::cached_state::{CommitmentStateDiff, StateMaps};
1010
use blockifier::state::stateful_compression_test_utils::decompress;
1111
use blockifier::test_utils::ALIAS_CONTRACT_ADDRESS;
12+
use blockifier::transaction::objects::TransactionExecutionInfo;
1213
use blockifier::transaction::transaction_execution::Transaction as BlockifierTransaction;
1314
use blockifier_test_utils::contracts::FeatureContract;
1415
use itertools::Itertools;
@@ -87,12 +88,17 @@ pub(crate) struct TestParameters {
8788
pub(crate) messages_to_l2: Vec<MessageToL2>,
8889
}
8990

91+
pub(crate) struct FlowTestTx {
92+
tx: BlockifierTransaction,
93+
expected_revert_reason: Option<String>,
94+
}
95+
9096
/// Manages the execution of flow tests by maintaining the initial state and transactions.
9197
pub(crate) struct TestManager<S: FlowTestState> {
9298
pub(crate) initial_state: InitialState<S>,
9399
pub(crate) execution_contracts: OsExecutionContracts,
94100

95-
per_block_transactions: Vec<Vec<BlockifierTransaction>>,
101+
per_block_transactions: Vec<Vec<FlowTestTx>>,
96102
}
97103

98104
pub(crate) struct OsTestExpectedValues {
@@ -263,7 +269,7 @@ impl<S: FlowTestState> TestManager<S> {
263269
self.per_block_transactions.push(vec![]);
264270
}
265271

266-
fn last_block_txs_mut(&mut self) -> &mut Vec<BlockifierTransaction> {
272+
fn last_block_txs_mut(&mut self) -> &mut Vec<FlowTestTx> {
267273
self.per_block_transactions
268274
.last_mut()
269275
.expect("Always initialized with at least one tx list (at least one block).")
@@ -279,9 +285,12 @@ impl<S: FlowTestState> TestManager<S> {
279285
else {
280286
panic!("Expected a V1 contract class");
281287
};
282-
self.last_block_txs_mut().push(BlockifierTransaction::new_for_sequencing(
283-
StarknetApiTransaction::Account(AccountTransaction::Declare(tx)),
284-
));
288+
self.last_block_txs_mut().push(FlowTestTx {
289+
tx: BlockifierTransaction::new_for_sequencing(StarknetApiTransaction::Account(
290+
AccountTransaction::Declare(tx),
291+
)),
292+
expected_revert_reason: None,
293+
});
285294

286295
self.execution_contracts
287296
.declared_class_hash_to_component_hashes
@@ -293,14 +302,29 @@ impl<S: FlowTestState> TestManager<S> {
293302
.insert(compiled_class_hash, casm.clone());
294303
}
295304

296-
pub(crate) fn add_invoke_tx(&mut self, tx: InvokeTransaction) {
297-
self.last_block_txs_mut().push(BlockifierTransaction::new_for_sequencing(
298-
StarknetApiTransaction::Account(AccountTransaction::Invoke(tx)),
299-
));
305+
pub(crate) fn add_invoke_tx(
306+
&mut self,
307+
tx: InvokeTransaction,
308+
expected_revert_reason: Option<String>,
309+
) {
310+
self.last_block_txs_mut().push(FlowTestTx {
311+
tx: BlockifierTransaction::new_for_sequencing(StarknetApiTransaction::Account(
312+
AccountTransaction::Invoke(tx),
313+
)),
314+
expected_revert_reason,
315+
});
300316
}
301317

302-
pub(crate) fn add_invoke_tx_from_args(&mut self, args: InvokeTxArgs, chain_id: &ChainId) {
303-
self.add_invoke_tx(InvokeTransaction::create(invoke_tx(args), chain_id).unwrap());
318+
pub(crate) fn add_invoke_tx_from_args(
319+
&mut self,
320+
args: InvokeTxArgs,
321+
chain_id: &ChainId,
322+
revert_reason: Option<String>,
323+
) {
324+
self.add_invoke_tx(
325+
InvokeTransaction::create(invoke_tx(args), chain_id).unwrap(),
326+
revert_reason,
327+
);
304328
}
305329

306330
pub(crate) fn add_cairo0_declare_tx(
@@ -311,24 +335,36 @@ impl<S: FlowTestState> TestManager<S> {
311335
let ContractClass::V0(class) = tx.class_info.contract_class.clone() else {
312336
panic!("Expected a V0 contract class");
313337
};
314-
self.last_block_txs_mut().push(BlockifierTransaction::new_for_sequencing(
315-
StarknetApiTransaction::Account(AccountTransaction::Declare(tx)),
316-
));
338+
self.last_block_txs_mut().push(FlowTestTx {
339+
tx: BlockifierTransaction::new_for_sequencing(StarknetApiTransaction::Account(
340+
AccountTransaction::Declare(tx),
341+
)),
342+
expected_revert_reason: None,
343+
});
317344
self.execution_contracts
318345
.executed_contracts
319346
.deprecated_contracts
320347
.insert(compiled_class_hash, class);
321348
}
322349

323350
pub(crate) fn add_deploy_account_tx(&mut self, tx: DeployAccountTransaction) {
324-
self.last_block_txs_mut().push(BlockifierTransaction::new_for_sequencing(
325-
StarknetApiTransaction::Account(AccountTransaction::DeployAccount(tx)),
326-
));
351+
self.last_block_txs_mut().push(FlowTestTx {
352+
tx: BlockifierTransaction::new_for_sequencing(StarknetApiTransaction::Account(
353+
AccountTransaction::DeployAccount(tx),
354+
)),
355+
expected_revert_reason: None,
356+
});
327357
}
328358

329-
pub(crate) fn add_l1_handler_tx(&mut self, tx: L1HandlerTransaction) {
330-
self.last_block_txs_mut()
331-
.push(BlockifierTransaction::new_for_sequencing(StarknetApiTransaction::L1Handler(tx)));
359+
pub(crate) fn add_l1_handler_tx(
360+
&mut self,
361+
tx: L1HandlerTransaction,
362+
expected_revert_reason: Option<String>,
363+
) {
364+
self.last_block_txs_mut().push(FlowTestTx {
365+
tx: BlockifierTransaction::new_for_sequencing(StarknetApiTransaction::L1Handler(tx)),
366+
expected_revert_reason,
367+
});
332368
}
333369

334370
/// Executes the test using default block contexts, starting from the given block number.
@@ -403,6 +439,28 @@ impl<S: FlowTestState> TestManager<S> {
403439
first_use_kzg_da
404440
}
405441

442+
/// Verifies all the execution outputs are as expected w.r.t. revert reasons.
443+
fn verify_execution_outputs(
444+
revert_reasons: &[Option<String>],
445+
execution_outputs: &[(TransactionExecutionInfo, StateMaps)],
446+
) {
447+
assert_eq!(revert_reasons.len(), execution_outputs.len());
448+
for (revert_reason, (execution_info, _)) in
449+
revert_reasons.iter().zip(execution_outputs.iter())
450+
{
451+
if let Some(revert_reason) = revert_reason {
452+
let actual_revert_reason =
453+
execution_info.revert_error.as_ref().unwrap().to_string();
454+
assert!(
455+
actual_revert_reason.contains(revert_reason),
456+
"Expected '{revert_reason}' to be in revert string:\n'{actual_revert_reason}'"
457+
);
458+
} else {
459+
assert!(execution_info.revert_error.is_none());
460+
}
461+
}
462+
}
463+
406464
/// Decompresses the state diff from the OS output using the given OS output, state and alias
407465
/// keys.
408466
fn get_decompressed_state_diff(
@@ -465,13 +523,19 @@ impl<S: FlowTestState> TestManager<S> {
465523
"use_kzg_da flag in block contexts must match the test parameter."
466524
);
467525
let mut alias_keys = HashSet::new();
468-
for (block_txs, block_context) in per_block_txs.into_iter().zip(block_contexts.into_iter())
526+
for (block_txs_with_reason, block_context) in
527+
per_block_txs.into_iter().zip(block_contexts.into_iter())
469528
{
529+
let (block_txs, revert_reasons): (Vec<_>, Vec<_>) = block_txs_with_reason
530+
.into_iter()
531+
.map(|flow_test_tx| (flow_test_tx.tx, flow_test_tx.expected_revert_reason))
532+
.unzip();
470533
// Clone the block info for later use.
471534
let block_info = block_context.block_info().clone();
472535
// Execute the transactions.
473536
let ExecutionOutput { execution_outputs, block_summary, mut final_state } =
474537
execute_transactions(state, &block_txs, block_context);
538+
Self::verify_execution_outputs(&revert_reasons, &execution_outputs);
475539
let extended_state_diff = final_state.cache.borrow().extended_state_diff();
476540
// Update the wrapped state.
477541
let state_diff = final_state.to_state_diff().unwrap();

crates/starknet_os_flow_tests/src/tests.rs

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ async fn declare_deploy_scenario(
144144
*FUNDED_ACCOUNT_ADDRESS,
145145
)
146146
.unwrap();
147-
test_manager.add_invoke_tx_from_args(invoke_tx_args, &CHAIN_ID_FOR_TESTS);
147+
test_manager.add_invoke_tx_from_args(invoke_tx_args, &CHAIN_ID_FOR_TESTS, None);
148148
test_manager.divide_transactions_into_n_blocks(n_blocks);
149149
let test_output = test_manager
150150
.execute_test_with_default_block_contexts(&TestParameters {
@@ -209,7 +209,7 @@ async fn trivial_diff_scenario(
209209
calldata: create_calldata(test_contract_address, function_name, &[key, value]),
210210
resource_bounds: *NON_TRIVIAL_RESOURCE_BOUNDS,
211211
};
212-
test_manager.add_invoke_tx_from_args(invoke_tx_args, &CHAIN_ID_FOR_TESTS);
212+
test_manager.add_invoke_tx_from_args(invoke_tx_args, &CHAIN_ID_FOR_TESTS, None);
213213

214214
// Move to next block, and add an invoke that reverts the previous change.
215215
test_manager.move_to_next_block();
@@ -219,7 +219,7 @@ async fn trivial_diff_scenario(
219219
calldata: create_calldata(test_contract_address, function_name, &[key, Felt::ZERO]),
220220
resource_bounds: *NON_TRIVIAL_RESOURCE_BOUNDS,
221221
};
222-
test_manager.add_invoke_tx_from_args(invoke_tx_args, &CHAIN_ID_FOR_TESTS);
222+
test_manager.add_invoke_tx_from_args(invoke_tx_args, &CHAIN_ID_FOR_TESTS, None);
223223

224224
// Execute the test.
225225
let test_output = test_manager
@@ -242,13 +242,18 @@ async fn trivial_diff_scenario(
242242
/// 1. All storage changes made before the revert are properly rolled back.
243243
/// 2. The transaction fee is still deducted from the caller's account.
244244
#[rstest]
245+
#[case::cairo0(
246+
FeatureContract::TestContract(CairoVersion::Cairo0),
247+
"ASSERT_EQ instruction failed: 1 != 0"
248+
)]
249+
#[case::cairo1(
250+
FeatureContract::TestContract(CairoVersion::Cairo1(RunnableCairo1::Casm)),
251+
"Panic for revert"
252+
)]
245253
#[tokio::test]
246254
async fn test_reverted_invoke_tx(
247-
#[values(
248-
FeatureContract::TestContract(CairoVersion::Cairo0),
249-
FeatureContract::TestContract(CairoVersion::Cairo1(RunnableCairo1::Casm))
250-
)]
251-
test_contract: FeatureContract,
255+
#[case] test_contract: FeatureContract,
256+
#[case] revert_reason: &str,
252257
) {
253258
let (use_kzg_da, full_output) = (true, false);
254259

@@ -263,10 +268,14 @@ async fn test_reverted_invoke_tx(
263268
let invoke_tx_args = invoke_tx_args! {
264269
sender_address: *FUNDED_ACCOUNT_ADDRESS,
265270
nonce: nonce_manager.next(*FUNDED_ACCOUNT_ADDRESS),
266-
calldata: create_calldata(test_contract_address, "write_and_revert", &[]),
271+
calldata: create_calldata(test_contract_address, "write_and_revert", &[Felt::ONE, Felt::TWO]),
267272
resource_bounds: *NON_TRIVIAL_RESOURCE_BOUNDS,
268273
};
269-
test_manager.add_invoke_tx_from_args(invoke_tx_args, &CHAIN_ID_FOR_TESTS);
274+
test_manager.add_invoke_tx_from_args(
275+
invoke_tx_args,
276+
&CHAIN_ID_FOR_TESTS,
277+
Some(revert_reason.to_string()),
278+
);
270279

271280
// Execute the test.
272281
let test_output = test_manager
@@ -290,13 +299,18 @@ async fn test_reverted_invoke_tx(
290299
/// Verifies that when an L1 handler modifies storage and then reverts, all storage changes made
291300
/// before the revert are properly rolled back.
292301
#[rstest]
302+
#[case::cairo0(
303+
FeatureContract::TestContract(CairoVersion::Cairo0),
304+
"ASSERT_EQ instruction failed: 1 != 0."
305+
)]
306+
#[case::cairo1(
307+
FeatureContract::TestContract(CairoVersion::Cairo1(RunnableCairo1::Casm)),
308+
"revert in l1 handler"
309+
)]
293310
#[tokio::test]
294311
async fn test_reverted_l1_handler_tx(
295-
#[values(
296-
FeatureContract::TestContract(CairoVersion::Cairo0),
297-
FeatureContract::TestContract(CairoVersion::Cairo1(RunnableCairo1::Casm))
298-
)]
299-
test_contract: FeatureContract,
312+
#[case] test_contract: FeatureContract,
313+
#[case] revert_reason: &str,
300314
) {
301315
let (mut test_manager, _, [test_contract_address]) =
302316
TestManager::<DictStateReader>::new_with_default_initial_state([(
@@ -319,7 +333,7 @@ async fn test_reverted_l1_handler_tx(
319333
Fee(1_000_000),
320334
)
321335
.unwrap();
322-
test_manager.add_l1_handler_tx(tx);
336+
test_manager.add_l1_handler_tx(tx, Some(revert_reason.to_string()));
323337

324338
let test_output =
325339
test_manager.execute_test_with_default_block_contexts(&TestParameters::default()).await;

0 commit comments

Comments
 (0)