Skip to content

Commit 83770e2

Browse files
starknet_os_flow_tests: add control for expected reverting txs (#9480)
1 parent f408a02 commit 83770e2

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;
@@ -90,13 +91,18 @@ pub(crate) struct TestParameters {
9091
pub(crate) private_keys: Option<Vec<Felt>>,
9192
}
9293

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

99-
per_block_transactions: Vec<Vec<BlockifierTransaction>>,
105+
per_block_transactions: Vec<Vec<FlowTestTx>>,
100106
}
101107

102108
pub(crate) struct OsTestExpectedValues {
@@ -273,7 +279,7 @@ impl<S: FlowTestState> TestManager<S> {
273279
self.per_block_transactions.push(vec![]);
274280
}
275281

276-
fn last_block_txs_mut(&mut self) -> &mut Vec<BlockifierTransaction> {
282+
fn last_block_txs_mut(&mut self) -> &mut Vec<FlowTestTx> {
277283
self.per_block_transactions
278284
.last_mut()
279285
.expect("Always initialized with at least one tx list (at least one block).")
@@ -289,9 +295,12 @@ impl<S: FlowTestState> TestManager<S> {
289295
else {
290296
panic!("Expected a V1 contract class");
291297
};
292-
self.last_block_txs_mut().push(BlockifierTransaction::new_for_sequencing(
293-
StarknetApiTransaction::Account(AccountTransaction::Declare(tx)),
294-
));
298+
self.last_block_txs_mut().push(FlowTestTx {
299+
tx: BlockifierTransaction::new_for_sequencing(StarknetApiTransaction::Account(
300+
AccountTransaction::Declare(tx),
301+
)),
302+
expected_revert_reason: None,
303+
});
295304

296305
self.execution_contracts
297306
.declared_class_hash_to_component_hashes
@@ -303,14 +312,29 @@ impl<S: FlowTestState> TestManager<S> {
303312
.insert(compiled_class_hash, casm.clone());
304313
}
305314

306-
pub(crate) fn add_invoke_tx(&mut self, tx: InvokeTransaction) {
307-
self.last_block_txs_mut().push(BlockifierTransaction::new_for_sequencing(
308-
StarknetApiTransaction::Account(AccountTransaction::Invoke(tx)),
309-
));
315+
pub(crate) fn add_invoke_tx(
316+
&mut self,
317+
tx: InvokeTransaction,
318+
expected_revert_reason: Option<String>,
319+
) {
320+
self.last_block_txs_mut().push(FlowTestTx {
321+
tx: BlockifierTransaction::new_for_sequencing(StarknetApiTransaction::Account(
322+
AccountTransaction::Invoke(tx),
323+
)),
324+
expected_revert_reason,
325+
});
310326
}
311327

312-
pub(crate) fn add_invoke_tx_from_args(&mut self, args: InvokeTxArgs, chain_id: &ChainId) {
313-
self.add_invoke_tx(InvokeTransaction::create(invoke_tx(args), chain_id).unwrap());
328+
pub(crate) fn add_invoke_tx_from_args(
329+
&mut self,
330+
args: InvokeTxArgs,
331+
chain_id: &ChainId,
332+
revert_reason: Option<String>,
333+
) {
334+
self.add_invoke_tx(
335+
InvokeTransaction::create(invoke_tx(args), chain_id).unwrap(),
336+
revert_reason,
337+
);
314338
}
315339

316340
/// Similar to `add_invoke_tx_from_args`, but with the sender address set to the funded account,
@@ -326,28 +350,41 @@ impl<S: FlowTestState> TestManager<S> {
326350
..additional_args
327351
},
328352
&CHAIN_ID_FOR_TESTS,
353+
None,
329354
);
330355
}
331356

332357
pub(crate) fn add_cairo0_declare_tx(&mut self, tx: DeclareTransaction, class_hash: ClassHash) {
333358
let ContractClass::V0(class) = tx.class_info.contract_class.clone() else {
334359
panic!("Expected a V0 contract class");
335360
};
336-
self.last_block_txs_mut().push(BlockifierTransaction::new_for_sequencing(
337-
StarknetApiTransaction::Account(AccountTransaction::Declare(tx)),
338-
));
361+
self.last_block_txs_mut().push(FlowTestTx {
362+
tx: BlockifierTransaction::new_for_sequencing(StarknetApiTransaction::Account(
363+
AccountTransaction::Declare(tx),
364+
)),
365+
expected_revert_reason: None,
366+
});
339367
self.execution_contracts.executed_contracts.deprecated_contracts.insert(class_hash, class);
340368
}
341369

342370
pub(crate) fn add_deploy_account_tx(&mut self, tx: DeployAccountTransaction) {
343-
self.last_block_txs_mut().push(BlockifierTransaction::new_for_sequencing(
344-
StarknetApiTransaction::Account(AccountTransaction::DeployAccount(tx)),
345-
));
371+
self.last_block_txs_mut().push(FlowTestTx {
372+
tx: BlockifierTransaction::new_for_sequencing(StarknetApiTransaction::Account(
373+
AccountTransaction::DeployAccount(tx),
374+
)),
375+
expected_revert_reason: None,
376+
});
346377
}
347378

348-
pub(crate) fn add_l1_handler_tx(&mut self, tx: L1HandlerTransaction) {
349-
self.last_block_txs_mut()
350-
.push(BlockifierTransaction::new_for_sequencing(StarknetApiTransaction::L1Handler(tx)));
379+
pub(crate) fn add_l1_handler_tx(
380+
&mut self,
381+
tx: L1HandlerTransaction,
382+
expected_revert_reason: Option<String>,
383+
) {
384+
self.last_block_txs_mut().push(FlowTestTx {
385+
tx: BlockifierTransaction::new_for_sequencing(StarknetApiTransaction::L1Handler(tx)),
386+
expected_revert_reason,
387+
});
351388
}
352389

353390
/// Executes the test using default block contexts, starting from the given block number.
@@ -422,6 +459,28 @@ impl<S: FlowTestState> TestManager<S> {
422459
first_use_kzg_da
423460
}
424461

462+
/// Verifies all the execution outputs are as expected w.r.t. revert reasons.
463+
fn verify_execution_outputs(
464+
revert_reasons: &[Option<String>],
465+
execution_outputs: &[(TransactionExecutionInfo, StateMaps)],
466+
) {
467+
assert_eq!(revert_reasons.len(), execution_outputs.len());
468+
for (revert_reason, (execution_info, _)) in
469+
revert_reasons.iter().zip(execution_outputs.iter())
470+
{
471+
if let Some(revert_reason) = revert_reason {
472+
let actual_revert_reason =
473+
execution_info.revert_error.as_ref().unwrap().to_string();
474+
assert!(
475+
actual_revert_reason.contains(revert_reason),
476+
"Expected '{revert_reason}' to be in revert string:\n'{actual_revert_reason}'"
477+
);
478+
} else {
479+
assert!(execution_info.revert_error.is_none());
480+
}
481+
}
482+
}
483+
425484
/// Decompresses the state diff from the OS output using the given OS output, state and alias
426485
/// keys.
427486
fn get_decompressed_state_diff(
@@ -485,13 +544,19 @@ impl<S: FlowTestState> TestManager<S> {
485544
"use_kzg_da flag in block contexts must match the test parameter."
486545
);
487546
let mut alias_keys = HashSet::new();
488-
for (block_txs, block_context) in per_block_txs.into_iter().zip(block_contexts.into_iter())
547+
for (block_txs_with_reason, block_context) in
548+
per_block_txs.into_iter().zip(block_contexts.into_iter())
489549
{
550+
let (block_txs, revert_reasons): (Vec<_>, Vec<_>) = block_txs_with_reason
551+
.into_iter()
552+
.map(|flow_test_tx| (flow_test_tx.tx, flow_test_tx.expected_revert_reason))
553+
.unzip();
490554
// Clone the block info for later use.
491555
let block_info = block_context.block_info().clone();
492556
// Execute the transactions.
493557
let ExecutionOutput { execution_outputs, block_summary, mut final_state } =
494558
execute_transactions(state, &block_txs, block_context);
559+
Self::verify_execution_outputs(&revert_reasons, &execution_outputs);
495560
let extended_state_diff = final_state.cache.borrow().extended_state_diff();
496561
// Update the wrapped state.
497562
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
@@ -318,13 +327,18 @@ async fn test_encrypted_state_diff(
318327
/// Verifies that when an L1 handler modifies storage and then reverts, all storage changes made
319328
/// before the revert are properly rolled back.
320329
#[rstest]
330+
#[case::cairo0(
331+
FeatureContract::TestContract(CairoVersion::Cairo0),
332+
"ASSERT_EQ instruction failed: 1 != 0."
333+
)]
334+
#[case::cairo1(
335+
FeatureContract::TestContract(CairoVersion::Cairo1(RunnableCairo1::Casm)),
336+
"revert in l1 handler"
337+
)]
321338
#[tokio::test]
322339
async fn test_reverted_l1_handler_tx(
323-
#[values(
324-
FeatureContract::TestContract(CairoVersion::Cairo0),
325-
FeatureContract::TestContract(CairoVersion::Cairo1(RunnableCairo1::Casm))
326-
)]
327-
test_contract: FeatureContract,
340+
#[case] test_contract: FeatureContract,
341+
#[case] revert_reason: &str,
328342
) {
329343
let (mut test_manager, [test_contract_address]) =
330344
TestManager::<DictStateReader>::new_with_default_initial_state([(
@@ -347,7 +361,7 @@ async fn test_reverted_l1_handler_tx(
347361
Fee(1_000_000),
348362
)
349363
.unwrap();
350-
test_manager.add_l1_handler_tx(tx);
364+
test_manager.add_l1_handler_tx(tx, Some(revert_reason.to_string()));
351365

352366
let test_output =
353367
test_manager.execute_test_with_default_block_contexts(&TestParameters::default()).await;

0 commit comments

Comments
 (0)