In inject_and_maybe_swap (pallets/subtensor/src/coinbase/run_coinbase.rs:71-176), when the per-subnet "excess TAO swap" branch runs (lines 99-132):
match Self::spend_tao(&subnet_account_id, remaining_credit, tao_to_swap_with) {
Ok(remainder) => {
remaining_credit = remainder;
let buy_swap_result = Self::swap_tao_for_alpha(
*netuid_i,
tao_to_swap_with,
T::SwapInterface::max_price(),
true,
);
if let Ok(buy_swap_result_ok) = buy_swap_result {
// ... SubnetProtocolAlpha += bought_alpha, SubnetExcessTao = actual_excess,
// record_protocol_inflow(actual_excess)
}
// No `else` branch: failure is silently swallowed.
}
Err(remainder) => { /* log + continue */ }
}
The spend_tao call atomically resolves tao_to_swap_with units of the block-emission Credit into subnet_account (coinbase/tao.rs:271-287 → Currency::resolve), permanently crediting subnet_account.free and counting the issuance in both subtensor::TotalIssuance (incremented earlier by mint_tao at tao.rs:252-266) and balances::TotalIssuance (incremented by Currency::issue inside mint_tao).
If swap_tao_for_alpha then returns Err, the AMM-side bookkeeping is reverted (the inner swap runs in transactional::with_transaction — pallets/swap/src/pallet/impls.rs:198-222), but the prior spend_tao is outside that transaction. The net post-condition for the failing subnet is:
subnet_account.free += tao_to_swap_with (the credit-resolved TAO sits on the pallet-derived account).
subtensor::TotalIssuance and balances::TotalIssuance each +tao_to_swap_with (matching balances side).
SubnetTAO[netuid] unchanged (would have been incremented inside swap_tao_for_alpha at stake_utils.rs:676-679 only on success).
TotalStake unchanged (would have been incremented on success at stake_utils.rs:682).
SubnetExcessTao[netuid] = 0 (zeroed unconditionally at line 96 and never re-written in the failure path).
SubnetProtocolAlpha[netuid] unchanged.
record_protocol_inflow(netuid, ...) never called, so the failed inflow is missing from SubnetProtocolFlow EMA accounting (coinbase/subnet_emissions.rs:94-98).
The tao_to_swap_with TAO is therefore orphaned: physically present on subnet_account (so Currency::total_issuance() keeps counting it), but the per-subnet accounting maps treat the swap as never having happened. The subnet's SubnetTAO reserve is now below its real on-chain balance.
Recommended remediation
Either:
- Wrap the
spend_tao + swap_tao_for_alpha pair in transactional::with_transaction so the credit resolution rolls back on swap failure, returning the credit (and thus the TAO) to recycle_credit.
- On swap failure, explicitly
transfer_tao the tao_to_swap_with back from subnet_account into a recycled imbalance (recycle_tao against subnet_account) so both subtensor::TotalIssuance and balances::TotalIssuance are decremented and the orphan never accumulates.
- At minimum, in the
Err / else-of-Ok path, mutate SubnetTAO[netuid] += tao_to_swap_with and TotalStake += tao_to_swap_with so the on-account TAO is at least tracked as a "free" reserve owned by the subnet rather than orphaned.
Option (1) is the cleanest and matches the pattern already used inside the swap pallet.
In
inject_and_maybe_swap(pallets/subtensor/src/coinbase/run_coinbase.rs:71-176), when the per-subnet "excess TAO swap" branch runs (lines 99-132):The
spend_taocall atomically resolvestao_to_swap_withunits of the block-emissionCreditintosubnet_account(coinbase/tao.rs:271-287→Currency::resolve), permanently creditingsubnet_account.freeand counting the issuance in bothsubtensor::TotalIssuance(incremented earlier bymint_taoattao.rs:252-266) andbalances::TotalIssuance(incremented byCurrency::issueinsidemint_tao).If
swap_tao_for_alphathen returnsErr, the AMM-side bookkeeping is reverted (the inner swap runs intransactional::with_transaction—pallets/swap/src/pallet/impls.rs:198-222), but the priorspend_taois outside that transaction. The net post-condition for the failing subnet is:subnet_account.free += tao_to_swap_with(the credit-resolved TAO sits on the pallet-derived account).subtensor::TotalIssuanceandbalances::TotalIssuanceeach +tao_to_swap_with(matching balances side).SubnetTAO[netuid]unchanged (would have been incremented insideswap_tao_for_alphaatstake_utils.rs:676-679only on success).TotalStakeunchanged (would have been incremented on success atstake_utils.rs:682).SubnetExcessTao[netuid] = 0(zeroed unconditionally at line 96 and never re-written in the failure path).SubnetProtocolAlpha[netuid]unchanged.record_protocol_inflow(netuid, ...)never called, so the failed inflow is missing fromSubnetProtocolFlowEMA accounting (coinbase/subnet_emissions.rs:94-98).The
tao_to_swap_withTAO is therefore orphaned: physically present onsubnet_account(soCurrency::total_issuance()keeps counting it), but the per-subnet accounting maps treat the swap as never having happened. The subnet'sSubnetTAOreserve is now below its real on-chain balance.Recommended remediation
Either:
spend_tao+swap_tao_for_alphapair intransactional::with_transactionso the credit resolution rolls back on swap failure, returning the credit (and thus the TAO) torecycle_credit.transfer_taothetao_to_swap_withback fromsubnet_accountinto a recycled imbalance (recycle_taoagainstsubnet_account) so bothsubtensor::TotalIssuanceandbalances::TotalIssuanceare decremented and the orphan never accumulates.Err/else-of-Okpath, mutateSubnetTAO[netuid] += tao_to_swap_withandTotalStake += tao_to_swap_withso the on-account TAO is at least tracked as a "free" reserve owned by the subnet rather than orphaned.Option (1) is the cleanest and matches the pattern already used inside the swap pallet.