Skip to content

Add price-protected cross-hotkey stake moves (move_stake_limit and full variant) #3081

Description

@alex-drocks

Is your feature request related to a problem? Please describe.

Subtensor currently exposes two complementary stake-transition calls, but no call combines their capabilities:

  • move_stake can change the destination hotkey, including across subnets, but it has no limit_price.
  • swap_stake_limit enforces an on-chain price bound, but it preserves the same hotkey.

This leaves wallets and swap interfaces without a canonical way to atomically move Alpha from one subnet/validator position directly into another subnet/validator position while retaining slippage protection.

A frontend can try to compose the existing calls with:

utility.batch_all([
  move_stake(source_hotkey, target_hotkey, origin_netuid, origin_netuid, amount),
  swap_stake_limit(target_hotkey, origin_netuid, destination_netuid, amount, limit_price, false),
])

However, the same-subnet move_stake passes through validator share accounting. The move debits the exact integer amount from the source position, but SafeFloat/share representation can make the newly minted target-hotkey position immediately readable as amount - 1 RAO.

This was deterministically reproduced against Finney runtime spec 445 for a partial SN28 → SN60 route:

requested source amount:             12,266,631,756 Alpha RAO
post-move executable target amount:  12,266,631,755 Alpha RAO
difference:                           1 Alpha RAO

The following swap_stake_limit(..., 12_266_631_756, ...) would then fail NotEnoughStakeToWithdraw, rolling back the batch.

The available application-level workarounds all compromise the intended operation:

  • Swap amount - 1 and add a third remove_stake_limit(..., 1, 0, false) cleanup call.
  • Leave a one-RAO temporary position on the origin subnet.
  • Silently adjust the user's exact amount, which is especially unsuitable for MAX.
  • Block the route and ask the user to change the amount or validator.
  • Swap first and move the finalized output in a second transaction, losing single-transaction atomicity.
  • Use cross-subnet move_stake directly, losing the on-chain price bound.

This is not a utility.batch_all problem: batch calls are statically SCALE-encoded and cannot pass the first call's runtime result into the second call.

Describe the solution you'd like

Please expose a price-protected stake move that accepts different origin and destination hotkeys, for example:

move_stake_limit(
  origin_hotkey,
  destination_hotkey,
  origin_netuid,
  destination_netuid,
  alpha_amount,
  limit_price,
  allow_partial,
)

Semantically, this would combine the destination-hotkey support of move_stake with the price-bound behavior of swap_stake_limit. It should debit the source position, perform the protected cross-subnet transition, and credit the destination subnet's selected hotkey directly, without creating an intermediate position on the origin subnet.

The existing internal transition_stake_internal already accepts distinct origin/destination hotkeys together with an optional limit price and partial-execution setting. The missing capability appears to be the public dispatch surface, corresponding validation/weights, and integration coverage.

For genuine full-position/MAX execution, please also consider a full variant analogous to remove_stake_full_limit, for example:

move_stake_full_limit(
  origin_hotkey,
  destination_hotkey,
  origin_netuid,
  destination_netuid,
  limit_price,
)

This variant should resolve the complete eligible source position during dispatch rather than requiring a quote-time u64 amount. If the full amount cannot satisfy the price bound or stake-lock invariants, it should fail atomically.

Suggested acceptance criteria:

  • Different origin and destination hotkeys.
  • Different origin and destination netuids.
  • On-chain price-limit enforcement equivalent to swap_stake_limit.
  • Exact-input execution when allow_partial = false.
  • A full-position/MAX path that resolves its amount during dispatch.
  • Direct credit to the requested destination hotkey, with no temporary origin-subnet target position.
  • Correct single-route fee treatment.
  • Existing minimum-stake, availability, collateral-lock, and root-hold invariants preserved.
  • Events identify both hotkeys, both netuids, and the executed transition.
  • Runtime benchmarks, signed-extension validation, proxy filtering, and integration tests cover the new calls.

Naming and whether exact/full behavior is exposed as two calls or one call with an explicit amount mode are open to maintainers.

Describe alternatives you've considered

The application-level alternatives are listed above. The only currently available one-transaction route that remains price-protected for an arbitrary destination validator requires pre-moving the stake and handling validator-share rounding with extra calls or route rejection.

A single cross-subnet move_stake is operationally simpler, but MEV privacy is not a substitute for an on-chain price bound: ordinary pool movement before inclusion can still violate the user's slippage tolerance.

Additional context

Relevant implementation areas:

  • pallets/subtensor/src/staking/move_stake.rs: do_move_stake, do_swap_stake_limit, and transition_stake_internal.
  • primitives/share-pool/src/lib.rs: SharePool::update_value_for_one and floored represented-value reads.

The goal is not to remove validator share accounting or promise that raw AMM output and represented stake can never differ by one RAO. The goal is to prevent that representation detail from making an intermediate frontend-composed route fail or require a separate one-RAO cleanup operation.

Metadata

Metadata

Assignees

No one assigned

    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