Skip to content

Commit 57fc21d

Browse files
starknet_os_flow_tests: add control for expected reverting txs
1 parent 8611c44 commit 57fc21d

File tree

2 files changed

+110
-37
lines changed

2 files changed

+110
-37
lines changed

crates/starknet_os_flow_tests/src/test_manager.rs

Lines changed: 80 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+
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 {
@@ -267,7 +273,7 @@ impl<S: FlowTestState> TestManager<S> {
267273
self.per_block_transactions.push(vec![]);
268274
}
269275

270-
fn last_block_txs_mut(&mut self) -> &mut Vec<BlockifierTransaction> {
276+
fn last_block_txs_mut(&mut self) -> &mut Vec<FlowTestTx> {
271277
self.per_block_transactions
272278
.last_mut()
273279
.expect("Always initialized with at least one tx list (at least one block).")
@@ -283,9 +289,12 @@ impl<S: FlowTestState> TestManager<S> {
283289
else {
284290
panic!("Expected a V1 contract class");
285291
};
286-
self.last_block_txs_mut().push(BlockifierTransaction::new_for_sequencing(
287-
StarknetApiTransaction::Account(AccountTransaction::Declare(tx)),
288-
));
292+
self.last_block_txs_mut().push(FlowTestTx {
293+
tx: BlockifierTransaction::new_for_sequencing(StarknetApiTransaction::Account(
294+
AccountTransaction::Declare(tx),
295+
)),
296+
revert_reason: None,
297+
});
289298

290299
self.execution_contracts
291300
.declared_class_hash_to_component_hashes
@@ -297,14 +306,25 @@ impl<S: FlowTestState> TestManager<S> {
297306
.insert(compiled_class_hash, casm.clone());
298307
}
299308

300-
pub(crate) fn add_invoke_tx(&mut self, tx: InvokeTransaction) {
301-
self.last_block_txs_mut().push(BlockifierTransaction::new_for_sequencing(
302-
StarknetApiTransaction::Account(AccountTransaction::Invoke(tx)),
303-
));
309+
pub(crate) fn add_invoke_tx(&mut self, tx: InvokeTransaction, revert_reason: Option<String>) {
310+
self.last_block_txs_mut().push(FlowTestTx {
311+
tx: BlockifierTransaction::new_for_sequencing(StarknetApiTransaction::Account(
312+
AccountTransaction::Invoke(tx),
313+
)),
314+
revert_reason,
315+
});
304316
}
305317

306-
pub(crate) fn add_invoke_tx_from_args(&mut self, args: InvokeTxArgs, chain_id: &ChainId) {
307-
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+
);
308328
}
309329

310330
pub(crate) fn add_cairo0_declare_tx(
@@ -315,24 +335,36 @@ impl<S: FlowTestState> TestManager<S> {
315335
let ContractClass::V0(class) = tx.class_info.contract_class.clone() else {
316336
panic!("Expected a V0 contract class");
317337
};
318-
self.last_block_txs_mut().push(BlockifierTransaction::new_for_sequencing(
319-
StarknetApiTransaction::Account(AccountTransaction::Declare(tx)),
320-
));
338+
self.last_block_txs_mut().push(FlowTestTx {
339+
tx: BlockifierTransaction::new_for_sequencing(StarknetApiTransaction::Account(
340+
AccountTransaction::Declare(tx),
341+
)),
342+
revert_reason: None,
343+
});
321344
self.execution_contracts
322345
.executed_contracts
323346
.deprecated_contracts
324347
.insert(compiled_class_hash, class);
325348
}
326349

327350
pub(crate) fn add_deploy_account_tx(&mut self, tx: DeployAccountTransaction) {
328-
self.last_block_txs_mut().push(BlockifierTransaction::new_for_sequencing(
329-
StarknetApiTransaction::Account(AccountTransaction::DeployAccount(tx)),
330-
));
351+
self.last_block_txs_mut().push(FlowTestTx {
352+
tx: BlockifierTransaction::new_for_sequencing(StarknetApiTransaction::Account(
353+
AccountTransaction::DeployAccount(tx),
354+
)),
355+
revert_reason: None,
356+
});
331357
}
332358

333-
pub(crate) fn add_l1_handler_tx(&mut self, tx: L1HandlerTransaction) {
334-
self.last_block_txs_mut()
335-
.push(BlockifierTransaction::new_for_sequencing(StarknetApiTransaction::L1Handler(tx)));
359+
pub(crate) fn add_l1_handler_tx(
360+
&mut self,
361+
tx: L1HandlerTransaction,
362+
revert_reason: Option<String>,
363+
) {
364+
self.last_block_txs_mut().push(FlowTestTx {
365+
tx: BlockifierTransaction::new_for_sequencing(StarknetApiTransaction::L1Handler(tx)),
366+
revert_reason,
367+
});
336368
}
337369

338370
/// Executes the test using default block contexts, starting from the given block number.
@@ -407,6 +439,27 @@ impl<S: FlowTestState> TestManager<S> {
407439
first_use_kzg_da
408440
}
409441

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+
for (revert_reason, (execution_info, _)) in
448+
revert_reasons.iter().zip(execution_outputs.iter())
449+
{
450+
if let Some(revert_reason) = revert_reason {
451+
let actual_revert_reason =
452+
execution_info.revert_error.as_ref().unwrap().to_string();
453+
assert!(
454+
actual_revert_reason.contains(revert_reason),
455+
"Expected '{revert_reason}' to be in revert string:\n'{actual_revert_reason}'"
456+
);
457+
} else {
458+
assert!(execution_info.revert_error.is_none());
459+
}
460+
}
461+
}
462+
410463
/// Decompresses the state diff from the OS output using the given OS output, state and alias
411464
/// keys.
412465
fn get_decompressed_state_diff(
@@ -466,13 +519,19 @@ impl<S: FlowTestState> TestManager<S> {
466519
"use_kzg_da flag in block contexts must match the test parameter."
467520
);
468521
let mut alias_keys = HashSet::new();
469-
for (block_txs, block_context) in per_block_txs.into_iter().zip(block_contexts.into_iter())
522+
for (block_txs_with_reason, block_context) in
523+
per_block_txs.into_iter().zip(block_contexts.into_iter())
470524
{
471525
// Clone the block info for later use.
526+
let (block_txs, revert_reasons): (Vec<_>, Vec<_>) = block_txs_with_reason
527+
.into_iter()
528+
.map(|flow_test_tx| (flow_test_tx.tx, flow_test_tx.revert_reason))
529+
.unzip();
472530
let block_info = block_context.block_info().clone();
473531
// Execute the transactions.
474532
let ExecutionOutput { execution_outputs, block_summary, mut final_state } =
475533
execute_transactions(state, &block_txs, block_context);
534+
Self::verify_execution_outputs(&revert_reasons, &execution_outputs);
476535
let extended_state_diff = final_state.cache.borrow().extended_state_diff();
477536
// Update the wrapped state.
478537
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
@@ -288,13 +297,18 @@ async fn test_reverted_invoke_tx(
288297
}
289298

290299
#[rstest]
300+
#[case::cairo0(
301+
FeatureContract::TestContract(CairoVersion::Cairo0),
302+
"ASSERT_EQ instruction failed: 1 != 0."
303+
)]
304+
#[case::cairo1(
305+
FeatureContract::TestContract(CairoVersion::Cairo1(RunnableCairo1::Casm)),
306+
"revert in l1 handler"
307+
)]
291308
#[tokio::test]
292309
async fn test_reverted_l1_handler_tx(
293-
#[values(
294-
FeatureContract::TestContract(CairoVersion::Cairo0),
295-
FeatureContract::TestContract(CairoVersion::Cairo1(RunnableCairo1::Casm))
296-
)]
297-
test_contract: FeatureContract,
310+
#[case] test_contract: FeatureContract,
311+
#[case] revert_reason: &str,
298312
) {
299313
let (mut test_manager, _, [test_contract_address]) =
300314
TestManager::<DictStateReader>::new_with_default_initial_state([(
@@ -316,7 +330,7 @@ async fn test_reverted_l1_handler_tx(
316330
Fee(1_000_000),
317331
)
318332
.unwrap();
319-
test_manager.add_l1_handler_tx(tx);
333+
test_manager.add_l1_handler_tx(tx, Some(revert_reason.to_string()));
320334

321335
let test_output =
322336
test_manager.execute_test_with_default_block_contexts(&TestParameters::default()).await;

0 commit comments

Comments
 (0)