Skip to content

feat(chain-extensions): add claim-root-with-hotkey for contract root-basket claims - #3099

Open
Benup211 wants to merge 6 commits into
RaoFoundation:mainfrom
Benup211:feat/claim-root-with-hotkey-extension
Open

feat(chain-extensions): add claim-root-with-hotkey for contract root-basket claims#3099
Benup211 wants to merge 6 commits into
RaoFoundation:mainfrom
Benup211:feat/claim-root-with-hotkey-extension

Conversation

@Benup211

@Benup211 Benup211 commented Aug 20, 2026

Copy link
Copy Markdown

Description

Adds a new chain-extension function (ClaimRootWithHotkeyV1, id 39) so an pallet/ink! contract that holds root (netuid 0) stake as a coldkey can claim its per-validator basket entitlement through the 0x1000 chain extension.

  • The contract is the coldkey; do_root_claim(env.caller(), vec![hotkey]) is invoked with the contract address as the origin.
  • Realized TAO is staked back to root on the hotkey (compounding), and the realized amount (RootClaimOutcome::tao) is written back so the contract can update its own bookkeeping.
  • Basket work is bounded up front: the arm rejects claims whose basket row count exceeds MAX_ROOT_CLAIM_WORK (256) before charging any weight, mirroring the rows clause of the signed extrinsic's root_claim_fits_declared_budget admission gate. The full claim_root(MAX_ROOT_CLAIM_WORK) weight is then charged via env.charge_weight (a chain extension has no post-dispatch weight refund).

Files changed:

  • chain-extensions/src/types.rs — add ClaimRootWithHotkeyV1 = 39 to FunctionId
  • chain-extensions/src/lib.rs — dispatch arm (decode → bound → charge weight → claim → write output)
  • chain-extensions/src/tests.rsclaim_root_with_hotkey_noop_returns_zero (success path: full 256-unit charge, zero payout) and claim_root_with_hotkey_rejects_basket_above_envelope (257-row basket → RuntimeError, no weight charged)

Related Issue(s)

None.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Other (please describe):

Breaking Change

No breaking change. This is purely additive: a new chain-extension function id (39) and dispatch arm. Existing function ids 0–38 and their SCALE encodings are unchanged.

Checklist

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have run ./scripts/fix_rust.sh to ensure my code is formatted and linted correctly
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

Screenshots (if applicable)

N/A

Additional Notes

  • Function id 39: ids 37/38 (MoveStakeLimitV1, CallerMoveStakeLimitV1) were taken by release-448, so this function landed on 39. The discriminant and roundtrip tests assert 16..=39.
  • Branch state: includes the release-449 merge; v449 changed no root-claim semantics (the claim engine, gate, and weights are identical), and the crate compiles with all tests passing post-merge.
  • Runtime-affecting: this adds a chain-extension function, so it needs a spec_version bump (or the no-spec-version-bump label) — left for the release train / maintainer to coordinate.
  • Verification performed (crate-scoped, not the full workspace gate):
    • cargo test -p subtensor-chain-extensions — 57 passed, 0 failed (incl. both claim tests)
    • cargo clippy -p subtensor-chain-extensions --all-targets — clean
    • cargo fmt -p subtensor-chain-extensions -- --check — clean
    • ./scripts/fix_rust.sh was not run (it auto-commits workspace-wide).
  • Design rationale: the upfront claim_root(MAX_ROOT_CLAIM_WORK) charge with a pre-charge row-count bound is deliberate — unlike the signed extrinsic (which declares the same 256-unit envelope and refunds post-dispatch down to root_claim_actual_weight), a chain extension has no post-dispatch refund, so the envelope must be charged in full and oversized baskets rejected before any weight is charged.

…basket claims

Exposes function 37 (ClaimRootWithHotkeyV1) so an ink! contract that holds
root stake as a coldkey can claim its per-validator basket entitlement
through the 0x1000 chain extension. The contract is the coldkey; realized
TAO is staked back to root on the hotkey and the realized amount is returned.
Basket work is bounded to MAX_ROOT_CLAIM_WORK and charged upfront (no
post-dispatch refund), mirroring the signed extrinsic's admission gate.
@vercel

vercel Bot commented Aug 20, 2026

Copy link
Copy Markdown

@Benup211 is attempting to deploy a commit to the RaoFoundation Team on Vercel.

A member of the Team first needs to authorize it.

@UnArbosSix UnArbosSix left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Successful contract claims can leave the root-staker coldkey index inconsistent. The new chain-extension arm calls do_root_claim(caller, vec![hotkey]) directly and returns the payout, but never calls maybe_add_coldkey_index(&caller). The normal claim_root_with_hotkey extrinsic explicitly does exactly that after do_root_claim, so this is a real semantic difference between the two entry points, not incidental cleanup.

…act root claims

ClaimRootWithHotkeyV1 called do_root_claim(caller, vec![hotkey]) and returned
the payout without registering the caller in the staking-coldkey index, unlike
the claim_root / claim_root_with_hotkey extrinsics, which call
maybe_add_coldkey_index(&coldkey) unconditionally after do_root_claim
(dispatches.rs:1988, :2023). A paying claim stakes its payout back on root
under the claiming coldkey (claim_root.rs:774-781), so the claim itself can be
a contract coldkey's first root stake;leaving it invisible to the index
until the slow per-block add-only sweep happens to reach it.

The arm now mirrors the signed flow: after a successful claim it calls
maybe_add_coldkey_index(&caller) before writing the output. Idempotent, and
covered by the pre-charged claim_root(MAX_ROOT_CLAIM_WORK) envelope; error and
over-envelope paths remain write-free as in the signed flows.

Tests: fresh coldkey is indexed after a zero-payout claim; a paying claim
(tao > 0) indexes the caller; repeat claims leave the index untouched; the
257-row rejection leaves no index trace.
@Benup211

Benup211 commented Sep 2, 2026

Copy link
Copy Markdown
Author

Good catch- ext-39 was the only claim entry point skipping index maintenance, and you're right that it's a real semantic difference, not incidental. do_root_claim stakes a successful payout back on root under the claiming coldkey (claim_root.rs:774-781) without touching the index, so a contract's first root stake arriving via the extension would leave it unindexed until the per-block add-only sweep happens to pick it up and a contract that only ever noop-claims would never be indexed at all, unlike on the signed path.

Fixed: the arm now calls maybe_add_coldkey_index(&caller) on the Ok path, restoring exact parity with claim_root_with_hotkey, which indexes the caller unconditionally after do_root_claim (zero-payout claims included). The Err and over-envelope paths remain write-free, matching the signed ? early-returns. No weight change is needed, the arm pre-charges claim_root(MAX_ROOT_CLAIM_WORK), the same envelope the signed extrinsic declares and the same cost the claim_root benchmark already measures with the index writes included.

Added regression tests covering: a fresh coldkey indexed after a zero-payout claim, a paying claim (tao > 0) indexing the caller, repeat claims leaving StakingColdkeys/StakingColdkeysByIndex/NumStakingColdkeys unchanged, and the 257-row rejection leaving no index trace. Full crate suite passes (60 tests) with clean clippy/fmt.

@Benup211
Benup211 requested a review from UnArbosSix September 2, 2026 10:30
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.

2 participants