Skip to content

Commit 4f1af43

Browse files
authored
fix: forc-call variable output default estimation with override (#7384)
## Description When using `forc-call` with a large `--amount`, the transaction incorrectly sets output count equal to the amount value (e.g., transferring 1000 units creates 1000 outputs). ### Solution - Added `--variable-output` parameter to explicitly control output count - Changed default behavior to estimate minimum outputs via dry-run instead of using using amount value as UTXO outputs - Improved type safety by changing `script_json: serde_json::Value` to `script: fuel_tx::Script` ## Testing - Updated tests to verify correct output counts - Added assertions to check `script.outputs().len()` matches expectations - Tests confirm behavior in both dry-run and live execution modes Addresses: #7383 ## Checklist - [x] I have linked to any relevant issues. - [x] I have commented my code, particularly in hard-to-understand areas. - [ ] I have updated the documentation where relevant (API docs, the reference, and the Sway book). - [ ] If my change requires substantial documentation changes, I have [requested support from the DevRel team](https://github.com/FuelLabs/devrel-requests/issues/new/choose) - [x] I have added tests that prove my fix is effective or that my feature works. - [x] I have added (or requested a maintainer to add) the necessary `Breaking*` or `New Feature` labels where relevant. - [x] I have done my best to ensure that my PR adheres to [the Fuel Labs Code Review Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md). - [x] I have requested a review from the relevant team or maintainers. --------- Co-authored-by: z <zees-dev@users.noreply.github.com>
1 parent 8fde717 commit 4f1af43

File tree

5 files changed

+28
-20
lines changed

5 files changed

+28
-20
lines changed

forc-plugins/forc-client/src/cmd/call.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,10 @@ pub struct Command {
419419
#[clap(long, short = 'o', default_value = "default", help_heading = "OUTPUT")]
420420
pub output: OutputFormat,
421421

422+
/// Contract call variable output count
423+
#[clap(long, alias = "variable-output", help_heading = "VARIABLE OUTPUT")]
424+
pub variable_output: Option<usize>,
425+
422426
/// Set verbosity levels; currently only supports max 2 levels
423427
/// - `-v=1`: Print decoded logs
424428
/// - `-v=2`: Additionally print receipts and script json

forc-plugins/forc-client/src/op/call/call_function.rs

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ pub async fn call_function(
5050
mut output,
5151
external_contracts,
5252
contract_abis,
53+
variable_output,
5354
..
5455
} = cmd;
5556

@@ -88,7 +89,9 @@ pub async fn call_function(
8889
};
8990

9091
// Setup variable output policy and log decoder
91-
let variable_output_policy = VariableOutputPolicy::Exactly(call_parameters.amount as usize);
92+
let variable_output_policy = variable_output
93+
.map(VariableOutputPolicy::Exactly)
94+
.unwrap_or(VariableOutputPolicy::EstimateMinimum);
9295
let error_codes = abi
9396
.unified
9497
.error_codes
@@ -275,8 +278,6 @@ pub async fn call_function(
275278
let fuel_tx::Transaction::Script(script) = &tx else {
276279
bail!("Transaction is not a script");
277280
};
278-
let script_json = serde_json::to_value(script)
279-
.map_err(|e| anyhow!("Failed to convert script to JSON: {e}"))?;
280281

281282
// Parse the result based on output format
282283
let mut receipt_parser =
@@ -330,7 +331,7 @@ pub async fn call_function(
330331

331332
super::display_detailed_call_info(
332333
&tx_execution,
333-
&script_json,
334+
script,
334335
&abi_map,
335336
cmd.verbosity,
336337
&mut output,
@@ -357,7 +358,7 @@ pub async fn call_function(
357358
result: Some(result),
358359
total_gas: *tx_execution.result.total_gas(),
359360
receipts: tx_execution.result.receipts().to_vec(),
360-
script_json: Some(script_json),
361+
script: Some(script.to_owned()),
361362
trace_events,
362363
})
363364
}
@@ -466,6 +467,7 @@ pub mod tests {
466467
cmd,
467468
op::call::{call, get_wallet, PrivateKeySigner},
468469
};
470+
use fuel_tx::field::Outputs;
469471
use fuels::{crypto::SecretKey, prelude::*};
470472
use std::path::PathBuf;
471473

@@ -493,6 +495,7 @@ pub mod tests {
493495
label: None,
494496
output: cmd::call::OutputFormat::Raw,
495497
list_functions: false,
498+
variable_output: None,
496499
verbosity: 0,
497500
debug: false,
498501
}
@@ -948,15 +951,9 @@ pub mod tests {
948951
gas_forwarded: None,
949952
};
950953
// validate balance is unchanged (dry-run)
951-
assert_eq!(
952-
call(operation.clone(), cmd.clone())
953-
.await
954-
.unwrap()
955-
.result
956-
.unwrap(),
957-
"()"
958-
);
959-
assert_eq!(get_contract_balance(id_2, provider.clone()).await, 0);
954+
let call_response = call(operation.clone(), cmd.clone()).await.unwrap();
955+
assert_eq!(call_response.result.unwrap(), "()");
956+
assert_eq!(call_response.script.unwrap().outputs().len(), 2);
960957
cmd.mode = cmd::call::ExecutionMode::Live;
961958
assert_eq!(call(operation, cmd).await.unwrap().result.unwrap(), "()");
962959
assert_eq!(get_contract_balance(id_2, provider.clone()).await, 1);
@@ -983,7 +980,9 @@ pub mod tests {
983980
};
984981
cmd.mode = cmd::call::ExecutionMode::Live;
985982
let operation = cmd.validate_and_get_operation().unwrap();
986-
assert_eq!(call(operation, cmd).await.unwrap().result.unwrap(), "()");
983+
let call_response = call(operation, cmd).await.unwrap();
984+
assert_eq!(call_response.result.unwrap(), "()");
985+
assert_eq!(call_response.script.unwrap().outputs().len(), 3);
987986
assert_eq!(
988987
get_recipient_balance(random_wallet.address(), provider.clone()).await,
989988
2
@@ -1041,7 +1040,9 @@ pub mod tests {
10411040
};
10421041
cmd.mode = cmd::call::ExecutionMode::Live;
10431042
let operation = cmd.validate_and_get_operation().unwrap();
1044-
assert_eq!(call(operation, cmd).await.unwrap().result.unwrap(), "()");
1043+
let call_response = call(operation, cmd).await.unwrap();
1044+
assert_eq!(call_response.result.unwrap(), "()");
1045+
assert_eq!(call_response.script.unwrap().outputs().len(), 3);
10451046
assert_eq!(
10461047
get_recipient_balance(random_wallet.address(), provider.clone()).await,
10471048
3

forc-plugins/forc-client/src/op/call/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ pub struct CallResponse {
4545
#[serde(skip_serializing_if = "Vec::is_empty")]
4646
pub trace_events: Vec<trace::TraceEvent>,
4747
#[serde(rename = "Script", skip_serializing_if = "Option::is_none")]
48-
pub script_json: Option<serde_json::Value>,
48+
pub script: Option<fuel_tx::Script>,
4949
}
5050

5151
/// A command for calling a contract function.
@@ -266,7 +266,7 @@ pub(crate) fn display_tx_info(
266266
/// Prints receipts and trace to the writer based on verbosity level
267267
pub(crate) fn display_detailed_call_info(
268268
tx: &TransactionExecutionStatus,
269-
script_json: &serde_json::Value,
269+
script: &fuel_tx::Script,
270270
abis: &HashMap<ContractId, Abi>,
271271
verbosity: u8,
272272
writer: &mut impl std::io::Write,
@@ -276,7 +276,7 @@ pub(crate) fn display_detailed_call_info(
276276
if verbosity >= 4 {
277277
forc_tracing::println_label_green(
278278
"transaction script:\n",
279-
&serde_json::to_string_pretty(script_json).unwrap(),
279+
&serde_json::to_string_pretty(script).unwrap(),
280280
);
281281
}
282282
if verbosity >= 3 {

forc-plugins/forc-client/src/op/call/transfer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ pub async fn transfer(
4848
total_gas: tx_response.tx_status.total_gas,
4949
result: None,
5050
receipts: tx_response.tx_status.receipts,
51-
script_json: None,
51+
script: None,
5252
trace_events: vec![],
5353
})
5454
}

forc-plugins/forc-mcp/src/forc_call/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,7 @@ fn build_call_command(
540540
gas,
541541
external_contracts: None,
542542
output: OutputFormat::Json,
543+
variable_output: None,
543544
verbosity,
544545
debug: false,
545546
})
@@ -582,6 +583,7 @@ fn build_list_command(contract_id: &str, abi: &str) -> anyhow::Result<forc_clien
582583
gas: None,
583584
external_contracts: None,
584585
output: OutputFormat::Default,
586+
variable_output: None,
585587
verbosity: 0,
586588
debug: false,
587589
})
@@ -641,6 +643,7 @@ fn build_transfer_command(
641643
gas: None,
642644
external_contracts: None,
643645
output: OutputFormat::Json,
646+
variable_output: None,
644647
verbosity,
645648
debug: false,
646649
})

0 commit comments

Comments
 (0)