Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Chain extension AddStakeRecycleV1 / AddStakeBurnV1 are non-atomic #2666

Open
Assignees
Labels
bugSomething isn't working

Description

Describe the bug

The composite chain-extension entry points AddStakeRecycleV1 / AddStakeBurnV1 (chain-extensions/src/lib.rs:792-853) invoke pallet-level pub fn helpers that are not #[pallet::call] dispatchables - so FRAME's automatic with_storage_layer wrap does not apply. If the second leg (do_recycle_alpha or do_burn_alpha) returns Err after the first leg (do_add_stake) succeeded, the first leg's storage writes persist. The doc-comment on the helper still claims atomicity ("without leaving residual stake if the second leg fails").

This is a silent regression: commit 46989aaba (2026年04月17日) originally wrapped both helpers in transactional::with_transaction to deliver the documented atomicity; commit d0702db5a (2026年05月04日, "add migration part") removed the wrapper without mention in the commit message.

To Reproduce

Simplest trigger: netuid = NetUid::ROOT. do_add_stake succeeds on the root subnet, then do_recycle_alpha / do_burn_alpha returns Error::CannotBurnOrRecycleOnRootSubnet (pallets/subtensor/src/staking/recycle_alpha.rs:28-31 and :89-92).

  1. Deploy any ink! contract whose #[ink(message)] calling the chain extension returns a non-Result type (so ink!'s automatic revert-on-Err dispatch wrapper does not trip). Raw WASM / PolkaVM contracts naturally expose the bug.
  2. From the contract, invoke AddStakeRecycleV1(hotkey, netuid = NetUid::ROOT, tao_amount = 100 TAO) (or the burn variant).
  3. The chain extension returns Output::CannotBurnOrRecycleOnRootSubnet = 21.
  4. Inspect the contract's TAO balance, AlphaV2((hotkey, contract, ROOT)), SubnetTAO[ROOT], SubnetAlphaOut[ROOT], TotalStake.

Observed: contract TAO debited by 100 TAO; root alpha credited 100e9 to (hotkey, contract, ROOT); SubnetTAO[ROOT], SubnetAlphaOut[ROOT], TotalStake all bumped - despite the chain extension returning a failure code that the composite was supposed to roll back.

Other plausible failure modes for the second leg:

  • A pre-existing Lock row on (contract-account, netuid, hotkey) reduces available_stake = total - locked - unlocked below the freshly-minted alpha, so ensure_available_stake (recycle_alpha.rs:54) returns StakeUnavailable.
  • An intermediate state-change inside do_add_stake -> stake_into_subnet can also commit partially: transfer_tao_to_subnet moves real pallet-balances TAO before the internal swap_tao_for_alpha, leaving the TAO stranded if the swap fails.

Expected behavior

AddStakeRecycleV1 / AddStakeBurnV1 must be atomic - either both legs succeed or no storage writes persist. The doc-comment on do_add_stake_recycle already states this contract: "so that contracts can compose the two operations without leaving residual stake if the second leg fails."

Screenshots

No response

Environment

opentensor/subtensor testnet @ e6a5f56cefeb96b9c63ea3ce6553a8e1066aaeb9

Additional context

Affected code

chain-extensions/src/lib.rs:792-853:

FunctionId::AddStakeRecycleV1 => {
 // ...
 Pallet::<T>::do_add_stake_recycle(origin, hotkey, netuid, amount) // <-- no with_storage_layer
}
FunctionId::AddStakeBurnV1 => {
 // ...
 Pallet::<T>::do_add_stake_burn_permissionless(origin, hotkey, netuid, amount) // <-- no with_storage_layer
}

pallets/subtensor/src/staking/recycle_alpha.rs:157-178:

pub fn do_add_stake_recycle(origin, hotkey, netuid, amount) -> Result<AlphaBalance, _> {
 let alpha = Self::do_add_stake(origin.clone(), hotkey.clone(), netuid, amount)?;
 Self::do_recycle_alpha(origin, hotkey, alpha, netuid)
}
pub fn do_add_stake_burn_permissionless(origin, hotkey, netuid, amount) -> Result<AlphaBalance, _> {
 let alpha = Self::do_add_stake(origin.clone(), hotkey.clone(), netuid, amount)?;
 Self::do_burn_alpha(origin, hotkey, alpha, netuid)
}

Metadata

Metadata

Labels

bugSomething isn't working

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

      AltStyle によって変換されたページ (->オリジナル) /