{"record":{"id":"d9c10344d0e2d3aa","repo":"FuelLabs/fuel-core","slug":"fee-index-out-of-bounds","errorCode":null,"errorMessage":"fee index out of bounds","messagePattern":"fee index out of bounds","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/fuel-core/src/schema/tx/assemble_tx.rs","lineNumber":268,"sourceCode":"            }\n        }\n\n        let mut change_output_policies = HashMap::<AssetId, ChangePolicy>::new();\n        let mut set_change_outputs = HashSet::<AssetId>::new();\n\n        for output in tx.outputs() {\n            if let Output::Change { to, asset_id, .. } = output {\n                change_output_policies.insert(*asset_id, ChangePolicy::Change(*to));\n                set_change_outputs.insert(*asset_id);\n            }\n        }\n\n        let mut base_asset_reserved: Option<u64> = None;\n\n        let fee_payer_account = arguments\n            .required_balances\n            .get(arguments.fee_index as usize)\n            .ok_or_else(|| anyhow::anyhow!(\"fee index out of bounds\"))?\n            .account\n            .clone();\n\n        let mut requested_asset = HashSet::new();\n        for required_balance in &arguments.required_balances {\n            let asset_id = required_balance.asset_id;\n            requested_asset.insert(asset_id);\n\n            if asset_id == base_asset_id\n                && fee_payer_account.owner() == required_balance.account.owner()\n            {\n                base_asset_reserved = Some(required_balance.amount);\n            }\n        }\n\n        for input_asset_id in &unique_used_asset {\n            // If the user didn't request the asset, we add it to the required balances\n            // with minimal amount `0` and `ChangePolicy::Change` policy.","sourceCodeStart":250,"sourceCodeEnd":286,"githubUrl":"https://github.com/FuelLabs/fuel-core/blob/b9d4d170da3a31c9ace5f963d633b326348e0d42/crates/fuel-core/src/schema/tx/assemble_tx.rs#L250-L286","documentation":"A second, defensive lookup of the fee payer: when assembly begins it fetches required_balances[fee_index] to get the account that covers gas, erroring if the index is out of bounds. Through the GraphQL API this is normally unreachable because AssembleTx::new already validated fee_index < required_balances.len() at construction.","triggerScenarios":"Practically limited to code embedding the assembler directly (AssembleTx::new + assemble) that mutates arguments.required_balances between construction and the assembly step, shrinking it below fee_index.","commonSituations":"Library embedders reusing an AssembleArguments struct after filtering its required_balances; test harnesses that drain the vec before calling assemble.","solutions":["Do not modify AssembleArguments.required_balances after AssembleTx::new — treat the arguments as consumed","Rebuild the assembler with a fresh AssembleArguments whenever the balances list changes","Keep the same fee_index < required_balances.len() invariant you already validated at construction"],"exampleFix":"// Rust embedder\n// before\nlet mut assembler = AssembleTx::new(tx, args)?;\nassembler.arguments_mut().required_balances.retain(|b| b.amount > 0); // may drop the fee payer\nlet tx = assembler.assemble().await?;\n\n// after\nlet mut args = args;\nargs.required_balances.retain(|b| b.amount > 0);\nargs.fee_index = args.fee_index.min((args.required_balances.len() as u16).saturating_sub(1));\nlet tx = AssembleTx::new(tx, args)?.assemble().await?;","handlingStrategy":"validation","validationCode":"// Rust embedder: validate immediately before assembly, not just at construction\nfn args_valid(args: &AssembleArguments<'_>) -> bool {\n    (args.fee_index as usize) < args.required_balances.len()\n}","typeGuard":null,"tryCatchPattern":"catch (e) { if (/fee index out of bounds/.test(e.message)) { /* rebuild AssembleArguments with a consistent fee_index */ } else throw e; }","preventionTips":["Treat AssembleArguments as consumed by AssembleTx::new — never mutate it afterwards","When filtering required_balances, clamp or recompute fee_index in the same code block","Prefer rebuilding the whole assembler over patching fields"],"tags":["transaction","fee","validation","assemble-tx","internal"],"backgroundTag":null,"analyzedSha":"b9d4d170da3a31c9ace5f963d633b326348e0d42","analyzedAt":"2026-08-16T08:56:42.692Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}