FuelLabs/fuel-core · error
During script gas limit estimation, dry-run returned incorre
Error message
During script gas limit estimation, dry-run returned incorrect transaction
What it means
During script gas limit estimation the assembler sends a Script transaction to its dry-run callback and expects the same transaction variant back (with receipts/status). The error fires when the dry-run returns a non-Script transaction (Create, Mint, Upgrade, or an unrelated tx).
Source
Thrown at crates/fuel-core/src/schema/tx/assemble_tx.rs:820
// We want to calculate `max_gas` for the script, but without script limit
*script.script_gas_limit_mut() = 0;
let gas_used_by_tx = script.max_gas(gas_costs, fee_params);
let max_gas_limit = max_tx_gas.saturating_sub(gas_used_by_tx);
*script.script_gas_limit_mut() = max_gas_limit;
let (updated_tx, new_status) = self.arguments.dry_run(script).await?;
self.dry_run_count = self
.dry_run_count
.checked_add(1)
.ok_or_else(|| anyhow::anyhow!("dry run count overflow"))?;
let Transaction::Script(updated_script) = updated_tx else {
return Err(anyhow::anyhow!(
"During script gas limit estimation, \
dry-run returned incorrect transaction"
));
};
script = updated_script;
status = new_status;
if !has_spendable_input {
script.inputs_mut().pop();
}
let mut contracts_not_in_inputs = Vec::new();
match &status.result {
TransactionExecutionResult::Success { .. } => break,
TransactionExecutionResult::Failed { receipts, .. } => {
for receipt in receipts.iter() {View on GitHub (pinned to b9d4d170da)
Solutions
- In custom dry_run implementations, always return the exact transaction object you received (same variant, possibly with updated fields)
- If using a stock node, capture the tx bytes and report the bug upstream
- In tests, return the input script unchanged on the success path
Example fix
// Rust (custom dry_run port)
// before
let dry_run = |tx: Script, _| async { Ok((Transaction::Create(build_create()), status)) };
// after
let dry_run = |mut tx: Script, _| async {
// estimate and possibly adjust script_gas_limit, but return the same Script
tx.script_gas_limit_mut().saturating_sub(0);
Ok((tx.into(), status))
}; Defensive patterns
Strategy: try-catch
Validate before calling
// Rust embedder: assert your dry_run port round-trips the variant
fn dry_run_ok(tx: &Transaction, out: &Transaction) -> bool { matches!((tx, out), (Transaction::Script(_), Transaction::Script(_))) } Try / catch
catch (e) { if (/dry-run returned incorrect transaction/.test(e.message)) { /* stock node: report bug; embedder: fix dry_run to return the same tx variant */ } else throw e; } Prevention
- Custom dry_run implementations must return the transaction they received, same variant
- In tests, return the input unchanged rather than a default/fabricated tx
- Log tx variant in/out at the dry-run boundary when wiring custom executors
When it happens
Trigger: Through a stock node this indicates a node bug. It is realistically hit by embedders supplying a custom AssembleArguments::dry_run implementation (or a mock in tests) that returns a transaction of a different variant than the one passed in.
Common situations: Writing custom dry-run mocks for tests that return Transaction::default() or a Create tx; proxying dry-run through a service that re-encodes the tx into another variant; version-skewed middleware between assembler and executor.
Related errors
- dry run count overflow
- missing contract id
- Run out of slots for the contract outputs
- fee index out of bounds
- The transaction estimation requires running of predicate mor
AI-assisted analysis of FuelLabs/fuel-core@b9d4d170da (2026-08-16).
Data as JSON: /api/errors/2e0e9a96b8feba80.
Report an issue: GitHub.