Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

forc-debug: Add ABI support for decoding log values #6856

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion docs/book/spell-check-custom-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -236,4 +236,5 @@ semiautomatically
FuelLabs
github
toml
hardcoded
hardcoded
subdirectories
296 changes: 64 additions & 232 deletions docs/book/src/debugging/debugging_with_cli.md

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions forc-plugins/forc-debug/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ 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-tx.workspace = true
fuel-types = { workspace = true, features = ["serde"] }
fuel-vm = { workspace = true, features = ["serde"] }
rayon.workspace = true
Expand Down
32 changes: 32 additions & 0 deletions forc-plugins/forc-debug/examples/example_abi.json
Original file line number Diff line number Diff line change
@@ -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": []
}
151 changes: 3 additions & 148 deletions forc-plugins/forc-debug/examples/example_tx.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
{
Expand All @@ -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]
}
]
}
Expand Down
122 changes: 112 additions & 10 deletions forc-plugins/forc-debug/src/cli/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ 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;
use sway_core::asm_generation::ProgramABI;

#[derive(Debug, Clone)]
pub struct Command {
Expand Down Expand Up @@ -159,24 +161,104 @@ 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 <contract_id>:<abi_file.json>`
///
/// 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<String>) -> Result<()> {
args.remove(0); // Remove the command name
ArgumentError::ensure_arg_count(&args, 1, 1)?; // Ensure exactly one argument
// 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::<fuel_abi_types::abi::program::ProgramABI>(
&abi_content,
)
.map_err(Error::JsonError)?;
state
.contract_abis
.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::<ContractId>().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::<fuel_abi_types::abi::program::ProgramABI>(&abi_content)
.map_err(Error::JsonError)?;

state
.contract_abis
.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);
pretty_print_run_result(&status, state);
Ok(())
}

Expand Down Expand Up @@ -205,7 +287,7 @@ pub async fn cmd_continue(state: &mut State, mut args: Vec<String>) -> Result<()
.await
.map_err(|e| Error::FuelClientError(e.to_string()))?;

pretty_print_run_result(&status);
pretty_print_run_result(&status, state);
Ok(())
}

Expand Down Expand Up @@ -364,9 +446,29 @@ 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, state: &mut State) {
for receipt in rr.receipts() {
println!("Receipt: {receipt:#?}");
println!("Receipt: {receipt:?}");

if let Receipt::LogData {
id,
rb,
data: Some(data),
..
} = receipt
{
// If the ABI is available, decode the log data
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)
{
println!(
"Decoded log value: {}, from contract: {}",
decoded_log_data.value, id
);
}
}
}
}
if let Some(bp) = &rr.breakpoint {
println!(
Expand Down
Loading
Loading