From 41b0f2c255f9fbe27f9d861d60ddb068f7f04a1d Mon Sep 17 00:00:00 2001 From: JoshuaBatty Date: Mon, 20 Jan 2025 13:24:45 +1100 Subject: [PATCH 01/16] init decode --- forc-plugins/forc-debug/Cargo.toml | 1 + forc-plugins/forc-debug/src/cli/commands.rs | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/forc-plugins/forc-debug/Cargo.toml b/forc-plugins/forc-debug/Cargo.toml index d00b45cd6aa..9f9f0706bda 100644 --- a/forc-plugins/forc-debug/Cargo.toml +++ b/forc-plugins/forc-debug/Cargo.toml @@ -18,6 +18,7 @@ forc-test.workspace = true forc-tracing.workspace = true fuel-core-client.workspace = true fuel-types = { workspace = true, features = ["serde"] } +fuel-tx.workspace = true fuel-vm = { workspace = true, features = ["serde"] } rayon.workspace = true rustyline.workspace = true diff --git a/forc-plugins/forc-debug/src/cli/commands.rs b/forc-plugins/forc-debug/src/cli/commands.rs index 87768eacafd..37ad5bb3d3d 100644 --- a/forc-plugins/forc-debug/src/cli/commands.rs +++ b/forc-plugins/forc-debug/src/cli/commands.rs @@ -4,6 +4,7 @@ use crate::{ names::{register_index, register_name}, ContractId, RunResult, Transaction, }; +use fuel_tx::Receipt; use fuel_vm::consts::{VM_MAX_RAM, VM_REGISTER_COUNT, WORD_SIZE}; use std::collections::HashSet; use strsim::levenshtein; @@ -367,6 +368,24 @@ pub async fn cmd_help(helper: &DebuggerHelper, args: &[String]) -> Result<()> { fn pretty_print_run_result(rr: &RunResult) { for receipt in rr.receipts() { println!("Receipt: {receipt:#?}"); + + if let Receipt::LogData { + rb, + data: Some(data), + .. + } = receipt + { + let decoded_log_data = forc_test::decode_log_data( + &rb.to_string(), + &data, + &rr.contract_abi.unwrap(), + ) + .unwrap(); + println!( + "Decoded log value: {}, log rb: {}", + decoded_log_data.value, rb + ); + } } if let Some(bp) = &rr.breakpoint { println!( From e44f29e331ec764fc4942e0c90e7184bd40d9ada Mon Sep 17 00:00:00 2001 From: JoshuaBatty Date: Wed, 22 Jan 2025 14:42:11 +1100 Subject: [PATCH 02/16] decode log when an ABI is passed in --- forc-plugins/forc-debug/Cargo.toml | 1 + forc-plugins/forc-debug/src/cli/commands.rs | 51 +++++---- forc-plugins/forc-debug/src/cli/state.rs | 116 ++++++++++++++------ 3 files changed, 112 insertions(+), 56 deletions(-) diff --git a/forc-plugins/forc-debug/Cargo.toml b/forc-plugins/forc-debug/Cargo.toml index 9f9f0706bda..d7b7747035a 100644 --- a/forc-plugins/forc-debug/Cargo.toml +++ b/forc-plugins/forc-debug/Cargo.toml @@ -16,6 +16,7 @@ dirs.workspace = true forc-pkg.workspace = true forc-test.workspace = true forc-tracing.workspace = true +fuel-abi-types.workspace = true fuel-core-client.workspace = true fuel-types = { workspace = true, features = ["serde"] } fuel-tx.workspace = true diff --git a/forc-plugins/forc-debug/src/cli/commands.rs b/forc-plugins/forc-debug/src/cli/commands.rs index 37ad5bb3d3d..cd5c0d36991 100644 --- a/forc-plugins/forc-debug/src/cli/commands.rs +++ b/forc-plugins/forc-debug/src/cli/commands.rs @@ -8,6 +8,7 @@ use fuel_tx::Receipt; use fuel_vm::consts::{VM_MAX_RAM, VM_REGISTER_COUNT, WORD_SIZE}; use std::collections::HashSet; use strsim::levenshtein; +use sway_core::asm_generation::ProgramABI; #[derive(Debug, Clone)] pub struct Command { @@ -162,7 +163,21 @@ impl Commands { pub async fn cmd_start_tx(state: &mut State, mut args: Vec) -> Result<()> { args.remove(0); // Remove the command name - ArgumentError::ensure_arg_count(&args, 1, 1)?; // Ensure exactly one argument + ArgumentError::ensure_arg_count(&args, 1, 2)?; // Allow 1 or 2 arguments + + // Get ABI path first if it exists (last argument) + let abi = if args.len() > 1 { + let abi_path = args.pop().unwrap(); + let abi_content = std::fs::read_to_string(&abi_path).map_err(Error::IoError)?; + // First deserialize into the fuel ABI type + let fuel_abi = + serde_json::from_str::(&abi_content) + .map_err(Error::JsonError)?; + // Then wrap it in our ProgramABI enum + Some(ProgramABI::Fuel(fuel_abi)) + } else { + None + }; let path_to_tx_json = args.pop().unwrap(); // Safe due to arg count check @@ -177,7 +192,7 @@ pub async fn cmd_start_tx(state: &mut State, mut args: Vec) -> Result<() .await .map_err(|e| Error::FuelClientError(e.to_string()))?; - pretty_print_run_result(&status); + pretty_print_run_result(&status, abi.as_ref()); Ok(()) } @@ -206,7 +221,7 @@ pub async fn cmd_continue(state: &mut State, mut args: Vec) -> Result<() .await .map_err(|e| Error::FuelClientError(e.to_string()))?; - pretty_print_run_result(&status); + pretty_print_run_result(&status, None); Ok(()) } @@ -365,26 +380,22 @@ pub async fn cmd_help(helper: &DebuggerHelper, args: &[String]) -> Result<()> { /// /// Outputs each receipt in the `RunResult` and details about the breakpoint if present. /// If the execution terminated without hitting a breakpoint, it prints "Terminated". -fn pretty_print_run_result(rr: &RunResult) { +fn pretty_print_run_result(rr: &RunResult, abi: Option<&ProgramABI>) { for receipt in rr.receipts() { println!("Receipt: {receipt:#?}"); - if let Receipt::LogData { - rb, - data: Some(data), - .. - } = receipt - { - let decoded_log_data = forc_test::decode_log_data( - &rb.to_string(), - &data, - &rr.contract_abi.unwrap(), - ) - .unwrap(); - println!( - "Decoded log value: {}, log rb: {}", - decoded_log_data.value, rb - ); + // If the ABI is available, decode the log data + if let Some(program_abi) = abi { + if let Receipt::LogData { + rb, + data: Some(data), + .. + } = receipt + { + let decoded_log_data = + forc_test::decode_log_data(&rb.to_string(), &data, program_abi).unwrap(); + println!("Decoded log value: {}", decoded_log_data.value,); + } } } if let Some(bp) = &rr.breakpoint { diff --git a/forc-plugins/forc-debug/src/cli/state.rs b/forc-plugins/forc-debug/src/cli/state.rs index e368da8b68d..3633a41f856 100644 --- a/forc-plugins/forc-debug/src/cli/state.rs +++ b/forc-plugins/forc-debug/src/cli/state.rs @@ -7,7 +7,7 @@ use rustyline::{ Context, Helper, }; use serde_json::Value; -use std::{borrow::Cow, fs, path::Path}; +use std::{borrow::Cow, collections::HashSet, fs}; pub struct State { pub client: FuelClient, @@ -51,6 +51,11 @@ impl Completer for DebuggerHelper { // Transaction command context if let Some(first_word) = words.first() { if self.commands.is_tx_command(first_word) && line[..word_start].ends_with(' ') { + // If we already have a transaction file argument, look for ABI files + if words.len() > 1 { + return Ok((word_start, get_abi_files(word_to_complete))); + } + // Otherwise look for transaction files return Ok((word_start, get_transaction_files(word_to_complete))); } @@ -141,56 +146,95 @@ impl Validator for DebuggerHelper { impl Helper for DebuggerHelper {} +/// Get valid ABI files matching the current word +fn get_abi_files(current_word: &str) -> Vec { + find_valid_json_files(current_word, is_valid_abi) +} + /// Returns valid transaction JSON files from current directory and subdirectories. /// Files must contain one of: Script, Create, Mint, Upgrade, Upload, or Blob keys. fn get_transaction_files(current_word: &str) -> Vec { - fn is_valid_transaction_json(path: &Path) -> bool { - // Read and parse the JSON file - let content = match fs::read_to_string(path) { - Ok(content) => content, - Err(_) => return false, - }; - let json: Value = match serde_json::from_str(&content) { - Ok(json) => json, - Err(_) => return false, - }; - - // Check if it's a valid transaction JSON - if let Value::Object(obj) = json { - // Check for transaction type - let has_valid_type = obj.keys().any(|key| { - matches!( - key.as_str(), - "Script" | "Create" | "Mint" | "Upgrade" | "Upload" | "Blob" - ) - }); - return has_valid_type; - } - false - } + find_valid_json_files(current_word, is_valid_transaction) +} +/// Generic function to find and validate JSON files +fn find_valid_json_files(current_word: &str, is_valid: F) -> Vec +where + F: Fn(&Value) -> bool, +{ let mut matches = Vec::new(); - - // Create the walker and iterate through entries let walker = walkdir::WalkDir::new(".").follow_links(true); + for entry in walker.into_iter().filter_map(|e| e.ok()) { if entry.file_type().is_file() { - let path = entry.path(); - - // Check if it's a .json file and starts with the current word - if let Some(filename) = path + if let Some(filename) = entry + .path() .to_string_lossy() .strip_prefix("./") .map(|f| f.to_string()) { - if filename.ends_with(".json") - && filename.starts_with(current_word) - && is_valid_transaction_json(path) - { - matches.push(filename); + if filename.ends_with(".json") && filename.starts_with(current_word) { + // Try to read and parse the file + if let Ok(content) = fs::read_to_string(entry.path()) { + if let Ok(json) = serde_json::from_str::(&content) { + if is_valid(&json) { + matches.push(filename); + } + } + } } } } } matches } + +/// Checks if a JSON value represents a valid transaction +fn is_valid_transaction(json: &Value) -> bool { + if let Value::Object(obj) = json { + // Check for transaction type + obj.keys().any(|key| { + matches!( + key.as_str(), + "Script" | "Create" | "Mint" | "Upgrade" | "Upload" | "Blob" + ) + }) + } else { + false + } +} + +/// Checks if a JSON value represents a valid ABI +fn is_valid_abi(json: &Value) -> bool { + if let Value::Object(obj) = json { + // Required fields for an ABI + let required_fields: HashSet<_> = [ + "programType", + "functions", + "concreteTypes", + "encodingVersion", + ] + .iter() + .collect(); + + // Check that all required fields exist and have the correct type + if !required_fields + .iter() + .all(|&field| obj.contains_key(*field)) + { + return false; + } + + // Validate functions array + if let Some(Value::Array(functions)) = obj.get("functions") { + // Every function should have a name and inputs field + functions.iter().all(|f| { + matches!(f, Value::Object(f_obj) if f_obj.contains_key("name") && f_obj.contains_key("inputs")) + }) + } else { + false + } + } else { + false + } +} From 3ee395ee7c97b52bfbcaab708f36024079007362 Mon Sep 17 00:00:00 2001 From: JoshuaBatty Date: Wed, 22 Jan 2025 16:22:14 +1100 Subject: [PATCH 03/16] Refactor transaction debugging command to support multiple contract ABIs and local development setup with improved argument handling. --- forc-plugins/forc-debug/src/cli/commands.rs | 136 +++++++++++++++----- forc-plugins/forc-debug/src/cli/state.rs | 79 ++++++++++-- forc-plugins/forc-debug/src/error.rs | 3 + 3 files changed, 176 insertions(+), 42 deletions(-) diff --git a/forc-plugins/forc-debug/src/cli/commands.rs b/forc-plugins/forc-debug/src/cli/commands.rs index cd5c0d36991..4d11e864e6e 100644 --- a/forc-plugins/forc-debug/src/cli/commands.rs +++ b/forc-plugins/forc-debug/src/cli/commands.rs @@ -161,38 +161,100 @@ impl Commands { } } +/// Start a debugging session for a transaction with optional ABI support. +/// +/// Handles two distinct modes of operation: +/// 1. Local Development: `tx transaction.json abi.json` +/// 2. Contract-specific: `tx transaction.json --abi :` +/// +/// In both modes, the function will automatically attempt to fetch ABIs for any +/// contract IDs encountered during execution if they haven't been explicitly provided. +/// +/// # Arguments format +/// - First argument: Path to transaction JSON file (required) +/// - Local dev mode: Optional path to ABI JSON file +/// - Contract mode: Multiple `--abi contract_id:abi_file.json` pairs +/// +/// # Example usage +/// ```text +/// tx transaction.json // No ABI +/// tx transaction.json abi.json // Local development +/// tx transaction.json --abi 0x123...:contract.json // Single contract +/// tx transaction.json --abi 0x123...:a.json --abi 0x456...:b.json // Multiple +/// ``` pub async fn cmd_start_tx(state: &mut State, mut args: Vec) -> Result<()> { - args.remove(0); // Remove the command name - ArgumentError::ensure_arg_count(&args, 1, 2)?; // Allow 1 or 2 arguments - - // Get ABI path first if it exists (last argument) - let abi = if args.len() > 1 { - let abi_path = args.pop().unwrap(); - let abi_content = std::fs::read_to_string(&abi_path).map_err(Error::IoError)?; - // First deserialize into the fuel ABI type - let fuel_abi = - serde_json::from_str::(&abi_content) - .map_err(Error::JsonError)?; - // Then wrap it in our ProgramABI enum - Some(ProgramABI::Fuel(fuel_abi)) - } else { - None - }; + // Remove command name from arguments + args.remove(0); + ArgumentError::ensure_arg_count(&args, 1, 2)?; + + let mut abi_args = Vec::new(); + let mut tx_path = None; + + // Parse arguments iteratively, handling both --abi flags and local dev mode + let mut i = 0; + while i < args.len() { + match args[i].as_str() { + "--abi" => { + if i + 1 < args.len() { + abi_args.push(args[i + 1].clone()); + i += 2; + } else { + return Err(ArgumentError::Invalid("Missing argument for --abi".into()).into()); + } + } + arg => { + if tx_path.is_none() { + // First non-flag argument is the transaction path + tx_path = Some(arg.to_string()); + } else if arg.ends_with(".json") { + // Second .json file is treated as local development ABI + let abi_content = std::fs::read_to_string(arg).map_err(Error::IoError)?; + let fuel_abi = + serde_json::from_str::( + &abi_content, + ) + .map_err(Error::JsonError)?; + state.register_abi(ContractId::zeroed(), ProgramABI::Fuel(fuel_abi)); + } + i += 1; + } + } + } - let path_to_tx_json = args.pop().unwrap(); // Safe due to arg count check + let tx_path = + tx_path.ok_or_else(|| ArgumentError::Invalid("Transaction file required".into()))?; + + // Process contract-specific ABI mappings from --abi arguments + for abi_arg in abi_args { + if let Some((contract_id, abi_path)) = abi_arg.split_once(':') { + let contract_id = contract_id.parse::().map_err(|_| { + ArgumentError::Invalid(format!("Invalid contract ID: {}", contract_id)) + })?; + + let abi_content = std::fs::read_to_string(abi_path).map_err(Error::IoError)?; + let fuel_abi = + serde_json::from_str::(&abi_content) + .map_err(Error::JsonError)?; + + state.register_abi(contract_id, ProgramABI::Fuel(fuel_abi)); + } else { + return Err( + ArgumentError::Invalid(format!("Invalid --abi argument: {}", abi_arg)).into(), + ); + } + } - // Read and parse the transaction JSON - let tx_json = std::fs::read(&path_to_tx_json).map_err(Error::IoError)?; + // Start transaction execution + let tx_json = std::fs::read(&tx_path).map_err(Error::IoError)?; let tx: Transaction = serde_json::from_slice(&tx_json).map_err(Error::JsonError)?; - // Start the transaction let status = state .client .start_tx(&state.session_id, &tx) .await .map_err(|e| Error::FuelClientError(e.to_string()))?; - pretty_print_run_result(&status, abi.as_ref()); + pretty_print_run_result(&status, state); Ok(()) } @@ -221,7 +283,7 @@ pub async fn cmd_continue(state: &mut State, mut args: Vec) -> Result<() .await .map_err(|e| Error::FuelClientError(e.to_string()))?; - pretty_print_run_result(&status, None); + pretty_print_run_result(&status, state); Ok(()) } @@ -380,21 +442,27 @@ pub async fn cmd_help(helper: &DebuggerHelper, args: &[String]) -> Result<()> { /// /// Outputs each receipt in the `RunResult` and details about the breakpoint if present. /// If the execution terminated without hitting a breakpoint, it prints "Terminated". -fn pretty_print_run_result(rr: &RunResult, abi: Option<&ProgramABI>) { +fn pretty_print_run_result(rr: &RunResult, state: &mut State) { for receipt in rr.receipts() { println!("Receipt: {receipt:#?}"); - // If the ABI is available, decode the log data - if let Some(program_abi) = abi { - if let Receipt::LogData { - rb, - data: Some(data), - .. - } = receipt - { - let decoded_log_data = - forc_test::decode_log_data(&rb.to_string(), &data, program_abi).unwrap(); - println!("Decoded log value: {}", decoded_log_data.value,); + if let Receipt::LogData { + id, + rb, + data: Some(data), + .. + } = receipt + { + // If the ABI is available, decode the log data + if let Some(abi) = state.get_or_fetch_abi(&id) { + if let Ok(decoded_log_data) = + forc_test::decode_log_data(&rb.to_string(), &data, abi) + { + println!( + "Decoded log value: {}, from contract: {}", + decoded_log_data.value, id + ); + } } } } diff --git a/forc-plugins/forc-debug/src/cli/state.rs b/forc-plugins/forc-debug/src/cli/state.rs index 3633a41f856..0ff9e10da77 100644 --- a/forc-plugins/forc-debug/src/cli/state.rs +++ b/forc-plugins/forc-debug/src/cli/state.rs @@ -1,4 +1,9 @@ -use crate::{cli::commands::Commands, names, FuelClient}; +use crate::{ + cli::commands::Commands, + error::{Error, Result}, + names, FuelClient, +}; +use fuel_types::ContractId; use rustyline::{ completion::Completer, highlight::{CmdKind, Highlighter}, @@ -7,11 +12,17 @@ use rustyline::{ Context, Helper, }; use serde_json::Value; -use std::{borrow::Cow, collections::HashSet, fs}; +use std::{ + borrow::Cow, + collections::{HashMap, HashSet}, + fs, +}; +use sway_core::asm_generation::ProgramABI; pub struct State { pub client: FuelClient, pub session_id: String, + pub contract_abis: HashMap, } impl State { @@ -19,8 +30,37 @@ impl State { Self { client, session_id: String::new(), + contract_abis: HashMap::new(), } } + + /// Registers the given ABI for the given contract ID. + pub fn register_abi(&mut self, contract_id: ContractId, abi: ProgramABI) { + self.contract_abis.insert(contract_id, abi); + } + + /// Either fetches the ABI from the Sway ABI Registry or returns it from the cache if it's already known. + pub fn get_or_fetch_abi(&mut self, contract_id: &ContractId) -> Option<&ProgramABI> { + // If we already have it, return it + if self.contract_abis.contains_key(contract_id) { + return self.contract_abis.get(contract_id); + } + + // Try to fetch from ABI Registry + match fetch_abi_from_api(contract_id) { + Ok(abi) => { + self.register_abi(*contract_id, abi); + self.contract_abis.get(contract_id) + } + Err(_) => None, + } + } +} + +/// Fetches the ABI for the given contract ID from the Sway ABI Registry. +fn fetch_abi_from_api(_contract_id: &ContractId) -> Result { + // TODO: Implement this once the Sway ABI Registry is available + Err(Error::AbiError("Not implemented yet".to_string())) } pub struct DebuggerHelper { @@ -50,13 +90,36 @@ impl Completer for DebuggerHelper { // Transaction command context if let Some(first_word) = words.first() { - if self.commands.is_tx_command(first_word) && line[..word_start].ends_with(' ') { - // If we already have a transaction file argument, look for ABI files - if words.len() > 1 { - return Ok((word_start, get_abi_files(word_to_complete))); + if self.commands.is_tx_command(first_word) { + match words.len() { + 1 => { + // First argument is transaction file + return Ok((word_start, get_transaction_files(word_to_complete))); + } + 2 => { + // Second argument could be either: + // 1. a local ABI file (ends in .json) + // 2. the --abi flag + if word_to_complete.is_empty() || word_to_complete.starts_with('-') { + return Ok((word_start, vec!["--abi".to_string()])); + } else { + return Ok((word_start, get_abi_files(word_to_complete))); + } + } + _ => { + // If previous word was --abi, we expect contract_id:abi.json + if words[words.len() - 2] == "--abi" { + // Here we could potentially provide completion for known contract IDs + // followed by a colon and the ABI file + let abi_files = get_abi_files(""); + let completions: Vec = abi_files + .into_iter() + .map(|abi| format!("contract_id:{}", abi)) + .collect(); + return Ok((word_start, completions)); + } + } } - // Otherwise look for transaction files - return Ok((word_start, get_transaction_files(word_to_complete))); } // Register command context diff --git a/forc-plugins/forc-debug/src/error.rs b/forc-plugins/forc-debug/src/error.rs index 4128f913edf..e4377850e33 100644 --- a/forc-plugins/forc-debug/src/error.rs +++ b/forc-plugins/forc-debug/src/error.rs @@ -23,6 +23,9 @@ pub enum Error { #[error("I/O error")] IoError(std::io::Error), + #[error("ABI error: {0}")] + AbiError(String), + #[error("Json error")] JsonError(#[from] serde_json::Error), From e4c42fe1effef091123cb882cd0c92b85bd2d534 Mon Sep 17 00:00:00 2001 From: JoshuaBatty Date: Thu, 23 Jan 2025 09:05:31 +1100 Subject: [PATCH 04/16] improve autocomplete for abi files --- forc-plugins/forc-debug/src/cli/state.rs | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/forc-plugins/forc-debug/src/cli/state.rs b/forc-plugins/forc-debug/src/cli/state.rs index 0ff9e10da77..ce58d136dfb 100644 --- a/forc-plugins/forc-debug/src/cli/state.rs +++ b/forc-plugins/forc-debug/src/cli/state.rs @@ -97,26 +97,14 @@ impl Completer for DebuggerHelper { return Ok((word_start, get_transaction_files(word_to_complete))); } 2 => { - // Second argument could be either: - // 1. a local ABI file (ends in .json) - // 2. the --abi flag - if word_to_complete.is_empty() || word_to_complete.starts_with('-') { - return Ok((word_start, vec!["--abi".to_string()])); - } else { - return Ok((word_start, get_abi_files(word_to_complete))); - } + // Second argument is local ABI file + return Ok((word_start, get_abi_files(word_to_complete))); } _ => { - // If previous word was --abi, we expect contract_id:abi.json + // After this, if someone explicitly types --abi, then we can help with contract_id:abi.json if words[words.len() - 2] == "--abi" { - // Here we could potentially provide completion for known contract IDs - // followed by a colon and the ABI file - let abi_files = get_abi_files(""); - let completions: Vec = abi_files - .into_iter() - .map(|abi| format!("contract_id:{}", abi)) - .collect(); - return Ok((word_start, completions)); + let abi_files = get_abi_files(word_to_complete); + return Ok((word_start, abi_files)); } } } From 09c60122804b5de61d07270ee373464253cf55de Mon Sep 17 00:00:00 2001 From: JoshuaBatty Date: Thu, 23 Jan 2025 10:38:26 +1100 Subject: [PATCH 05/16] rebase From 48f1a811e4ddbba106b6ec2f5303989ce0b61cfb Mon Sep 17 00:00:00 2001 From: JoshuaBatty Date: Thu, 23 Jan 2025 10:51:59 +1100 Subject: [PATCH 06/16] move AbiMap to types --- forc-plugins/forc-debug/src/cli/commands.rs | 10 +++- forc-plugins/forc-debug/src/cli/state.rs | 46 ++-------------- forc-plugins/forc-debug/src/types.rs | 61 ++++++++++++++++++++- 3 files changed, 71 insertions(+), 46 deletions(-) diff --git a/forc-plugins/forc-debug/src/cli/commands.rs b/forc-plugins/forc-debug/src/cli/commands.rs index 4d11e864e6e..e8f5be3422c 100644 --- a/forc-plugins/forc-debug/src/cli/commands.rs +++ b/forc-plugins/forc-debug/src/cli/commands.rs @@ -214,7 +214,9 @@ pub async fn cmd_start_tx(state: &mut State, mut args: Vec) -> Result<() &abi_content, ) .map_err(Error::JsonError)?; - state.register_abi(ContractId::zeroed(), ProgramABI::Fuel(fuel_abi)); + state + .contract_abis + .register_abi(ContractId::zeroed(), ProgramABI::Fuel(fuel_abi)); } i += 1; } @@ -236,7 +238,9 @@ pub async fn cmd_start_tx(state: &mut State, mut args: Vec) -> Result<() serde_json::from_str::(&abi_content) .map_err(Error::JsonError)?; - state.register_abi(contract_id, ProgramABI::Fuel(fuel_abi)); + state + .contract_abis + .register_abi(contract_id, ProgramABI::Fuel(fuel_abi)); } else { return Err( ArgumentError::Invalid(format!("Invalid --abi argument: {}", abi_arg)).into(), @@ -454,7 +458,7 @@ fn pretty_print_run_result(rr: &RunResult, state: &mut State) { } = receipt { // If the ABI is available, decode the log data - if let Some(abi) = state.get_or_fetch_abi(&id) { + if let Some(abi) = state.contract_abis.get_or_fetch_abi(&id) { if let Ok(decoded_log_data) = forc_test::decode_log_data(&rb.to_string(), &data, abi) { diff --git a/forc-plugins/forc-debug/src/cli/state.rs b/forc-plugins/forc-debug/src/cli/state.rs index ce58d136dfb..6c7b2620712 100644 --- a/forc-plugins/forc-debug/src/cli/state.rs +++ b/forc-plugins/forc-debug/src/cli/state.rs @@ -1,9 +1,4 @@ -use crate::{ - cli::commands::Commands, - error::{Error, Result}, - names, FuelClient, -}; -use fuel_types::ContractId; +use crate::{cli::commands::Commands, names, types::AbiMap, FuelClient}; use rustyline::{ completion::Completer, highlight::{CmdKind, Highlighter}, @@ -12,17 +7,12 @@ use rustyline::{ Context, Helper, }; use serde_json::Value; -use std::{ - borrow::Cow, - collections::{HashMap, HashSet}, - fs, -}; -use sway_core::asm_generation::ProgramABI; +use std::{borrow::Cow, collections::HashSet, fs}; pub struct State { pub client: FuelClient, pub session_id: String, - pub contract_abis: HashMap, + pub contract_abis: AbiMap, } impl State { @@ -30,39 +20,11 @@ impl State { Self { client, session_id: String::new(), - contract_abis: HashMap::new(), - } - } - - /// Registers the given ABI for the given contract ID. - pub fn register_abi(&mut self, contract_id: ContractId, abi: ProgramABI) { - self.contract_abis.insert(contract_id, abi); - } - - /// Either fetches the ABI from the Sway ABI Registry or returns it from the cache if it's already known. - pub fn get_or_fetch_abi(&mut self, contract_id: &ContractId) -> Option<&ProgramABI> { - // If we already have it, return it - if self.contract_abis.contains_key(contract_id) { - return self.contract_abis.get(contract_id); - } - - // Try to fetch from ABI Registry - match fetch_abi_from_api(contract_id) { - Ok(abi) => { - self.register_abi(*contract_id, abi); - self.contract_abis.get(contract_id) - } - Err(_) => None, + contract_abis: AbiMap::default(), } } } -/// Fetches the ABI for the given contract ID from the Sway ABI Registry. -fn fetch_abi_from_api(_contract_id: &ContractId) -> Result { - // TODO: Implement this once the Sway ABI Registry is available - Err(Error::AbiError("Not implemented yet".to_string())) -} - pub struct DebuggerHelper { pub commands: Commands, } diff --git a/forc-plugins/forc-debug/src/types.rs b/forc-plugins/forc-debug/src/types.rs index 4eb9b194eea..233702d8258 100644 --- a/forc-plugins/forc-debug/src/types.rs +++ b/forc-plugins/forc-debug/src/types.rs @@ -1,5 +1,12 @@ +use crate::error::{Error, Result}; use dap::types::Breakpoint; -use std::{collections::HashMap, path::PathBuf}; +use fuel_types::ContractId; +use std::{ + collections::HashMap, + ops::{Deref, DerefMut}, + path::PathBuf, +}; +use sway_core::asm_generation::ProgramABI; pub type Line = i64; pub type ExitCode = i64; @@ -7,3 +14,55 @@ pub type Instruction = u64; pub type FileSourceMap = HashMap>; pub type SourceMap = HashMap; pub type Breakpoints = HashMap>; + +/// A map storing ABIs for contracts, capable of fetching ABIs from the registry for unknown contracts. +pub struct AbiMap(HashMap); + +impl AbiMap { + /// Registers the given ABI for the given contract ID. + pub fn register_abi(&mut self, contract_id: ContractId, abi: ProgramABI) { + self.insert(contract_id, abi); + } + + /// Either fetches the ABI from the Sway ABI Registry or returns it from the cache if it's already known. + pub fn get_or_fetch_abi(&mut self, contract_id: &ContractId) -> Option<&ProgramABI> { + // If we already have it, return it + if self.contains_key(contract_id) { + return self.get(contract_id); + } + + // Try to fetch from ABI Registry + match fetch_abi_from_api(contract_id) { + Ok(abi) => { + self.register_abi(*contract_id, abi); + self.get(contract_id) + } + Err(_) => None, + } + } +} + +impl Default for AbiMap { + fn default() -> Self { + Self(HashMap::new()) + } +} + +impl Deref for AbiMap { + type Target = HashMap; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for AbiMap { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// Fetches the ABI for the given contract ID from the Sway ABI Registry. +fn fetch_abi_from_api(_contract_id: &ContractId) -> Result { + // TODO: Implement this once the Sway ABI Registry is available + Err(Error::AbiError("Not implemented yet".to_string())) +} From ca3bfc3a6269723c93c790803276422765345ea5 Mon Sep 17 00:00:00 2001 From: JoshuaBatty Date: Thu, 23 Jan 2025 13:29:39 +1100 Subject: [PATCH 07/16] update CLI documentation --- docs/book/src/debugging/debugging_with_cli.md | 282 +++++------------- 1 file changed, 71 insertions(+), 211 deletions(-) diff --git a/docs/book/src/debugging/debugging_with_cli.md b/docs/book/src/debugging/debugging_with_cli.md index 740eb3e5bc3..6c3e70eb455 100644 --- a/docs/book/src/debugging/debugging_with_cli.md +++ b/docs/book/src/debugging/debugging_with_cli.md @@ -46,7 +46,7 @@ After this the resulting binary should be located at `out/debug/dbg_example.bin` forc parse-bytecode out/debug/dbg_example.bin ``` -Which should give us something like + + +We can recognize the main loop by observing the control flow. Looking around halfword 58-60, we can see: + +```text + half-word byte op raw + 58 232 MOVI { dst: 0x11, val: 5 } 72 44 00 05 + 59 236 LT { dst: 0x10, lhs: 0x10, rhs: 0x11 } 16 41 04 40 + 60 240 JNZF { cond_nz: 0x10, dynamic: 0x0, fixed: 81 } 76 40 00 51 +``` + +Here we can see our `factorial(5)` being set up with `MOVI` setting the value 5, followed by the `LT` comparison and conditional jump `JNZF`. The multiplication for our factorial happens at halfword 147 with `MUL { dst: 0x10, lhs: 0x10, rhs: 0x11 }`. Finally, we can spot our log statement at halfword 139 with the `LOGD` instruction. ## Setting up the debugging -We can start up the debug infrastructure. On a new terminal session run `fuel-core run --db-type in-memory --debug`; we need to have that running because it actually executes the program. Now we can fire up the debugger itself: `forc-debug`. Now -if everything is set up correctly, you should see the debugger prompt (`>>`). You can use `help` command to list available commands. +We can start up the debug infrastructure. On a new terminal session run `fuel-core run --db-type in-memory --debug`; we need to have that running because it actually executes the program. Now we can fire up the debugger itself: `forc-debug`. Now if everything is set up correctly, you should see the debugger prompt (`>>`). You can use `help` command to list available commands. + +The debugger supports tab completion to help you discover files in your current working directory (and its subdirectories): + +- Type `tx` and press tab to recursively search for valid transaction JSON files +- After selecting a transaction file, press tab again to search for ABI files +- You can keep pressing tab to cycle through the found files +- Of course, you can also manually type the full path to any transaction or ABI file, they don't have to be in your current directory Now we would like to inspect the program while it's running. To do this, we first need to send the script to the executor, i.e. `fuel-core`. To do so, we need a *transaction specification*, `tx.json`. It looks something like this: @@ -87,94 +104,14 @@ Now we would like to inspect the program while it's running. To do this, we firs "body": { "script_gas_limit": 1000000, "script": [ - 144, - 0, - 0, - 4, - 71, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 68, - 93, - 252, - 192, - 1, - 16, - 255, - 243, - 0, - 26, - 72, - 16, - 0, - 26, - 68, - 0, - 0, - 93, - 67, - 240, - 0, - 22, - 65, - 20, - 0, - 115, - 64, - 0, - 13, - 51, - 72, - 0, - 0, - 36, - 0, - 0, - 0, - 16, - 69, - 16, - 64, - 27, - 73, - 36, - 64, - 144, - 0, - 0, - 8, - 71, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 5 + 26, 240, 48, 0, 116, 0, 0, 2, 0, 0, 0, 0, 0, 0, 3, 96, 93, 255, 192, 1, 16, 255, 255, 0, 26, 236, 80, 0, 145, 0, 0, 184, 80, 67, 176, 80, 32, 248, 51, 0, 88, 251, 224, 2, 80, 251, 224, 4, 116, 0, 0, 37, 80, 71, 176, 40, 26, 233, 16, 0, 32, 248, 51, 0, 88, 251, 224, 2, 80, 251, 224, 4, 116, 0, 0, 136, 26, 71, 208, 0, 114, 72, 0, 24, 40, 237, 20, 128, 80, 79, 176, 120, 114, 68, 0, 24, 40, 79, 180, 64, 80, 71, 176, 160, 114, 72, 0, 24, 40, 69, 52, 128, 80, 71, 176, 96, 114, 72, 0, 24, 40, 69, 52, 128, 80, 75, 176, 64, 26, 233, 16, 0, 26, 229, 32, 0, 32, 248, 51, 0, 88, 251, 224, 2, 80, 251, 224, 4, 116, 0, 0, 144, 26, 71, 208, 0, 80, 75, 176, 24, 114, 76, 0, 16, 40, 73, 20, 192, 80, 71, 176, 144, 114, 76, 0, 16, 40, 69, 36, 192, 114, 72, 0, 16, 40, 65, 20, 128, 93, 69, 0, 1, 93, 65, 0, 0, 37, 65, 16, 0, 149, 0, 0, 63, 150, 8, 0, 0, 26, 236, 80, 0, 145, 0, 1, 88, 26, 87, 224, 0, 95, 236, 16, 42, 95, 236, 0, 41, 93, 67, 176, 41, 114, 68, 0, 5, 22, 65, 4, 64, 118, 64, 0, 81, 93, 67, 176, 42, 80, 71, 176, 200, 26, 233, 16, 0, 32, 248, 51, 0, 88, 251, 224, 2, 80, 251, 224, 4, 116, 0, 0, 87, 26, 71, 208, 0, 114, 72, 0, 24, 40, 237, 20, 128, 80, 71, 176, 160, 114, 72, 0, 24, 40, 71, 180, 128, 80, 75, 176, 24, 114, 76, 0, 24, 40, 73, 20, 192, 80, 71, 176, 88, 114, 76, 0, 24, 40, 69, 36, 192, 93, 83, 176, 11, 93, 79, 176, 12, 93, 71, 176, 13, 114, 72, 0, 8, 16, 73, 20, 128, 21, 73, 36, 192, 118, 72, 0, 1, 116, 0, 0, 7, 114, 72, 0, 2, 27, 73, 52, 128, 114, 76, 0, 8, 16, 77, 36, 192, 38, 76, 0, 0, 40, 29, 68, 64, 26, 80, 112, 0, 16, 73, 68, 64, 95, 73, 0, 0, 114, 64, 0, 8, 16, 65, 20, 0, 80, 71, 176, 112, 95, 237, 64, 14, 95, 237, 48, 15, 95, 237, 0, 16, 80, 67, 176, 48, 114, 72, 0, 24, 40, 65, 20, 128, 80, 71, 176, 136, 114, 72, 0, 24, 40, 69, 4, 128, 80, 67, 177, 8, 114, 72, 0, 24, 40, 65, 20, 128, 80, 71, 177, 48, 114, 72, 0, 24, 40, 69, 4, 128, 80, 67, 177, 48, 80, 71, 176, 240, 114, 72, 0, 24, 40, 69, 4, 128, 80, 67, 176, 224, 26, 233, 16, 0, 26, 229, 0, 0, 32, 248, 51, 0, 88, 251, 224, 2, 80, 251, 224, 4, 116, 0, 0, 56, 26, 67, 208, 0, 80, 71, 176, 72, 114, 72, 0, 16, 40, 69, 4, 128, 80, 67, 177, 32, 114, 72, 0, 16, 40, 65, 20, 128, 80, 71, 176, 184, 114, 72, 0, 16, 40, 69, 4, 128, 93, 67, 240, 0, 93, 71, 176, 23, 93, 75, 176, 24, 52, 1, 4, 82, 26, 244, 0, 0, 116, 0, 0, 8, 93, 67, 176, 41, 16, 65, 0, 64, 95, 237, 0, 41, 93, 67, 176, 42, 93, 71, 176, 41, 27, 65, 4, 64, 95, 237, 0, 42, 117, 0, 0, 91, 146, 0, 1, 88, 26, 249, 80, 0, 152, 8, 0, 0, 151, 0, 0, 63, 74, 248, 0, 0, 149, 0, 0, 15, 150, 8, 0, 0, 26, 236, 80, 0, 145, 0, 0, 72, 26, 67, 160, 0, 26, 71, 224, 0, 114, 72, 4, 0, 38, 72, 0, 0, 26, 72, 112, 0, 80, 79, 176, 24, 95, 237, 32, 3, 114, 72, 4, 0, 95, 237, 32, 4, 95, 236, 0, 5, 114, 72, 0, 24, 40, 237, 52, 128, 80, 75, 176, 48, 114, 76, 0, 24, 40, 75, 180, 192, 114, 76, 0, 24, 40, 65, 36, 192, 26, 245, 0, 0, 146, 0, 0, 72, 26, 249, 16, 0, 152, 8, 0, 0, 151, 0, 0, 15, 74, 248, 0, 0, 149, 0, 0, 63, 150, 8, 0, 0, 26, 236, 80, 0, 145, 0, 0, 104, 26, 67, 160, 0, 26, 71, 144, 0, 26, 75, 224, 0, 80, 79, 176, 80, 114, 80, 0, 24, 40, 77, 5, 0, 114, 64, 0, 24, 40, 237, 52, 0, 80, 67, 176, 40, 114, 76, 0, 24, 40, 67, 180, 192, 93, 79, 176, 5, 80, 65, 0, 16, 80, 83, 176, 64, 95, 237, 48, 8, 80, 77, 64, 8, 114, 84, 0, 8, 40, 77, 5, 64, 80, 67, 176, 24, 114, 76, 0, 16, 40, 65, 68, 192, 114, 76, 0, 16, 40, 69, 4, 192, 26, 245, 16, 0, 146, 0, 0, 104, 26, 249, 32, 0, 152, 8, 0, 0, 151, 0, 0, 63, 74, 248, 0, 0, 71, 0, 0, 0, 21, 6, 230, 244, 76, 29, 98, 145 ], "script_data": [], "receipts_root": "0000000000000000000000000000000000000000000000000000000000000000" }, "policies": { "bits": "MaxFee", - "values": [ - 0, - 0, - 0, - 0 - ] + "values": [0, 0, 0, 0] }, "inputs": [ { @@ -201,72 +138,7 @@ Now we would like to inspect the program while it's running. To do this, we firs "outputs": [], "witnesses": [ { - "data": [ - 156, - 254, - 34, - 102, - 65, - 96, - 133, - 170, - 254, - 105, - 147, - 35, - 196, - 199, - 179, - 133, - 132, - 240, - 208, - 149, - 11, - 46, - 30, - 96, - 44, - 91, - 121, - 195, - 145, - 184, - 159, - 235, - 117, - 82, - 135, - 41, - 84, - 154, - 102, - 61, - 61, - 16, - 99, - 123, - 58, - 173, - 75, - 226, - 219, - 139, - 62, - 33, - 41, - 176, - 16, - 18, - 132, - 178, - 8, - 125, - 130, - 169, - 32, - 108 - ] + "data": [156, 254, 34, 102, 65, 96, 133, 170, 254, 105, 147, 35, 196, 199, 179, 133, 132, 240, 208, 149, 11, 46, 30, 96, 44, 91, 121, 195, 145, 184, 159, 235, 117, 82, 135, 41, 84, 154, 102, 61, 61, 16, 99, 123, 58, 173, 75, 226, 219, 139, 62, 33, 41, 176, 16, 18, 132, 178, 8, 125, 130, 169, 32, 108] } ] } @@ -283,27 +155,27 @@ So now we replace the script array with the result, and save it as `tx.json`. ## Using the debugger -Now we can actually execute the script: +Now we can actually execute the script with an ABI to decode the log values: ```text ->> start_tx tx.json +>> tx tx.json out/debug/dbg_example-abi.json -Receipt: Log { id: 0000000000000000000000000000000000000000000000000000000000000000, ra: 120, rb: 0, rc: 0, rd: 0, pc: 10380, is: 10336 } -Receipt: Return { id: 0000000000000000000000000000000000000000000000000000000000000000, val: 0, pc: 10384, is: 10336 } -Receipt: ScriptResult { result: Success, gas_used: 60 } +Receipt: LogData { id: 0000000000000000000000000000000000000000000000000000000000000000, ra: 0, rb: 1515152261580153489, ptr: 67107840, len: 8, digest: d2b80ebb9ce633ad49a9ccfcc58ac7ad33a9ab4741529ae4247a3b07e8fa1c74, pc: 10924, is: 10368, data: Some(0000000000000078) } +Decoded log value: 120, from contract: 0000000000000000000000000000000000000000000000000000000000000000 +Receipt: ReturnData { id: 0000000000000000000000000000000000000000000000000000000000000000, ptr: 67106816, len: 0, digest: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855, pc: 10564, is: 10368, data: Some() } +Receipt: ScriptResult { result: Success, gas_used: 1273 } Terminated ``` -Looking at the first output line, we can see that it logged `ra: 120` which is the correct return value for `factorial(5)`. It also tells us that the execution terminated without hitting any breakpoints. That's unsurprising, because we haven't set up any. We can do so with `breakpoint` command: +Looking at the output, we can see our `factorial(5)` result as the decoded log value of 120. The ABI has helped us decode the raw bytes `(0000000000000078)` into a meaningful value. It also tells us that the execution terminated without hitting any breakpoints. That's unsurprising, because we haven't set up any. We can do so with `breakpoint` command: ```text >> breakpoint 0 ->> start_tx tx.json +>> start_tx tx.json out/debug/dbg_example-abi.json Receipt: ScriptResult { result: Success, gas_used: 0 } Stopped on breakpoint at address 0 of contract 0x0000000000000000000000000000000000000000000000000000000000000000 - ``` Now we have stopped execution at the breakpoint on entry (address `0`). We can now inspect the initial state of the VM. @@ -315,7 +187,7 @@ reg[0x9] = 1000000 # ggas >> memory 0x10 0x8 - 000010: e9 5c 58 86 c8 87 26 dd + 000010: db f3 63 c9 1c 7f ec 95 ``` However, that's not too interesting either, so let's just execute until the end, and then reset the VM to remove the breakpoints. @@ -323,66 +195,59 @@ However, that's not too interesting either, so let's just execute until the end, ```text >> continue -Receipt: Log { id: 0000000000000000000000000000000000000000000000000000000000000000, ra: 120, rb: 0, rc: 0, rd: 0, pc: 10380, is: 10336 } -Receipt: Return { id: 0000000000000000000000000000000000000000000000000000000000000000, val: 0, pc: 10384, is: 10336 } +Receipt: LogData { id: 0000000000000000000000000000000000000000000000000000000000000000, ra: 0, rb: 1515152261580153489, ptr: 67107840, len: 8, digest: d2b80ebb9ce633ad49a9ccfcc58ac7ad33a9ab4741529ae4247a3b07e8fa1c74, pc: 10924, is: 10368, data: Some(0000000000000078) } +Decoded log value: 120, from contract: 0000000000000000000000000000000000000000000000000000000000000000 +Receipt: ReturnData { id: 0000000000000000000000000000000000000000000000000000000000000000, ptr: 67106816, len: 0, digest: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855, pc: 10564, is: 10368, data: Some() } Terminated >> reset - ``` -Next, we will setup a breakpoint to check the state on each iteration of the `while` loop. For instance, if we'd like to see what numbers get multiplied together, we could set up a breakpoint before the operation. The bytecode has only a single `MUL` instruction: +Next, we will setup a breakpoint to check the state on each iteration of the `while` loop. For instance, if we'd like to see what numbers get multiplied together, we could set up a breakpoint before the operation. Looking at our bytecode we can see the main multiplication for our factorial happens at: ```text - half-word byte op raw notes - 14 56 MUL { ra: 18, rb: 18, rc: 17 } 1b 49 24 40 + half-word byte op raw + 147 588 MUL { dst: 0x10, lhs: 0x10, rhs: 0x11 } 1b 41 04 40 ``` -We can set a breakpoint on its address, at halfword-offset `14`. +We can set a breakpoint on its address, at halfword-offset `147`. ```text ->>> breakpoint 14 - ->> start_tx tx.json +>>> breakpoint 147 -Receipt: ScriptResult { result: Success, gas_used: 9 } -Stopped on breakpoint at address 56 of contract 0x0000000000000000000000000000000000000000000000000000000000000000 +>> start_tx tx.json out/debug/dbg_example-abi.json +Receipt: ScriptResult { result: Success, gas_used: 82 } +Stopped on breakpoint at address 588 of contract 0x0000000000000000000000000000000000000000000000000000000000000000 ``` -Now we can inspect the inputs to multiply. Looking at [the specification](https://github.com/FuelLabs/fuel-specs/blob/master/src/fuel-vm/instruction-set.md#mul-multiply) tells us that the instruction `MUL { ra: 18, rb: 18, rc: 17 }` means `reg[18] = reg[18] * reg[17]`. So inspecting the inputs tells us that +Now we can inspect the inputs to multiply. Looking at [the specification](https://github.com/FuelLabs/fuel-specs/blob/master/src/fuel-vm/instruction-set.md#mul-multiply) tells us that the instruction `MUL { dst: 0x10, lhs: 0x10, rhs: 0x11 }` means `reg[0x10] = reg[0x10] * reg[0x11]`. So inspecting the inputs: ```text ->> r 18 17 - -reg[0x12] = 1 # reg18 +>> r 0x10 0x11 +reg[0x10] = 1 # reg16 reg[0x11] = 1 # reg17 ``` -So on the first round the numbers are `1` and `1`, so we can continue to the next iteration: +So on the first round the numbers are 1 and 1, so we can continue to the next iteration with the `c` command: ```text >> c +Stopped on breakpoint at address 588 of contract 0x0000000000000000000000000000000000000000000000000000000000000000 -Stopped on breakpoint at address 56 of contract 0x0000000000000000000000000000000000000000000000000000000000000000 - ->> r 18 17 - -reg[0x12] = 1 # reg18 +>> r 0x10 0x11 +reg[0x10] = 1 # reg16 reg[0x11] = 2 # reg17 - ``` And the next one: ```text >> c +Stopped on breakpoint at address 588 of contract 0x0000000000000000000000000000000000000000000000000000000000000000 -Stopped on breakpoint at address 56 of contract 0x0000000000000000000000000000000000000000000000000000000000000000 - ->> r 18 17 - -reg[0x12] = 2 # reg18 +>> r 0x10 0x11 +reg[0x10] = 2 # reg16 reg[0x11] = 3 # reg17 ``` @@ -390,46 +255,41 @@ And fourth one: ```text >> c +Stopped on breakpoint at address 588 of contract 0x0000000000000000000000000000000000000000000000000000000000000000 -Stopped on breakpoint at address 56 of contract 0x0000000000000000000000000000000000000000000000000000000000000000 - ->> r 18 17 - -reg[0x12] = 6 # reg18 +>> r 0x10 0x11 +reg[0x10] = 6 # reg16 reg[0x11] = 4 # reg17 - ``` And round 5: ```text >> c +Stopped on breakpoint at address 588 of contract 0x0000000000000000000000000000000000000000000000000000000000000000 -Stopped on breakpoint at address 56 of contract 0x0000000000000000000000000000000000000000000000000000000000000000 - ->> r 18 17 - -reg[0x12] = 24 # reg18 +>> r 0x10 0x11 +reg[0x10] = 24 # reg16 reg[0x11] = 5 # reg17 - ``` At this point we can look at the values -17 | 18 ----|---- -1 | 1 -2 | 1 -3 | 2 -4 | 6 -5 | 24 +0x10 | 0x11 +-----|------ +1 | 1 +1 | 2 +2 | 3 +6 | 4 +24 | 5 -From this we can clearly see that the left side, register `17` is the `counter` variable, and register `18` is `result`. Now the counter equals the given factorial function argument `5`, and the loop terminates. So when we continue, the program finishes without encountering any more breakpoints: +From this we can clearly see that the left side, register `0x10` is the `result` variable which accumulates the factorial calculation (1, 1, 2, 6, 24), and register `0x11` is the `counter` which increments from 1 to 5. Now the counter equals the given factorial function argument `5`, and the loop terminates. So when we continue, the program finishes without encountering any more breakpoints: ```text >> c -Receipt: Log { id: 0000000000000000000000000000000000000000000000000000000000000000, ra: 120, rb: 0, rc: 0, rd: 0, pc: 10380, is: 10336 } -Receipt: Return { id: 0000000000000000000000000000000000000000000000000000000000000000, val: 0, pc: 10384, is: 10336 } +Receipt: LogData { id: 0000000000000000000000000000000000000000000000000000000000000000, ra: 0, rb: 1515152261580153489, ptr: 67107840, len: 8, digest: d2b80ebb9ce633ad49a9ccfcc58ac7ad33a9ab4741529ae4247a3b07e8fa1c74, pc: 10924, is: 10368, data: Some(0000000000000078) } +Decoded log value: 120, from contract: 0000000000000000000000000000000000000000000000000000000000000000 +Receipt: ReturnData { id: 0000000000000000000000000000000000000000000000000000000000000000, ptr: 67106816, len: 0, digest: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855, pc: 10564, is: 10368, data: Some() } Terminated ``` From 2156d5984ac7bb3bedb5ed19bc604eecb0d7a98d Mon Sep 17 00:00:00 2001 From: JoshuaBatty Date: Thu, 23 Jan 2025 13:30:15 +1100 Subject: [PATCH 08/16] Fix function name typo and update print statement format string. --- forc-plugins/forc-debug/src/cli/commands.rs | 2 +- forc-plugins/forc-debug/src/types.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/forc-plugins/forc-debug/src/cli/commands.rs b/forc-plugins/forc-debug/src/cli/commands.rs index e8f5be3422c..4b0674da01b 100644 --- a/forc-plugins/forc-debug/src/cli/commands.rs +++ b/forc-plugins/forc-debug/src/cli/commands.rs @@ -448,7 +448,7 @@ pub async fn cmd_help(helper: &DebuggerHelper, args: &[String]) -> Result<()> { /// If the execution terminated without hitting a breakpoint, it prints "Terminated". fn pretty_print_run_result(rr: &RunResult, state: &mut State) { for receipt in rr.receipts() { - println!("Receipt: {receipt:#?}"); + println!("Receipt: {receipt:?}"); if let Receipt::LogData { id, diff --git a/forc-plugins/forc-debug/src/types.rs b/forc-plugins/forc-debug/src/types.rs index 233702d8258..f7ac814f539 100644 --- a/forc-plugins/forc-debug/src/types.rs +++ b/forc-plugins/forc-debug/src/types.rs @@ -32,7 +32,7 @@ impl AbiMap { } // Try to fetch from ABI Registry - match fetch_abi_from_api(contract_id) { + match fetch_abi_from_registry(contract_id) { Ok(abi) => { self.register_abi(*contract_id, abi); self.get(contract_id) @@ -62,7 +62,7 @@ impl DerefMut for AbiMap { } /// Fetches the ABI for the given contract ID from the Sway ABI Registry. -fn fetch_abi_from_api(_contract_id: &ContractId) -> Result { +fn fetch_abi_from_registry(_contract_id: &ContractId) -> Result { // TODO: Implement this once the Sway ABI Registry is available Err(Error::AbiError("Not implemented yet".to_string())) } From 8c4141aef3dfdcb21cb77cc2e78b1664ce6ede35 Mon Sep 17 00:00:00 2001 From: JoshuaBatty Date: Thu, 23 Jan 2025 13:53:13 +1100 Subject: [PATCH 09/16] clean up docs --- docs/book/src/debugging/debugging_with_cli.md | 30 +------------------ 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/docs/book/src/debugging/debugging_with_cli.md b/docs/book/src/debugging/debugging_with_cli.md index 6c3e70eb455..7e3da67da03 100644 --- a/docs/book/src/debugging/debugging_with_cli.md +++ b/docs/book/src/debugging/debugging_with_cli.md @@ -46,34 +46,6 @@ After this the resulting binary should be located at `out/debug/dbg_example.bin` forc parse-bytecode out/debug/dbg_example.bin ``` - - We can recognize the main loop by observing the control flow. Looking around halfword 58-60, we can see: ```text @@ -158,7 +130,7 @@ So now we replace the script array with the result, and save it as `tx.json`. Now we can actually execute the script with an ABI to decode the log values: ```text ->> tx tx.json out/debug/dbg_example-abi.json +>> start_tx tx.json out/debug/dbg_example-abi.json Receipt: LogData { id: 0000000000000000000000000000000000000000000000000000000000000000, ra: 0, rb: 1515152261580153489, ptr: 67107840, len: 8, digest: d2b80ebb9ce633ad49a9ccfcc58ac7ad33a9ab4741529ae4247a3b07e8fa1c74, pc: 10924, is: 10368, data: Some(0000000000000078) } Decoded log value: 120, from contract: 0000000000000000000000000000000000000000000000000000000000000000 From 540ab0c75616fdb221d6b658d00b4f3ceea41942 Mon Sep 17 00:00:00 2001 From: JoshuaBatty Date: Thu, 23 Jan 2025 13:56:03 +1100 Subject: [PATCH 10/16] add subdirectories to spell check custom words --- docs/book/spell-check-custom-words.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/book/spell-check-custom-words.txt b/docs/book/spell-check-custom-words.txt index 3f082eb0d5f..833d7ed01eb 100644 --- a/docs/book/spell-check-custom-words.txt +++ b/docs/book/spell-check-custom-words.txt @@ -236,4 +236,5 @@ semiautomatically FuelLabs github toml -hardcoded \ No newline at end of file +hardcoded +subdirectories \ No newline at end of file From 564b2ad931fd2f18ae290f937ccdc3ac6b246d13 Mon Sep 17 00:00:00 2001 From: JoshuaBatty Date: Thu, 23 Jan 2025 13:58:54 +1100 Subject: [PATCH 11/16] Reorder dependencies and features in Cargo.toml for forc-debug plugin. --- forc-plugins/forc-debug/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forc-plugins/forc-debug/Cargo.toml b/forc-plugins/forc-debug/Cargo.toml index d7b7747035a..ea8e1681b9a 100644 --- a/forc-plugins/forc-debug/Cargo.toml +++ b/forc-plugins/forc-debug/Cargo.toml @@ -18,8 +18,8 @@ forc-test.workspace = true forc-tracing.workspace = true fuel-abi-types.workspace = true fuel-core-client.workspace = true -fuel-types = { workspace = true, features = ["serde"] } fuel-tx.workspace = true +fuel-types = { workspace = true, features = ["serde"] } fuel-vm = { workspace = true, features = ["serde"] } rayon.workspace = true rustyline.workspace = true From 2117e3904b23f3902d741147d3947ae0481ad987 Mon Sep 17 00:00:00 2001 From: JoshuaBatty Date: Thu, 23 Jan 2025 14:01:04 +1100 Subject: [PATCH 12/16] clippy --- forc-plugins/forc-debug/src/types.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/forc-plugins/forc-debug/src/types.rs b/forc-plugins/forc-debug/src/types.rs index f7ac814f539..7d1fb552abd 100644 --- a/forc-plugins/forc-debug/src/types.rs +++ b/forc-plugins/forc-debug/src/types.rs @@ -16,6 +16,7 @@ pub type SourceMap = HashMap; pub type Breakpoints = HashMap>; /// A map storing ABIs for contracts, capable of fetching ABIs from the registry for unknown contracts. +#[derive(Debug, Default)] pub struct AbiMap(HashMap); impl AbiMap { @@ -42,12 +43,6 @@ impl AbiMap { } } -impl Default for AbiMap { - fn default() -> Self { - Self(HashMap::new()) - } -} - impl Deref for AbiMap { type Target = HashMap; fn deref(&self) -> &Self::Target { From 29b22598643f48dc166faca9995cdb6376b53233 Mon Sep 17 00:00:00 2001 From: JoshuaBatty Date: Tue, 28 Jan 2025 10:24:31 +1100 Subject: [PATCH 13/16] rebase and add Cargo.lock --- Cargo.lock | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 6dab0cc2304..221716b5717 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2787,7 +2787,9 @@ dependencies = [ "forc-pkg", "forc-test", "forc-tracing 0.66.6", + "fuel-abi-types", "fuel-core-client", + "fuel-tx", "fuel-types", "fuel-vm", "portpicker", From 1164ee9c17ee706adff539bbef53c021d52ac4a2 Mon Sep 17 00:00:00 2001 From: JoshuaBatty Date: Tue, 28 Jan 2025 10:53:47 +1100 Subject: [PATCH 14/16] Add ABI JSON support to CLI integration tests and update test case to check decoded log value. --- .../forc-debug/examples/example_abi.json | 32 ++++ .../forc-debug/examples/example_tx.json | 151 +----------------- .../forc-debug/tests/cli_integration.rs | 11 +- 3 files changed, 40 insertions(+), 154 deletions(-) create mode 100644 forc-plugins/forc-debug/examples/example_abi.json diff --git a/forc-plugins/forc-debug/examples/example_abi.json b/forc-plugins/forc-debug/examples/example_abi.json new file mode 100644 index 00000000000..80bae7e1150 --- /dev/null +++ b/forc-plugins/forc-debug/examples/example_abi.json @@ -0,0 +1,32 @@ +{ + "programType": "script", + "specVersion": "1", + "encodingVersion": "1", + "concreteTypes": [ + { + "type": "()", + "concreteTypeId": "2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d" + }, + { + "type": "u64", + "concreteTypeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0" + } + ], + "metadataTypes": [], + "functions": [ + { + "inputs": [], + "name": "main", + "output": "2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d", + "attributes": null + } + ], + "loggedTypes": [ + { + "logId": "1515152261580153489", + "concreteTypeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0" + } + ], + "messagesTypes": [], + "configurables": [] + } \ No newline at end of file diff --git a/forc-plugins/forc-debug/examples/example_tx.json b/forc-plugins/forc-debug/examples/example_tx.json index 3ee0a10afb9..51b6bad28e8 100644 --- a/forc-plugins/forc-debug/examples/example_tx.json +++ b/forc-plugins/forc-debug/examples/example_tx.json @@ -3,94 +3,14 @@ "body": { "script_gas_limit": 1000000, "script": [ - 144, - 0, - 0, - 4, - 71, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 68, - 93, - 252, - 192, - 1, - 16, - 255, - 243, - 0, - 26, - 72, - 16, - 0, - 26, - 68, - 0, - 0, - 93, - 67, - 240, - 0, - 22, - 65, - 20, - 0, - 115, - 64, - 0, - 13, - 51, - 72, - 0, - 0, - 36, - 0, - 0, - 0, - 16, - 69, - 16, - 64, - 27, - 73, - 36, - 64, - 144, - 0, - 0, - 8, - 71, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 5 + 26, 240, 48, 0, 116, 0, 0, 2, 0, 0, 0, 0, 0, 0, 3, 96, 93, 255, 192, 1, 16, 255, 255, 0, 26, 236, 80, 0, 145, 0, 0, 184, 80, 67, 176, 80, 32, 248, 51, 0, 88, 251, 224, 2, 80, 251, 224, 4, 116, 0, 0, 37, 80, 71, 176, 40, 26, 233, 16, 0, 32, 248, 51, 0, 88, 251, 224, 2, 80, 251, 224, 4, 116, 0, 0, 136, 26, 71, 208, 0, 114, 72, 0, 24, 40, 237, 20, 128, 80, 79, 176, 120, 114, 68, 0, 24, 40, 79, 180, 64, 80, 71, 176, 160, 114, 72, 0, 24, 40, 69, 52, 128, 80, 71, 176, 96, 114, 72, 0, 24, 40, 69, 52, 128, 80, 75, 176, 64, 26, 233, 16, 0, 26, 229, 32, 0, 32, 248, 51, 0, 88, 251, 224, 2, 80, 251, 224, 4, 116, 0, 0, 144, 26, 71, 208, 0, 80, 75, 176, 24, 114, 76, 0, 16, 40, 73, 20, 192, 80, 71, 176, 144, 114, 76, 0, 16, 40, 69, 36, 192, 114, 72, 0, 16, 40, 65, 20, 128, 93, 69, 0, 1, 93, 65, 0, 0, 37, 65, 16, 0, 149, 0, 0, 63, 150, 8, 0, 0, 26, 236, 80, 0, 145, 0, 1, 88, 26, 87, 224, 0, 95, 236, 16, 42, 95, 236, 0, 41, 93, 67, 176, 41, 114, 68, 0, 5, 22, 65, 4, 64, 118, 64, 0, 81, 93, 67, 176, 42, 80, 71, 176, 200, 26, 233, 16, 0, 32, 248, 51, 0, 88, 251, 224, 2, 80, 251, 224, 4, 116, 0, 0, 87, 26, 71, 208, 0, 114, 72, 0, 24, 40, 237, 20, 128, 80, 71, 176, 160, 114, 72, 0, 24, 40, 71, 180, 128, 80, 75, 176, 24, 114, 76, 0, 24, 40, 73, 20, 192, 80, 71, 176, 88, 114, 76, 0, 24, 40, 69, 36, 192, 93, 83, 176, 11, 93, 79, 176, 12, 93, 71, 176, 13, 114, 72, 0, 8, 16, 73, 20, 128, 21, 73, 36, 192, 118, 72, 0, 1, 116, 0, 0, 7, 114, 72, 0, 2, 27, 73, 52, 128, 114, 76, 0, 8, 16, 77, 36, 192, 38, 76, 0, 0, 40, 29, 68, 64, 26, 80, 112, 0, 16, 73, 68, 64, 95, 73, 0, 0, 114, 64, 0, 8, 16, 65, 20, 0, 80, 71, 176, 112, 95, 237, 64, 14, 95, 237, 48, 15, 95, 237, 0, 16, 80, 67, 176, 48, 114, 72, 0, 24, 40, 65, 20, 128, 80, 71, 176, 136, 114, 72, 0, 24, 40, 69, 4, 128, 80, 67, 177, 8, 114, 72, 0, 24, 40, 65, 20, 128, 80, 71, 177, 48, 114, 72, 0, 24, 40, 69, 4, 128, 80, 67, 177, 48, 80, 71, 176, 240, 114, 72, 0, 24, 40, 69, 4, 128, 80, 67, 176, 224, 26, 233, 16, 0, 26, 229, 0, 0, 32, 248, 51, 0, 88, 251, 224, 2, 80, 251, 224, 4, 116, 0, 0, 56, 26, 67, 208, 0, 80, 71, 176, 72, 114, 72, 0, 16, 40, 69, 4, 128, 80, 67, 177, 32, 114, 72, 0, 16, 40, 65, 20, 128, 80, 71, 176, 184, 114, 72, 0, 16, 40, 69, 4, 128, 93, 67, 240, 0, 93, 71, 176, 23, 93, 75, 176, 24, 52, 1, 4, 82, 26, 244, 0, 0, 116, 0, 0, 8, 93, 67, 176, 41, 16, 65, 0, 64, 95, 237, 0, 41, 93, 67, 176, 42, 93, 71, 176, 41, 27, 65, 4, 64, 95, 237, 0, 42, 117, 0, 0, 91, 146, 0, 1, 88, 26, 249, 80, 0, 152, 8, 0, 0, 151, 0, 0, 63, 74, 248, 0, 0, 149, 0, 0, 15, 150, 8, 0, 0, 26, 236, 80, 0, 145, 0, 0, 72, 26, 67, 160, 0, 26, 71, 224, 0, 114, 72, 4, 0, 38, 72, 0, 0, 26, 72, 112, 0, 80, 79, 176, 24, 95, 237, 32, 3, 114, 72, 4, 0, 95, 237, 32, 4, 95, 236, 0, 5, 114, 72, 0, 24, 40, 237, 52, 128, 80, 75, 176, 48, 114, 76, 0, 24, 40, 75, 180, 192, 114, 76, 0, 24, 40, 65, 36, 192, 26, 245, 0, 0, 146, 0, 0, 72, 26, 249, 16, 0, 152, 8, 0, 0, 151, 0, 0, 15, 74, 248, 0, 0, 149, 0, 0, 63, 150, 8, 0, 0, 26, 236, 80, 0, 145, 0, 0, 104, 26, 67, 160, 0, 26, 71, 144, 0, 26, 75, 224, 0, 80, 79, 176, 80, 114, 80, 0, 24, 40, 77, 5, 0, 114, 64, 0, 24, 40, 237, 52, 0, 80, 67, 176, 40, 114, 76, 0, 24, 40, 67, 180, 192, 93, 79, 176, 5, 80, 65, 0, 16, 80, 83, 176, 64, 95, 237, 48, 8, 80, 77, 64, 8, 114, 84, 0, 8, 40, 77, 5, 64, 80, 67, 176, 24, 114, 76, 0, 16, 40, 65, 68, 192, 114, 76, 0, 16, 40, 69, 4, 192, 26, 245, 16, 0, 146, 0, 0, 104, 26, 249, 32, 0, 152, 8, 0, 0, 151, 0, 0, 63, 74, 248, 0, 0, 71, 0, 0, 0, 21, 6, 230, 244, 76, 29, 98, 145 ], "script_data": [], "receipts_root": "0000000000000000000000000000000000000000000000000000000000000000" }, "policies": { "bits": "MaxFee", - "values": [ - 0, - 0, - 0, - 0 - ] + "values": [0, 0, 0, 0] }, "inputs": [ { @@ -117,72 +37,7 @@ "outputs": [], "witnesses": [ { - "data": [ - 156, - 254, - 34, - 102, - 65, - 96, - 133, - 170, - 254, - 105, - 147, - 35, - 196, - 199, - 179, - 133, - 132, - 240, - 208, - 149, - 11, - 46, - 30, - 96, - 44, - 91, - 121, - 195, - 145, - 184, - 159, - 235, - 117, - 82, - 135, - 41, - 84, - 154, - 102, - 61, - 61, - 16, - 99, - 123, - 58, - 173, - 75, - 226, - 219, - 139, - 62, - 33, - 41, - 176, - 16, - 18, - 132, - 178, - 8, - 125, - 130, - 169, - 32, - 108 - ] + "data": [156, 254, 34, 102, 65, 96, 133, 170, 254, 105, 147, 35, 196, 199, 179, 133, 132, 240, 208, 149, 11, 46, 30, 96, 44, 91, 121, 195, 145, 184, 159, 235, 117, 82, 135, 41, 84, 154, 102, 61, 61, 16, 99, 123, 58, 173, 75, 226, 219, 139, 62, 33, 41, 176, 16, 18, 132, 178, 8, 125, 130, 169, 32, 108] } ] } diff --git a/forc-plugins/forc-debug/tests/cli_integration.rs b/forc-plugins/forc-debug/tests/cli_integration.rs index 4b3b3bfd37d..518cdd78276 100644 --- a/forc-plugins/forc-debug/tests/cli_integration.rs +++ b/forc-plugins/forc-debug/tests/cli_integration.rs @@ -49,7 +49,7 @@ fn test_cli() { cmd.send_line("breakpoint 0").unwrap(); cmd.exp_string(prompt).unwrap(); - cmd.send_line("start_tx examples/example_tx.json").unwrap(); + cmd.send_line("start_tx examples/example_tx.json examples/example_abi.json").unwrap(); cmd.exp_regex(r"Stopped on breakpoint at address 0 of contract 0x0{64}") .unwrap(); @@ -58,22 +58,21 @@ fn test_cli() { cmd.exp_string(prompt).unwrap(); cmd.send_line("continue").unwrap(); - cmd.exp_regex(r"Stopped on breakpoint at address 16 of contract 0x0{64}") + cmd.exp_regex(r"Stopped on breakpoint at address 4 of contract 0x0{64}") .unwrap(); cmd.exp_string(prompt).unwrap(); cmd.send_line("step off").unwrap(); cmd.exp_string(prompt).unwrap(); - cmd.send_line("continue").unwrap(); - cmd.exp_regex(r"Receipt: Return").unwrap(); + cmd.send_line("continue").unwrap(); cmd.exp_string(prompt).unwrap(); cmd.send_line("reset").unwrap(); cmd.exp_string(prompt).unwrap(); - cmd.send_line("start_tx examples/example_tx.json").unwrap(); - cmd.exp_regex(r"Receipt: Return").unwrap(); + cmd.send_line("start_tx examples/example_tx.json examples/example_abi.json").unwrap(); + cmd.exp_regex(r"Decoded log value: 120").unwrap(); cmd.exp_string(prompt).unwrap(); cmd.send_line("quit").unwrap(); From 1ed4dd8c000492c01d4d47254d6afc1fb0ebeb54 Mon Sep 17 00:00:00 2001 From: JoshuaBatty Date: Tue, 28 Jan 2025 11:01:43 +1100 Subject: [PATCH 15/16] link to github issue --- forc-plugins/forc-debug/src/types.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/forc-plugins/forc-debug/src/types.rs b/forc-plugins/forc-debug/src/types.rs index 7d1fb552abd..103a6b0e404 100644 --- a/forc-plugins/forc-debug/src/types.rs +++ b/forc-plugins/forc-debug/src/types.rs @@ -59,5 +59,6 @@ impl DerefMut for AbiMap { /// Fetches the ABI for the given contract ID from the Sway ABI Registry. fn fetch_abi_from_registry(_contract_id: &ContractId) -> Result { // TODO: Implement this once the Sway ABI Registry is available + // See this github issue: https://github.com/FuelLabs/sway/issues/6862 Err(Error::AbiError("Not implemented yet".to_string())) } From f2cdf73af0b579deef24d57675b83ae11b53fae2 Mon Sep 17 00:00:00 2001 From: JoshuaBatty Date: Tue, 28 Jan 2025 11:21:06 +1100 Subject: [PATCH 16/16] fmt --- forc-plugins/forc-debug/tests/cli_integration.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/forc-plugins/forc-debug/tests/cli_integration.rs b/forc-plugins/forc-debug/tests/cli_integration.rs index 518cdd78276..b430674f080 100644 --- a/forc-plugins/forc-debug/tests/cli_integration.rs +++ b/forc-plugins/forc-debug/tests/cli_integration.rs @@ -49,7 +49,8 @@ fn test_cli() { cmd.send_line("breakpoint 0").unwrap(); cmd.exp_string(prompt).unwrap(); - cmd.send_line("start_tx examples/example_tx.json examples/example_abi.json").unwrap(); + cmd.send_line("start_tx examples/example_tx.json examples/example_abi.json") + .unwrap(); cmd.exp_regex(r"Stopped on breakpoint at address 0 of contract 0x0{64}") .unwrap(); @@ -65,13 +66,14 @@ fn test_cli() { cmd.send_line("step off").unwrap(); cmd.exp_string(prompt).unwrap(); - cmd.send_line("continue").unwrap(); + cmd.send_line("continue").unwrap(); cmd.exp_string(prompt).unwrap(); cmd.send_line("reset").unwrap(); cmd.exp_string(prompt).unwrap(); - cmd.send_line("start_tx examples/example_tx.json examples/example_abi.json").unwrap(); + cmd.send_line("start_tx examples/example_tx.json examples/example_abi.json") + .unwrap(); cmd.exp_regex(r"Decoded log value: 120").unwrap(); cmd.exp_string(prompt).unwrap();