@@ -83,50 +83,11 @@ void TxExecution::simulate(const Tx& tx)
8383 insert_non_revertibles (tx);
8484
8585 // Setup.
86- for (const auto & call : tx.setupEnqueuedCalls ) {
87- info (" [SETUP] Executing enqueued call to " , call.request .contractAddress );
88- TxContextEvent state_before = tx_context.serialize_tx_context_event ();
89- Gas start_gas = tx_context.gas_used ;
90- auto context = context_provider.make_enqueued_context (call.request .contractAddress ,
91- call.request .msgSender ,
92- /* transaction_fee=*/ FF (0 ),
93- call.calldata ,
94- call.request .isStaticCall ,
95- gas_limit,
96- start_gas,
97- tx_context.side_effect_states ,
98- TransactionPhase::SETUP);
99- // This call should not throw unless it's an unexpected unrecoverable failure.
100- ExecutionResult result = call_execution.execute (std::move (context));
101- tx_context.side_effect_states = result.side_effect_states ;
102- tx_context.gas_used = result.gas_used ;
103- emit_public_call_request (call,
104- TransactionPhase::SETUP,
105- /* transaction_fee=*/ FF (0 ),
106- result.success ,
107- start_gas,
108- tx_context.gas_used ,
109- state_before,
110- tx_context.serialize_tx_context_event ());
111- if (!result.success ) {
112- // This will result in an unprovable tx.
113- throw TxExecutionException (
114- format (" [SETUP] UNRECOVERABLE ERROR! Enqueued call to " , call.request .contractAddress , " failed" ));
115- }
116- }
117- SideEffectStates end_setup_side_effect_states = tx_context.side_effect_states ;
118-
119- // The checkpoint we should go back to if anything from now on reverts.
120- merkle_db.create_checkpoint ();
121-
122- try {
123- // Insert revertibles. This can throw if there is a nullifier collision.
124- // Such an exception should be handled and the tx be provable.
125- insert_revertibles (tx);
126-
127- // App logic.
128- for (const auto & call : tx.appLogicEnqueuedCalls ) {
129- info (" [APP_LOGIC] Executing enqueued call to " , call.request .contractAddress );
86+ if (tx.setupEnqueuedCalls .empty ()) {
87+ emit_empty_phase (TransactionPhase::SETUP);
88+ } else {
89+ for (const auto & call : tx.setupEnqueuedCalls ) {
90+ info (" [SETUP] Executing enqueued call to " , call.request .contractAddress );
13091 TxContextEvent state_before = tx_context.serialize_tx_context_event ();
13192 Gas start_gas = tx_context.gas_used ;
13293 auto context = context_provider.make_enqueued_context (call.request .contractAddress ,
@@ -137,23 +98,70 @@ void TxExecution::simulate(const Tx& tx)
13798 gas_limit,
13899 start_gas,
139100 tx_context.side_effect_states ,
140- TransactionPhase::APP_LOGIC );
101+ TransactionPhase::SETUP );
141102 // This call should not throw unless it's an unexpected unrecoverable failure.
142103 ExecutionResult result = call_execution.execute (std::move (context));
143104 tx_context.side_effect_states = result.side_effect_states ;
144105 tx_context.gas_used = result.gas_used ;
145106 emit_public_call_request (call,
146- TransactionPhase::APP_LOGIC ,
107+ TransactionPhase::SETUP ,
147108 /* transaction_fee=*/ FF (0 ),
148109 result.success ,
149110 start_gas,
150111 tx_context.gas_used ,
151112 state_before,
152113 tx_context.serialize_tx_context_event ());
153114 if (!result.success ) {
154- // This exception should be handled and the tx be provable .
115+ // This will result in an unprovable tx .
155116 throw TxExecutionException (
156- format (" [APP_LOGIC] Enqueued call to " , call.request .contractAddress , " failed" ));
117+ format (" [SETUP] UNRECOVERABLE ERROR! Enqueued call to " , call.request .contractAddress , " failed" ));
118+ }
119+ }
120+ }
121+ SideEffectStates end_setup_side_effect_states = tx_context.side_effect_states ;
122+
123+ // The checkpoint we should go back to if anything from now on reverts.
124+ merkle_db.create_checkpoint ();
125+
126+ try {
127+ // Insert revertibles. This can throw if there is a nullifier collision.
128+ // Such an exception should be handled and the tx be provable.
129+ insert_revertibles (tx);
130+
131+ // App logic.
132+ if (tx.appLogicEnqueuedCalls .empty ()) {
133+ emit_empty_phase (TransactionPhase::APP_LOGIC);
134+ } else {
135+ for (const auto & call : tx.appLogicEnqueuedCalls ) {
136+ info (" [APP_LOGIC] Executing enqueued call to " , call.request .contractAddress );
137+ TxContextEvent state_before = tx_context.serialize_tx_context_event ();
138+ Gas start_gas = tx_context.gas_used ;
139+ auto context = context_provider.make_enqueued_context (call.request .contractAddress ,
140+ call.request .msgSender ,
141+ /* transaction_fee=*/ FF (0 ),
142+ call.calldata ,
143+ call.request .isStaticCall ,
144+ gas_limit,
145+ start_gas,
146+ tx_context.side_effect_states ,
147+ TransactionPhase::APP_LOGIC);
148+ // This call should not throw unless it's an unexpected unrecoverable failure.
149+ ExecutionResult result = call_execution.execute (std::move (context));
150+ tx_context.side_effect_states = result.side_effect_states ;
151+ tx_context.gas_used = result.gas_used ;
152+ emit_public_call_request (call,
153+ TransactionPhase::APP_LOGIC,
154+ /* transaction_fee=*/ FF (0 ),
155+ result.success ,
156+ start_gas,
157+ tx_context.gas_used ,
158+ state_before,
159+ tx_context.serialize_tx_context_event ());
160+ if (!result.success ) {
161+ // This exception should be handled and the tx be provable.
162+ throw TxExecutionException (
163+ format (" [APP_LOGIC] Enqueued call to " , call.request .contractAddress , " failed" ));
164+ }
157165 }
158166 }
159167 } catch (const TxExecutionException& e) {
@@ -175,7 +183,9 @@ void TxExecution::simulate(const Tx& tx)
175183
176184 // Teardown.
177185 try {
178- if (tx.teardownEnqueuedCall ) {
186+ if (!tx.teardownEnqueuedCall ) {
187+ emit_empty_phase (TransactionPhase::TEARDOWN);
188+ } else {
179189 info (" [TEARDOWN] Executing enqueued call to " , tx.teardownEnqueuedCall ->request .contractAddress );
180190 // Teardown has its own gas limit and usage.
181191 Gas start_gas = { 0 , 0 };
@@ -334,18 +344,30 @@ void TxExecution::insert_non_revertibles(const Tx& tx)
334344 tx.hash );
335345
336346 // 1. Write the already siloed nullifiers.
337- for (const auto & nullifier : tx.nonRevertibleAccumulatedData .nullifiers ) {
338- emit_nullifier (false , nullifier);
347+ if (tx.nonRevertibleAccumulatedData .nullifiers .empty ()) {
348+ emit_empty_phase (TransactionPhase::NR_NULLIFIER_INSERTION);
349+ } else {
350+ for (const auto & nullifier : tx.nonRevertibleAccumulatedData .nullifiers ) {
351+ emit_nullifier (false , nullifier);
352+ }
339353 }
340354
341355 // 2. Write already unique note hashes.
342- for (const auto & unique_note_hash : tx.nonRevertibleAccumulatedData .noteHashes ) {
343- emit_note_hash (false , unique_note_hash);
356+ if (tx.nonRevertibleAccumulatedData .noteHashes .empty ()) {
357+ emit_empty_phase (TransactionPhase::NR_NOTE_INSERTION);
358+ } else {
359+ for (const auto & unique_note_hash : tx.nonRevertibleAccumulatedData .noteHashes ) {
360+ emit_note_hash (false , unique_note_hash);
361+ }
344362 }
345363
346364 // 3. Write l2_l1 messages
347- for (const auto & l2_to_l1_msg : tx.nonRevertibleAccumulatedData .l2ToL1Messages ) {
348- emit_l2_to_l1_message (false , l2_to_l1_msg);
365+ if (tx.nonRevertibleAccumulatedData .l2ToL1Messages .empty ()) {
366+ emit_empty_phase (TransactionPhase::NR_L2_TO_L1_MESSAGE);
367+ } else {
368+ for (const auto & l2_to_l1_msg : tx.nonRevertibleAccumulatedData .l2ToL1Messages ) {
369+ emit_l2_to_l1_message (false , l2_to_l1_msg);
370+ }
349371 }
350372}
351373
@@ -362,18 +384,30 @@ void TxExecution::insert_revertibles(const Tx& tx)
362384 tx.hash );
363385
364386 // 1. Write the already siloed nullifiers.
365- for (const auto & siloed_nullifier : tx.revertibleAccumulatedData .nullifiers ) {
366- emit_nullifier (true , siloed_nullifier);
387+ if (tx.revertibleAccumulatedData .nullifiers .empty ()) {
388+ emit_empty_phase (TransactionPhase::R_NULLIFIER_INSERTION);
389+ } else {
390+ for (const auto & siloed_nullifier : tx.revertibleAccumulatedData .nullifiers ) {
391+ emit_nullifier (true , siloed_nullifier);
392+ }
367393 }
368394
369395 // 2. Write the siloed non uniqued note hashes
370- for (const auto & siloed_note_hash : tx.revertibleAccumulatedData .noteHashes ) {
371- emit_note_hash (true , siloed_note_hash);
396+ if (tx.revertibleAccumulatedData .noteHashes .empty ()) {
397+ emit_empty_phase (TransactionPhase::R_NOTE_INSERTION);
398+ } else {
399+ for (const auto & siloed_note_hash : tx.revertibleAccumulatedData .noteHashes ) {
400+ emit_note_hash (true , siloed_note_hash);
401+ }
372402 }
373403
374404 // 3. Write L2 to L1 messages.
375- for (const auto & l2_to_l1_msg : tx.revertibleAccumulatedData .l2ToL1Messages ) {
376- emit_l2_to_l1_message (true , l2_to_l1_msg);
405+ if (tx.revertibleAccumulatedData .l2ToL1Messages .empty ()) {
406+ emit_empty_phase (TransactionPhase::R_L2_TO_L1_MESSAGE);
407+ } else {
408+ for (const auto & l2_to_l1_msg : tx.revertibleAccumulatedData .l2ToL1Messages ) {
409+ emit_l2_to_l1_message (true , l2_to_l1_msg);
410+ }
377411 }
378412}
379413
@@ -426,4 +460,14 @@ void TxExecution::cleanup()
426460 .event = CleanupEvent{} });
427461}
428462
463+ void TxExecution::emit_empty_phase (TransactionPhase phase)
464+ {
465+ TxContextEvent current_state = tx_context.serialize_tx_context_event ();
466+ events.emit (TxPhaseEvent{ .phase = phase,
467+ .state_before = current_state,
468+ .state_after = current_state,
469+ .reverted = false ,
470+ .event = EmptyPhaseEvent{} });
471+ }
472+
429473} // namespace bb::avm2::simulation
0 commit comments