Skip to content

Repatriate residual stake and locks on lease termination#2709

Open
l0r1s wants to merge 6 commits into
devnet-readyfrom
fix-lease-handoff
Open

Repatriate residual stake and locks on lease termination#2709
l0r1s wants to merge 6 commits into
devnet-readyfrom
fix-lease-handoff

Conversation

@l0r1s
Copy link
Copy Markdown
Collaborator

@l0r1s l0r1s commented Jun 2, 2026

Repatriate residual stake and locks on lease termination

Closes #2663.

Problem

do_terminate_lease handed subnet ownership to the beneficiary but left the
accumulated owner-cut alpha and lock state on the lease's derived coldkey
(blake2_256("leasing/coldkey", lease_id)). That coldkey has no private key and
the beneficiary proxy can't extract stake, so every fixed-end lease termination
permanently stranded the owner emissions accrued during the lease.

Change

Before tearing down the lease coldkey, termination now:

  • moves the lease coldkey's lock to the beneficiary's hotkey, preserving the
    conviction earned during the lease (do_move_lock(..., preserve_conviction));
  • re-points the subnet owner aggregate buckets to the new owner hotkey
    (reassign_subnet_owner_lock_aggregates, extracted so it's shared with the
    existing owner-change path);
  • repatriates the residual alpha stake to the beneficiary;
  • moves any remaining lock to the beneficiary (transfer_full_lock_to_coldkey);
  • clears the lease coldkey's storage references and SubnetUidToLeaseId;
  • removes the beneficiary proxy before dec_providers, then sweeps the
    unreserved proxy deposit to the beneficiary, so the keyless coldkey is reaped
    cleanly with no dangling account, refcount, or stranded funds.

Post-condition: no alpha, lock, or ownership rows remain under the lease coldkey.

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review — see the sticky summary comment for the verdict and the inline comments below for specific findings.

Comment thread pallets/subtensor/src/subnets/leasing.rs
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 2, 2026

🛡️ AI Review — Skeptic (security review)

VERDICT: VULNERABLE

LOW scrutiny: l0r1s has repo write permission, a 2018 account, substantial contribution history, matching commit authors, and no protected instruction/dependency/build-script changes; branch is fix-lease-handoff -> devnet-ready.

No .github/ai-review/*, copilot-instruction, dependency, build-script, or lockfile changes were present. Static review only; no build or tests were run per Skeptic policy.

Findings

Sev File Finding
HIGH pallets/subtensor/src/subnets/leasing.rs:343 Hotkey-swap work is discarded from terminate_lease weight inline
MEDIUM pallets/subtensor/src/subnets/leasing.rs:237 Lease termination can still commit a partial handoff on lock conflict inline

Prior-comment reconciliation

  • 5069e74c: not addressed — The current diff still performs lock/owner/aggregate mutations before a later beneficiary-controlled LockHotkeyMismatch can be returned, and terminate_lease still has no storage transaction wrapper.

Conclusion

The PR still has a non-transactional lease termination path that can leave partial state committed on a later error, and it adds an under-accounted hotkey-swap path inside terminate_lease. These are security-relevant runtime safety issues, so the verdict remains vulnerable.


📜 Previous run (superseded)
Sev File Finding Status
MEDIUM pallets/subtensor/src/subnets/leasing.rs:223 Lease termination can still commit a partial handoff on lock conflict ➡️ Carried forward to current findings
The current diff still performs lock/owner/aggregate mutations before a later beneficiary-controlled LockHotkeyMismatch can be returned, and terminate_lease still has no storage transaction wrapper.

# 🔍 AI Review — Auditor (domain review) has not yet run on this PR.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 2, 2026

🔄 AI review updated — Skeptic: VULNERABLE

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review — see the sticky summary comment for the verdict and the inline comments below for specific findings.


// Move any lease coldkey lock to the beneficiary-controlled hotkey without
// assigning ownership of the generated lease hotkey to the beneficiary.
Self::move_lease_lock_to_beneficiary_hotkey(&lease, &hotkey)?;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] Lease termination can still commit a partial handoff on lock conflict

terminate_lease is not wrapped in #[transactional] or with_transaction, but this call mutates lock rows via do_move_lock before later fallible calls. In the conflict case covered by the new test, repatriate_lease_coldkey_alpha can still return LockHotkeyMismatch after this mutation and after owner/aggregate updates, leaving the lease in storage while the subnet owner and lock state have already changed. Make the whole termination path storage-transactional, or preflight every fallible lock/proxy/currency operation before the first mutation.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 2, 2026

🔄 AI review updated — Skeptic: VULNERABLE

gztensor
gztensor previously approved these changes Jun 2, 2026
Comment thread pallets/subtensor/src/subnets/leasing.rs
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review — see the sticky summary comment for the verdict and the inline comments below for specific findings.

Comment on lines +228 to +237
Self::move_lease_lock_to_beneficiary_hotkey(&lease, &hotkey)?;

// Transfer ownership to the beneficiary
SubnetOwner::<T>::insert(lease.netuid, lease.beneficiary.clone());
// Set the owner hotkey before moving locks so the destination lock is
// accounted in the subnet-owner aggregate, not the regular hotkey aggregate.
Self::set_subnet_owner_hotkey(lease.netuid, &hotkey)?;
Self::reassign_subnet_owner_lock_aggregates(lease.netuid, &lease.hotkey, &hotkey);

Self::repatriate_lease_coldkey_alpha(&lease, &hotkey)?;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] Lease termination can still commit a partial handoff on lock conflict

terminate_lease is still not wrapped in #[transactional] or with_transaction. A beneficiary that already has a lock on a different hotkey can make repatriate_lease_coldkey_alpha return LockHotkeyMismatch via transfer_stake_within_subnet after the earlier lock move, owner change, owner-hotkey set, and aggregate reassignment have already written storage. Dispatch errors do not roll those writes back, so the lease can be left partially handed off. Preflight the beneficiary lock compatibility before any mutation, or wrap the whole handoff in a storage transaction and rollback on every Err.

Comment on lines +342 to +343
let mut weight = Weight::zero();
Self::perform_hotkey_swap_on_one_subnet(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] Hotkey-swap work is discarded from terminate_lease weight

This creates a local Weight::zero() and passes it into perform_hotkey_swap_on_one_subnet, but the accumulated weight is never returned from terminate_lease or otherwise charged. That helper walks and rewrites dynamic storage such as PendingChildKeys::<T>::iter_prefix(netuid), Alpha::<T>::iter_prefix(old_hotkey), and AlphaV2::<T>::iter_prefix(old_hotkey). The dispatch weight for terminate_lease is still only keyed by contributor count, so a lease termination can execute hotkey-swap work that is not reflected in the block weight. Account for the helper's accumulated weight in the post-dispatch result and regenerate/update the lease termination weights with bounds for the dynamic hotkey-swap state, or avoid this helper in favor of a bounded lease-specific migration.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 4, 2026

🔄 AI review updated — Skeptic: VULNERABLE

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants