Skip to content

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

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

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions