1use crate::{bootloader_debug::BootloaderDebug, utils::to_human_size};
2use anvil_zksync_common::sh_println;
3use colored::Colorize;
4use serde::Deserialize;
5use zksync_multivm::interface::VmExecutionResultAndLogs;
6use zksync_types::{fee_model::FeeModelConfigV2, StorageLogWithPreviousValue, U256};
7
8use super::{address::address_to_human_readable, pubdata_bytes::PubdataBytesInfo};
9
10#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
13pub struct GasDetails {
14 total_gas_limit: U256,
15 intrinsic_gas: U256,
16 gas_for_validation: U256,
17 gas_spent_on_compute: U256,
18 gas_used: U256,
19 bytes_published: u64,
20 spent_on_pubdata: u64,
21 gas_spent_on_bytecode_preparation: U256,
22 refund_computed: U256,
23 refund_by_operator: U256,
24 required_overhead: U256,
25 operator_overhead: U256,
26 intrinsic_overhead: U256,
27 overhead_for_length: U256,
28 overhead_for_slot: U256,
29 gas_per_pubdata: U256,
30 total_gas_limit_from_user: U256,
31 gas_spent_on_execution: U256,
32 gas_limit_after_intrinsic: U256,
33 gas_after_validation: U256,
34 reserved_gas: U256,
35}
36
37pub fn compute_gas_details(
39 bootloader_debug: &BootloaderDebug,
40 spent_on_pubdata: u64,
41) -> GasDetails {
42 let total_gas_limit = bootloader_debug
43 .total_gas_limit_from_user
44 .saturating_sub(bootloader_debug.reserved_gas);
45 let intrinsic_gas = total_gas_limit - bootloader_debug.gas_limit_after_intrinsic;
46 let gas_for_validation =
47 bootloader_debug.gas_limit_after_intrinsic - bootloader_debug.gas_after_validation;
48 let gas_spent_on_compute = bootloader_debug.gas_spent_on_execution
49 - bootloader_debug.gas_spent_on_bytecode_preparation;
50 let gas_used = intrinsic_gas
51 + gas_for_validation
52 + bootloader_debug.gas_spent_on_bytecode_preparation
53 + gas_spent_on_compute;
54
55 let bytes_published = spent_on_pubdata / bootloader_debug.gas_per_pubdata.as_u64();
56
57 GasDetails {
58 total_gas_limit,
59 intrinsic_gas,
60 gas_for_validation,
61 gas_spent_on_compute,
62 gas_used,
63 bytes_published,
64 spent_on_pubdata,
65 gas_spent_on_bytecode_preparation: bootloader_debug.gas_spent_on_bytecode_preparation,
66 refund_computed: bootloader_debug.refund_computed,
67 refund_by_operator: bootloader_debug.refund_by_operator,
68 required_overhead: bootloader_debug.required_overhead,
69 operator_overhead: bootloader_debug.operator_overhead,
70 intrinsic_overhead: bootloader_debug.intrinsic_overhead,
71 overhead_for_length: bootloader_debug.overhead_for_length,
72 overhead_for_slot: bootloader_debug.overhead_for_slot,
73 gas_per_pubdata: bootloader_debug.gas_per_pubdata,
74 total_gas_limit_from_user: bootloader_debug.total_gas_limit_from_user,
75 gas_spent_on_execution: bootloader_debug.gas_spent_on_execution,
76 gas_limit_after_intrinsic: bootloader_debug.gas_limit_after_intrinsic,
77 gas_after_validation: bootloader_debug.gas_after_validation,
78 reserved_gas: bootloader_debug.reserved_gas,
79 }
80}
81pub struct Formatter {
83 sibling_stack: Vec<bool>,
84}
85
86impl Default for Formatter {
87 fn default() -> Self {
88 Self::new()
89 }
90}
91
92impl Formatter {
93 pub fn new() -> Self {
95 Formatter {
96 sibling_stack: Vec::new(),
97 }
98 }
99 pub fn section<F>(&mut self, title: &str, is_last_sibling: bool, f: F)
101 where
102 F: FnOnce(&mut Self),
103 {
104 self.format_log(is_last_sibling, title);
105 self.enter_scope(is_last_sibling);
106 f(self);
107 self.exit_scope();
108 }
109 pub fn item(&mut self, is_last_sibling: bool, key: &str, value: &str) {
111 self.format_log(
112 is_last_sibling,
113 &format!("{}: {}", key.bold(), value.dimmed()),
114 );
115 }
116 pub fn enter_scope(&mut self, has_more_siblings: bool) {
118 self.sibling_stack.push(has_more_siblings);
119 }
120 pub fn exit_scope(&mut self) {
122 self.sibling_stack.pop();
123 }
124 pub fn format_log(&self, is_last_sibling: bool, message: &str) {
126 let prefix = build_prefix(&self.sibling_stack, is_last_sibling);
127 sh_println!("{}{}", prefix, message);
128 }
129 pub fn format_error(&self, is_last_sibling: bool, message: &str) {
131 let prefix = build_prefix(&self.sibling_stack, is_last_sibling);
132 sh_println!("{}", format!("{}{}", prefix, message).red());
133 }
134 pub fn print_gas_details(
136 &mut self,
137 gas_details: &GasDetails,
138 fee_model_config: &FeeModelConfigV2,
139 ) {
140 let GasDetails {
141 total_gas_limit,
142 intrinsic_gas,
143 gas_for_validation,
144 gas_spent_on_compute,
145 gas_used,
146 bytes_published,
147 spent_on_pubdata,
148 gas_spent_on_bytecode_preparation,
149 refund_computed,
150 refund_by_operator,
151 required_overhead: _required_overhead,
152 operator_overhead,
153 intrinsic_overhead,
154 overhead_for_length,
155 overhead_for_slot,
156 gas_per_pubdata,
157 total_gas_limit_from_user,
158 ..
159 } = *gas_details;
160
161 self.section("[Gas Details]", true, |gas_details_section| {
162 let mut total_items = 0;
163 let mut warnings = Vec::new();
164
165 if refund_computed != refund_by_operator {
167 warnings.push(format!(
168 "WARNING: Refund by VM: {}, but operator refunded: {}",
169 to_human_size(refund_computed),
170 to_human_size(refund_by_operator)
171 ));
172 }
173
174 if total_gas_limit_from_user != total_gas_limit {
175 warnings.push(format!(
176 "WARNING: User provided more gas ({}), but system had a lower max limit.",
177 to_human_size(total_gas_limit_from_user)
178 ));
179 }
180
181 total_items += 1; total_items += warnings.len(); total_items += 1; total_items += 1; total_items += 1; total_items += 1; let mut item_index = 0;
190
191 let is_last_sibling = item_index == total_items - 1;
193 gas_details_section.section("Gas Summary", is_last_sibling, |gas_summary_section| {
194 let items = vec![
195 ("Limit", to_human_size(total_gas_limit)),
196 ("Used", to_human_size(gas_used)),
197 ("Refunded", to_human_size(refund_by_operator)),
198 ("Paid:", to_human_size(total_gas_limit - refund_by_operator)),
199 ];
200
201 let num_items = items.len();
202 for (i, (key, value)) in items.into_iter().enumerate() {
203 let is_last_item = i == num_items - 1;
204 gas_summary_section.item(is_last_item, key, &value);
205 }
206 });
207 item_index += 1;
208
209 for warning in warnings {
211 let is_last_sibling = item_index == total_items - 1;
212 gas_details_section.format_error(is_last_sibling, &warning);
213 item_index += 1;
214 }
215
216 let is_last_sibling = item_index == total_items - 1;
218 gas_details_section.section(
219 "Execution Gas Breakdown",
220 is_last_sibling,
221 |execution_breakdown_section| {
222 let gas_breakdown_items = vec![
223 (
224 "Transaction Setup",
225 intrinsic_gas,
226 intrinsic_gas * 100 / gas_used,
227 ),
228 (
229 "Bytecode Preparation",
230 gas_spent_on_bytecode_preparation,
231 gas_spent_on_bytecode_preparation * 100 / gas_used,
232 ),
233 (
234 "Account Validation",
235 gas_for_validation,
236 gas_for_validation * 100 / gas_used,
237 ),
238 (
239 "Computations (Opcodes)",
240 gas_spent_on_compute,
241 gas_spent_on_compute * 100 / gas_used,
242 ),
243 ];
244
245 let num_items = gas_breakdown_items.len();
246 for (i, (description, amount, percentage)) in
247 gas_breakdown_items.iter().enumerate()
248 {
249 let is_last_item = i == num_items - 1;
250 execution_breakdown_section.item(
251 is_last_item,
252 description,
253 &format!("{} gas ({:>2}%)", to_human_size(*amount), percentage),
254 );
255 }
256 },
257 );
258 item_index += 1;
259
260 let is_last_sibling = item_index == total_items - 1;
262 gas_details_section.section(
263 "Transaction Setup Cost Breakdown",
264 is_last_sibling,
265 |transaction_setup_section| {
266 let items = vec![
267 (
268 "Total Setup Cost",
269 format!("{} gas", to_human_size(intrinsic_gas)),
270 ),
271 (
272 "Fixed Cost",
273 format!(
274 "{} gas ({:>2}%)",
275 to_human_size(intrinsic_overhead),
276 intrinsic_overhead * 100 / intrinsic_gas
277 ),
278 ),
279 (
280 "Operator Cost",
281 format!(
282 "{} gas ({:>2}%)",
283 to_human_size(operator_overhead),
284 operator_overhead * 100 / intrinsic_gas
285 ),
286 ),
287 ];
288
289 let num_items = items.len();
290 for (i, (key, value)) in items.into_iter().enumerate() {
291 let is_last_item = i == num_items - 1;
292 transaction_setup_section.item(is_last_item, key, &value);
293 }
294 },
295 );
296 item_index += 1;
297
298 let is_last_sibling = item_index == total_items - 1;
300 gas_details_section.section(
301 "L1 Publishing Costs",
302 is_last_sibling,
303 |l1_publishing_section| {
304 let items = vec![
305 (
306 "Published",
307 format!("{} bytes", to_human_size(bytes_published.into())),
308 ),
309 (
310 "Cost per Byte",
311 format!("{} gas", to_human_size(gas_per_pubdata)),
312 ),
313 (
314 "Total Gas Cost",
315 format!("{} gas", to_human_size(spent_on_pubdata.into())),
316 ),
317 ];
318
319 let num_items = items.len();
320 for (i, (key, value)) in items.into_iter().enumerate() {
321 let is_last_item = i == num_items - 1;
322 l1_publishing_section.item(is_last_item, key, &value);
323 }
324 },
325 );
326 item_index += 1;
327
328 let is_last_sibling = item_index == total_items - 1;
330 gas_details_section.section("Block Contribution", is_last_sibling, |block_section| {
331 let full_block_cost = gas_per_pubdata * fee_model_config.batch_overhead_l1_gas;
332
333 let items = vec![
334 (
335 "Length Overhead",
336 format!("{} gas", to_human_size(overhead_for_length)),
337 ),
338 (
339 "Slot Overhead",
340 format!("{} gas", to_human_size(overhead_for_slot)),
341 ),
342 (
343 "Full Block Cost",
344 format!("~{} L2 gas", to_human_size(full_block_cost)),
345 ),
346 ];
347
348 let num_items = items.len();
349 for (i, (key, value)) in items.into_iter().enumerate() {
350 let is_last_item = i == num_items - 1;
351 block_section.item(is_last_item, key, &value);
352 }
353 });
354 });
355 }
356 pub fn print_storage_logs(
358 &mut self,
359 log_query: &StorageLogWithPreviousValue,
360 pubdata_bytes: Option<PubdataBytesInfo>,
361 log_index: usize,
362 is_last: bool,
363 ) {
364 self.section(&format!("Log #{}", log_index), is_last, |log_section| {
365 let mut items = vec![
366 ("Kind", format!("{:?}", log_query.log.kind)),
367 (
368 "Address",
369 address_to_human_readable(*log_query.log.key.address())
370 .unwrap_or_else(|| format!("{:?}", log_query.log.key.address())),
371 ),
372 ("Key", format!("{:#066x}", log_query.log.key.key())),
373 ("Read Value", format!("{:#066x}", log_query.previous_value)),
374 ];
375
376 if log_query.log.is_write() {
377 items.push(("Written Value", format!("{:#066x}", log_query.log.value)));
378 }
379
380 let pubdata_bytes_str = pubdata_bytes
381 .map(|p| format!("{}", p))
382 .unwrap_or_else(|| "None".to_string());
383 items.push(("Pubdata Bytes", pubdata_bytes_str));
384
385 let num_items = items.len();
386 for (i, (key, value)) in items.iter().enumerate() {
387 let is_last_item = i == num_items - 1;
388 log_section.item(is_last_item, key, value);
389 }
390 });
391 }
392 pub fn print_vm_details(&mut self, result: &VmExecutionResultAndLogs) {
394 self.section("[VM Execution Results]", true, |section| {
395 let stats = [
396 (
397 "Cycles Used",
398 to_human_size(result.statistics.cycles_used.into()),
399 ),
400 (
401 "Computation Gas Used",
402 to_human_size(result.statistics.computational_gas_used.into()),
403 ),
404 (
405 "Contracts Used",
406 to_human_size(result.statistics.contracts_used.into()),
407 ),
408 ];
409
410 for (key, value) in stats.iter() {
411 section.item(false, key, value);
412 }
413
414 match &result.result {
416 zksync_multivm::interface::ExecutionResult::Success { .. } => {
417 section.item(true, "Execution Outcome", "Success");
418 }
419 zksync_multivm::interface::ExecutionResult::Revert { output } => {
420 section.item(false, "Execution Outcome", "Failure");
421 section.format_error(
422 true,
423 &format!("Revert Reason: {}", output.to_user_friendly_string()),
424 );
425 }
426 zksync_multivm::interface::ExecutionResult::Halt { reason } => {
427 section.item(false, "Execution Outcome", "Failure");
428 section.format_error(true, &format!("Halt Reason: {}", reason));
429 }
430 }
431 });
432 }
433}
434fn build_prefix(sibling_stack: &[bool], is_last_sibling: bool) -> String {
436 let mut prefix = String::new();
437 if !sibling_stack.is_empty() {
438 for &is_last in sibling_stack {
439 if !is_last {
440 prefix.push_str("│ ");
441 } else {
442 prefix.push_str(" ");
443 }
444 }
445 let branch = if is_last_sibling {
446 "└─ "
447 } else {
448 "├─ "
449 };
450 prefix.push_str(branch);
451 }
452 prefix
453}