Skip to content

feat: forc call tracing #7196

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

Merged
merged 26 commits into from
May 30, 2025
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e712b9a
ansiterm dep
zees-dev May 23, 2025
6c1d13c
using lowercase script; rename script -> script_json
zees-dev May 23, 2025
1c306b5
Abi struct
zees-dev May 23, 2025
a9bd829
CallData struct
zees-dev May 23, 2025
f14addd
upd process_transaction_output function
zees-dev May 23, 2025
120f0f1
added print_receipts_and_trace func
zees-dev May 23, 2025
97168c2
upd transfer.rs due refactor in mod file
zees-dev May 23, 2025
e6a2627
upd call_function.rs due refactor in mod file
zees-dev May 23, 2025
5379607
upd mod imports
zees-dev May 23, 2025
664ff7d
transaction_trace file
zees-dev May 23, 2025
4eed603
Node struct
zees-dev May 23, 2025
7fd10b3
Node fmt/display impl
zees-dev May 23, 2025
bf90bcf
format_transaction_trace function impl
zees-dev May 23, 2025
d0399df
forc call tracing unit tests
zees-dev May 23, 2025
fa94053
handle receipt formatting errors
zees-dev May 23, 2025
e324cb3
fix cargo clippy errors
zees-dev May 23, 2025
8a9c870
fixed contract_value_forwarding unit test
zees-dev May 23, 2025
580038b
fixed cargo clippy errors
zees-dev May 23, 2025
eaa2ac0
added docs for transaction tracing - with example
zees-dev May 23, 2025
7609925
markdown linting
zees-dev May 23, 2025
d6491b4
abi required
zees-dev May 28, 2025
c7afeac
added todo for type_lookup deadcode
zees-dev May 28, 2025
fe17d0e
cargo fmt --all
zees-dev May 28, 2025
5db03ef
Merge branch 'master' into feat/forc-call-tracing
zees-dev May 28, 2025
05ad6c8
Merge branch 'master' into feat/forc-call-tracing
JoshuaBatty May 30, 2025
c13d8cd
Merge branch 'master' into feat/forc-call-tracing
zees-dev May 30, 2025
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
1 change: 1 addition & 0 deletions Cargo.lock

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

68 changes: 68 additions & 0 deletions docs/book/src/testing/testing_with_forc_call.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,74 @@ forc call <CONTRACT_ID> --abi <PATH> <FUNCTION> --gas-forwarded 1000
forc call <CONTRACT_ID> --abi <PATH> <FUNCTION> --max-fee 5000
```

### Transaction Tracing

When you need to debug contract interactions or understand the execution flow, `forc call` provides detailed transaction traces with verbosity level 3 or higher (`-vvv` or `-v=3`).

```sh
# Enable transaction tracing
forc call <CONTRACT_ID> --abi <PATH> <FUNCTION> -vvv
```

The transaction trace provides a hierarchical view of all contract calls, showing:

- Gas consumption for each call (`[gas_amount]`)
- Contract addresses being called
- Return values and data
- Emitted logs and events
- Nested contract calls with proper indentation
- Overall transaction result and gas usage

#### Example Transaction Trace Output

```bash
forc call 0x9275a76531bce733cfafdbcb6727ea533ebbdc358d685152169b3c4eaa47b965 \
--abi ./demo/demo-caller-abi.json \
call_increment_count -vvv
```

**Output:**

```log
Traces:
[Script]
├─ [124116] 0x9275a76531bce733cfafdbcb6727ea533ebbdc358d685152169b3c4eaa47b965
│ ├─ [111500] 0xb792b1e233a2c06bccec611711acc3bb61bdcb28f16abdde86d1478ee02f6e42
│ │ └─ ← ()
│ ├─ emit AsciiString { data: "incremented count" }
│ ├─ [86284] 0xb792b1e233a2c06bccec611711acc3bb61bdcb28f16abdde86d1478ee02f6e42
│ │ └─ ← 0x0000000000000002
│ ├─ emit 2
│ ├─ emit AsciiString { data: "done" }
│ ├─ [72699] 0xb792b1e233a2c06bccec611711acc3bb61bdcb28f16abdde86d1478ee02f6e42
│ │ └─ ← ()
│ ├─ [48287] 0xb792b1e233a2c06bccec611711acc3bb61bdcb28f16abdde86d1478ee02f6e42
│ │ └─ ← 0x0000000000000003
│ └─ ← 0x0000000000000003
└─ ← [Return] val: 1
[ScriptResult] result: Success, gas_used: 89279

Transaction successfully executed.
Gas used: 160676
```

#### Understanding the Trace Format

- `[Script]` - The root transaction script
- `├─ [gas_amount] 0xcontract_address` - A contract call with gas consumption
- `│ └─ ← value` - Return value from the contract call
- `emit data` - Log/event emitted by the contract
- Indentation shows the call hierarchy (nested calls are indented further)
- `[ScriptResult]` - Final transaction result with gas used by the script
- `Gas used: <gas_used>` - Total gas used by the transaction

This tracing feature is particularly useful for:

- Debugging failed transactions
- Understanding gas consumption patterns
- Analyzing complex multi-contract interactions
- Verifying expected contract behavior

### Common Use Cases

#### Contract State Queries
Expand Down
1 change: 1 addition & 0 deletions forc-plugins/forc-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ license.workspace = true
repository.workspace = true

[dependencies]
ansiterm.workspace = true
anyhow.workspace = true
async-trait.workspace = true
aws-config.workspace = true
Expand Down
59 changes: 41 additions & 18 deletions forc-plugins/forc-client/src/op/call/call_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@ use crate::{
op::call::{
missing_contracts::determine_missing_contracts,
parser::{param_type_val_to_token, token_to_string},
CallResponse, Either,
Abi, CallData, CallResponse, Either,
},
};
use anyhow::{anyhow, bail, Result};
use fuel_abi_types::abi::{program::ProgramABI, unified_program::UnifiedProgramABI};
use fuel_abi_types::abi::unified_program::UnifiedProgramABI;
use fuels::{
accounts::ViewOnlyAccount,
programs::calls::{
receipt_parser::ReceiptParser,
traits::{ContractDependencyConfigurator, TransactionTuner},
ContractCall,
},
types::tx_status::TxStatus,
};
use fuels_core::{
codec::{
Expand All @@ -29,7 +30,7 @@ use fuels_core::{
ContractId,
},
};
use std::{collections::HashMap, path::PathBuf};
use std::{collections::HashMap, path::PathBuf, str::FromStr};
use url::Url;

/// Calls a contract function with the given parameters
Expand All @@ -46,20 +47,19 @@ pub async fn call_function(
caller,
call_parameters,
gas,
output,
mut output,
external_contracts,
..
} = cmd;

// Load ABI (already provided in the operation)
let abi_str = super::load_abi(&abi).await?;
let parsed_abi: ProgramABI = serde_json::from_str(&abi_str)?;
let unified_program_abi = UnifiedProgramABI::from_counterpart(&parsed_abi)?;
let abi = Abi::from_str(&abi_str).map_err(|e| anyhow!("Failed to parse ABI: {}", e))?;

let cmd::call::FuncType::Selector(selector) = function;

let (encoded_data, output_param) =
prepare_contract_call_data(&unified_program_abi, &selector, &function_args)?;
prepare_contract_call_data(&abi.unified, &selector, &function_args)?;

// Setup connection to node
let (wallet, tx_policies, base_asset_id) = super::setup_connection(&node, caller, &gas).await?;
Expand All @@ -84,8 +84,10 @@ pub async fn call_function(

// Setup variable output policy and log decoder
let variable_output_policy = VariableOutputPolicy::Exactly(call_parameters.amount as usize);
let error_codes = unified_program_abi
let error_codes = abi
.unified
.error_codes
.as_ref()
.map_or(HashMap::new(), |error_codes| {
error_codes
.iter()
Expand Down Expand Up @@ -193,7 +195,7 @@ pub async fn call_function(
};

// Display the script JSON when verbosity level is 2 or higher (vv)
let script = if cmd.verbosity >= 2 {
let script_json = if cmd.verbosity >= 2 {
let script_json = serde_json::to_value(&script).unwrap();
forc_tracing::println_label_green(
"transaction script:\n",
Expand All @@ -204,10 +206,28 @@ pub async fn call_function(
None
};

let abi_map = HashMap::from([(contract_id, abi)]);

// Process transaction results
let receipts = tx_status
.take_receipts_checked(Some(&log_decoder))
.map_err(|e| anyhow!("Failed to take receipts: {e}"))?;
let receipts = match tx_status.clone().take_receipts_checked(Some(&log_decoder)) {
Ok(receipts) => receipts,
Err(e) => {
// Print receipts when an error occurs and verbosity is high enough
super::print_receipts_and_trace(
tx_status.total_gas(),
&tx_status.clone().take_receipts(),
cmd.verbosity,
&abi_map,
&mut output,
)?;
match tx_status {
TxStatus::Failure(e) | TxStatus::PreconfirmationFailure(e) => {
bail!("Failed to process transaction; reason: {:#?}", e.reason);
}
_ => bail!("Failed to process transaction: {:#?}", e),
}
}
};

// Parse the result based on output format
let mut receipt_parser = ReceiptParser::new(&receipts, DecoderConfig::default());
Expand All @@ -230,18 +250,21 @@ pub async fn call_function(
};

// Process and return the final output
let program_abi = sway_core::asm_generation::ProgramABI::Fuel(parsed_abi);
let mut call_response = super::process_transaction_output(
&receipts,
tx_status,
&tx_hash.to_string(),
&program_abi,
Some(result),
&mode,
&node,
cmd.verbosity,
&mut output,
Some(CallData {
contract_id,
abis: abi_map,
result,
}),
)?;
if cmd.verbosity >= 2 {
call_response.script = script;
call_response.script_json = script_json;
}

Ok(call_response)
Expand Down Expand Up @@ -855,7 +878,7 @@ pub mod tests {
.await
.unwrap_err()
.to_string()
.contains("PanicInstruction { reason: NotEnoughBalance"));
.contains("Failed to process transaction; reason: \"NotEnoughBalance\""));
assert_eq!(get_contract_balance(id, provider.clone()).await, 1);

// contract call transfer funds to another address
Expand Down
Loading
Loading