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

  1. In custom dry_run implementations, always return the exact transaction object you received (same variant, possibly with updated fields)
  2. If using a stock node, capture the tx bytes and report the bug upstream
  3. 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

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


AI-assisted analysis of FuelLabs/fuel-core@b9d4d170da (2026-08-16). Data as JSON: /api/errors/2e0e9a96b8feba80. Report an issue: GitHub.