Skip to content

Commit f88053c

Browse files
starknet_os_flow_tests: add control for expected reverting txs
1 parent 58caca8 commit f88053c

File tree

2 files changed

+113
-34
lines changed

2 files changed

+113
-34
lines changed

crates/starknet_os_flow_tests/src/test_manager.rs

Lines changed: 86 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;
@@ -88,13 +89,18 @@ pub(crate) struct TestParameters {
8889
pub(crate) messages_to_l2: Vec<MessageToL2>,
8990
}
9091

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

97-
per_block_transactions: Vec<Vec<BlockifierTransaction>>,
103+
per_block_transactions: Vec<Vec<FlowTestTx>>,
98104
}
99105

100106
pub(crate) struct OsTestExpectedValues {
@@ -270,7 +276,7 @@ impl<S: FlowTestState> TestManager<S> {
270276
self.per_block_transactions.push(vec![]);
271277
}
272278

273-
fn last_block_txs_mut(&mut self) -> &mut Vec<BlockifierTransaction> {
279+
fn last_block_txs_mut(&mut self) -> &mut Vec<FlowTestTx> {
274280
self.per_block_transactions
275281
.last_mut()
276282
.expect("Always initialized with at least one tx list (at least one block).")
@@ -286,9 +292,12 @@ impl<S: FlowTestState> TestManager<S> {
286292
else {
287293
panic!("Expected a V1 contract class");
288294
};
289-
self.last_block_txs_mut().push(BlockifierTransaction::new_for_sequencing(
290-
StarknetApiTransaction::Account(AccountTransaction::Declare(tx)),
291-
));
295+
self.last_block_txs_mut().push(FlowTestTx {
296+
tx: BlockifierTransaction::new_for_sequencing(StarknetApiTransaction::Account(
297+
AccountTransaction::Declare(tx),
298+
)),
299+
expected_revert_reason: None,
300+
});
292301

293302
self.execution_contracts
294303
.declared_class_hash_to_component_hashes
@@ -300,14 +309,29 @@ impl<S: FlowTestState> TestManager<S> {
300309
.insert(compiled_class_hash, casm.clone());
301310
}
302311

303-
pub(crate) fn add_invoke_tx(&mut self, tx: InvokeTransaction) {
304-
self.last_block_txs_mut().push(BlockifierTransaction::new_for_sequencing(
305-
StarknetApiTransaction::Account(AccountTransaction::Invoke(tx)),
306-
));
312+
pub(crate) fn add_invoke_tx(
313+
&mut self,
314+
tx: InvokeTransaction,
315+
expected_revert_reason: Option<String>,
316+
) {
317+
self.last_block_txs_mut().push(FlowTestTx {
318+
tx: BlockifierTransaction::new_for_sequencing(StarknetApiTransaction::Account(
319+
AccountTransaction::Invoke(tx),
320+
)),
321+
expected_revert_reason,
322+
});
307323
}
308324

309-
pub(crate) fn add_invoke_tx_from_args(&mut self, args: InvokeTxArgs, chain_id: &ChainId) {
310-
self.add_invoke_tx(InvokeTransaction::create(invoke_tx(args), chain_id).unwrap());
325+
pub(crate) fn add_invoke_tx_from_args(
326+
&mut self,
327+
args: InvokeTxArgs,
328+
chain_id: &ChainId,
329+
revert_reason: Option<String>,
330+
) {
331+
self.add_invoke_tx(
332+
InvokeTransaction::create(invoke_tx(args), chain_id).unwrap(),
333+
revert_reason,
334+
);
311335
}
312336

313337
/// Similar to `add_invoke_tx_from_args`, but with the sender address set to the funded account,
@@ -323,6 +347,7 @@ impl<S: FlowTestState> TestManager<S> {
323347
..additional_args
324348
},
325349
&CHAIN_ID_FOR_TESTS,
350+
None,
326351
);
327352
}
328353

@@ -334,24 +359,36 @@ impl<S: FlowTestState> TestManager<S> {
334359
let ContractClass::V0(class) = tx.class_info.contract_class.clone() else {
335360
panic!("Expected a V0 contract class");
336361
};
337-
self.last_block_txs_mut().push(BlockifierTransaction::new_for_sequencing(
338-
StarknetApiTransaction::Account(AccountTransaction::Declare(tx)),
339-
));
362+
self.last_block_txs_mut().push(FlowTestTx {
363+
tx: BlockifierTransaction::new_for_sequencing(StarknetApiTransaction::Account(
364+
AccountTransaction::Declare(tx),
365+
)),
366+
expected_revert_reason: None,
367+
});
340368
self.execution_contracts
341369
.executed_contracts
342370
.deprecated_contracts
343371
.insert(compiled_class_hash, class);
344372
}
345373

346374
pub(crate) fn add_deploy_account_tx(&mut self, tx: DeployAccountTransaction) {
347-
self.last_block_txs_mut().push(BlockifierTransaction::new_for_sequencing(
348-
StarknetApiTransaction::Account(AccountTransaction::DeployAccount(tx)),
349-
));
375+
self.last_block_txs_mut().push(FlowTestTx {
376+
tx: BlockifierTransaction::new_for_sequencing(StarknetApiTransaction::Account(
377+
AccountTransaction::DeployAccount(tx),
378+
)),
379+
expected_revert_reason: None,
380+
});
350381
}
351382

352-
pub(crate) fn add_l1_handler_tx(&mut self, tx: L1HandlerTransaction) {
353-
self.last_block_txs_mut()
354-
.push(BlockifierTransaction::new_for_sequencing(StarknetApiTransaction::L1Handler(tx)));
383+
pub(crate) fn add_l1_handler_tx(
384+
&mut self,
385+
tx: L1HandlerTransaction,
386+
expected_revert_reason: Option<String>,
387+
) {
388+
self.last_block_txs_mut().push(FlowTestTx {
389+
tx: BlockifierTransaction::new_for_sequencing(StarknetApiTransaction::L1Handler(tx)),
390+
expected_revert_reason,
391+
});
355392
}
356393

357394
/// Executes the test using default block contexts, starting from the given block number.
@@ -426,6 +463,28 @@ impl<S: FlowTestState> TestManager<S> {
426463
first_use_kzg_da
427464
}
428465

466+
/// Verifies all the execution outputs are as expected w.r.t. revert reasons.
467+
fn verify_execution_outputs(
468+
revert_reasons: &[Option<String>],
469+
execution_outputs: &[(TransactionExecutionInfo, StateMaps)],
470+
) {
471+
assert_eq!(revert_reasons.len(), execution_outputs.len());
472+
for (revert_reason, (execution_info, _)) in
473+
revert_reasons.iter().zip(execution_outputs.iter())
474+
{
475+
if let Some(revert_reason) = revert_reason {
476+
let actual_revert_reason =
477+
execution_info.revert_error.as_ref().unwrap().to_string();
478+
assert!(
479+
actual_revert_reason.contains(revert_reason),
480+
"Expected '{revert_reason}' to be in revert string:\n'{actual_revert_reason}'"
481+
);
482+
} else {
483+
assert!(execution_info.revert_error.is_none());
484+
}
485+
}
486+
}
487+
429488
/// Decompresses the state diff from the OS output using the given OS output, state and alias
430489
/// keys.
431490
fn get_decompressed_state_diff(
@@ -488,13 +547,19 @@ impl<S: FlowTestState> TestManager<S> {
488547
"use_kzg_da flag in block contexts must match the test parameter."
489548
);
490549
let mut alias_keys = HashSet::new();
491-
for (block_txs, block_context) in per_block_txs.into_iter().zip(block_contexts.into_iter())
550+
for (block_txs_with_reason, block_context) in
551+
per_block_txs.into_iter().zip(block_contexts.into_iter())
492552
{
553+
let (block_txs, revert_reasons): (Vec<_>, Vec<_>) = block_txs_with_reason
554+
.into_iter()
555+
.map(|flow_test_tx| (flow_test_tx.tx, flow_test_tx.expected_revert_reason))
556+
.unzip();
493557
// Clone the block info for later use.
494558
let block_info = block_context.block_info().clone();
495559
// Execute the transactions.
496560
let ExecutionOutput { execution_outputs, block_summary, mut final_state } =
497561
execute_transactions(state, &block_txs, block_context);
562+
Self::verify_execution_outputs(&revert_reasons, &execution_outputs);
498563
let extended_state_diff = final_state.cache.borrow().extended_state_diff();
499564
// Update the wrapped state.
500565
let state_diff = final_state.to_state_diff().unwrap();

crates/starknet_os_flow_tests/src/tests.rs

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -226,13 +226,18 @@ async fn trivial_diff_scenario(
226226
/// 1. All storage changes made before the revert are properly rolled back.
227227
/// 2. The transaction fee is still deducted from the caller's account.
228228
#[rstest]
229+
#[case::cairo0(
230+
FeatureContract::TestContract(CairoVersion::Cairo0),
231+
"ASSERT_EQ instruction failed: 1 != 0"
232+
)]
233+
#[case::cairo1(
234+
FeatureContract::TestContract(CairoVersion::Cairo1(RunnableCairo1::Casm)),
235+
"Panic for revert"
236+
)]
229237
#[tokio::test]
230238
async fn test_reverted_invoke_tx(
231-
#[values(
232-
FeatureContract::TestContract(CairoVersion::Cairo0),
233-
FeatureContract::TestContract(CairoVersion::Cairo1(RunnableCairo1::Casm))
234-
)]
235-
test_contract: FeatureContract,
239+
#[case] test_contract: FeatureContract,
240+
#[case] revert_reason: &str,
236241
) {
237242
let (use_kzg_da, full_output) = (true, false);
238243

@@ -247,10 +252,14 @@ async fn test_reverted_invoke_tx(
247252
let invoke_tx_args = invoke_tx_args! {
248253
sender_address: *FUNDED_ACCOUNT_ADDRESS,
249254
nonce: test_manager.next_nonce(*FUNDED_ACCOUNT_ADDRESS),
250-
calldata: create_calldata(test_contract_address, "write_and_revert", &[]),
255+
calldata: create_calldata(test_contract_address, "write_and_revert", &[Felt::ONE, Felt::TWO]),
251256
resource_bounds: *NON_TRIVIAL_RESOURCE_BOUNDS,
252257
};
253-
test_manager.add_invoke_tx_from_args(invoke_tx_args, &CHAIN_ID_FOR_TESTS);
258+
test_manager.add_invoke_tx_from_args(
259+
invoke_tx_args,
260+
&CHAIN_ID_FOR_TESTS,
261+
Some(revert_reason.to_string()),
262+
);
254263

255264
// Execute the test.
256265
let test_output = test_manager
@@ -274,13 +283,18 @@ async fn test_reverted_invoke_tx(
274283
/// Verifies that when an L1 handler modifies storage and then reverts, all storage changes made
275284
/// before the revert are properly rolled back.
276285
#[rstest]
286+
#[case::cairo0(
287+
FeatureContract::TestContract(CairoVersion::Cairo0),
288+
"ASSERT_EQ instruction failed: 1 != 0."
289+
)]
290+
#[case::cairo1(
291+
FeatureContract::TestContract(CairoVersion::Cairo1(RunnableCairo1::Casm)),
292+
"revert in l1 handler"
293+
)]
277294
#[tokio::test]
278295
async fn test_reverted_l1_handler_tx(
279-
#[values(
280-
FeatureContract::TestContract(CairoVersion::Cairo0),
281-
FeatureContract::TestContract(CairoVersion::Cairo1(RunnableCairo1::Casm))
282-
)]
283-
test_contract: FeatureContract,
296+
#[case] test_contract: FeatureContract,
297+
#[case] revert_reason: &str,
284298
) {
285299
let (mut test_manager, [test_contract_address]) =
286300
TestManager::<DictStateReader>::new_with_default_initial_state([(
@@ -303,7 +317,7 @@ async fn test_reverted_l1_handler_tx(
303317
Fee(1_000_000),
304318
)
305319
.unwrap();
306-
test_manager.add_l1_handler_tx(tx);
320+
test_manager.add_l1_handler_tx(tx, Some(revert_reason.to_string()));
307321

308322
let test_output =
309323
test_manager.execute_test_with_default_block_contexts(&TestParameters::default()).await;

0 commit comments

Comments
 (0)