Skip to content

OrderSwapInterface::transfer_tao burns dust at origin without decrementing subtensor::TotalIssuance #2738

@gztensor

Description

@gztensor

Description

OrderSwapInterface::transfer_tao (pallets/subtensor/src/staking/order_swap.rs:116-119) calls <T as Config>::Currency::transfer(from, to, amount, Preservation::Expendable) directly. With Preservation::Expendable and the runtime configured as type DustRemoval = () (runtime/src/lib.rs:523), if the transfer would leave from with a positive residual balance strictly less than the existential deposit (ED = 500 RAO, runtime/src/lib.rs:510-512), the balances pallet reaps from, burns the dust, and decrements balances::TotalIssuance by that dust.

The sibling Pallet::transfer_tao in pallets/subtensor/src/coinbase/tao.rs:70-80 is designed exactly to avoid this drift: it delegates to transfer_allow_death_update_ti (tao.rs:38-64), which snapshots balances::TotalIssuance before and after the transfer and decrements subtensor::TotalIssuance by any observed delta so the two issuance counters stay locked. The OrderSwapInterface implementation bypasses this wrapper entirely and re-implements the unsafe direct-currency path.

Net effect: every limit-orders fee transfer that happens to land the signer's residual balance below ED silently widens the gap balances::TI < subtensor::TI by the dust amount (up to 499 RAO per occurrence). This is exactly the class of divergence that migrate_fix_total_issuance_evm_fees (pallets/subtensor/src/migrations/migrate_fix_total_issuance_evm_fees.rs) was added to repair for the prior EVM-fee burn bug — the same root cause (a Credit / dust burn that decrements balances::TI without a matching subtensor::TI decrement) is being re-introduced here.

subtensor::TotalIssuance is consensus-affecting:

  • block_emission.rs:32-34 reads Self::get_total_issuance() (= TotalIssuance::<T>::get()) and uses it to compute the per-block emission curve. A persistently inflated subtensor TI suppresses emission (the log2 curve treats the chain as more mature than it really is).
  • mint_tao (tao.rs:252-266) clamps against <T as Config>::Currency::total_issuance() (balances TI) for the 21M cap. With drift, the two halves of the same accounting question (get_block_emission and mint_tao's cap) get inconsistent answers, and a future migration similar to migrate_fix_total_issuance_evm_fees will be needed to reconcile.

Recommendation

Either:

  • Route the trait impl through the existing safe wrapper: replace the body with Pallet::<T>::transfer_tao(from, to, amount) (tao.rs:70-80), which already calls transfer_allow_death_update_ti; or
  • Inline the same before/after balances::total_issuance() snapshot + subtensor::TotalIssuance::saturating_sub(burned) reconciliation that transfer_allow_death_update_ti performs.

Either fix removes the structural class of bug (any future direct-currency-transfer path needs the same wrapper).

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions