feat(subtensor): add MinTradeDelay post-start trading delay (#2844)#2887
feat(subtensor): add MinTradeDelay post-start trading delay (#2844)#2887Elv92 wants to merge 1 commit into
Conversation
…ation#2844) Prevent subnet-start self-snipes: a subnet owner can currently submit batch_all[start_call, add_stake] and buy alpha atomically in the block trading opens (the SN99 case, issue RaoFoundation#2844), because do_start_call enables SubtokenEnabled in the same block. Add a sudo-configurable MinTradeDelay (blocks). Staking in/out is rejected with TradingNotOpenYet until current_block >= start_block + MinTradeDelay, where start_block = FirstEmissionBlockNumber - 1. The check lives in ensure_subtoken_enabled (the single gate for add_stake/remove_stake/recycle/ buy_alpha/transfer_staked_alpha); validate_stake_transition is also routed through it so swap_stake/move_stake/transfer_stake cannot bypass the delay. Mirrors the existing StartCallDelay hyperparameter (storage, config const, event, getter/setter, sudo_set_min_trade_delay call_index 97). Runtime default prod_or_fast!(360, 0); spec_version 429->430; ValueQuery default => no migration. Existing/started subnets, root, and emission timing are unaffected. Adds tests.
|
@Elv92 is attempting to deploy a commit to the RaoFoundation Team on Vercel. A member of the Team first needs to authorize it. |
|
Context in case it helps triage: this implements the staking-start delay from #2844. It's deliberately narrow and complementary to the May 8 owner-alpha-at-registration fix — that closed the registration free-alpha vector; this closes the distinct launch-timing vector (an owner bundling
|
PR: Add
MinTradeDelay— a post-start trading delay to prevent subnet-start self-snipesCloses / implements the mechanism requested in #2844.
Problem
do_start_callsetsSubtokenEnabled = truein the same block as the subnet start(
subnets/subnet.rs). Becauseadd_stake(and the swap/move/transfer family) validate againstSubtokenEnabled, a subnet owner can submitbatch_all[start_call, add_stake]and buy alphaatomically in the block trading opens, before anyone else can react — the SN99 case in #2844.
Change
Adds a sudo-configurable hyperparameter
MinTradeDelay(blocks) and enforces a post-starttrading-open delay: staking in/out is rejected with a new
TradingNotOpenYeterror untilcurrent_block >= start_block + MinTradeDelay, wherestart_block = FirstEmissionBlockNumber - 1(set atomically by
do_start_call). This mirrors the existingStartCallDelayhyperparameter endto end (storage / config const / event / getter-setter /
sudo_set_min_trade_delay).The check lives in
Pallet::ensure_subtoken_enabled, which is the single gate foradd_stake,add_stake_limit,remove_stake,recycle,buy_alpha, andtransfer_staked_alpha. Theswap_stake/move_stake/transfer_stakefamily was checkingSubtokenEnableddirectly(
validate_stake_transition), which would have let an owner pre-stake into root and bundlebatch_all[start_call, swap_stake(root -> new)]to snipe anyway — so those two checks are nowrouted through
ensure_subtoken_enabledtoo, closing that bypass.Result: in the start block (with delay > 0) no alpha-acquiring path succeeds — the owner's
bundled buy reverts
TradingNotOpenYet. Everyone, owner included, can only stake at the known,public block
start + MinTradeDelay.Files
pallets/subtensor/src/lib.rs—MinTradeDelaystorage + the delay check inensure_subtoken_enabled.pallets/subtensor/src/macros/{config,events,errors}.rs—InitialMinTradeDelay,MinTradeDelaySet,TradingNotOpenYet.pallets/subtensor/src/coinbase/root.rs—get/set_min_trade_delay.pallets/subtensor/src/staking/stake_utils.rs— routevalidate_stake_transitionthrough the gate.pallets/admin-utils/src/lib.rs—sudo_set_min_trade_delay(call_index 97).runtime/src/lib.rs—InitialMinTradeDelay = prod_or_fast!(360, 0), wiring,spec_version429→430.runtime/src/proxy_filters/call_groups.rs— add the new sudo call to the proxy-filter allow-group.InitialMinTradeDelay(compile requirement).pallets/subtensor/src/tests/subnet.rsandpallets/admin-utils/src/tests/mod.rs.Compatibility
FirstEmissionBlockNumberis in the past, sostart + delayis in thepast → not retroactively frozen.
FirstEmissionBlockNumber = None): delay branch skipped → behave as today.FirstEmissionBlockNumberis unchanged and emission eligibility uses.is_some(), not ablock compare — emission timing is unaffected.
ValueQuery+ default ⇒ no migration.Open questions for reviewers (policy, not mechanism)
prod_or_fast!(360, 0)(~72 min on prod, 0 on fast-blocks) so theprotection is on by default while remaining sudo-tunable. Happy to default
0(mechanism only, opt-invia sudo) if the team prefers a no-behaviour-change merge, matching how
StartCallDelayshipped.remove_stake. Some sellpaths (
unstake_all,remove_stake_limit) intentionally no-op / aren't gated today; I left thatpre-existing behaviour alone. If "no staking in or out during the window" is desired verbatim, those
can be routed through the gate too — flagging as a scope decision.